diff --git a/src/shared/constants/widgets.ts b/src/shared/constants/widgets.ts index 0802de432d..d8f7b67b97 100644 --- a/src/shared/constants/widgets.ts +++ b/src/shared/constants/widgets.ts @@ -1,4 +1,4 @@ -import {type ValueOf} from '../types'; +import type {ColorSettings, ValueOf} from '../types'; export const CustomPaletteBgColors = { LIKE_CHART: 'like-chart-bg', @@ -6,14 +6,9 @@ export const CustomPaletteBgColors = { } as const; export const LIKE_CHART_COLOR_TOKEN = 'var(--g-color-base-float)'; +export const TRANSPARENT_COLOR_HEX = '#00000000'; export const BASE_GREY_BACKGROUND_COLOR = 'var(--g-color-base-generic)'; -export type CustomPaletteBgColor = ValueOf; - -export function isCustomPaletteBgColor(color: string): color is CustomPaletteBgColor { - return Object.values(CustomPaletteBgColors).includes(color as CustomPaletteBgColor); -} - export const WIDGET_BG_HEAVY_COLORS_PRESET = [ 'var(--g-color-base-info-heavy)', 'var(--g-color-base-positive-heavy)', @@ -104,6 +99,13 @@ export const CONTROLS_PLACEMENT_MODE = { export function getDefaultWidgetBackgroundColor( isCommonChartDashSettingsEnabled?: boolean, defaultColor: string = CustomPaletteBgColors.NONE, -) { - return isCommonChartDashSettingsEnabled ? '' : defaultColor; + enableMultiThemeColors = true, +): ColorSettings['color'] { + return isCommonChartDashSettingsEnabled + ? getColorObject(undefined, enableMultiThemeColors) + : defaultColor; +} + +export function getColorObject(color: string | undefined, enableMultiThemeColors: boolean) { + return enableMultiThemeColors ? {light: color, dark: color} : {common: color}; } diff --git a/src/shared/modules/dash-scheme-converter.ts b/src/shared/modules/dash-scheme-converter.ts index 108e5bfba5..8f0f061731 100644 --- a/src/shared/modules/dash-scheme-converter.ts +++ b/src/shared/modules/dash-scheme-converter.ts @@ -3,13 +3,23 @@ import omitBy from 'lodash/omitBy'; import {DASH_CURRENT_SCHEME_VERSION} from '../constants/dash'; import {DUPLICATED_WIDGET_BG_COLORS_PRESET} from '../constants/widgets'; -import type {BackgroundSettings, DashData, DashTab, DashTabItem} from '../types'; -import {DashTabConnectionKind, DashTabItemControlElementType, DashTabItemType} from '../types'; +import type {ColorSettings, DashData, DashTab, DashTabItem} from '../types'; +import { + DashTabConnectionKind, + DashTabItemControlElementType, + DashTabItemType, + isOldBackgroundSettings, +} from '../types'; const DATE_FORMAT_V7 = 'YYYY-MM-DD'; -function getActualBackground(background?: BackgroundSettings): BackgroundSettings | undefined { - if (background && DUPLICATED_WIDGET_BG_COLORS_PRESET.includes(background.color)) { +function getActualBackground(background?: ColorSettings): ColorSettings | undefined { + if ( + background && + isOldBackgroundSettings(background) && + background.color && + DUPLICATED_WIDGET_BG_COLORS_PRESET.includes(background.color) + ) { return { color: background.color.replace('medium', 'light-hover'), }; @@ -22,20 +32,12 @@ export function migrateBgColor(item: DashTabItem): DashTabItem { const newItem: DashTabItem = Object.assign({...item}, {data: Object.assign({}, item.data)}); if ('background' in newItem.data) { - if ( - newItem.data.background && - DUPLICATED_WIDGET_BG_COLORS_PRESET.includes(newItem.data.background.color) - ) { - newItem.data.background = getActualBackground(newItem.data.background); - - return newItem; - } + newItem.data.background = getActualBackground(newItem.data.background); + return newItem; } if (newItem.type === DashTabItemType.Widget) { - newItem.data.tabs = newItem.data.tabs.map((tab) => ({ - ...tab, - background: getActualBackground(tab.background), - })); + newItem.data.background = getActualBackground(newItem.data.tabs?.[0]?.background); + newItem.data.tabs.forEach((tab) => delete tab.background); return newItem; } diff --git a/src/shared/types/dash.ts b/src/shared/types/dash.ts index 766c3787f9..414f39395a 100644 --- a/src/shared/types/dash.ts +++ b/src/shared/types/dash.ts @@ -1,4 +1,5 @@ import type {ItemDropProps} from '@gravity-ui/dashkit'; +import type {ThemeType} from '@gravity-ui/uikit'; import type {Operations} from '../modules'; @@ -89,6 +90,7 @@ export type DashSettings = { loadOnlyVisibleCharts?: boolean; margins?: [number, number]; enableAssistant?: boolean; + background?: ActualColorSettings; }; export interface DashData { @@ -138,12 +140,19 @@ export type DashTabItem = | DashTabItemGroupControl | DashTabItemImage; -export type BackgroundSettings = { +export type OldBackgroundSettings = { enabled?: boolean; - color: string; + color?: string; }; +// TODO: remove +export type BackgroundSettings = OldBackgroundSettings; -export function isBackgroundSettings(value: unknown): value is BackgroundSettings { +export type ColorType = ThemeType | 'common'; +export type ColorByTheme = Partial>; +export type ActualColorSettings = {color?: ColorByTheme; enabled?: undefined}; +export type ColorSettings = OldBackgroundSettings | ActualColorSettings; + +export function isOldBackgroundSettings(value: unknown): value is OldBackgroundSettings { return ( typeof value === 'object' && value !== null && @@ -155,6 +164,35 @@ export function isBackgroundSettings(value: unknown): value is BackgroundSetting ); } +export function isActualColorSettings(value: unknown): value is ActualColorSettings { + return ( + typeof value === 'object' && + value !== null && + 'color' in value && + isColorByTheme(value.color) + ); +} + +export function isBackgroundSettings(value: unknown): value is ColorSettings { + return isOldBackgroundSettings(value) || isActualColorSettings(value); +} + +export function isColorByTheme(value: unknown): value is ColorByTheme { + return ( + typeof value === 'object' && + value !== null && + Object.entries(value).every( + ([key, val]) => + ['light', 'dark', 'common'].includes(key) && + ['string', 'undefined'].includes(typeof val), + ) + ); +} + +export function isColorByThemeOrUndefined(value: unknown): value is ColorByTheme | undefined { + return isColorByTheme(value) || value === undefined; +} + export interface DashTabItemBase { id: string; namespace: string; @@ -169,7 +207,7 @@ export interface DashTabItemText extends DashTabItemBase { data: { text: string; autoHeight?: boolean; - background?: BackgroundSettings; + background?: ColorSettings; }; } @@ -187,15 +225,15 @@ export interface DashTabItemTitle extends DashTabItemBase { size: DashTitleSize; showInTOC: boolean; autoHeight?: boolean; - background?: BackgroundSettings; - textColor?: string; + background?: ColorSettings; + textColor?: string | ColorByTheme; hint?: HintSettings; }; } export interface DashTabItemWidget extends DashTabItemBase { type: DashTabItemType.Widget; - data: {hideTitle: boolean; tabs: DashTabItemWidgetTab[]}; + data: {hideTitle: boolean; tabs: DashTabItemWidgetTab[]; background?: ColorSettings}; } export interface DashTabItemWidgetTab { @@ -210,7 +248,7 @@ export interface DashTabItemWidgetTab { params: StringParams; autoHeight?: boolean; enableActionParams?: boolean; - background?: BackgroundSettings; + background?: ColorSettings; } export interface DashTabItemControl extends DashTabItemBase { @@ -386,7 +424,7 @@ export interface DashTabItemImage extends DashTabItemBase { data: { src: string; alt?: string; - background?: BackgroundSettings; + background?: ColorSettings; preserveAspectRatio?: boolean; }; } diff --git a/src/ui/components/DashKit/DashKit.ts b/src/ui/components/DashKit/DashKit.ts index a304c957e5..2382728094 100644 --- a/src/ui/components/DashKit/DashKit.ts +++ b/src/ui/components/DashKit/DashKit.ts @@ -1,5 +1,6 @@ import type {Plugin, PluginDefaultLayout} from '@gravity-ui/dashkit'; import {DashKit} from '@gravity-ui/dashkit'; +import type {ColorSettings} from 'shared'; import {registry} from 'ui/registry'; import {DL} from '../../constants'; @@ -34,21 +35,32 @@ const wrapPlugins = (plugins: Plugin[], pluginDefaultsGetter?: typeof currentDef }); }; +export interface CommonPluginSettings { + background?: ColorSettings; +} + export const getConfiguredDashKit = ( pluginDefaultsGetter: typeof currentDefaultsGetter = null, - options?: {disableHashNavigation?: boolean; disableTitleHints?: boolean}, + options?: { + disableHashNavigation?: boolean; + disableTitleHints?: boolean; + settings: CommonPluginSettings; + }, ) => { if (currentDefaultsGetter !== pluginDefaultsGetter || !isConfigured) { const titleSettings = { + ...options?.settings, hideAnchor: options?.disableHashNavigation, hideHint: options?.disableTitleHints, }; const textSettings = { + ...options?.settings, apiHandler: MarkdownProvider.getMarkdown, }; const controlSettings = { + ...options?.settings, getDistincts: getDistinctsAction(), }; @@ -58,8 +70,8 @@ export const getConfiguredDashKit = ( textPlugin.setSettings(textSettings), pluginControl.setSettings(controlSettings), pluginGroupControl.setSettings(controlSettings), - widgetPlugin, - pluginImage, + widgetPlugin.setSettings(options?.settings ?? {}), + pluginImage.setSettings(options?.settings ?? {}), ], pluginDefaultsGetter, ); diff --git a/src/ui/components/DashKit/plugins/Image/Image.tsx b/src/ui/components/DashKit/plugins/Image/Image.tsx index 53c0bbd3ef..37a6cd32b7 100644 --- a/src/ui/components/DashKit/plugins/Image/Image.tsx +++ b/src/ui/components/DashKit/plugins/Image/Image.tsx @@ -2,12 +2,13 @@ import React from 'react'; import type {Plugin, PluginWidgetProps} from '@gravity-ui/dashkit'; import block from 'bem-cn-lite'; -import type {DashTabItemImage} from 'shared'; -import {DashTabItemType} from 'shared'; +import type {ColorSettings, DashTabItemImage} from 'shared'; +import {CustomPaletteBgColors, DashTabItemType} from 'shared'; import {useBeforeLoad} from '../../../../hooks/useBeforeLoad'; +import type {CommonPluginSettings} from '../../DashKit'; import {useWidgetContext} from '../../context/WidgetContext'; -import {getPreparedWrapSettings} from '../../utils'; +import {usePreparedWrapSettings} from '../../utils'; import {RendererWrapper} from '../RendererWrapper/RendererWrapper'; import './Image.scss'; @@ -18,7 +19,24 @@ type Props = PluginWidgetProps & { data: DashTabItemImage['data'] & PluginWidgetProps['data']; }; -function PluginImage(props: Props, _ref?: React.LegacyRef) { +type PluginImageObjectSettings = CommonPluginSettings; + +type PluginImage = Plugin & { + setSettings: (settings: PluginImageObjectSettings) => PluginImage; + background?: ColorSettings; +}; + +export const pluginImage: PluginImage = { + type: DashTabItemType.Image, + defaultLayout: {w: 12, h: 12, minH: 1, minW: 1}, + setSettings: (settings: PluginImageObjectSettings) => { + pluginImage.background = settings.background; + return pluginImage; + }, + renderer: PluginImageRenderer, +}; + +function PluginImageRenderer(props: Props, _ref?: React.LegacyRef) { const { id, data: {alt, background, src, preserveAspectRatio}, @@ -39,9 +57,11 @@ function PluginImage(props: Props, _ref?: React.LegacyRef) { w: null, }; - const {classMod, style} = React.useMemo(() => { - return getPreparedWrapSettings(background); - }, [background]); + const {style} = usePreparedWrapSettings({ + widgetBackground: background, + globalBackground: pluginImage.background, + defaultOldColor: CustomPaletteBgColors.NONE, + }); React.useEffect(() => { handleUpdate?.(); @@ -56,13 +76,7 @@ function PluginImage(props: Props, _ref?: React.LegacyRef) { ]); return ( - + {alt}) { ); } - -export const pluginImage: Plugin = { - type: DashTabItemType.Image, - defaultLayout: {w: 12, h: 12, minH: 1, minW: 1}, - renderer: PluginImage, -}; diff --git a/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.scss b/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.scss index 9ef546f67e..35e0f6edfe 100644 --- a/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.scss +++ b/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.scss @@ -3,6 +3,7 @@ .dashkit-plugin-container { &__wrapper { height: 100%; + background-color: var(--dl-color-widget-background); &_widget { width: 100%; diff --git a/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.tsx b/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.tsx index ed1ae7f4ce..e7ddadd06a 100644 --- a/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.tsx +++ b/src/ui/components/DashKit/plugins/RendererWrapper/RendererWrapper.tsx @@ -16,24 +16,29 @@ type RendererProps = { beforeContentNode?: React.ReactNode; }; -export const RendererWrapper: React.FC = React.memo( - ({children, type, nodeRef, classMod, beforeContentNode, ...props}) => { - return ( - - {beforeContentNode} -
- {children} -
-
- ); - }, -); - -RendererWrapper.displayName = 'RendererWrapper'; +export const RendererWrapper: React.FC = React.memo(function RendererWrapper({ + children, + type, + nodeRef, + classMod, + beforeContentNode, + style, + ...props +}) { + return ( + + {beforeContentNode} +
+ {children} +
+
+ ); +}); diff --git a/src/ui/components/DashKit/plugins/Text/Text.tsx b/src/ui/components/DashKit/plugins/Text/Text.tsx index 1c50d57215..eb73935681 100644 --- a/src/ui/components/DashKit/plugins/Text/Text.tsx +++ b/src/ui/components/DashKit/plugins/Text/Text.tsx @@ -1,20 +1,26 @@ import React from 'react'; -import type {PluginTextObjectSettings, PluginTextProps} from '@gravity-ui/dashkit'; -import {PluginText, pluginText} from '@gravity-ui/dashkit'; +import type { + PluginTextObjectSettings as DashkitPluginTextObjectSettings, + Plugin, + PluginTextProps, +} from '@gravity-ui/dashkit'; +import {PluginText as PluginTextRenderer, pluginText} from '@gravity-ui/dashkit'; import block from 'bem-cn-lite'; import debounce from 'lodash/debounce'; import get from 'lodash/get'; -import type {DashTabItemText} from 'shared'; +import {CustomPaletteBgColors} from 'shared'; +import type {ColorSettings, DashTabItemText} from 'shared'; import { adjustWidgetLayout as dashkitAdjustWidgetLayout, - getPreparedWrapSettings, + usePreparedWrapSettings, } from 'ui/components/DashKit/utils'; import {YFM_MARKDOWN_CLASSNAME} from 'ui/constants/yfm'; import {usePrevious} from 'ui/hooks'; import {useBeforeLoad} from '../../../../hooks/useBeforeLoad'; import {YfmWrapper} from '../../../YfmWrapper/YfmWrapper'; +import type {CommonPluginSettings} from '../../DashKit'; import {useWidgetContext} from '../../context/WidgetContext'; import {RendererWrapper} from '../RendererWrapper/RendererWrapper'; @@ -72,16 +78,23 @@ const useWatchDomResizeObserver = ({ }, [domElement, onResizeRef]); }; -const textPlugin = { +type PluginTextObjectSettings = CommonPluginSettings & DashkitPluginTextObjectSettings; + +type PluginText = Plugin & { + setSettings: (settings: PluginTextObjectSettings) => PluginText; + background?: ColorSettings; +}; +const textPlugin: PluginText = { ...pluginText, setSettings(settings: PluginTextObjectSettings) { - const {apiHandler} = settings; + const {apiHandler, background} = settings; + textPlugin.background = background; pluginText._apiHandler = apiHandler; return textPlugin; }, renderer: function Wrapper( props: Props, - forwardedRef: React.LegacyRef | undefined, + forwardedRef: React.LegacyRef | undefined, ) { const rootNodeRef = React.useRef(null); const [metaScripts, setMetaScripts] = React.useState(); @@ -174,11 +187,17 @@ const textPlugin = { enable: props.data.autoHeight as boolean, }); - const content = ; + const content = ( + + ); const data = props.data as DashTabItemText['data']; - const {classMod, style, showBgColor} = getPreparedWrapSettings(data.background); + const {style, hasBgColor: showBgColor} = usePreparedWrapSettings({ + widgetBackground: data.background, + globalBackground: textPlugin.background, + defaultOldColor: CustomPaletteBgColors.NONE, + }); const currentLayout = props.layout.find(({i}) => i === props.id) || { x: null, @@ -208,7 +227,6 @@ const textPlugin = { currentLayout.y, currentLayout.h, currentLayout.w, - classMod, data.background?.color, ]); @@ -222,13 +240,7 @@ const textPlugin = { }, [YfmWrapperKeyRef, data.text]); return ( - } - classMod={classMod} - > + & { setSettings: (settings: PluginTitleObjectSettings) => PluginTitle; hideAnchor?: boolean; hideHint?: boolean; + background?: ColorSettings; }; const WIDGET_RESIZE_DEBOUNCE_TIMEOUT = 100; @@ -51,14 +58,14 @@ const MIN_AVAILABLE_TOP_OFFSET = -5; const titlePlugin: PluginTitle = { ...pluginTitle, setSettings(settings: PluginTitleObjectSettings) { - const {hideAnchor, hideHint} = settings; + const {hideAnchor, hideHint, background} = settings; titlePlugin.hideAnchor = hideAnchor; titlePlugin.hideHint = hideHint; - + titlePlugin.background = background; return titlePlugin; }, - renderer: function Wrapper( + renderer: function PluginTitleRenderer( props: Props, forwardedRef: React.LegacyRef | undefined, ) { @@ -125,13 +132,14 @@ const titlePlugin: PluginTitle = { const withAbsoluteAnchor = showAnchor && !isInlineExtraElements; const withAbsoluteHint = showHint && !isInlineExtraElements; - const {classMod, style, showBgColor} = getPreparedWrapSettings( - data.background, - { - position: showAnchor ? undefined : 'relative', - }, - data.textColor, - ); + const {style, hasBgColor: showBgColor} = usePreparedWrapSettings({ + widgetBackground: data.background, + globalBackground: titlePlugin.background, + defaultOldColor: CustomPaletteBgColors.NONE, + }); + + const textColorStyles = useTextColorStyles(data.textColor); + const wrapperStyles = {...style, ...textColorStyles}; const currentLayout = props.layout.find(({i}) => i === props.id) || { x: null, @@ -148,8 +156,7 @@ const titlePlugin: PluginTitle = { currentLayout.y, currentLayout.h, currentLayout.w, - classMod, - data.background?.color, + data.background, data.size, data.text, ]); @@ -222,13 +229,7 @@ const titlePlugin: PluginTitle = { }; return ( - +
& { + setSettings: (settings: PluginWidgetObjectSettings) => PluginWidget; + background?: ColorSettings; +}; + +const widgetPlugin: PluginWidget = { type: 'widget', defaultLayout: {w: 12, h: 12}, + setSettings: (settings: PluginWidgetObjectSettings) => { + widgetPlugin.background = settings.background; + return widgetPlugin; + }, renderer: function Wrapper( props: WidgetPluginProps, forwardedRef: React.RefObject, @@ -24,9 +39,15 @@ const plugin = { const workbookId = props.context.workbookId; const enableAssistant = props.context.enableAssistant; + const propsBg = props.data.background ?? props.data.tabs?.[0]?.background; + const {style} = usePreparedWrapSettings({ + widgetBackground: isBackgroundSettings(propsBg) ? propsBg : undefined, + globalBackground: widgetPlugin.background, + defaultOldColor: CustomPaletteBgColors.LIKE_CHART, + }); return ( - + ({ + color: + typeof textColor === 'string' + ? textColor + : textColor?.[theme || 'common'] ?? textColor?.common, + }), + [textColor, theme], + ); +} + +export function usePreparedWrapSettings({ + widgetBackground, + globalBackground, + additionalStyle, + defaultOldColor, +}: { + widgetBackground: ColorSettings | undefined; + globalBackground: ColorSettings | undefined; + additionalStyle?: CSSProperties; + defaultOldColor: string; +}) { + const theme = useThemeType(); + return React.useMemo( + () => + getPreparedWrapSettings( + { + color: + getResultedBgColor(widgetBackground, theme, defaultOldColor) ?? + getResultedBgColor(globalBackground, theme, defaultOldColor), + }, + additionalStyle, + ), + [widgetBackground, globalBackground, additionalStyle, theme, defaultOldColor], + ); +} + +function getResultedBgColor( + bgColor: ColorSettings | undefined, + theme: ThemeType, + defaultColor: string, +) { + if (isOldBackgroundSettings(bgColor)) { + return bgColor.enabled === false ? defaultColor : bgColor.color; + } + return bgColor?.color?.[theme || 'common'] ?? bgColor?.color?.common; +} diff --git a/src/ui/components/DialogChartWidget/DialogChartWidget.scss b/src/ui/components/DialogChartWidget/DialogChartWidget.scss index 301410b482..c765b8c748 100644 --- a/src/ui/components/DialogChartWidget/DialogChartWidget.scss +++ b/src/ui/components/DialogChartWidget/DialogChartWidget.scss @@ -17,7 +17,6 @@ position: relative; display: flex; flex-direction: column; - gap: var(--g-spacing-4); &:after { content: ''; diff --git a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx index db51c0f66e..e2c534deb4 100644 --- a/src/ui/components/DialogChartWidget/DialogChartWidget.tsx +++ b/src/ui/components/DialogChartWidget/DialogChartWidget.tsx @@ -22,6 +22,7 @@ import type {CustomCommands, Spec} from 'immutability-helper'; import update, {Context} from 'immutability-helper'; import omit from 'lodash/omit'; import type { + ColorByTheme, DashTabItemWidget, DashTabItemWidgetTab, StringParams, @@ -29,7 +30,15 @@ import type { WidgetType, WizardVisualizationId, } from 'shared'; -import {DashCommonQa, DialogDashWidgetQA, EntryScope, Feature, ParamsSettingsQA} from 'shared'; +import { + CustomPaletteBgColors, + DashCommonQa, + DialogDashWidgetQA, + EntryScope, + Feature, + LIKE_CHART_COLOR_TOKEN, + ParamsSettingsQA, +} from 'shared'; import {getEntryVisualizationType} from 'shared/schema/mix/helpers'; import {Collapse} from 'ui/components/Collapse/Collapse'; import {Interpolate} from 'ui/components/Interpolate'; @@ -38,6 +47,8 @@ import type {UpdateState} from 'ui/components/TabMenu/types'; import {TabActionType} from 'ui/components/TabMenu/types'; import {DL, URL_OPTIONS} from 'ui/constants/common'; import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; +import {getWidgetColors} from 'ui/utils/widgetColors'; +import type {ValuesType} from 'utility-types'; import {registry} from '../../registry'; import NavigationInput from '../../units/dash/components/NavigationInput/NavigationInput'; @@ -88,6 +99,7 @@ export interface DialogChartWidgetFeatureProps { enableAutoheight?: boolean; enableBackgroundColor?: boolean; enableCustomBgColorSelector?: boolean; + enableSeparateThemeColorSelector?: boolean; enableFilteringSetting?: boolean; } export interface DialogChartWidgetProps extends DialogChartWidgetFeatureProps { @@ -114,6 +126,7 @@ type DialogChartWidgetState = { hideTitle: boolean; prevVisible: boolean; error: boolean; + backgroundColor?: string | ColorByTheme; data: DashTabItemWidget['data']; tabIndex: number; isManualTitle: boolean; @@ -125,6 +138,7 @@ type DialogChartWidgetState = { legacyChanged: number; visualizationType?: WizardVisualizationId; + activeTab: ValuesType; }; const INPUT_FILTERING_ID = 'chartFilteringField'; @@ -134,6 +148,8 @@ const INPUT_DESCRIPTION_ID = 'chartDescriptionField'; const INPUT_AUTOHEIGHT_ID = 'chartAutoheightField'; const INPUT_HINT_ID = 'chartHintField'; +const isCommonDashSettingsEnabled = isEnabledFeature(Feature.EnableCommonChartDashSettings); + // TODO: put in defaultPath navigation key from entry class DialogChartWidget extends React.PureComponent< DialogChartWidgetProps, @@ -144,8 +160,17 @@ class DialogChartWidget extends React.PureComponent< enableBackgroundColor: false, enableCustomBgColorSelector: false, enableFilteringSetting: true, + enableSeparateThemeColorSelector: true, openedItemData: { hideTitle: false, + background: { + color: getWidgetColors({ + color: undefined, + enabled: true, + defaultOldColor: LIKE_CHART_COLOR_TOKEN, + enableMultiThemeColors: true, + }), + }, tabs: [ { get title() { @@ -153,9 +178,6 @@ class DialogChartWidget extends React.PureComponent< }, isDefault: true, description: '', - background: { - color: 'transparent', - }, enableHint: false, hint: '', enableDescription: false, @@ -190,6 +212,12 @@ class DialogChartWidget extends React.PureComponent< isManualTitle: Boolean(nextProps.openedItemId), selectedWidgetType: null, selectedEntryType: null, + backgroundColor: getWidgetColors({ + color: nextProps.openedItemData.background?.color, + enabled: true, + defaultOldColor: CustomPaletteBgColors.LIKE_CHART, + enableMultiThemeColors: true, + }), // new params logic, local state for current tab params tabParams: nextProps.openedItemData.tabs[tabIndex]?.params || {}, legacyChanged: 0, @@ -205,6 +233,7 @@ class DialogChartWidget extends React.PureComponent< isManualTitle: false, tabParams: {}, legacyChanged: 0, + activeTab: TAB_TYPE.TABS, }; private navigationInputRef = React.createRef(); @@ -215,8 +244,7 @@ class DialogChartWidget extends React.PureComponent< const sidebar = this.renderDialogSidebar(); const mainTabSettingsContent = this.renderTabSettingsContent(); - const shouldRenderTabs = false; - // isEnabledFeature(Feature.EnableCommonChartDashSettings) && !withoutSidebar; + const shouldRenderTabs = isCommonDashSettingsEnabled && !withoutSidebar; const tabsTabContent = (
{!withoutSidebar && ( @@ -228,6 +256,7 @@ class DialogChartWidget extends React.PureComponent< {mainTabSettingsContent}
); + const widgetSettingsTabContent = this.renderWidgetSettingsContent(); return ( {shouldRenderTabs ? ( this.setState({activeTab: value})} + value={this.state.activeTab} + onUpdate={(value) => + this.setState({activeTab: value as ValuesType}) + } > @@ -257,7 +288,7 @@ class DialogChartWidget extends React.PureComponent< {tabsTabContent} -
{tabsTabContent}
+
{widgetSettingsTabContent}
) : ( @@ -308,6 +339,9 @@ class DialogChartWidget extends React.PureComponent< if (tabIndex === -1) { const newData = { hideTitle: tabs.length === 1 && this.state.hideTitle, + background: this.state.data.background + ? {color: this.state.data.background.color} + : undefined, tabs: tabs.map(({title, params, ...rest}, index) => { let tabParams = index === this.state.tabIndex @@ -448,16 +482,17 @@ class DialogChartWidget extends React.PureComponent< this.handleUpdateField('hint', val); }; - handleBackgroundColorSelected = (color: string) => { + handleBackgroundColorSelected = (color: string | ColorByTheme) => { const {data, tabIndex} = this.state; this.setState({ data: update(data, { + background: { + color: {$set: color}, + }, tabs: { [tabIndex]: { - background: { - color: {$set: color}, - }, + background: {$set: undefined}, }, }, }), @@ -623,6 +658,43 @@ class DialogChartWidget extends React.PureComponent< ); }; + renderBackgroundColorSettings = () => { + const {data, tabIndex} = this.state; + const { + theme, + enableCustomBgColorSelector, + enableSeparateThemeColorSelector = true, + } = this.props; + const backgroundColor = getWidgetColors({ + ...(data.background || data.tabs[tabIndex].background), + defaultOldColor: CustomPaletteBgColors.NONE, + enableMultiThemeColors: enableSeparateThemeColorSelector, + }); + return ( + + + + ); + }; + + renderWidgetSettingsContent = () => { + return ( +
+
{this.renderBackgroundColorSettings()}
+
+ ); + }; + renderTabSettingsContent = () => { const {data, tabIndex, selectedWidgetType} = this.state; const { @@ -630,7 +702,6 @@ class DialogChartWidget extends React.PureComponent< navigationPath, enableAutoheight, enableBackgroundColor, - enableCustomBgColorSelector, enableFilteringSetting, changeNavigationPath, } = this.props; @@ -641,16 +712,8 @@ class DialogChartWidget extends React.PureComponent< ); - const { - title, - chartId, - description, - autoHeight, - background, - hint, - enableHint, - enableDescription, - } = data.tabs[tabIndex]; + const {title, chartId, description, autoHeight, hint, enableHint, enableDescription} = + data.tabs[tabIndex]; const hasDesc = enableDescription === undefined ? Boolean(description) : Boolean(enableDescription); @@ -818,24 +881,7 @@ class DialogChartWidget extends React.PureComponent< /> )} - {enableBackgroundColor && ( - - - {i18n('dash.widget-dialog.edit', 'field_background')} - -
- } - > - - - )} + {enableBackgroundColor && this.renderBackgroundColorSettings()} {this.renderParams()} diff --git a/src/ui/components/DialogImageWidget/DialogImageWidget.tsx b/src/ui/components/DialogImageWidget/DialogImageWidget.tsx index afad23274e..b224cbdb15 100644 --- a/src/ui/components/DialogImageWidget/DialogImageWidget.tsx +++ b/src/ui/components/DialogImageWidget/DialogImageWidget.tsx @@ -7,9 +7,10 @@ import block from 'bem-cn-lite'; import {i18n} from 'i18n'; import cloneDeep from 'lodash/cloneDeep'; import merge from 'lodash/merge'; -import {DialogDashWidgetItemQA, DialogDashWidgetQA} from 'shared'; +import {CustomPaletteBgColors, DialogDashWidgetItemQA, DialogDashWidgetQA} from 'shared'; import type {DashTabItemImage, EntryScope, RecursivePartial} from 'shared'; import {registry} from 'ui/registry'; +import {getWidgetColors} from 'ui/utils/widgetColors'; import {PaletteBackground} from '../..//units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground'; import type {SetItemDataArgs} from '../../units/dash/store/actions/dashTyped'; @@ -29,7 +30,9 @@ const DEFAULT_ITEM_DATA: DashTabItemImage['data'] = { preserveAspectRatio: true, }; -export type DialogImageWidgetFeatureProps = {}; +export type DialogImageWidgetFeatureProps = { + enableSeparateThemeColorSelector?: boolean; +}; type Props = { openedItemId: string | null; @@ -59,6 +62,8 @@ export function DialogImageWidget(props: Props) { onClose, onApply, scope, + theme, + enableSeparateThemeColorSelector, } = props; const [data, setData] = React.useState(openedItemData); const [validationErrors, setValidationErrors] = React.useState>({}); @@ -151,9 +156,16 @@ export function DialogImageWidget(props: Props) { label={i18n('dash.dashkit-plugin-common.view', 'label_background-checkbox')} > updateData({background: {color}})} enableCustomBgColorSelector + enableSeparateThemeColorSelector={enableSeparateThemeColorSelector} /> diff --git a/src/ui/components/DialogTextWidget/DialogTextWidget.tsx b/src/ui/components/DialogTextWidget/DialogTextWidget.tsx index 735a337383..2656075c23 100644 --- a/src/ui/components/DialogTextWidget/DialogTextWidget.tsx +++ b/src/ui/components/DialogTextWidget/DialogTextWidget.tsx @@ -5,9 +5,12 @@ import type {RealTheme} from '@gravity-ui/uikit'; import {Checkbox, Dialog} from '@gravity-ui/uikit'; import block from 'bem-cn-lite'; import {i18n} from 'i18n'; -import type {DashTabItemText} from 'shared'; -import {DialogDashWidgetItemQA, DialogDashWidgetQA} from 'shared'; +import type {ColorByTheme, DashTabItemText} from 'shared'; +import {DialogDashWidgetItemQA, DialogDashWidgetQA, Feature} from 'shared'; +import {CustomPaletteBgColors, getDefaultWidgetBackgroundColor} from 'shared/constants/widgets'; import {PaletteBackground} from 'ui/units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground'; +import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; +import {getWidgetColors} from 'ui/utils/widgetColors'; import type {SetItemDataArgs} from '../../units/dash/store/actions/dashTyped'; import {TextEditor} from '../TextEditor/TextEditor'; @@ -19,6 +22,7 @@ const b = block('dialog-text'); export interface DialogTextWidgetFeatureProps { enableAutoheight?: boolean; enableCustomBgColorSelector?: boolean; + enableSeparateThemeColorSelector?: boolean; } export interface DialogTextWidgetProps extends DialogTextWidgetFeatureProps { @@ -36,19 +40,25 @@ interface DialogTextWidgetState { text?: string; prevVisible?: boolean; autoHeight?: boolean; - backgroundColor?: string; + backgroundColor?: string | ColorByTheme; } const INPUT_TEXT_ID = 'widgetTextField'; const INPUT_AUTOHEIGHT_ID = 'widgetAutoHeightField'; +const isCommonDashSettingsEnabled = isEnabledFeature(Feature.EnableCommonChartDashSettings); class DialogTextWidget extends React.PureComponent { static defaultProps = { enableAutoheight: true, + enableSeparateThemeColorSelector: true, openedItemData: { text: '', autoHeight: false, - backgroundColor: 'transparent', + backgroundColor: getDefaultWidgetBackgroundColor( + isCommonDashSettingsEnabled, + CustomPaletteBgColors.NONE, + true, + ), }, }; @@ -64,7 +74,12 @@ class DialogTextWidget extends React.PureComponent {enableAutoheight && ( @@ -181,7 +204,7 @@ class DialogTextWidget extends React.PureComponent { + handleHasBackgroundSelected = (color: string | ColorByTheme) => { this.setState({backgroundColor: color}); }; } diff --git a/src/ui/components/DialogTitleWidget/DialogTitleWidget.tsx b/src/ui/components/DialogTitleWidget/DialogTitleWidget.tsx index a08ebd2cc0..4a50519877 100644 --- a/src/ui/components/DialogTitleWidget/DialogTitleWidget.tsx +++ b/src/ui/components/DialogTitleWidget/DialogTitleWidget.tsx @@ -21,7 +21,7 @@ import { import block from 'bem-cn-lite'; import {FieldWrapper} from 'components/FieldWrapper/FieldWrapper'; import {i18n} from 'i18n'; -import type {DashTabItemTitle, DashTabItemTitleSize, HintSettings} from 'shared'; +import type {ColorByTheme, DashTabItemTitle, DashTabItemTitleSize, HintSettings} from 'shared'; import { DashTabItemTitleSizes, DialogDashTitleQA, @@ -32,6 +32,7 @@ import {CustomPaletteBgColors, CustomPaletteTextColors} from 'shared/constants/w import {registry} from 'ui/registry'; import {PaletteBackground} from 'ui/units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground'; import {PaletteText} from 'ui/units/dash/containers/Dialogs/components/PaletteText/PaletteText'; +import {getWidgetColors} from 'ui/utils/widgetColors'; import type {SetItemDataArgs} from '../../units/dash/store/actions/dashTyped'; @@ -75,8 +76,8 @@ interface DialogTitleWidgetState { previousSelectedFontSize: number; showInTOC?: boolean; autoHeight?: boolean; - backgroundColor?: string; - textColor?: string; + backgroundColor?: string | ColorByTheme; + textColor?: string | ColorByTheme; hint?: HintSettings; } @@ -85,6 +86,7 @@ export interface DialogTitleWidgetFeatureProps { enableShowInTOC?: boolean; enableCustomFontSize?: boolean; enableCustomBgColorSelector?: boolean; + enableSeparateThemeColorSelector?: boolean; enableCustomTextColorSelector?: boolean; } interface DialogTitleWidgetProps extends DialogTitleWidgetFeatureProps { @@ -110,8 +112,8 @@ const defaultOpenedItemData: DashTabItemTitle['data'] = { size: FONT_SIZE_OPTIONS[0].value, showInTOC: true, autoHeight: false, - background: {color: CustomPaletteBgColors.NONE}, - textColor: CustomPaletteTextColors.PRIMARY, + background: {color: undefined}, + textColor: undefined, }; function DialogTitleWidget(props: DialogTitleWidgetProps) { @@ -121,6 +123,7 @@ function DialogTitleWidget(props: DialogTitleWidgetProps) { enableAutoheight = true, enableShowInTOC = true, enableCustomBgColorSelector, + enableSeparateThemeColorSelector = true, enableCustomTextColorSelector = false, theme, closeDialog, @@ -145,8 +148,18 @@ function DialogTitleWidget(props: DialogTitleWidgetProps) { }), showInTOC: openedItemData.showInTOC, autoHeight: Boolean(openedItemData.autoHeight), - backgroundColor: openedItemData.background?.color || '', - textColor: openedItemData.textColor || CustomPaletteTextColors.PRIMARY, + backgroundColor: getWidgetColors({ + color: openedItemData.background?.color, + enabled: true, + defaultOldColor: CustomPaletteBgColors.NONE, + enableMultiThemeColors: true, + }), + textColor: getWidgetColors({ + color: openedItemData.textColor, + enabled: true, + defaultOldColor: CustomPaletteTextColors.PRIMARY, + enableMultiThemeColors: true, + }), hint: openedItemData.hint, }); const { @@ -291,11 +304,11 @@ function DialogTitleWidget(props: DialogTitleWidgetProps) { setState((prevState) => ({...prevState, autoHeight: !prevState.autoHeight})); }, []); - const handleHasBackgroundSelected = React.useCallback((color: string) => { + const handleHasBackgroundSelected = React.useCallback((color?: string | ColorByTheme) => { setState((prevState) => ({...prevState, backgroundColor: color})); }, []); - const handleTextColorChanged = React.useCallback((color: string) => { + const handleTextColorChanged = React.useCallback((color: string | ColorByTheme) => { setState((prevState) => ({...prevState, textColor: color})); }, []); @@ -303,16 +316,13 @@ function DialogTitleWidget(props: DialogTitleWidgetProps) { const {MarkdownControl} = registry.common.components.getAll(); - React.useEffect(() => { - // TODO remove and use "initialFocus={inputRef}" in Dialog props when switch to uikit7 - // delay is needed so that the autofocus of the dialog does not interrupt the focus on the input - setTimeout(() => { - inputRef.current?.focus(); - }); - }, []); - return ( - + diff --git a/src/ui/components/Widgets/Chart/ChartWidget.scss b/src/ui/components/Widgets/Chart/ChartWidget.scss index f4e11d9901..b3c672b7a3 100644 --- a/src/ui/components/Widgets/Chart/ChartWidget.scss +++ b/src/ui/components/Widgets/Chart/ChartWidget.scss @@ -6,17 +6,6 @@ $dashPublicContainerClassName: '.dash'; $hintOffset: 3px; .dl-widget { - --dl-resulted-chart-bg-color: var(--g-color-base-float); - - &_with-app-theme-color:not(&_fullscreen) { - --dl-resulted-chart-bg-color: var(--dl-color-widget-background); - } - - #{$dashContainerClassName} &, - #{$dashPublicContainerClassName} & { - background-color: var(--dl-resulted-chart-bg-color); - } - &__overlay { position: absolute; width: 100%; @@ -86,7 +75,6 @@ $hintOffset: 3px; /** styles for flat controls */ &__controls-corner-wrapper { - background-color: var(--dl-resulted-chart-bg-color); border-radius: var(--g-border-radius-m); margin-left: 8px; padding: 0px; @@ -105,8 +93,6 @@ $hintOffset: 3px; } &:hover &__controls-corner-wrapper { - background-color: var(--dl-resulted-chart-bg-color); - & .chartkit-insights__button { opacity: 1; } @@ -126,8 +112,6 @@ $hintOffset: 3px; } .widget-header_mobile &__controls-corner-wrapper { - background-color: var(--dl-resulted-chart-bg-color); - & .chartkit-insights__button, & .dl-widget__filter-button { opacity: 1; @@ -140,7 +124,6 @@ $hintOffset: 3px; #{$dashPublicContainerClassName} &_pulsate &__container, #{$dashContainerClassName} &_pulsate &__container { - background-color: var(--dl-resulted-chart-bg-color); @include loading-pulsate-animation-style(); } diff --git a/src/ui/components/Widgets/Chart/ChartWidget.tsx b/src/ui/components/Widgets/Chart/ChartWidget.tsx index 74e228a164..581a658a68 100644 --- a/src/ui/components/Widgets/Chart/ChartWidget.tsx +++ b/src/ui/components/Widgets/Chart/ChartWidget.tsx @@ -11,16 +11,9 @@ import isEmpty from 'lodash/isEmpty'; import isEqual from 'lodash/isEqual'; import omit from 'lodash/omit'; import pick from 'lodash/pick'; -import { - ChartkitMenuDialogsQA, - CustomPaletteBgColors, - Feature, - type StringParams, - getDefaultWidgetBackgroundColor, -} from 'shared'; +import {ChartkitMenuDialogsQA, type StringParams} from 'shared'; import {DL} from 'ui/constants/common'; import {ExtendedDashKitContext} from 'ui/units/dash/utils/context'; -import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; import type {ChartKit} from '../../../libs/DatalensChartkit/ChartKit/ChartKit'; import Loader from '../../../libs/DatalensChartkit/components/ChartKitBase/components/Loader/Loader'; @@ -28,7 +21,6 @@ import {getDataProviderData} from '../../../libs/DatalensChartkit/components/Cha import settings from '../../../libs/DatalensChartkit/modules/settings/settings'; import DebugInfoTool from '../../DashKit/plugins/DebugInfoTool/DebugInfoTool'; import type {CurrentTab, WidgetPluginDataWithTabs} from '../../DashKit/plugins/Widget/types'; -import {getPreparedWrapSettings} from '../../DashKit/utils'; import {MarkdownHelpPopover} from '../../MarkdownHelpPopover/MarkdownHelpPopover'; import {Content} from './components/Content'; @@ -544,15 +536,6 @@ export const ChartWidget = (props: ChartWidgetProps) => { }; }, [editMode, widgetType]); - const {classMod, style} = getPreparedWrapSettings({ - color: - currentTab.background?.color ?? - getDefaultWidgetBackgroundColor( - isEnabledFeature(Feature.EnableCommonChartDashSettings), - CustomPaletteBgColors.LIKE_CHART, - ), - }); - const disableControls = noControls || urlNoControls; const commonHeaderContentProps = { @@ -615,13 +598,11 @@ export const ChartWidget = (props: ChartWidgetProps) => { className={`${b({ ...mods, autoheight: isAutoHeightEnabled, - [String(classMod)]: Boolean(classMod), ['wait-for-init']: !isInit, 'default-mobile': DL.IS_MOBILE && !isFullscreen, pulsate: (showContentLoader || showLoaderVeil) && !isFirstLoadingFloat, 'loading-mobile-height': DL.IS_MOBILE && isFirstLoadingFloat, })}`} - style={style} data-qa={ChartkitMenuDialogsQA.chartWidget} data-qa-mod={isFullscreen ? 'fullscreen' : ''} > @@ -663,7 +644,6 @@ export const ChartWidget = (props: ChartWidgetProps) => { widgetType={widgetType} widgetDashState={widgetDashState} rootNodeRef={rootNodeRef} - backgroundColor={style?.backgroundColor} needRenderContentControls={false} chartRevIdRef={null} {...commonHeaderContentProps} diff --git a/src/ui/units/dash/containers/Body/Body.tsx b/src/ui/units/dash/containers/Body/Body.tsx index aeda8ad53b..026ad02b0f 100644 --- a/src/ui/units/dash/containers/Body/Body.tsx +++ b/src/ui/units/dash/containers/Body/Body.tsx @@ -1141,7 +1141,12 @@ class Body extends React.PureComponent { const isEmptyTab = !tabDataConfig?.items.length; - const DashKit = getConfiguredDashKit(undefined, {disableHashNavigation}); + const DashKit = getConfiguredDashKit(undefined, { + disableHashNavigation, + settings: { + background: settings.background, + }, + }); const hasFixedHeaderControlsElements = Boolean( this.getWidgetLayoutByGroup(FIXED_GROUP_HEADER_ID)?.length, diff --git a/src/ui/units/dash/containers/Dialogs/Settings/Settings.tsx b/src/ui/units/dash/containers/Dialogs/Settings/Settings.tsx index 888260d656..555f370dc7 100644 --- a/src/ui/units/dash/containers/Dialogs/Settings/Settings.tsx +++ b/src/ui/units/dash/containers/Dialogs/Settings/Settings.tsx @@ -11,8 +11,8 @@ import {openDialog} from 'ui/store/actions/dialog'; import type {DatalensGlobalState} from '../../../../..'; import {i18n} from '../../../../../../i18n'; -import type {DashSettings, DashSettingsGlobalParams} from '../../../../../../shared'; -import {DashLoadPriority} from '../../../../../../shared'; +import type {ColorByTheme, DashSettings, DashSettingsGlobalParams} from '../../../../../../shared'; +import {DashLoadPriority, getColorObject} from '../../../../../../shared'; import {DIALOG_ENTRY_DESCRIPTION} from '../../../../../components/DialogEntryDescription'; import EntryDialogues from '../../../../../components/EntryDialogues/EntryDialogues'; import {DIALOG_TYPE} from '../../../../../constants/dialogs'; @@ -73,6 +73,9 @@ const Settings = () => { const [accessDescription, setAccessDesc] = React.useState(accessDesc); const [supportDescription, setSupportDesc] = React.useState(supportDesc); const [margins, setMargins] = React.useState(settings.margins || DEFAULT_DASH_MARGINS); + const [backgroundColor, setBackgroundColor] = React.useState( + settings.background?.color || getColorObject(undefined, true), + ); const [otherSettinsState, setOtherSettingsState] = React.useState>({}); const entryDialoguesRef = React.useRef(null); @@ -135,6 +138,9 @@ const Settings = () => { hideDashTitle, expandTOC, loadPriority, + background: { + color: backgroundColor, + }, ...otherSettinsState, }; @@ -218,6 +224,10 @@ const Settings = () => { } }, []); + const handleBackgroundColorChange = React.useCallback((color: ColorByTheme) => { + setBackgroundColor(color); + }, []); + const showDependentSelectors = !settings.dependentSelectors; return settings ? ( @@ -256,6 +266,8 @@ const Settings = () => { onChangeHideDashTitle={() => setHideTitle(!hideDashTitle)} expandTOCValue={expandTOC} onChangeExpandTOC={() => setExpandTOC(!expandTOC)} + backgroundColor={backgroundColor} + onChangeBackgroundColor={handleBackgroundColorChange} /> void; expandTOCValue: boolean; onChangeExpandTOC: () => void; + backgroundColor: ColorByTheme | undefined; + onChangeBackgroundColor: (color: ColorByTheme) => void; }; export const Display = ({ @@ -37,6 +41,8 @@ export const Display = ({ onChangeHideDashTitle, expandTOCValue, onChangeExpandTOC, + backgroundColor, + onChangeBackgroundColor, }: DisplayProps) => { return ( )} + {isEnabledFeature(Feature.EnableCommonChartDashSettings) && ( + + + <ColorInputsGroup + value={backgroundColor || {}} + onUpdate={onChangeBackgroundColor} + isSingleColorSelector={false} + direction="column" + /> + </Row> + )} </SectionWrapper> ); }; diff --git a/src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.scss b/src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.scss new file mode 100644 index 0000000000..7124553d5b --- /dev/null +++ b/src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.scss @@ -0,0 +1,18 @@ +.color-inputs-group { + display: flex; + gap: var(--g-spacing-4); + + &__item { + display: flex; + align-items: center; + gap: var(--g-spacing-2); + } + + &__theme-icon { + color: var(--g-color-text-hint); + } + + &__color-input { + flex: 0 1 200px; + } +} diff --git a/src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.tsx b/src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.tsx new file mode 100644 index 0000000000..8b6e45af23 --- /dev/null +++ b/src/ui/units/dash/containers/Dialogs/components/ColorInputsGroup/ColorInputsGroup.tsx @@ -0,0 +1,80 @@ +import React from 'react'; + +import {Moon, Sun} from '@gravity-ui/icons'; +import type {FlexProps, RealTheme} from '@gravity-ui/uikit'; +import {Flex, Icon} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; +import ColorPickerInputWithPreset from 'ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset'; + +import {type ColorByTheme} from '../../../../../../../shared'; +import {ColorPickerInput} from '../../../../../../components/ColorPickerInput/ColorPickerInput'; + +import './ColorInputsGroup.scss'; +const b = block('color-inputs-group'); + +export interface ColorInputsGroupProps { + theme?: RealTheme; + value: ColorByTheme | undefined; + onUpdate: (value: ColorByTheme) => void; + isSingleColorSelector?: boolean; + direction?: FlexProps['direction']; + className?: string; + mainPresetOptions?: string[]; + paletteOptions?: string[]; +} + +export function ColorInputsGroup({ + theme, + value, + onUpdate, + className, + isSingleColorSelector = false, + direction = 'row', + mainPresetOptions, + paletteOptions, +}: ColorInputsGroupProps) { + return ( + <Flex className={b(null, className)} direction={direction}> + {isSingleColorSelector ? ( + <ColorPickerInput + className={b('color-input')} + theme={theme} + value={value?.common} + onUpdate={(color) => onUpdate({common: color ?? undefined})} + hasOpacityInput + /> + ) : ( + <React.Fragment> + <div className={b('item')}> + <Icon data={Sun} size={16} className={b('theme-icon')} /> + <ColorPickerInputWithPreset + mainPresetOptions={mainPresetOptions} + paletteOptions={paletteOptions} + className={b('color-input')} + value={value?.light} + theme="light" + onUpdate={(color) => + onUpdate({light: color ?? undefined, dark: value?.dark}) + } + hasOpacityInput + /> + </div> + <div className={b('item')}> + <Icon data={Moon} size={16} className={b('theme-icon')} /> + <ColorPickerInputWithPreset + mainPresetOptions={mainPresetOptions} + paletteOptions={paletteOptions} + className={b('color-input')} + value={value?.dark} + theme="dark" + onUpdate={(color) => + onUpdate({dark: color ?? undefined, light: value?.light}) + } + hasOpacityInput + /> + </div> + </React.Fragment> + )} + </Flex> + ); +} diff --git a/src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.scss b/src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.scss new file mode 100644 index 0000000000..dcfc0b9326 --- /dev/null +++ b/src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.scss @@ -0,0 +1,122 @@ +@import '~@gravity-ui/uikit/styles/mixins'; + +.color-picker-input-with-preset { + $class: &; + display: inline-block; + position: relative; + width: 100%; + + $color-item-size: 28px; + + &__palette-list { + display: flex; + flex-direction: column; + padding: var(--g-spacing-4); + } + + &__preset { + margin-block-end: var(--g-spacing-2); + } + + &__palette { + margin-block-start: var(--g-spacing-5); + } + + &__color-picker { + display: block; + width: 140px; + min-width: 100%; + } + + &__custom-palette-bg-btn, + &__highlight-wrapper { + width: $color-item-size; + height: $color-item-size; + flex-grow: 0; + flex-shrink: 0; + } + + &__custom-palette-bg-btn, + &__palette-list-btn { + // disable inheritance from theme + --g-button-border-radius: 6px; + } + + &__palette-list-btn, + &__custom-palette-bg-btn { + // disable selected additional background on button + --g-button-background-color: transparent; + --g-button-background-color-hover: transparent; + } + + &__highlight-wrapper { + --_--color-item-border-color: transparent; + position: relative; + border-radius: 6px; + + &_with-border { + --_--color-item-border-color: var(--g-color-line-generic); + } + + &::before { + content: ''; + position: absolute; + inset: 0; + border: 1px solid var(--_--color-item-border-color); + border-radius: 6px; + } + + &_selected, + &:hover { + --_--color-item-border-color: transparent; + } + + &:not(&_selected):hover::before { + content: ''; + position: absolute; + inset: -3px; + border: 2px solid var(--g-color-line-generic-hover); + border-radius: 9px; + } + + &_selected::before { + content: ''; + position: absolute; + inset: -3px; + border: 2px solid var(--g-color-line-brand); + border-radius: 9px; + } + } + + &__color-item { + display: inline-block; + width: $color-item-size; + height: $color-item-size; + background-clip: padding-box; + + &_transparent { + background-image: repeating-conic-gradient( + var(--g-color-base-generic) 0 25%, + var(--g-color-base-background) 0 50% + ); + border: 1px solid transparent; + } + + &_widget-bg { + background-color: var(--g-color-base-float); + } + } + + &__theme { + background-clip: padding-box; + background-color: var(--g-color-base-float); + width: $color-item-size; + height: $color-item-size; + overflow: hidden; + } + + &__theme, + &__color-item { + border-radius: 6px; + } +} diff --git a/src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.tsx b/src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.tsx new file mode 100644 index 0000000000..60dfc82ab8 --- /dev/null +++ b/src/ui/units/dash/containers/Dialogs/components/ColorPickerInputWithPreset/ColorPickerInputWithPreset.tsx @@ -0,0 +1,92 @@ +import React from 'react'; + +import {Popup} from '@gravity-ui/uikit'; +import block from 'bem-cn-lite'; + +import { + ColorPickerInput, + type ColorPickerInputProps, +} from '../../../../../../components/ColorPickerInput/ColorPickerInput'; +import {ColorPalette} from '../ColorPalette/ColorPalette'; + +import './ColorPickerInputWithPreset.scss'; + +const b = block('color-picker-input-with-preset'); + +export interface ColorPickerInputWithPresetProps extends ColorPickerInputProps { + mainPresetOptions?: string[]; + paletteOptions?: string[]; +} + +export function ColorPickerInputWithPreset({ + mainPresetOptions, + paletteOptions, + value, + onUpdate, + theme, + ...colorPickerInputProps +}: ColorPickerInputWithPresetProps) { + const [isPopupOpen, setIsPopupOpen] = React.useState(false); + const inputRef = React.useRef<HTMLInputElement>(null); + + const handleSelectColor = React.useCallback( + (color: string) => { + onUpdate(color || null); + setIsPopupOpen(false); + }, + [onUpdate], + ); + + const handleFocus = React.useCallback(() => { + setIsPopupOpen(true); + }, []); + + const handleClosePopup = React.useCallback((open: boolean) => { + if (!open) { + setIsPopupOpen(false); + } + }, []); + + if (!mainPresetOptions?.length && !paletteOptions?.length) { + return ( + <ColorPickerInput + {...colorPickerInputProps} + value={value} + onUpdate={onUpdate} + theme={theme} + /> + ); + } + + return ( + <React.Fragment> + <ColorPickerInput + {...colorPickerInputProps} + value={value} + onUpdate={onUpdate} + theme={theme} + onFocus={handleFocus} + ref={inputRef} + /> + <Popup + open={isPopupOpen} + anchorElement={inputRef.current} + hasArrow + onOpenChange={handleClosePopup} + className={b('popup')} + > + <ColorPalette + onSelect={handleSelectColor} + selectedColor={value || ''} + mainPresetOptions={mainPresetOptions || []} + paletteOptions={paletteOptions || []} + theme={theme} + paletteColumns={5} + showItemBorder + /> + </Popup> + </React.Fragment> + ); +} + +export default ColorPickerInputWithPreset; diff --git a/src/ui/units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground.tsx b/src/ui/units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground.tsx index 005d2754f4..9915ee2400 100644 --- a/src/ui/units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground.tsx +++ b/src/ui/units/dash/containers/Dialogs/components/PaletteBackground/PaletteBackground.tsx @@ -1,27 +1,50 @@ import React from 'react'; -import {type RealTheme} from '@gravity-ui/uikit'; +import type {FlexProps, RealTheme} from '@gravity-ui/uikit'; +import type {ColorByTheme} from 'shared'; +import {Feature, isColorByTheme, isColorByThemeOrUndefined} from 'shared'; import { BASE_GREY_BACKGROUND_COLOR, CustomPaletteBgColors, WIDGET_BG_COLORS_PRESET, WIDGET_BG_HEAVY_COLORS_PRESET, } from 'shared/constants/widgets'; +import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; +import {ColorInputsGroup} from '../ColorInputsGroup/ColorInputsGroup'; import {ColorPicker} from '../ColorPicker/ColorPicker'; type PaletteBackgroundProps = { - color?: string; - onSelect: (color: string) => void; + color?: string | ColorByTheme; + onSelect: (color: string | ColorByTheme) => void; enableCustomBgColorSelector?: boolean; theme?: RealTheme; + enableSeparateThemeColorSelector?: boolean; + direction?: FlexProps['direction']; }; +const isCommonDashSettingsEnabled = isEnabledFeature(Feature.EnableCommonChartDashSettings); + +const MAIN_PRESET_OPTIONS = [ + 'var(--g-color-base-background)', + CustomPaletteBgColors.NONE, + 'var(--g-color-private-cool-grey-20-solid)', +]; + +const PALETTE_OPTIONS = [ + 'var(--g-color-private-blue-50)', + 'var(--g-color-private-green-50)', + 'var(--g-color-private-yellow-50)', + 'var(--g-color-private-red-50)', + 'var(--g-color-private-purple-50)', +]; export const PaletteBackground = ({ onSelect, color, enableCustomBgColorSelector, theme, + enableSeparateThemeColorSelector = true, + direction = 'row', }: PaletteBackgroundProps) => { const mainPresetOptions = [ CustomPaletteBgColors.NONE, @@ -37,10 +60,27 @@ export const PaletteBackground = ({ [enableCustomBgColorSelector], ); + if ( + (isCommonDashSettingsEnabled && isColorByThemeOrUndefined(color)) || + isColorByTheme(color) + ) { + return ( + <ColorInputsGroup + theme={theme} + value={color} + onUpdate={onSelect} + isSingleColorSelector={!enableSeparateThemeColorSelector} + direction={direction} + mainPresetOptions={MAIN_PRESET_OPTIONS} + paletteOptions={PALETTE_OPTIONS} + /> + ); + } + return ( <ColorPicker onSelect={onSelect} - color={color} + color={/* color */ ''} enableCustomColorSelector={enableCustomBgColorSelector} mainPresetOptions={mainPresetOptions} paletteOptions={paletteOptions} diff --git a/src/ui/units/dash/containers/Dialogs/components/PaletteText/PaletteText.tsx b/src/ui/units/dash/containers/Dialogs/components/PaletteText/PaletteText.tsx index 7cab610029..acd56ed144 100644 --- a/src/ui/units/dash/containers/Dialogs/components/PaletteText/PaletteText.tsx +++ b/src/ui/units/dash/containers/Dialogs/components/PaletteText/PaletteText.tsx @@ -1,15 +1,19 @@ import React from 'react'; import {type RealTheme} from '@gravity-ui/uikit'; +import {type ColorByTheme, Feature, isColorByTheme, isColorByThemeOrUndefined} from 'shared'; import {CustomPaletteTextColors, TITLE_WIDGET_TEXT_COLORS_PRESET} from 'shared/constants/widgets'; +import {isEnabledFeature} from 'ui/utils/isEnabledFeature'; +import {ColorInputsGroup} from '../ColorInputsGroup/ColorInputsGroup'; import {ColorPicker} from '../ColorPicker/ColorPicker'; type PaletteTextProps = { - color?: string; - onSelect: (color: string) => void; + color?: string | ColorByTheme; + onSelect: (color: string | ColorByTheme) => void; theme?: RealTheme; enableCustomColorSelector?: boolean; + enableSeparateThemeColorSelector?: boolean; }; const mainPresetOptions = [ CustomPaletteTextColors.PRIMARY, @@ -18,13 +22,28 @@ const mainPresetOptions = [ CustomPaletteTextColors.HINT, CustomPaletteTextColors.INVERTED_PRIMARY, ]; - +const isCommonDashSettingsEnabled = isEnabledFeature(Feature.EnableCommonChartDashSettings); export const PaletteText = ({ onSelect, color, theme, enableCustomColorSelector, + enableSeparateThemeColorSelector = true, }: PaletteTextProps) => { + if ( + (isCommonDashSettingsEnabled && isColorByThemeOrUndefined(color)) || + isColorByTheme(color) + ) { + return ( + <ColorInputsGroup + theme={theme} + value={color} + onUpdate={onSelect} + isSingleColorSelector={!enableSeparateThemeColorSelector} + /> + ); + } + return ( <ColorPicker onSelect={onSelect} diff --git a/src/ui/utils/copyItems.ts b/src/ui/utils/copyItems.ts index 402c9ccf5c..cd77cc6b04 100644 --- a/src/ui/utils/copyItems.ts +++ b/src/ui/utils/copyItems.ts @@ -1,10 +1,11 @@ import type {ConfigItem, ConfigItemData} from '@gravity-ui/dashkit'; -import {WIDGET_BG_COLORS_PRESET, getDefaultWidgetBackgroundColor} from 'shared'; -import type {BackgroundSettings} from 'shared/types'; +import {CustomPaletteBgColors, getDefaultWidgetBackgroundColor} from 'shared'; +import type {ColorSettings} from 'shared/types'; import {DashTabItemType, Feature, isBackgroundSettings} from 'shared/types'; import type {ConnectionsData} from 'ui/components/DialogRelations/types'; import {isEnabledFeature} from './isEnabledFeature'; +import {getWidgetColors} from './widgetColors'; // targetId - item is copied data from localStorage // id - item is already created via DashKit.setItem @@ -98,17 +99,21 @@ export const getUpdatedConnections = ({ export function getUpdatedBackgroundValue( background: unknown, - allowCusomValues?: boolean, -): Omit<BackgroundSettings, 'enabled'> { + allowCustomValues?: boolean, +): ColorSettings | undefined { + const isCustomDahsSettingsEnabled = isEnabledFeature(Feature.EnableCommonChartDashSettings); + if (!isBackgroundSettings(background)) { + return { + color: getDefaultWidgetBackgroundColor(isCustomDahsSettingsEnabled), + }; + } + return { - color: - isBackgroundSettings(background) && - background?.color && - background.enabled !== false && - (allowCusomValues || WIDGET_BG_COLORS_PRESET.includes(background.color)) - ? background.color - : getDefaultWidgetBackgroundColor( - isEnabledFeature(Feature.EnableCommonChartDashSettings), - ), + color: getWidgetColors({ + color: background?.color, + enabled: background?.enabled, + defaultOldColor: CustomPaletteBgColors.NONE, + enableMultiThemeColors: !allowCustomValues, + }), }; } diff --git a/src/ui/utils/widgetColors.ts b/src/ui/utils/widgetColors.ts new file mode 100644 index 0000000000..d1fcb478e2 --- /dev/null +++ b/src/ui/utils/widgetColors.ts @@ -0,0 +1,82 @@ +import {color as d3Color} from 'd3-color'; +import type {ColorByTheme} from 'shared'; +import { + CustomPaletteBgColors, + Feature, + LIKE_CHART_COLOR_TOKEN, + TRANSPARENT_COLOR_HEX, + getColorObject, +} from 'shared'; + +import {isEnabledFeature} from './isEnabledFeature'; + +const isCommonDashSettingsEnabled = isEnabledFeature(Feature.EnableCommonChartDashSettings); + +export function computeColorFromToken(token?: string) { + if (!token) { + return undefined; + } + + const div = document.createElement('div'); + div.style.backgroundColor = token; + div.style.position = 'absolute'; + div.style.top = '-1000px'; + div.style.left = '-1000px'; + div.style.width = '0'; + div.style.height = '0'; + document.body.appendChild(div); + const color = getComputedStyle(div).backgroundColor; + return d3Color(color) ? color : undefined; +} + +export function getWidgetColors({ + color, + enabled = true, + defaultOldColor = CustomPaletteBgColors.NONE, + enableMultiThemeColors = true, +}: { + color?: string | ColorByTheme; + enabled?: boolean; + defaultOldColor: string; + enableMultiThemeColors: boolean; +}): ColorByTheme | string | undefined { + const defaultColor = + defaultOldColor === CustomPaletteBgColors.LIKE_CHART + ? LIKE_CHART_COLOR_TOKEN + : defaultOldColor === CustomPaletteBgColors.NONE + ? TRANSPARENT_COLOR_HEX + : defaultOldColor; + const defaultColorComputed = computeColorFromToken(defaultColor); + const allowCustomValues = !enableMultiThemeColors; + if (isCommonDashSettingsEnabled) { + if (typeof color === 'string') { + let colorComputed: string | undefined; + if (enabled === false) { + colorComputed = defaultColorComputed; + } else if (color && !d3Color(color)) { + // if color token var + colorComputed = computeColorFromToken(color); + } else { + colorComputed = color; + } + return getColorObject(colorComputed, enableMultiThemeColors); + } + return color ?? getColorObject(undefined, enableMultiThemeColors); + } else if (typeof color === 'string') { + if (enabled === false) { + return defaultColor; + } else { + // if user-defined hex color + if ( + !allowCustomValues && + color !== CustomPaletteBgColors.NONE && + color && + d3Color(color) + ) { + return defaultColor; + } + return color ?? defaultOldColor; + } + } + return color; +}