From 584d551f67e0fb4dad03d05c5c057620bffd8603 Mon Sep 17 00:00:00 2001 From: Andrey Melikhov Date: Tue, 24 Dec 2024 12:35:27 +0300 Subject: [PATCH 01/18] Add zitadel headers (#1969) --- .../components/processor/data-fetcher.ts | 6 +++--- .../charts-engine/controllers/charts.ts | 2 +- .../middlewareAdapters/charts-with-dataset.ts | 10 +++++++--- .../middlewareAdapters/controls-with-dataset.ts | 2 ++ .../request-with-dataset/request-dataset.ts | 17 +++++++++++++++-- src/server/modes/charts/plugins/types.ts | 2 ++ .../registry/units/common/functions-map.ts | 3 +-- 7 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/server/components/charts-engine/components/processor/data-fetcher.ts b/src/server/components/charts-engine/components/processor/data-fetcher.ts index 62e562ef1d..f9a757babd 100644 --- a/src/server/components/charts-engine/components/processor/data-fetcher.ts +++ b/src/server/components/charts-engine/components/processor/data-fetcher.ts @@ -149,12 +149,12 @@ export type DataFetcherResult = { data?: any; }; -type ZitadelParams = { +export type ZitadelParams = { accessToken?: string; serviceUserAccessToken?: string; }; -function addZitadelHeaders({ +export function addZitadelHeaders({ headers, zitadelParams, }: { @@ -725,7 +725,6 @@ export class DataFetcher { ); const sourceAuthorizationHeaders = getSourceAuthorizationHeaders({ - req, ctx, sourceConfig, subrequestHeaders, @@ -803,6 +802,7 @@ export class DataFetcher { ChartsEngine: chartsEngine, userId: userId === undefined ? null : userId, rejectFetchingSource, + zitadelParams, }); } } diff --git a/src/server/components/charts-engine/controllers/charts.ts b/src/server/components/charts-engine/controllers/charts.ts index f59ae4f077..9696f3f810 100644 --- a/src/server/components/charts-engine/controllers/charts.ts +++ b/src/server/components/charts-engine/controllers/charts.ts @@ -90,7 +90,7 @@ function prepareChartData( return {chart, type, links, template}; } -export const getHeaders = (req: Request) => { +const getHeaders = (req: Request) => { const headers = { ...req.headers, ...(req.ctx.config.isZitadelEnabled ? {...Utils.pickZitadelHeaders(req)} : {}), diff --git a/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts index 2eb899c3b3..9fb132d35c 100644 --- a/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts +++ b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/charts-with-dataset.ts @@ -22,8 +22,11 @@ export default async ( workbookId, rejectFetchingSource, pluginOptions, + zitadelParams, } = args; + const ctx = req.ctx; + const cacheClient = ChartsEngine.cacheClient as Cache; const [datasetId, layerId] = getDatasetIdAndLayerIdFromKey(sourceName); @@ -47,19 +50,20 @@ export default async ( datasetId, workbookId: workbookId ?? null, req, - ctx: req.ctx, + ctx, cacheClient, userId, iamToken, rejectFetchingSource, pluginOptions, + zitadelParams, }); revisionId = datasetFieldsResponse.revisionId; datasetFields = datasetFieldsResponse.datasetFields; } - req.ctx.log('CHARTS_DATASET_FIELDS_RECEIVED', { + ctx.log('CHARTS_DATASET_FIELDS_RECEIVED', { count: datasetFields.length, }); @@ -73,7 +77,7 @@ export default async ( revisionId, }); - req.ctx.log('CHARTS_DATASET_FIELDS_PROCESSED'); + ctx.log('CHARTS_DATASET_FIELDS_PROCESSED'); return {...source, data}; }; diff --git a/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts index bbe095bf85..6ffa3d4d16 100644 --- a/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts +++ b/src/server/modes/charts/plugins/request-with-dataset/middlewareAdapters/controls-with-dataset.ts @@ -19,6 +19,7 @@ export default async ( workbookId, rejectFetchingSource, pluginOptions, + zitadelParams, } = args; const ctx = req.ctx; @@ -37,6 +38,7 @@ export default async ( iamToken, rejectFetchingSource, pluginOptions, + zitadelParams, }); const datasetFields = datasetFieldsResponse.datasetFields; diff --git a/src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts b/src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts index 889316fd6c..40b6c28ef3 100644 --- a/src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts +++ b/src/server/modes/charts/plugins/request-with-dataset/request-dataset.ts @@ -8,7 +8,10 @@ import type {WorkbookId} from '../../../../../shared'; import {DL_EMBED_TOKEN_HEADER} from '../../../../../shared'; import type {GetDataSetFieldsByIdResponse, PartialDatasetField} from '../../../../../shared/schema'; import Cache from '../../../../components/cache-client'; -import {getHeaders} from '../../../../components/charts-engine/controllers/charts'; +import { + type ZitadelParams, + addZitadelHeaders, +} from '../../../../components/charts-engine/components/processor/data-fetcher'; import {registry} from '../../../../registry'; import type {DatalensGatewaySchemas} from '../../../../types/gateway'; @@ -27,6 +30,7 @@ const getDatasetFieldsById = async ({ rejectFetchingSource, iamToken, pluginOptions, + zitadelParams, }: { datasetId: string; workbookId: string | null; @@ -35,6 +39,7 @@ const getDatasetFieldsById = async ({ rejectFetchingSource: (reason?: any) => void; iamToken?: string; pluginOptions?: ConfigurableRequestWithDatasetPluginOptions; + zitadelParams: ZitadelParams | undefined; }): Promise => { const {gatewayApi} = registry.getGatewayApi(); @@ -43,7 +48,11 @@ const getDatasetFieldsById = async ({ const requestDatasetFieldsByToken = gatewayApi.bi.embedsGetDataSetFieldsById; try { - const headers = getHeaders(req); + const headers = {...req.headers}; + + if (zitadelParams) { + addZitadelHeaders({headers, zitadelParams}); + } const response = req.headers[DL_EMBED_TOKEN_HEADER] ? await requestDatasetFieldsByToken({ @@ -96,6 +105,7 @@ export const getDatasetFields = async (args: { userId: string | null; rejectFetchingSource: (reason: any) => void; pluginOptions?: ConfigurableRequestWithDatasetPluginOptions; + zitadelParams: ZitadelParams | undefined; }): Promise<{datasetFields: PartialDatasetField[]; revisionId: string}> => { const { datasetId, @@ -107,6 +117,7 @@ export const getDatasetFields = async (args: { iamToken, rejectFetchingSource, pluginOptions, + zitadelParams, } = args; const cacheKey = `${datasetId}__${userId}`; @@ -134,6 +145,7 @@ export const getDatasetFields = async (args: { rejectFetchingSource, iamToken, pluginOptions, + zitadelParams, }); datasetFields = response.fields; revisionId = response.revision_id; @@ -166,6 +178,7 @@ export const getDatasetFields = async (args: { rejectFetchingSource, iamToken, pluginOptions, + zitadelParams, }); datasetFields = response.fields; revisionId = response.revision_id; diff --git a/src/server/modes/charts/plugins/types.ts b/src/server/modes/charts/plugins/types.ts index 44bd8dca15..33cfdefe7a 100644 --- a/src/server/modes/charts/plugins/types.ts +++ b/src/server/modes/charts/plugins/types.ts @@ -2,6 +2,7 @@ import type {Request} from '@gravity-ui/expresskit'; import type {WorkbookId} from '../../../../shared'; import type {ChartsEngine} from '../../../components/charts-engine'; +import type {ZitadelParams} from '../../../components/charts-engine/components/processor/data-fetcher'; import type { CHARTS_MIDDLEWARE_URL_TYPE, @@ -53,6 +54,7 @@ export interface MiddlewareSourceAdapterArgs { ChartsEngine: ChartsEngine; userId: string | null; rejectFetchingSource: (reason: any) => void; + zitadelParams: ZitadelParams | undefined; } export interface ProcessorHookInitArgs { diff --git a/src/server/registry/units/common/functions-map.ts b/src/server/registry/units/common/functions-map.ts index 3d1a734f20..615d0e7fda 100644 --- a/src/server/registry/units/common/functions-map.ts +++ b/src/server/registry/units/common/functions-map.ts @@ -1,4 +1,4 @@ -import type {Request, Response} from '@gravity-ui/expresskit'; +import type {Response} from '@gravity-ui/expresskit'; import type {AppContext} from '@gravity-ui/nodekit'; import type {Palette} from '../../../../shared/constants/colors'; @@ -11,7 +11,6 @@ export const commonFunctionsMap = { getSourceAuthorizationHeaders: makeFunctionTemplate< (args: { - req?: Request; ctx: AppContext; sourceConfig: SourceConfig; subrequestHeaders: Record; From 1aeec0069c046857a3d22215b10cd465f1c00951 Mon Sep 17 00:00:00 2001 From: Irina Kuzmina Date: Tue, 24 Dec 2024 13:20:11 +0300 Subject: [PATCH 02/18] Up @gravity-ui/chartkit 5.19.0 -> 5.19.1 (#1972) --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index e12dc98241..1da208a3fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@diplodoc/transform": "^4.20.0", "@gravity-ui/app-layout": "^2.1.0", "@gravity-ui/browserslist-config": "^4.3.0", - "@gravity-ui/chartkit": "^5.19.0", + "@gravity-ui/chartkit": "^5.19.1", "@gravity-ui/dashkit": "^8.22.1", "@gravity-ui/date-utils": "^2.5.6", "@gravity-ui/expresskit": "^2.1.0", @@ -4998,9 +4998,10 @@ "integrity": "sha512-GhZemc/gPq/Kf4OYyXZpeZLgL69pI8oidWhdOKUHOWSYQyKviDDZDxrMzjZzvCWOxrpQVFtmqiCBMW8nqJivEQ==" }, "node_modules/@gravity-ui/chartkit": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/@gravity-ui/chartkit/-/chartkit-5.19.0.tgz", - "integrity": "sha512-CtjnX+F47+i0RpgL5h6sXzQvyd/BQ27yto/7kyikj1LUEbJZELGyebyZfbzhl6isFXPNWprpo91i5kt5M2oFnA==", + "version": "5.19.1", + "resolved": "https://registry.npmjs.org/@gravity-ui/chartkit/-/chartkit-5.19.1.tgz", + "integrity": "sha512-fzV1TK2Q+jHMoUFZeppmNdtZ7BS7D1Jdqvi2ZOTu6ccJzjU83zbmhkqngl313nZyRaIvrb29HEaLwrLqfu1hQw==", + "license": "MIT", "dependencies": { "@bem-react/classname": "^1.6.0", "@gravity-ui/charts": "^0.5.0", diff --git a/package.json b/package.json index 926d8e97c0..cfcb6ca64e 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "@diplodoc/transform": "^4.20.0", "@gravity-ui/app-layout": "^2.1.0", "@gravity-ui/browserslist-config": "^4.3.0", - "@gravity-ui/chartkit": "^5.19.0", + "@gravity-ui/chartkit": "^5.19.1", "@gravity-ui/dashkit": "^8.22.1", "@gravity-ui/date-utils": "^2.5.6", "@gravity-ui/expresskit": "^2.1.0", From a142fb584bfef6041b19da477496fd0d9d5d36cf Mon Sep 17 00:00:00 2001 From: Daria Larionova Date: Tue, 24 Dec 2024 14:20:59 +0300 Subject: [PATCH 03/18] Add nullMode to GradientColorConfig & Up @gravity-ui/uikit 6.37.0 -> 6.39.0 (#1937) --- package-lock.json | 8 +- package.json | 2 +- src/i18n-keysets/wizard/en.json | 2 + src/i18n-keysets/wizard/ru.json | 2 + .../helpers/backgroundColor.ts | 26 ++- .../backend-pivot-table/helpers/color.ts | 6 +- ...kground-colors-map-by-continuous-column.ts | 25 ++- .../datalens/preparers/flat-table/index.ts | 3 +- src/shared/constants/gradients/index.ts | 8 + src/shared/types/config/wizard/v12.ts | 3 +- .../types/wizard/background-settings.ts | 3 +- src/shared/types/wizard/index.ts | 2 + src/ui/units/wizard/actions/dialog.ts | 1 + src/ui/units/wizard/actions/dialogColor.ts | 4 +- .../ColorSettingsContainer.tsx | 1 + .../Dialogs/DialogColor/DialogColor.scss | 2 +- .../Dialogs/DialogColor/DialogColor.tsx | 27 ++- .../DialogColorGradient.scss | 35 +-- .../DialogColorGradient.tsx | 202 +++++++++--------- .../BackgroundSettings/BackgroundSettings.tsx | 28 ++- .../hooks/useBackgroundNullModeSettings.tsx | 31 +++ .../ButtonColorDialog/ButtonColorDialog.tsx | 1 + .../DialogField/utils/backgroundSettings.ts | 1 + src/ui/units/wizard/constants/dialogColor.ts | 14 +- .../utils/clearUnusedVisualizationItems.ts | 17 +- 25 files changed, 294 insertions(+), 160 deletions(-) create mode 100644 src/ui/units/wizard/components/Dialogs/DialogField/components/BackgroundSettings/hooks/useBackgroundNullModeSettings.tsx diff --git a/package-lock.json b/package-lock.json index 1da208a3fa..6b417bc3f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,7 +78,7 @@ "@gravity-ui/stylelint-config": "^4.0.1", "@gravity-ui/tsconfig": "^1.0.0", "@gravity-ui/ui-logger": "^1.1.0", - "@gravity-ui/uikit": "^6.37.0", + "@gravity-ui/uikit": "^6.39.0", "@jest/types": "^29.6.3", "@microsoft/fetch-event-source": "^2.0.1", "@playwright/test": "^1.48.2", @@ -5309,9 +5309,9 @@ "license": "MIT" }, "node_modules/@gravity-ui/uikit": { - "version": "6.37.0", - "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.37.0.tgz", - "integrity": "sha512-XwtD0+HFYaJv5CiRceuHATD97hZJxigF/8swwYlJx7SZauiR5GMOqB/3+yY/nYcJkWte5H/cC1JnVp3nHv5ELw==", + "version": "6.39.0", + "resolved": "https://registry.npmjs.org/@gravity-ui/uikit/-/uikit-6.39.0.tgz", + "integrity": "sha512-ORb5OHBNxE7J/NwhwSjs7V2pnEwdItnGyOg6eoL9SVM/IUiG++CpsrApWaVMAsbhWChCTKCdAQK9lnJsGBnfZQ==", "dependencies": { "@bem-react/classname": "^1.6.0", "@gravity-ui/i18n": "^1.6.0", diff --git a/package.json b/package.json index cfcb6ca64e..4843d0a6f7 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "@gravity-ui/stylelint-config": "^4.0.1", "@gravity-ui/tsconfig": "^1.0.0", "@gravity-ui/ui-logger": "^1.1.0", - "@gravity-ui/uikit": "^6.37.0", + "@gravity-ui/uikit": "^6.39.0", "@jest/types": "^29.6.3", "@microsoft/fetch-event-source": "^2.0.1", "@playwright/test": "^1.48.2", diff --git a/src/i18n-keysets/wizard/en.json b/src/i18n-keysets/wizard/en.json index 95dc2cc31f..1968619125 100644 --- a/src/i18n-keysets/wizard/en.json +++ b/src/i18n-keysets/wizard/en.json @@ -226,6 +226,8 @@ "label_overlap": "Labels overlap", "label_overriden-from-dashboard": "overriden from dashboard", "label_pagination": "Pagination", + "label_painting-as-0": "Paint as 0", + "label_painting-ignore": "Do not paint", "label_palette": "Palette", "label_part": "Date part", "label_percent": "Percentage", diff --git a/src/i18n-keysets/wizard/ru.json b/src/i18n-keysets/wizard/ru.json index 26c36dc16d..d16eba7119 100644 --- a/src/i18n-keysets/wizard/ru.json +++ b/src/i18n-keysets/wizard/ru.json @@ -226,6 +226,8 @@ "label_overlap": "Перекрытие подписей", "label_overriden-from-dashboard": "перезаписан с дашборда", "label_pagination": "Пагинация", + "label_painting-as-0": "Окрашивать как 0", + "label_painting-ignore": "Не окрашивать", "label_palette": "Палитра", "label_part": "Часть даты", "label_percent": "В процентах", diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts index d55cbd5433..493d21bbac 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/backgroundColor.ts @@ -7,6 +7,7 @@ import type { } from '../../../../../../../../shared'; import { ApiV2Annotations, + GradientNullModes, PseudoFieldTitle, getFakeTitleOrTitle, } from '../../../../../../../../shared'; @@ -231,8 +232,19 @@ export const prepareBackgroundColorSettings = (args: PrepareBackgroundColorSetti const fieldColorValues = Array.from(colorValues); continuousColorsByField[guid] = {}; + const nilValue = + backgroundSettings.settings.gradientState.nullMode === GradientNullModes.AsZero + ? 0 + : null; + + const colorValuesWithoutNull = fieldColorValues.reduce((acc, cv) => { + const colorValue = cv === null ? nilValue : cv; + if (colorValue !== null) { + acc.push(Number(colorValue)); + } - const colorValuesWithoutNull = fieldColorValues.filter((cv): cv is number => cv !== null); + return acc; + }, []); const min = Math.min(...colorValuesWithoutNull); const max = Math.max(...colorValuesWithoutNull); @@ -250,12 +262,15 @@ export const prepareBackgroundColorSettings = (args: PrepareBackgroundColorSetti fieldColorValues.forEach((value) => { const colorValue = getContinuousColorValue(value); - if (colorValue === null) { + if ( + colorValue === null && + backgroundSettings.settings.gradientState.nullMode !== GradientNullModes.AsZero + ) { return; } const color = colorizePivotTableCell(colorValue, chartColorsConfig, [min, max]); - continuousColorsByField[guid][String(value)] = color?.backgroundColor || null; + continuousColorsByField[guid][String(colorValue)] = color?.backgroundColor || null; }); }); @@ -317,7 +332,10 @@ export const colorizePivotTableByFieldBackgroundSettings = ( const {settings, colorFieldGuid} = backgroundColorSettings; const colorKey = cell.colorKey; - if (!colorKey) { + if ( + !colorKey && + backgroundColorSettings.settings.gradientState.nullMode !== GradientNullModes.AsZero + ) { continue; } diff --git a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts index 5efdbee2b8..38d632e514 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/backend-pivot-table/helpers/color.ts @@ -1,5 +1,5 @@ import type {ServerColor} from '../../../../../../../../shared'; -import {ApiV2Annotations, isMeasureName} from '../../../../../../../../shared'; +import {ApiV2Annotations, GradientNullModes, isMeasureName} from '../../../../../../../../shared'; import type {ChartColorsConfig} from '../../../types'; import {colorizePivotTableCell} from '../../../utils/color-helpers'; import type {AnnotationsMap, PivotDataCellValue, PivotDataRows} from '../types'; @@ -110,11 +110,13 @@ export const colorizePivotTableByColorField = (args: ColorizeByColorFieldArgs) = } const {colorValues, min, max} = colorSettings; + const nilValue = colorsConfig.nullMode === GradientNullModes.AsZero ? 0 : null; rows.forEach((row, rowIndex) => { for (let i = rowHeaderLength; i < row.cells.length; i++) { const cell = row.cells[i]; - const colorValue = colorValues[rowIndex][i - rowHeaderLength]; + const rawColorValue = colorValues[rowIndex][i - rowHeaderLength]; + const colorValue = rawColorValue === null ? nilValue : rawColorValue; const isInvalidColorValue = colorValue === null; diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts index ff36a0c25e..bc5f603f64 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/helpers/background-settings/get-background-colors-map-by-continuous-column.ts @@ -1,10 +1,11 @@ import isNil from 'lodash/isNil'; import isNumber from 'lodash/isNumber'; -import type { - RGBColor, - ServerField, - TableFieldBackgroundSettings, +import { + GradientNullModes, + type RGBColor, + type ServerField, + type TableFieldBackgroundSettings, } from '../../../../../../../../../shared'; import type {ChartColorsConfig} from '../../../../types'; import { @@ -46,15 +47,13 @@ export function colorizeFlatTableColumn({ index: number; colorsConfig: ChartColorsConfig; }) { - const colorValues = data.reduce( - (acc, row) => { - const rowValue = row[index]; - const parsedRowValue = isNil(rowValue) ? null : parseFloat(rowValue); - - return [...acc, parsedRowValue]; - }, - [] as (number | null)[], - ); + const nilValue = colorsConfig.nullMode === GradientNullModes.AsZero ? 0 : null; + const colorValues = data.reduce<(number | null)[]>((acc, row) => { + const rowValue = row[index]; + const parsedRowValue = isNil(rowValue) ? nilValue : parseFloat(rowValue); + acc.push(parsedRowValue); + return acc; + }, []); const {min, mid, max} = getThresholdValues(colorsConfig, colorValues.filter(isNumber)); const currentGradient = getCurrentGradient(colorsConfig); diff --git a/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts b/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts index eec53d17d4..326eca2b3e 100644 --- a/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts +++ b/src/server/modes/charts/plugins/datalens/preparers/flat-table/index.ts @@ -7,6 +7,7 @@ import type { } from '../../../../../../../shared'; import { DATASET_FIELD_TYPES, + GradientNullModes, IS_NULL_FILTER_TEMPLATE, MINIMUM_FRACTION_DIGITS, isDateField, @@ -277,7 +278,7 @@ function prepareFlatTable({ if (colors.length) { const valueColor = values[iColor]; - if (valueColor !== null) { + if (valueColor !== null || colorsConfig.nullMode === GradientNullModes.AsZero) { cell.color = Number(valueColor); } } diff --git a/src/shared/constants/gradients/index.ts b/src/shared/constants/gradients/index.ts index 8b223cf25f..9ec8d6e65c 100644 --- a/src/shared/constants/gradients/index.ts +++ b/src/shared/constants/gradients/index.ts @@ -1,4 +1,5 @@ import type {ColorPalette} from '../../types/color-palettes'; +import type {ValueOf} from '../../types/utility-types'; import { THREE_POINT_DEFAULT_GRADIENT, @@ -27,6 +28,13 @@ export interface Gradient { colors: string[]; } +export const GradientNullModes = { + Ignore: 'ignore', + AsZero: 'as-0', +}; + +export type GradientNullMode = ValueOf; + export interface RGBColor { red: number; green: number; diff --git a/src/shared/types/config/wizard/v12.ts b/src/shared/types/config/wizard/v12.ts index 905a357f26..d70e5e8097 100644 --- a/src/shared/types/config/wizard/v12.ts +++ b/src/shared/types/config/wizard/v12.ts @@ -1,4 +1,4 @@ -import type {MarkupType, WidgetSizeType} from '../../..'; +import type {GradientNullMode, MarkupType, WidgetSizeType} from '../../..'; import type {ColorMode} from '../../../constants'; import type {DatasetFieldCalcMode, ParameterDefaultValue} from '../../dataset'; import type { @@ -294,6 +294,7 @@ export type V12ColorsConfig = { coloredByMeasure?: boolean; palette?: string; colorMode?: ColorMode; + nullMode?: GradientNullMode; }; export type V12ShapesConfig = { diff --git a/src/shared/types/wizard/background-settings.ts b/src/shared/types/wizard/background-settings.ts index 1d6901002a..50ca20cfde 100644 --- a/src/shared/types/wizard/background-settings.ts +++ b/src/shared/types/wizard/background-settings.ts @@ -7,7 +7,8 @@ type GradientFields = | 'rightThreshold' | 'gradientPalette' | 'gradientMode' - | 'reversed'; + | 'reversed' + | 'nullMode'; export interface TableFieldBackgroundSettings { enabled: boolean; diff --git a/src/shared/types/wizard/index.ts b/src/shared/types/wizard/index.ts index 17be61dc03..11a9cfc930 100644 --- a/src/shared/types/wizard/index.ts +++ b/src/shared/types/wizard/index.ts @@ -13,6 +13,7 @@ import type { } from '../'; import type { ColorMode, + GradientNullMode, GradientType, NavigatorLinesMode, NavigatorPeriod, @@ -75,6 +76,7 @@ export interface ColorsConfig { coloredByMeasure?: boolean; palette?: string; colorMode?: ColorMode; + nullMode?: GradientNullMode; } export enum LabelsPositions { diff --git a/src/ui/units/wizard/actions/dialog.ts b/src/ui/units/wizard/actions/dialog.ts index af67863c82..2e6f8ee37e 100644 --- a/src/ui/units/wizard/actions/dialog.ts +++ b/src/ui/units/wizard/actions/dialog.ts @@ -279,6 +279,7 @@ export function openDialogColors({item, onApply, colorSectionFields}: OpenDialog } }, colorsConfig, + canSetNullMode: true, }), ); } diff --git a/src/ui/units/wizard/actions/dialogColor.ts b/src/ui/units/wizard/actions/dialogColor.ts index 290ef56f0f..1f40c9d5f1 100644 --- a/src/ui/units/wizard/actions/dialogColor.ts +++ b/src/ui/units/wizard/actions/dialogColor.ts @@ -1,6 +1,6 @@ import {selectAvailableClientGradients} from 'constants/common'; -import type {ColorsConfig, Field, GradientType, PartialBy} from 'shared'; +import type {ColorsConfig, Field, GradientNullMode, GradientType, PartialBy} from 'shared'; import {closeDialog, openDialog} from 'store/actions/dialog'; import type {DatalensGlobalState} from 'ui'; @@ -42,6 +42,7 @@ export interface GradientState { rightThreshold?: string; gradientPalette: string; validationStatus?: ValidationStatus; + nullMode?: GradientNullMode; } export const RESET_DIALOG_COLOR_STATE = Symbol('wizard/dialogColor/RESET_DIALOG_COLOR_STATE'); @@ -141,6 +142,7 @@ export function prepareDialogColorState(props: { middleThreshold: colorsConfig?.middleThreshold || defaultGradientState.middleThreshold, rightThreshold: colorsConfig?.rightThreshold || defaultGradientState.rightThreshold, gradientPalette: colorsConfig?.gradientPalette || defaultGradientState.gradientPalette, + nullMode: colorsConfig?.nullMode || defaultGradientState.nullMode, }; const gradients = selectAvailableClientGradients(getState(), gradientState.gradientMode); diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx b/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx index a468781bca..5323e38cd9 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/ColorSettingsContainer/ColorSettingsContainer.tsx @@ -48,6 +48,7 @@ type OwnProps = { onColorModeChange: (value: ColorMode) => void; isColorModeChangeAvailable: boolean; colorSectionFields?: Field[]; + canSetNullMode?: boolean; }; type StateProps = ReturnType; diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss index a255a9839a..33fde7af43 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.scss @@ -11,7 +11,7 @@ } &_gradient-mode { - height: 370px; + height: 380px; .color-settings-container { &__row { diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx index f9a047ed7a..7d974d64a8 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColor.tsx @@ -11,10 +11,15 @@ import {bindActionCreators} from 'redux'; import type {ColorsConfig, Field} from 'shared'; import {ColorMode, isMeasureValue} from 'shared'; import type {DatalensGlobalState} from 'ui'; +import {ALLOWED_FOR_NULL_MODE_VISUALIZATIONS} from 'ui/units/wizard/constants/dialogColor'; import {setDialogColorPaletteState} from 'units/wizard/actions/dialogColor'; import {selectDataset, selectParameters} from 'units/wizard/selectors/dataset'; import {selectUpdates} from 'units/wizard/selectors/preview'; -import {selectDashboardParameters, selectFilters} from 'units/wizard/selectors/visualization'; +import { + selectDashboardParameters, + selectFilters, + selectVisualization, +} from 'units/wizard/selectors/visualization'; import { isGradientDialog, @@ -46,6 +51,7 @@ interface OwnProps { colorsConfig: ColorsConfig; extra?: ExtraSettings; isColorModeChangeAvailable: boolean; + canSetNullMode?: boolean; } type StateProps = ReturnType; @@ -87,15 +93,26 @@ class DialogColorComponent extends React.Component { } render() { - const {item, items, dataset, isColorModeChangeAvailable, colorSectionFields} = this.props; + const { + item, + items, + dataset, + isColorModeChangeAvailable, + colorSectionFields, + visualization, + } = this.props; const {mountedColors = {}} = this.props.paletteState; const {validationStatus} = this.props.gradientState; const {colorMode} = this.state; - if (!item || !dataset) { + if (!item || !dataset || !visualization) { return null; } + const canSetNullMode = + this.props.canSetNullMode && + (ALLOWED_FOR_NULL_MODE_VISUALIZATIONS as string[]).includes(visualization.id); + return (
@@ -117,6 +134,7 @@ class DialogColorComponent extends React.Component { colorMode={this.state.colorMode} isColorModeChangeAvailable={isColorModeChangeAvailable} onColorModeChange={this.onColorModeChange} + canSetNullMode={canSetNullMode} /> { leftThreshold, middleThreshold, rightThreshold, + nullMode, } = this.props.gradientState; config = { @@ -195,6 +214,7 @@ class DialogColorComponent extends React.Component { middleThreshold, rightThreshold, colorMode, + nullMode, }; return config; @@ -228,6 +248,7 @@ const mapStateToProps = (state: DatalensGlobalState) => { dataset: selectDataset(state), gradientState: selectDialogColorGradientState(state), paletteState: selectDialogColorPaletteState(state), + visualization: selectVisualization(state), }; }; diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss index 39bc2eb049..f90f3bcd7b 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.scss @@ -2,48 +2,36 @@ .dialog-color-gradient { &__row { - margin-bottom: 20px; - vertical-align: middle; - line-height: 20px; - display: flex; - align-items: center; - } + --gc-form-row-label-width: 220px; + margin-bottom: var(--g-spacing-4); - &__label { - display: inline-block; - margin-right: 20px; + :last-of-type { + margin-bottom: 0; + } } &__palette-select { - width: 204px; + padding-inline-end: var(--g-spacing-2); + width: var(--gc-form-row-label-width); display: inline-block; } &__reverse-button { - margin-left: 6px; + margin-left: var(--g-spacing-2); line-height: 24px; vertical-align: middle; } - &__thresholds-checkbox { - margin-right: 10px; - } - &__threshold-inputs-wrapper { display: flex; width: 395px; - padding-right: 36px; - height: 28px; + padding-right: 44px; justify-content: space-between; } &__threshold-input { display: inline-block; - width: 80px; - } - - &__error-label { - margin: 20px 32px; + width: 100px; } &__preview { @@ -51,7 +39,6 @@ height: 28px; background: #000000; vertical-align: middle; - margin-left: 12px; } &__select { @@ -63,6 +50,6 @@ } &__hint-icon { - margin-left: 5px; + margin-left: var(--g-spacing-1); } } diff --git a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx index 481fea1f4d..7c1ab98061 100644 --- a/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx +++ b/src/ui/units/wizard/components/Dialogs/DialogColor/DialogColorGradient/DialogColorGradient.tsx @@ -1,10 +1,9 @@ import React from 'react'; -import {HelpPopover} from '@gravity-ui/components'; +import {FormRow, HelpPopover} from '@gravity-ui/components'; import {ArrowRightArrowLeft} from '@gravity-ui/icons'; -import {Button, Checkbox, Icon, RadioButton, Select, TextInput} from '@gravity-ui/uikit'; +import {Button, Checkbox, Flex, Icon, RadioButton, Select, TextInput} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; -import {FieldWrapper} from 'components/FieldWrapper/FieldWrapper'; import {i18n} from 'i18n'; import type {DatalensGlobalState} from 'index'; import {connect} from 'react-redux'; @@ -13,6 +12,7 @@ import {bindActionCreators} from 'redux'; import type {Field, GradientPalettes} from 'shared'; import {DialogColorQa, GradientType, isMeasureValue} from 'shared'; import {selectAvailableClientGradients, selectDefaultClientGradient} from 'ui'; +import {NULLS_OPTIONS} from 'ui/units/wizard/constants/dialogColor'; import type {GradientState} from 'units/wizard/actions/dialogColor'; import {setDialogColorGradientState} from 'units/wizard/actions/dialogColor'; import {GradientPalettePreview} from 'units/wizard/components/GradientPalettePreview/GradientPalettePreview'; @@ -40,6 +40,7 @@ interface Props extends StateProps, DispatchProps { }; setGradientState: (newGradientState: Partial) => void; item: Field; + canSetNullMode?: boolean; } class DialogColorGradientBody extends React.Component { @@ -75,7 +76,7 @@ class DialogColorGradientBody extends React.Component { rightThreshold, validationStatus, } = this.props.gradientState; - const {extra = {}, item, gradients} = this.props; + const {extra = {}, item, gradients, canSetNullMode} = this.props; const currentPalette = this.getCurrentGradientPalette(gradients); const options = getGradientSelectorItems(gradients); @@ -90,15 +91,17 @@ class DialogColorGradientBody extends React.Component { colors.reverse(); } + const leftThresholdError = validationStatus?.left?.text; + const middleThresholdError = validationStatus?.middle?.text; + const rightThresholdError = validationStatus?.right?.text; + return ( -
- {i18n('wizard', 'label_gradient-type')} + { - const gradientType = event.target.value as GradientType; + onUpdate={(gradientType) => { this.props.setGradientState({ gradientMode: gradientType, gradientPalette: selectDefaultClientGradient(gradientType), @@ -113,10 +116,9 @@ class DialogColorGradientBody extends React.Component { {i18n('wizard', 'label_3-point')} -
+ {extra.polygonBorders && ( -
- {i18n('wizard', 'label_borders')} + { {i18n('wizard', 'label_hide')} -
+ )} -
+
= ( props: ParameterNameInputProps, @@ -43,7 +44,7 @@ export const ParameterNameInput: React.FC = ( ); return ( - + { +const OperationSelector = ({className}: {className?: string}) => { const dispatch = useDispatch(); const {operation} = useSelector(selectSelectorDialog); const operations = useSelector(selectInputOperations); @@ -67,7 +67,7 @@ const OperationSelector: React.FC = () => { ); return ( - + = (props: ListVal return ( - {hasMultiselectValue && } + {hasMultiselectValue && } {props.type === 'manual' && ( - + - + = (props: ListVal )} {props.type === 'dynamic' && ( - + { +export const MultiselectableCheckbox = ({className}: {className?: string}) => { const dispatch = useDispatch(); const {multiselectable} = useSelector(selectSelectorDialog); const isFieldDisabled = useSelector(selectIsControlConfigurationDisabled); @@ -34,7 +34,7 @@ export const MultiselectableCheckbox = () => { ); return ( - + { +export const RequiredValueCheckbox = ({className}: {className?: string}) => { const dispatch = useDispatch(); const required = useSelector(selectSelectorRequired); @@ -39,7 +39,7 @@ export const RequiredValueCheckbox = () => { const value = required ?? false; return ( - + { +const InputValueControl = ({className}: {className?: string}) => { const dispatch = useDispatch(); const defaultValue = useSelector(selectSelectorDefaultValue); const isFieldDisabled = useSelector(selectIsControlConfigurationDisabled); @@ -53,7 +53,7 @@ const InputValueControl = () => { }, []); return ( - + { ); }; -const DateValueControl = () => { +const DateValueControl = ({rowClassName}: {rowClassName?: string}) => { const {isRange, acceptableValues, defaultValue, fieldType, sourceType} = useSelector(selectSelectorDialog); const isFieldDisabled = useSelector(selectIsControlConfigurationDisabled); @@ -102,7 +102,7 @@ const DateValueControl = () => { return ( - + { {sourceType === 'manual' ? ( - + { /> ) : null} - + { ); }; -const CheckboxValueControl = () => { +const CheckboxValueControl = ({className}: {className?: string}) => { const dispatch = useDispatch(); const defaultValue = useSelector(selectSelectorDefaultValue); const isFieldDisabled = useSelector(selectIsControlConfigurationDisabled); @@ -159,7 +159,7 @@ const CheckboxValueControl = () => { ); return ( - + { ); }; -const ValueSelector = () => { +const ValueSelector = ({rowClassName}: {rowClassName?: string}) => { const {sourceType} = useSelector(selectSelectorDialog); const controlType = useSelector(selectSelectorControlType); const workbookId = useSelector(selectWorkbookId); @@ -271,19 +271,19 @@ const ValueSelector = () => { switch (controlType) { case 'date': { - inputControl = ; + inputControl = ; break; } case 'select': { - inputControl = ; + inputControl = ; break; } case 'input': { - inputControl = ; + inputControl = ; break; } case 'checkbox': { - inputControl = ; + inputControl = ; } } diff --git a/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.scss b/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.scss index 4a77a9de4a..8d3846f549 100644 --- a/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.scss +++ b/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.scss @@ -1,34 +1,24 @@ .two-column-dialog { - $dialogPaddingHorizontal: 24px; + --dialog-sidebar-horizontal-padding: var(--g-spacing-4); + --dialog-footer-vertical-padding: 28px; // equal to gravity Dialog footer vertical padding // Header $dialogHeaderPaddingTop: 20px; $dialogHeaderPaddingBottom: 10px; - $dialogHeaderPaddingHorizontal: $dialogPaddingHorizontal; - - // Body - $dialogBodyPaddingHorizontal: $dialogPaddingHorizontal; - $dialogBodyPaddingTop: 14px; - $dialogBodyPaddingTopWithoutSidebar: 10px; - $dialogBodyPaddingBottom: 0px; - - // Footer - $dialogFooterPaddingHorizontal: $dialogPaddingHorizontal; - $dialogFooterPaddingVertical: 28px; border-radius: 5px; overflow: hidden; flex: 1; display: flex; - &__header { - padding: #{$dialogHeaderPaddingTop} #{$dialogHeaderPaddingHorizontal} #{$dialogHeaderPaddingBottom}; - } - &__sidebar { background: var(--g-color-base-generic-ultralight); } + &__sidebar-header { + padding-inline: var(--dialog-sidebar-horizontal-padding); + } + &__sidebar-body { padding: 0; overflow: hidden; @@ -45,16 +35,4 @@ ); } } - - &__content-body { - padding: #{$dialogBodyPaddingTop} #{$dialogBodyPaddingHorizontal} #{$dialogBodyPaddingBottom}; - - &_without-sidebar { - padding-top: #{$dialogBodyPaddingTopWithoutSidebar}; - } - } - - & .g-dialog-footer { - padding: #{$dialogFooterPaddingVertical} #{$dialogFooterPaddingHorizontal}; - } } diff --git a/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.tsx b/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.tsx index 3556acbd43..854a0e2d9e 100644 --- a/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.tsx +++ b/src/ui/components/ControlComponents/TwoColumnDialog/TwoColumnDialog.tsx @@ -46,7 +46,7 @@ function TwoColumnDialog({
{!withoutSidebar && (
- + {sidebar}
)} diff --git a/src/ui/components/DialogChartWidget/DialogChartWidget.scss b/src/ui/components/DialogChartWidget/DialogChartWidget.scss index 2dd91dfec9..930cb9c170 100644 --- a/src/ui/components/DialogChartWidget/DialogChartWidget.scss +++ b/src/ui/components/DialogChartWidget/DialogChartWidget.scss @@ -23,6 +23,7 @@ flex: 1; flex-direction: column; width: 255px; + padding-block-end: var(--dialog-footer-vertical-padding, var(--g-spacing-6)); } &__add-icon { diff --git a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx index 4bb9992ce6..64a3ae8cd2 100644 --- a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx +++ b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx @@ -19,6 +19,9 @@ import {DashCommonQa, DialogDashWidgetQA, EntryScope, Feature, ParamsSettingsQA} import {getEntryHierarchy, getEntryVisualizationType} from 'shared/schema/mix/helpers'; import {Collapse} from 'ui/components/Collapse/Collapse'; import {Interpolate} from 'ui/components/Interpolate'; +import {TabMenu} from 'ui/components/TabMenu/TabMenu'; +import type {UpdateState} from 'ui/components/TabMenu/types'; +import {TabActionType} from 'ui/components/TabMenu/types'; import {DL} from 'ui/constants/common'; import {registry} from '../../registry'; @@ -38,10 +41,6 @@ import type {SetItemDataArgs} from '../../units/dash/store/actions/dashTyped'; import Utils from '../../utils'; import TwoColumnDialog from '../ControlComponents/TwoColumnDialog/TwoColumnDialog'; -import {TabMenu} from './TabMenu/TabMenu'; -import type {UpdateState} from './TabMenu/types'; -import {TabActionType} from './TabMenu/types'; - import './DialogChartWidget.scss'; const imm = new Context(); diff --git a/src/ui/components/DialogChartWidget/TabMenu/TabMenu.scss b/src/ui/components/DialogChartWidget/TabMenu/TabMenu.scss deleted file mode 100644 index 9985fe3065..0000000000 --- a/src/ui/components/DialogChartWidget/TabMenu/TabMenu.scss +++ /dev/null @@ -1,91 +0,0 @@ -$class: '.tab-menu'; -$tabButtonFlatHeight: 40px; -$tabButtonOutlinedHeight: 28px; -$tabButtonOutlinedPadding: 8px; - -#{$class} { - $listItemPadding: 0 24px; - - width: 100%; - max-height: 100%; - font-size: 13px; - - &__list-item { - padding: $listItemPadding; - } - - &__item { - display: flex; - align-items: center; - border-radius: 4px; - height: 40px; - width: 100%; - cursor: pointer; - justify-content: space-between; - } - - &__item-star { - margin-right: 8px; - line-height: 0; - } - - &__item-star-inactive { - color: var(--g-color-text-secondary); - } - - &__item-star-active { - color: var(--g-color-base-misc-heavy-hover); - } - - &__item-text { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - max-width: 160px; - } - - &__item-content { - display: flex; - align-items: center; - width: 100%; - } - - &__list { - height: calc(100% - $tabButtonFlatHeight); - overflow-y: auto; - } - - &_view_outlined { - height: calc(100% - ($tabButtonOutlinedHeight + 2 * $tabButtonOutlinedPadding)); - - #{$class}__buttons-row { - display: flex; - padding: $tabButtonOutlinedPadding 24px; - gap: 6px; - } - - #{$class}__list { - height: 100%; - } - } - - &_view_flat { - #{$class}__action-button { - color: var(--g-color-text-secondary); - display: flex; - padding: $listItemPadding; - align-items: center; - cursor: pointer; - height: 40px; - - &:hover, - &:focus { - background-color: var(--g-color-base-simple-hover); - } - } - - #{$class}__action-button-icon { - margin-right: 8px; - } - } -} diff --git a/src/ui/components/DialogControlsPlacement/DialogControlsPlacement.tsx b/src/ui/components/DialogControlsPlacement/DialogControlsPlacement.tsx deleted file mode 100644 index 304613c988..0000000000 --- a/src/ui/components/DialogControlsPlacement/DialogControlsPlacement.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React from 'react'; - -import {Dialog, List} from '@gravity-ui/uikit'; -import block from 'bem-cn-lite'; -import DialogManager from 'components/DialogManager/DialogManager'; -import {I18n} from 'i18n'; -import {useDispatch, useSelector} from 'react-redux'; -import {DialogGroupControlQa} from 'shared'; -import {BackButton} from 'ui/components/ControlComponents/BackButton/BackButton'; -import {updateSelectorsGroup} from 'ui/store/actions/controlDialog'; -import {selectSelectorsGroup} from 'ui/store/selectors/controlDialog'; -import type {SelectorDialogState} from 'ui/store/typings/controlDialog'; - -import {CONTROLS_PLACEMENT_MODE} from '../../constants/dialogs'; - -import {ControlPlacementRow} from './ControlPlacementRow/ControlPlacementRow'; - -import './DialogControlsPlacement.scss'; - -export const DIALOG_SELECTORS_PLACEMENT = Symbol('DIALOG_SELECTORS_PLACEMENT'); - -export type ControlsPlacementDialogProps = { - onClose: () => void; -}; - -export type OpenDialogControlsPlacementArgs = { - id: typeof DIALOG_SELECTORS_PLACEMENT; - props: ControlsPlacementDialogProps; -}; - -const b = block('controls-placement-dialog'); - -const i18n = I18n.keyset('dash.controls-placement-dialog.edit'); - -const resetAutoValues = (group: SelectorDialogState[]) => - group.map((item) => - item.placementMode === CONTROLS_PLACEMENT_MODE.AUTO ? {...item, width: ''} : item, - ); - -const DialogControlsPlacement = ({onClose}: ControlsPlacementDialogProps) => { - const selectorsGroup = useSelector(selectSelectorsGroup); - const [itemsState, setItemsState] = React.useState(selectorsGroup.group); - const [errorsIndexes, setErrorsIndexes] = React.useState([]); - const [showErrors, setShowErrors] = React.useState(false); - const dispatch = useDispatch(); - - React.useEffect(() => { - if (!errorsIndexes.length) { - setShowErrors(false); - } - }, [errorsIndexes.length]); - - const moveItem = React.useCallback((oldIndex: number, newIndex: number) => { - setItemsState((prevItemsState) => { - const dragItem = prevItemsState[oldIndex]; - const newItemsState = prevItemsState.filter((_, index) => index !== oldIndex); - newItemsState.splice(newIndex, 0, dragItem); - return newItemsState; - }); - }, []); - - const handleApplyClick = React.useCallback(() => { - if (errorsIndexes.length) { - setShowErrors(true); - return; - } - const updatedItemsState = resetAutoValues(itemsState); - dispatch( - updateSelectorsGroup({ - ...selectorsGroup, - group: updatedItemsState, - }), - ); - onClose(); - }, [selectorsGroup, itemsState, dispatch, onClose, errorsIndexes.length]); - - const handlePlacementModeUpdate = React.useCallback( - (targetIndex: number, newType: SelectorDialogState['placementMode']) => { - setItemsState((prevItemsState) => { - return prevItemsState.map((item, index) => { - if (index !== targetIndex) { - return item; - } - return {...item, placementMode: newType}; - }); - }); - }, - [], - ); - - const handlePlacementValueUpdate = React.useCallback( - (targetIndex: number, newValue: string) => { - setItemsState((prevItemsState) => { - return prevItemsState.map((item, index) => { - if (index !== targetIndex) { - return item; - } - return {...item, width: newValue}; - }); - }); - }, - [], - ); - - const handleError = React.useCallback((index: number, isError: boolean) => { - if (!isError) { - setErrorsIndexes((prevErrors) => prevErrors.filter((error) => error !== index)); - return; - } - - setErrorsIndexes((prevErrors) => { - if (prevErrors.includes(index)) { - return prevErrors; - } - return [...prevErrors, index]; - }); - }, []); - - const renderControlPlacementRow = React.useCallback( - (item, _, index) => { - return ( - - ); - }, - [handleError, handlePlacementModeUpdate, handlePlacementValueUpdate, showErrors], - ); - - return ( - - } - /> - -
{i18n('label_note')}
-
- moveItem(oldIndex, newIndex)} - itemClassName={b('list-item-container')} - renderItem={renderControlPlacementRow} - /> -
- {showErrors &&
{i18n('label_field-validation')}
} -
- -
- ); -}; - -DialogManager.registerDialog(DIALOG_SELECTORS_PLACEMENT, DialogControlsPlacement); diff --git a/src/ui/components/DialogControlsPlacement/ControlPlacementRow/ControlPlacementRow.tsx b/src/ui/components/DialogExtendedSettings/ControlPlacementRow/ControlPlacementRow.tsx similarity index 94% rename from src/ui/components/DialogControlsPlacement/ControlPlacementRow/ControlPlacementRow.tsx rename to src/ui/components/DialogExtendedSettings/ControlPlacementRow/ControlPlacementRow.tsx index 1a6d41013e..d987afeb61 100644 --- a/src/ui/components/DialogControlsPlacement/ControlPlacementRow/ControlPlacementRow.tsx +++ b/src/ui/components/DialogExtendedSettings/ControlPlacementRow/ControlPlacementRow.tsx @@ -8,11 +8,11 @@ import {EMPTY_VALUE} from 'ui/units/dash/modules/constants'; import {CONTROLS_PLACEMENT_MODE} from '../../../constants/dialogs'; -import '../DialogControlsPlacement.scss'; +import '../DialogExtendedSettings.scss'; -const b = block('controls-placement-dialog'); +const b = block('extended-settings-dialog'); -const i18n = I18n.keyset('dash.controls-placement-dialog.edit'); +const i18n = I18n.keyset('dash.extended-settings-dialog.edit'); type ControlPlacementRowProps = { onPlacementModeUpdate: ( diff --git a/src/ui/components/DialogControlsPlacement/DialogControlsPlacement.scss b/src/ui/components/DialogExtendedSettings/DialogExtendedSettings.scss similarity index 58% rename from src/ui/components/DialogControlsPlacement/DialogControlsPlacement.scss rename to src/ui/components/DialogExtendedSettings/DialogExtendedSettings.scss index e3872d0a01..e0dcbc12b1 100644 --- a/src/ui/components/DialogControlsPlacement/DialogControlsPlacement.scss +++ b/src/ui/components/DialogExtendedSettings/DialogExtendedSettings.scss @@ -1,6 +1,6 @@ @import '~@gravity-ui/uikit/styles/mixins'; -.controls-placement-dialog { +.extended-settings-dialog { width: 600px; display: flex; flex-direction: column; @@ -8,22 +8,30 @@ &__body { display: flex; flex-direction: column; - padding-bottom: 0; + padding-block-end: 0; + height: 430px; } - &__selectors { - overflow-y: auto; - height: 280px; + &__row { + --gc-form-row-label-width: 272px; + margin-block-end: var(--g-spacing-2); + + &:last-child { + margin-block-end: 0; + } + } + + &__help-icon { + left: var(--g-spacing-1); } &__note { @include text-body-1(); - color: var(--g-color-text-secondary); - margin-bottom: 20px; + margin-block-end: var(--g-spacing-5); } &__list-item-container { - padding: 0 7px; + padding: 0 var(--g-spacing-2); } &__list-item { @@ -34,18 +42,22 @@ height: 40px; width: 100%; cursor: pointer; - gap: 8px; + gap: var(--g-spacing-2); } &__item-title { - flex: 1; + width: 240px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } + &__checkbox { + margin-block-start: 6px; + } + &__radio-button { - width: 180px; + flex: 1; } &__text-input { @@ -54,7 +66,7 @@ &__error { width: 100%; - margin-top: 8px; + margin-block-start: var(--g-spacing-2); color: var(--g-color-text-danger); } } diff --git a/src/ui/components/DialogExtendedSettings/DialogExtendedSettings.tsx b/src/ui/components/DialogExtendedSettings/DialogExtendedSettings.tsx new file mode 100644 index 0000000000..00a790ba34 --- /dev/null +++ b/src/ui/components/DialogExtendedSettings/DialogExtendedSettings.tsx @@ -0,0 +1,328 @@ +import React from 'react'; + +import {FormRow, HelpPopover} from '@gravity-ui/components'; +import {Checkbox, Dialog, List} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import DialogManager from 'components/DialogManager/DialogManager'; +import {I18n} from 'i18n'; +import {useDispatch, useSelector} from 'react-redux'; +import {DialogGroupControlQa, TitlePlacementOption} from 'shared'; +import {BackButton} from 'ui/components/ControlComponents/BackButton/BackButton'; +import {useEffectOnce} from 'ui/hooks'; +import {updateSelectorsGroup} from 'ui/store/actions/controlDialog'; +import {selectSelectorsGroup} from 'ui/store/selectors/controlDialog'; +import type {SelectorDialogState} from 'ui/store/typings/controlDialog'; + +import {CONTROLS_PLACEMENT_MODE} from '../../constants/dialogs'; +import {FormSection} from '../FormSection/FormSection'; + +import {ControlPlacementRow} from './ControlPlacementRow/ControlPlacementRow'; + +import './DialogExtendedSettings.scss'; + +export const DIALOG_EXTENDED_SETTINGS = Symbol('DIALOG_EXTENDED_SETTINGS'); + +export type ExtendedSettingsDialogProps = { + onClose: () => void; + + enableAutoheightDefault?: boolean; +}; + +export type OpenDialogExtendedSettingsArgs = { + id: typeof DIALOG_EXTENDED_SETTINGS; + props: ExtendedSettingsDialogProps; +}; + +const b = block('extended-settings-dialog'); + +const i18n = I18n.keyset('dash.extended-settings-dialog.edit'); + +const resetAutoValues = (group: SelectorDialogState[]) => + group.map((item) => + item.placementMode === CONTROLS_PLACEMENT_MODE.AUTO ? {...item, width: ''} : item, + ); + +const DialogExtendedSettings = ({ + onClose, + enableAutoheightDefault, +}: ExtendedSettingsDialogProps) => { + const selectorsGroup = useSelector(selectSelectorsGroup); + const [itemsState, setItemsState] = React.useState(selectorsGroup.group); + const [errorsIndexes, setErrorsIndexes] = React.useState([]); + const [showErrors, setShowErrors] = React.useState(false); + const dispatch = useDispatch(); + + React.useEffect(() => { + if (!errorsIndexes.length) { + setShowErrors(false); + } + }, [errorsIndexes.length]); + + const moveItem = React.useCallback((oldIndex: number, newIndex: number) => { + setItemsState((prevItemsState) => { + const dragItem = prevItemsState[oldIndex]; + const newItemsState = prevItemsState.filter((_, index) => index !== oldIndex); + newItemsState.splice(newIndex, 0, dragItem); + return newItemsState; + }); + }, []); + + const handleApplyClick = React.useCallback(() => { + if (errorsIndexes.length) { + setShowErrors(true); + return; + } + const updatedItemsState = resetAutoValues(itemsState); + dispatch( + updateSelectorsGroup({ + ...selectorsGroup, + group: updatedItemsState, + }), + ); + onClose(); + }, [selectorsGroup, itemsState, dispatch, onClose, errorsIndexes.length]); + + const handlePlacementModeUpdate = React.useCallback( + (targetIndex: number, newType: SelectorDialogState['placementMode']) => { + setItemsState((prevItemsState) => { + return prevItemsState.map((item, index) => { + if (index !== targetIndex) { + return item; + } + return {...item, placementMode: newType}; + }); + }); + }, + [], + ); + + const handlePlacementValueUpdate = React.useCallback( + (targetIndex: number, newValue: string) => { + setItemsState((prevItemsState) => { + return prevItemsState.map((item, index) => { + if (index !== targetIndex) { + return item; + } + return {...item, width: newValue}; + }); + }); + }, + [], + ); + + const handleError = React.useCallback((index: number, isError: boolean) => { + if (!isError) { + setErrorsIndexes((prevErrors) => prevErrors.filter((error) => error !== index)); + return; + } + + setErrorsIndexes((prevErrors) => { + if (prevErrors.includes(index)) { + return prevErrors; + } + return [...prevErrors, index]; + }); + }, []); + + const renderControlPlacementRow = React.useCallback( + (item, _, index) => { + return ( + + ); + }, + [handleError, handlePlacementModeUpdate, handlePlacementValueUpdate, showErrors], + ); + + const isMultipleSelectors = selectorsGroup.group?.length > 1; + + const handleChangeAutoHeight = React.useCallback( + (value) => { + dispatch( + updateSelectorsGroup({ + ...selectorsGroup, + autoHeight: value, + }), + ); + }, + [dispatch, selectorsGroup], + ); + + const handleChangeButtonApply = React.useCallback( + (value) => { + dispatch( + updateSelectorsGroup({ + ...selectorsGroup, + buttonApply: value, + }), + ); + }, + [dispatch, selectorsGroup], + ); + + const handleChangeButtonReset = React.useCallback( + (value) => { + dispatch( + updateSelectorsGroup({ + ...selectorsGroup, + buttonReset: value, + }), + ); + }, + [dispatch, selectorsGroup], + ); + + const handleChangeUpdateControls = React.useCallback( + (value: boolean) => { + dispatch( + updateSelectorsGroup({ + ...selectorsGroup, + updateControlsOnChange: value, + }), + ); + }, + [dispatch, selectorsGroup], + ); + + useEffectOnce(() => { + if (enableAutoheightDefault) { + handleChangeAutoHeight(true); + } + }); + + const showAutoHeight = + (isMultipleSelectors || + selectorsGroup.buttonApply || + selectorsGroup.buttonReset || + // until we have supported automatic height adjustment for case with top title placement, + // we allow to enable autoheight + selectorsGroup.group[0].titlePlacement === TitlePlacementOption.Top) && + !enableAutoheightDefault; + const showUpdateControlsOnChange = selectorsGroup.buttonApply && isMultipleSelectors; + + return ( + + } + /> + + + + {i18n('label_apply-button-checkbox')} + + + } + > + + + + {i18n('label_reset-button-checkbox')} + + + } + > + + + {showAutoHeight && ( + + + + )} + {showUpdateControlsOnChange && ( + + {i18n('label_update-controls-on-change')} + + + } + > + + + )} + + {isMultipleSelectors && ( + +
{i18n('label_note')}
+
+ moveItem(oldIndex, newIndex)} + itemClassName={b('list-item-container')} + renderItem={renderControlPlacementRow} + /> +
+ {showErrors && ( +
{i18n('label_field-validation')}
+ )} +
+ )} +
+ +
+ ); +}; + +DialogManager.registerDialog(DIALOG_EXTENDED_SETTINGS, DialogExtendedSettings); diff --git a/src/ui/components/DialogGroupControl/DialogGroupControl.scss b/src/ui/components/DialogGroupControl/DialogGroupControl.scss index 01b5258e01..8f86d65944 100644 --- a/src/ui/components/DialogGroupControl/DialogGroupControl.scss +++ b/src/ui/components/DialogGroupControl/DialogGroupControl.scss @@ -4,19 +4,6 @@ display: flex; flex-direction: column; - &__section { - margin-bottom: 15px; - - &_top-divider { - border-top: 1px solid var(--g-color-line-generic); - padding-top: 15px; - } - - &_bottom-divider { - border-bottom: 1px solid var(--g-color-line-generic); - } - } - &__sidebar { display: flex; flex-direction: column; @@ -27,50 +14,24 @@ } &__selectors-list { - overflow-y: auto; + overflow-y: hidden; flex: 1; display: flex; flex-direction: column; } &__settings { - border-top: 1px solid var(--g-color-line-generic); - padding: 25px 24px 32px; - } - - &__settings-container { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 5px; - - &:not(:last-of-type) { - margin-bottom: 22px; - } - } - - &__order-selectors-button { - margin-top: 12px; - } - - &__help-icon { - left: 5px; + padding: var(--g-spacing-2) var(--dialog-sidebar-horizontal-padding) + calc(var(--dialog-footer-vertical-padding, 28px) + var(--g-spacing-1)); } &__row { display: flex; - align-items: center; - border-radius: 3px; - font-size: 13px; width: 100%; - height: 40px; + margin-block-end: var(--g-spacing-2); - svg { - margin-right: 10px; - } - - &:not(:last-child) { - margin-bottom: 10px; + &:last-child { + margin-block-end: 0; } &_add { @@ -88,8 +49,4 @@ display: flex; flex-direction: column; } - - &__body { - padding: 14px 20px 0px 32px; - } } diff --git a/src/ui/components/DialogGroupControl/GroupControlBody/GroupControlBody.tsx b/src/ui/components/DialogGroupControl/GroupControlBody/GroupControlBody.tsx index d16ca032ae..3a78c6a95f 100644 --- a/src/ui/components/DialogGroupControl/GroupControlBody/GroupControlBody.tsx +++ b/src/ui/components/DialogGroupControl/GroupControlBody/GroupControlBody.tsx @@ -20,9 +20,11 @@ import {ELEMENT_TYPE} from 'ui/store/constants/controlDialog'; import {selectSelectorControlType} from 'ui/store/selectors/controlDialog'; import Utils from 'ui/utils/utils'; +import {FormSection} from '../../FormSection/FormSection'; + import '../DialogGroupControl.scss'; -const b = block('group-control-dialog'); +export const b = block('group-control-dialog'); const i18n = I18n.keyset('dash.group-controls-dialog.edit'); export const GroupControlBody: React.FC<{ @@ -35,53 +37,36 @@ export const GroupControlBody: React.FC<{ return ( - - - -
+ + + + -
- -
- -
-
- -
- {isTypeNotCheckbox && ( - -
- -
-
- -
-
- -
-
- )} -
- -
- - {!Utils.isEnabledFeature(Feature.ConnectionBasedControl) && ( -
- -
- )} - {isTypeNotCheckbox && ( -
- -
- )} -
- -
+ + + + {!Utils.isEnabledFeature(Feature.ConnectionBasedControl) && ( + + )} + + {isTypeNotCheckbox && } + + + + {isTypeNotCheckbox && ( + + + + + + )} + +
); }; diff --git a/src/ui/components/DialogGroupControl/GroupControlSidebar/GroupControlSidebar.tsx b/src/ui/components/DialogGroupControl/GroupControlSidebar/GroupControlSidebar.tsx index 1ff5c54c53..f6d943c470 100644 --- a/src/ui/components/DialogGroupControl/GroupControlSidebar/GroupControlSidebar.tsx +++ b/src/ui/components/DialogGroupControl/GroupControlSidebar/GroupControlSidebar.tsx @@ -1,19 +1,15 @@ import React from 'react'; -import {HelpPopover} from '@gravity-ui/components'; import {Gear} from '@gravity-ui/icons'; -import {Button, Checkbox, Icon} from '@gravity-ui/uikit'; +import {Button, Icon} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {I18n} from 'i18n'; import {useDispatch, useSelector} from 'react-redux'; import type {DashTabItemControlData, DashTabItemGroupControlData} from 'shared'; -import { - DashTabItemControlSourceType, - DashTabItemType, - DialogGroupControlQa, - TitlePlacementOption, -} from 'shared'; -import {useEffectOnce} from 'ui/hooks'; +import {DashTabItemControlSourceType, DashTabItemType, DialogGroupControlQa} from 'shared'; +import {TabMenu} from 'ui/components/TabMenu/TabMenu'; +import type {TabMenuItemData, UpdateState} from 'ui/components/TabMenu/types'; +import {TabActionType} from 'ui/components/TabMenu/types'; import { addSelectorToGroup, setActiveSelectorIndex, @@ -30,10 +26,7 @@ import type {SelectorDialogState, SelectorsGroupDialogState} from 'ui/store/typi import type {CopiedConfigData} from 'ui/units/dash/modules/helpers'; import {isItemPasteAllowed} from 'ui/units/dash/modules/helpers'; -import {TabMenu} from '../../DialogChartWidget/TabMenu/TabMenu'; -import type {TabMenuItemData, UpdateState} from '../../DialogChartWidget/TabMenu/types'; -import {TabActionType} from '../../DialogChartWidget/TabMenu/types'; -import {DIALOG_SELECTORS_PLACEMENT} from '../../DialogControlsPlacement/DialogControlsPlacement'; +import {DIALOG_EXTENDED_SETTINGS} from '../../DialogExtendedSettings/DialogExtendedSettings'; import '../DialogGroupControl.scss'; @@ -92,8 +85,6 @@ export const GroupControlSidebar: React.FC<{ selectorsGroup.group?.[0]?.title === i18n('label_default-tab', {index: 1}) ? 2 : 1; const [defaultTabIndex, setDefaultTabIndex] = React.useState(initialTabIndex); - const isMultipleSelectors = selectorsGroup.group?.length > 1; - const updateSelectorsList = React.useCallback( ({items, selectedItemIndex, action}: UpdateState) => { if (action === TabActionType.Skipped) { @@ -129,64 +120,17 @@ export const GroupControlSidebar: React.FC<{ dispatch(closeDialog()); }, [dispatch]); - const handleSelectorsPlacementClick = React.useCallback(() => { + const handleExtendedSettingsClick = React.useCallback(() => { dispatch( openDialog({ - id: DIALOG_SELECTORS_PLACEMENT, + id: DIALOG_EXTENDED_SETTINGS, props: { onClose: handleClosePlacementDialog, + enableAutoheightDefault, }, }), ); - }, [dispatch, handleClosePlacementDialog]); - - const handleChangeAutoHeight = React.useCallback( - (value) => { - dispatch( - updateSelectorsGroup({ - ...selectorsGroup, - autoHeight: value, - }), - ); - }, - [dispatch, selectorsGroup], - ); - - const handleChangeButtonApply = React.useCallback( - (value) => { - dispatch( - updateSelectorsGroup({ - ...selectorsGroup, - buttonApply: value, - }), - ); - }, - [dispatch, selectorsGroup], - ); - - const handleChangeButtonReset = React.useCallback( - (value) => { - dispatch( - updateSelectorsGroup({ - ...selectorsGroup, - buttonReset: value, - }), - ); - }, - [dispatch, selectorsGroup], - ); - - const handleChangeUpdateControls = React.useCallback( - (value: boolean) => { - dispatch( - updateSelectorsGroup({ - ...selectorsGroup, - updateControlsOnChange: value, - }), - ); - }, - [dispatch, selectorsGroup], - ); + }, [dispatch, handleClosePlacementDialog, enableAutoheightDefault]); const handleUpdateItem = React.useCallback( (title: string) => { @@ -199,22 +143,6 @@ export const GroupControlSidebar: React.FC<{ [dispatch], ); - useEffectOnce(() => { - if (enableAutoheightDefault) { - handleChangeAutoHeight(true); - } - }); - - const showAutoHeight = - (isMultipleSelectors || - selectorsGroup.buttonApply || - selectorsGroup.buttonReset || - // until we have supported automatic height adjustment for case with top title placement, - // we allow to enable autoheight - selectorsGroup.group[0].titlePlacement === TitlePlacementOption.Top) && - !enableAutoheightDefault; - const showUpdateControlsOnChange = selectorsGroup.buttonApply && isMultipleSelectors; - return (
@@ -228,85 +156,20 @@ export const GroupControlSidebar: React.FC<{ enableActionMenu={true} onPasteItems={handlePasteItems} canPasteItems={canPasteItems} - addButtonView="outlined" onCopyItem={handleCopyItem} onUpdateItem={handleUpdateItem} />
- {showUpdateControlsOnChange && ( -
-
- {i18n('label_update-controls-on-change')} - -
- -
- )} - {showAutoHeight && ( -
-
- {i18n('label_autoheight-checkbox')} -
- -
- )} -
-
- {i18n('label_apply-button-checkbox')} - -
- -
-
-
- {i18n('label_reset-button-checkbox')} - -
- -
- - {isMultipleSelectors && ( - - )} +
); diff --git a/src/ui/components/FormSection/FormSection.scss b/src/ui/components/FormSection/FormSection.scss new file mode 100644 index 0000000000..b703a28799 --- /dev/null +++ b/src/ui/components/FormSection/FormSection.scss @@ -0,0 +1,10 @@ +.form-section { + &:not(:last-child) { + margin-block-end: var(--g-spacing-6); + } + + &__title { + display: block; + margin-block-end: var(--g-spacing-4); + } +} diff --git a/src/ui/components/FormSection/FormSection.tsx b/src/ui/components/FormSection/FormSection.tsx new file mode 100644 index 0000000000..8c9a51bb7f --- /dev/null +++ b/src/ui/components/FormSection/FormSection.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import {Text} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import './FormSection.scss'; + +export const b = block('form-section'); + +interface FormSectionProps { + title: string; + children: React.ReactNode; + className?: string; +} + +export function FormSection({title, children, className}: FormSectionProps) { + return ( +
+ + {title} + + {children} +
+ ); +} diff --git a/src/ui/components/ListWithMenu/ListWithMenu.tsx b/src/ui/components/ListWithMenu/ListWithMenu.tsx index 06044dde68..9df93a9692 100644 --- a/src/ui/components/ListWithMenu/ListWithMenu.tsx +++ b/src/ui/components/ListWithMenu/ListWithMenu.tsx @@ -6,7 +6,7 @@ import {DropdownMenu, Icon, List} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {I18n} from 'i18n'; import {DialogGroupControlQa, TabMenuQA} from 'shared'; -import {TabActionType} from 'ui/components/DialogChartWidget/TabMenu/types'; +import {TabActionType} from 'ui/components/TabMenu/types'; import EditedTabItem from 'ui/units/dash/containers/Dialogs/Tabs/EditedTabItem'; import './ListWithMenu.scss'; diff --git a/src/ui/components/TabMenu/TabMenu.scss b/src/ui/components/TabMenu/TabMenu.scss new file mode 100644 index 0000000000..ea5df04e76 --- /dev/null +++ b/src/ui/components/TabMenu/TabMenu.scss @@ -0,0 +1,62 @@ +$class: '.tab-menu'; + +#{$class} { + width: 100%; + max-height: 100%; + font-size: var(--g-text-body-1-font-size); + + --_--tab-button-outlined-padding: var(--g-spacing-2); + --_--tab-button-add-height: calc(var(--_--tab-button-outlined-padding) * 2 + 28px); + + &__list-item { + padding: 0 var(--dialog-sidebar-horizontal-padding, var(--g-spacing-6)); + } + + &__item { + display: flex; + align-items: center; + border-radius: 4px; + height: 40px; + width: 100%; + cursor: pointer; + justify-content: space-between; + } + + &__item-star { + margin-inline-end: var(--g-spacing-2); + line-height: 0; + } + + &__item-star-inactive { + color: var(--g-color-text-secondary); + } + + &__item-star-active { + color: var(--g-color-base-misc-heavy-hover); + } + + &__item-text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 160px; + } + + &__item-content { + display: flex; + align-items: center; + width: 100%; + } + + &__list { + height: calc(100% - var(--_--tab-button-add-height)); + overflow-y: auto; + } + + &__buttons-row { + display: flex; + padding: var(--_--tab-button-outlined-padding) + var(--dialog-sidebar-horizontal-padding, var(--g-spacing-6)); + gap: var(--g-spacing-2); + } +} diff --git a/src/ui/components/DialogChartWidget/TabMenu/TabMenu.tsx b/src/ui/components/TabMenu/TabMenu.tsx similarity index 91% rename from src/ui/components/DialogChartWidget/TabMenu/TabMenu.tsx rename to src/ui/components/TabMenu/TabMenu.tsx index a974e91e37..463d46a96e 100644 --- a/src/ui/components/DialogChartWidget/TabMenu/TabMenu.tsx +++ b/src/ui/components/TabMenu/TabMenu.tsx @@ -28,7 +28,6 @@ export const TabMenu = ({ enableActionMenu, items, selectedItemIndex, - addButtonView = 'flat', onUpdate, defaultTabText, onPasteItems, @@ -226,29 +225,12 @@ export const TabMenu = ({ }; const renderButtons = () => { - if (addButtonView === 'flat') { - return ( -
-
- - - {addButtonText || i18n('dash.widget-dialog.edit', 'button_add')} - -
-
- ); - } - + const addBtnText = + !pasteConfig && addButtonText + ? addButtonText + : i18n('dash.widget-dialog.edit', 'button_add'); return ( -
+
{pasteConfig && (