From 3a64d5aaf0b5baa06a293630d574e6d6680170da Mon Sep 17 00:00:00 2001 From: TheMerret Date: Tue, 29 Sep 2026 16:50:51 +0300 Subject: [PATCH 1/9] feat: refresh action panel editing UI --- README-ru.md | 4 +- README.md | 4 +- src/components/ActionPanel/ActionPanel.scss | 109 +++++++++++++----- src/components/ActionPanel/ActionPanel.tsx | 34 ++++++ .../DashKitDnDWrapper/DashKitDnDWrapper.tsx | 9 +- src/components/GridItem/GridItem.scss | 5 +- src/context/DashKitDnDContext.ts | 2 +- src/hooks/__tests__/useDnDItemProps.test.tsx | 57 +++++++++ src/hooks/useDnDItemProps.ts | 2 +- 9 files changed, 191 insertions(+), 35 deletions(-) create mode 100644 src/hooks/__tests__/useDnDItemProps.test.tsx diff --git a/README-ru.md b/README-ru.md index e85a303..bdac518 100644 --- a/README-ru.md +++ b/README-ru.md @@ -410,7 +410,7 @@ interface DashKitDnDWrapperProps { } ``` -- `dragImageSrc` — изображение для предпросмотра при перетаскивании элемента. По умолчанию используется прозрачный PNG-файл размером 1 пиксель в формате `base64`. +- `dragImageSrc` — необязательное изображение для предпросмотра при перетаскивании. По умолчанию отображается сам перетаскиваемый элемент `ActionPanel`. - `onDragStart` — обратный вызов, срабатывающий при начале перетаскивания элемента из `ActionPanel`. - `onDragEnd` — обратный вызов, срабатывающий при завершении перетаскивания элемента или его отмене. @@ -472,7 +472,9 @@ const onDrop = (dropProps: ItemDropProps) => { | `--dashkit-action-panel-item-color` | Цвет фона. | | `--dashkit-action-panel-item-text-color` | Цвет текста. | | `--dashkit-action-panel-item-color-hover` | Цвет фона при наведении. | +| `--dashkit-action-panel-item-color-dragging` | Цвет фона при перетаскивании. | | `--dashkit-action-panel-item-text-color-hover` | Цвет текста при наведении. | +| `--dashkit-action-panel-item-text-color-dragging` | Цвет текста при перетаскивании. | | Переменные оверлея | | | `--dashkit-overlay-border-color` | Цвет границы. | | `--dashkit-overlay-color` | Цвет фона. | diff --git a/README.md b/README.md index e784d6f..ccf7fbb 100644 --- a/README.md +++ b/README.md @@ -487,7 +487,7 @@ interface DashKitDnDWrapperProps { } ``` -- **dragImageSrc**: Drag image preview, by default used transparent 1px png base64 +- **dragImageSrc**: Optional custom drag image. By default, the dragged ActionPanel item is shown as the preview. - **onDragStart**: Callback called when element is dragged from ActionPanel - **onDragEnd**: Callback called when element dropped or drag canceled @@ -550,7 +550,9 @@ const onDrop = (dropProps: ItemDropProps) => { | `--dashkit-action-panel-item-color` | Backgroud color | | `--dashkit-action-panel-item-text-color` | Text color | | `--dashkit-action-panel-item-color-hover` | Hover backgroud color | +| `--dashkit-action-panel-item-color-dragging` | Drag background color | | `--dashkit-action-panel-item-text-color-hover` | Hover text color | +| `--dashkit-action-panel-item-text-color-dragging` | Drag text color | | Overlay variables | | | `--dashkit-overlay-border-color` | Border color | | `--dashkit-overlay-color` | Background color | diff --git a/src/components/ActionPanel/ActionPanel.scss b/src/components/ActionPanel/ActionPanel.scss index 5a43929..c8af0db 100644 --- a/src/components/ActionPanel/ActionPanel.scss +++ b/src/components/ActionPanel/ActionPanel.scss @@ -2,23 +2,34 @@ $show_panel_transform: translateX(-50%) translateY(0); $hide_panel_transform: translateX(-50%) translateY(calc(100% + 20px)); - --_--dashkit-action-panel-color: var(--dashkit-action-panel-color, var(--g-color-base-float)); - --_--dashkit-action-panel-border-color: var( - --dashkit-action-panel-border-color, - var(--g-color-base-brand) + --_--dashkit-action-panel-default-color: #595959cc; + --_--dashkit-action-panel-default-item-dragging-color: #595959; + --_--dashkit-action-panel-default-item-hover-color: var( + --g-color-private-white-200, + rgba(255, 255, 255, 0.2) ); + --_--dashkit-action-panel-color: var( + --dashkit-action-panel-color, + var(--_--dashkit-action-panel-default-color) + ); + --_--dashkit-action-panel-border-color: var(--dashkit-action-panel-border-color, transparent); --_--dashkit-action-panel-border-radius: var( --dashkit-action-panel-border-radius, - var(--g-border-radius-xl) + var(--g-border-radius-2xl, 16px) ); background-color: var(--_--dashkit-action-panel-color); + backdrop-filter: blur(18px); position: fixed; bottom: 20px; display: flex; + width: max-content; + max-width: calc(100vw - 40px); + overflow-x: auto; border-radius: var(--_--dashkit-action-panel-border-radius); - border: 2px solid var(--_--dashkit-action-panel-border-color); - padding: 8px; + outline: 2px solid var(--_--dashkit-action-panel-border-color); + outline-offset: -2px; + padding: 6px; gap: 0; left: 50%; transform: $show_panel_transform; @@ -48,53 +59,99 @@ --_--dashkit-action-panel-item-color: var(--dashkit-action-panel-item-color, transparent); --_--dashkit-action-panel-item-text-color: var( --dashkit-action-panel-item-text-color, - var(--g-color-text-primary) + #fff ); --_--dashkit-action-panel-item-color-hover: var( --dashkit-action-panel-item-color-hover, - var(--g-color-base-simple-hover) + var(--_--dashkit-action-panel-default-item-hover-color) + ); + --_--dashkit-action-panel-item-color-dragging: var( + --dashkit-action-panel-item-color-dragging, + var(--_--dashkit-action-panel-default-item-dragging-color) ); --_--dashkit-action-panel-item-text-color-hover: var( --dashkit-action-panel-item-text-color-hover, - var(--g-color-text-primary) + #fff ); - height: 68px; - width: 98px; + min-height: 40px; + max-width: 200px; + flex: 0 0 auto; display: flex; - flex-direction: column; + gap: 4px; justify-content: center; align-items: center; - transition: - 300ms color ease-in-out, - 300ms background-color ease-in-out; - border-radius: 6px; - padding: 0 12px; + border-radius: var(--g-border-radius-xl, 10px); + padding: 0 16px; box-sizing: border-box; white-space: nowrap; - overflow: hidden; background-color: var(--_--dashkit-action-panel-item-color); color: var(--_--dashkit-action-panel-item-text-color); - will-change: color, backgroung-color; + cursor: pointer; - &:hover { - cursor: pointer; + &:hover, + &:focus-visible { background-color: var(--_--dashkit-action-panel-item-color-hover); color: var(--_--dashkit-action-panel-item-text-color-hover); } + + &_draggable { + // Keep rounded corners when Chrome captures the item as a native drag image. + transform: translate(0, 0); + cursor: grab; + + &:hover, + &:active { + transform: translateY(-2px); + box-shadow: 0 3px 5px rgba(0, 0, 0, 0.15); + } + + &:active { + cursor: grabbing; + } + } + + &_dragging, + &_dragging:hover { + transform: translate(0, 0); + background-color: var(--_--dashkit-action-panel-item-color-dragging); + color: var(--dashkit-action-panel-item-text-color-dragging, #fff); + } + + &_dragging-source-disabled, + &_dragging-source-disabled:hover { + opacity: 0.5; + box-shadow: none; + } } &__icon { - margin-bottom: 4px; + display: flex; + flex: 0 0 auto; + width: 16px; + height: 16px; + align-items: center; + justify-content: center; + + svg { + width: 16px; + height: 16px; + } } &__title { - font-size: 13px; + min-width: 0; + font-size: 12px; font-weight: 400; line-height: 16px; - max-width: 100%; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } } + +.g-root_theme_light .dashkit-action-panel, +.g-root_theme_light-hc .dashkit-action-panel { + --_--dashkit-action-panel-default-color: #000000b2; + --_--dashkit-action-panel-default-item-dragging-color: #4d4d4d; + --_--dashkit-action-panel-default-item-hover-color: #ffffff12; +} diff --git a/src/components/ActionPanel/ActionPanel.tsx b/src/components/ActionPanel/ActionPanel.tsx index f8bb805..17f042b 100644 --- a/src/components/ActionPanel/ActionPanel.tsx +++ b/src/components/ActionPanel/ActionPanel.tsx @@ -13,6 +13,38 @@ const b = cn('dashkit-action-panel'); export const ActionPanelItemContainer = ({item}: {item: ActionPanelItem}) => { const dndProps = useDnDItemProps(item); + const draggingClassName = `${b('item')}_dragging`; + const disabledSourceClassName = `${b('item')}_dragging-source-disabled`; + const disableSourceTimer = React.useRef | null>(null); + + React.useEffect( + () => () => { + if (disableSourceTimer.current !== null) { + clearTimeout(disableSourceTimer.current); + } + }, + [], + ); + + const onDragStart = (event: React.DragEvent) => { + const source = event.currentTarget; + source.classList.add(draggingClassName); + dndProps?.onDragStart(event); + // The native drag image captures the solid item before the panel source is disabled. + disableSourceTimer.current = setTimeout(() => { + source.classList.add(disabledSourceClassName); + disableSourceTimer.current = null; + }, 0); + }; + + const onDragEnd = (event: React.DragEvent) => { + if (disableSourceTimer.current !== null) { + clearTimeout(disableSourceTimer.current); + disableSourceTimer.current = null; + } + event.currentTarget.classList.remove(draggingClassName, disabledSourceClassName); + dndProps?.onDragEnd(event); + }; return (
{ onClick={item.onClick} data-qa={item.qa} {...dndProps} + onDragStart={dndProps ? onDragStart : undefined} + onDragEnd={dndProps ? onDragEnd : undefined} >
{item.icon}
diff --git a/src/components/DashKitDnDWrapper/DashKitDnDWrapper.tsx b/src/components/DashKitDnDWrapper/DashKitDnDWrapper.tsx index 626c1d9..e9e3062 100644 --- a/src/components/DashKitDnDWrapper/DashKitDnDWrapper.tsx +++ b/src/components/DashKitDnDWrapper/DashKitDnDWrapper.tsx @@ -14,15 +14,16 @@ type DashKitDnDWrapperProps = { children: React.ReactElement; }; -const defaultImageSrc = - 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; - export const DashKitDnDWrapper: React.FC = (props) => { const [dragProps, setDragProps] = React.useState(null); const dragImagePreview = React.useMemo(() => { + if (!props.dragImageSrc) { + return null; + } + const img = new Image(); - img.src = props.dragImageSrc || defaultImageSrc; + img.src = props.dragImageSrc; return img; }, [props.dragImageSrc]); diff --git a/src/components/GridItem/GridItem.scss b/src/components/GridItem/GridItem.scss index 81a88da..2493578 100644 --- a/src/components/GridItem/GridItem.scss +++ b/src/components/GridItem/GridItem.scss @@ -57,6 +57,8 @@ } .react-grid-item.dropping { + // RGL's slot-sized dropping node follows the cursor; keep only its separate grid placeholder visible. + opacity: 0; // Disable evety mouse event for dropping element placeholder user-select: none; pointer-events: none; @@ -92,10 +94,11 @@ } .react-grid-item.react-grid-placeholder { - --_-dashkit-placeholder-color: var(--dashkit-placeholder-color, #fc0); + --_-dashkit-placeholder-color: var(--dashkit-placeholder-color, #027bfe80); --_-dashkit-placeholder-opacity: var(--dashkit-placeholder-opacity, 0.2); background: var(--_-dashkit-placeholder-color); + border-radius: var(--dashkit-grid-item-border-radius, 3px); opacity: var(--_-dashkit-placeholder-opacity); transition-duration: 100ms; z-index: 2; diff --git a/src/context/DashKitDnDContext.ts b/src/context/DashKitDnDContext.ts index 3c21923..d022f80 100644 --- a/src/context/DashKitDnDContext.ts +++ b/src/context/DashKitDnDContext.ts @@ -4,7 +4,7 @@ import type {DraggedOverItem, ItemDragProps} from '../shared/types'; export type DashKitDnDCtxShape = { dragProps: ItemDragProps | null; - dragImagePreview: HTMLImageElement; + dragImagePreview: HTMLImageElement | null; onDragStart: (e: React.DragEvent, itemDragProps: ItemDragProps) => void; onDragEnd: (e: React.DragEvent) => void; onDropDragOver?: ( diff --git a/src/hooks/__tests__/useDnDItemProps.test.tsx b/src/hooks/__tests__/useDnDItemProps.test.tsx new file mode 100644 index 0000000..0a296e0 --- /dev/null +++ b/src/hooks/__tests__/useDnDItemProps.test.tsx @@ -0,0 +1,57 @@ +/** @jest-environment jsdom */ + +import React from 'react'; + +import {act, fireEvent, render, screen} from '@testing-library/react'; + +import {ActionPanel} from '../../components/ActionPanel/ActionPanel'; +import {DashKitDnDWrapper} from '../../components/DashKitDnDWrapper/DashKitDnDWrapper'; + +const item = {id: 'chart', title: 'Chart', icon: 'icon', dragProps: {type: 'chart'}}; + +describe('ActionPanel drag preview', () => { + it('captures the solid item before disabling the panel source and restores it on drag end', () => { + jest.useFakeTimers(); + const onDragStart = jest.fn(); + const onDragEnd = jest.fn(); + const setDragImage = jest.fn(); + + render( + + + , + ); + const source = screen.getByRole('button', {name: /Chart/}); + const disabledSourceClassName = 'dashkit-action-panel__item_dragging-source-disabled'; + + setDragImage.mockImplementation(() => { + expect(source.classList.contains('dashkit-action-panel__item_dragging')).toBe(true); + expect(source.classList.contains(disabledSourceClassName)).toBe(false); + }); + fireEvent.dragStart(source, {dataTransfer: {setDragImage}}); + expect(setDragImage).toHaveBeenCalledWith(source, 0, 0); + expect(onDragStart).toHaveBeenCalledWith(item.dragProps); + act(() => jest.runOnlyPendingTimers()); + expect(source.classList.contains(disabledSourceClassName)).toBe(true); + + fireEvent.dragEnd(source); + expect(source.classList.contains('dashkit-action-panel__item_dragging')).toBe(false); + expect(source.classList.contains(disabledSourceClassName)).toBe(false); + expect(onDragEnd).toHaveBeenCalledTimes(1); + jest.useRealTimers(); + }); + + it('respects a custom drag image', () => { + const setDragImage = jest.fn(); + render( + + + , + ); + + fireEvent.dragStart(screen.getByRole('button', {name: /Chart/}), { + dataTransfer: {setDragImage}, + }); + expect(setDragImage.mock.calls[0][0]).toBeInstanceOf(HTMLImageElement); + }); +}); diff --git a/src/hooks/useDnDItemProps.ts b/src/hooks/useDnDItemProps.ts index 9fd5956..c39a784 100644 --- a/src/hooks/useDnDItemProps.ts +++ b/src/hooks/useDnDItemProps.ts @@ -17,7 +17,7 @@ export const useDnDItemProps = (item: ActionPanelItem): DndProps => { (e: React.DragEvent) => { if (dragContext && item.dragProps) { dragContext.onDragStart(e, item.dragProps); - e.dataTransfer.setDragImage(dragContext.dragImagePreview, 0, 0); + e.dataTransfer.setDragImage(dragContext.dragImagePreview || e.currentTarget, 0, 0); } }, [dragContext, item.dragProps], From b70e4924182e06f0f80ce22c54a69ebc230c54e0 Mon Sep 17 00:00:00 2001 From: TheMerret Date: Wed, 30 Sep 2026 11:53:06 +0300 Subject: [PATCH 2/9] fix: scope action panel colors to nearest theme --- src/components/ActionPanel/ActionPanel.scss | 6 +- src/components/ActionPanel/ActionPanel.tsx | 4 +- .../ActionPanel/__tests__/theme.test.tsx | 68 +++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/components/ActionPanel/__tests__/theme.test.tsx diff --git a/src/components/ActionPanel/ActionPanel.scss b/src/components/ActionPanel/ActionPanel.scss index c8af0db..43b15dc 100644 --- a/src/components/ActionPanel/ActionPanel.scss +++ b/src/components/ActionPanel/ActionPanel.scss @@ -149,8 +149,10 @@ } } -.g-root_theme_light .dashkit-action-panel, -.g-root_theme_light-hc .dashkit-action-panel { +.dashkit-action-panel_theme_light, +.dashkit-action-panel_theme_light-hc, +.g-root_theme_light .dashkit-action-panel_theme_unset, +.g-root_theme_light-hc .dashkit-action-panel_theme_unset { --_--dashkit-action-panel-default-color: #000000b2; --_--dashkit-action-panel-default-item-dragging-color: #4d4d4d; --_--dashkit-action-panel-default-item-hover-color: #ffffff12; diff --git a/src/components/ActionPanel/ActionPanel.tsx b/src/components/ActionPanel/ActionPanel.tsx index 17f042b..ac72998 100644 --- a/src/components/ActionPanel/ActionPanel.tsx +++ b/src/components/ActionPanel/ActionPanel.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import {ThemeContext} from '@gravity-ui/uikit'; import {CSSTransition} from 'react-transition-group'; import {useDnDItemProps} from '../../hooks/useDnDItemProps'; @@ -65,12 +66,13 @@ export const ActionPanelItemContainer = ({item}: {item: ActionPanelItem}) => { }; export const ActionPanel = (props: ActionPanelProps) => { + const theme = React.useContext(ThemeContext)?.themeValue ?? 'unset'; const isDisabled = props.disable ?? false; const isAnimated = props.toggleAnimation ?? false; const nodeRef = React.useRef(null); const content = ( -
+
{props.items.map(({wrapTo, ...item}) => { const key = `dk-action-panel-${item.id}`; const children = ; diff --git a/src/components/ActionPanel/__tests__/theme.test.tsx b/src/components/ActionPanel/__tests__/theme.test.tsx new file mode 100644 index 0000000..8f6c7d2 --- /dev/null +++ b/src/components/ActionPanel/__tests__/theme.test.tsx @@ -0,0 +1,68 @@ +/** @jest-environment jsdom */ + +import {readFileSync} from 'fs'; +import {join} from 'path'; + +import React from 'react'; + +import {ThemeProvider} from '@gravity-ui/uikit'; +import {render} from '@testing-library/react'; +// Sass 1.53 does not expose its bundled declarations through TS bundler resolution. +// @ts-expect-error sass declarations are not resolvable with moduleResolution: bundler +import * as sass from 'sass'; + +import {ActionPanel} from '../ActionPanel'; + +it('uses the nearest theme provider instead of an outer light theme', () => { + const stylesheet = document.createElement('style'); + const scss = readFileSync(join(__dirname, '../ActionPanel.scss'), 'utf8'); + stylesheet.textContent = sass.compileString(scss).css; + document.head.appendChild(stylesheet); + + const {container, rerender} = render( + + + + + , + ); + + const panel = container.querySelector('.dashkit-action-panel') as HTMLElement; + expect(panel.classList.contains('dashkit-action-panel_theme_dark')).toBe(true); + const darkStyles = getComputedStyle(panel); + expect(darkStyles.getPropertyValue('--_--dashkit-action-panel-default-color')).toBe( + '#595959cc', + ); + expect( + darkStyles.getPropertyValue('--_--dashkit-action-panel-default-item-dragging-color'), + ).toBe('#595959'); + + rerender( + + + + + , + ); + expect(panel.classList.contains('dashkit-action-panel_theme_light-hc')).toBe(true); + const lightStyles = getComputedStyle(panel); + expect(lightStyles.getPropertyValue('--_--dashkit-action-panel-default-color')).toBe( + '#000000b2', + ); + expect(lightStyles.getPropertyValue('--_--dashkit-action-panel-default-item-hover-color')).toBe( + '#ffffff12', + ); + + // CSS-only theme setups should keep their previous light defaults without a ThemeProvider. + rerender( +
+ +
, + ); + const unthemedPanel = container.querySelector('.dashkit-action-panel') as HTMLElement; + expect(unthemedPanel.classList.contains('dashkit-action-panel_theme_unset')).toBe(true); + expect( + getComputedStyle(unthemedPanel).getPropertyValue('--_--dashkit-action-panel-default-color'), + ).toBe('#000000b2'); + stylesheet.remove(); +}); From 9178496118dfdba77352b844c561ea7ebc225912 Mon Sep 17 00:00:00 2001 From: TheMerret Date: Wed, 30 Sep 2026 12:09:07 +0300 Subject: [PATCH 3/9] fix: avoid Sass type suppression in theme test --- src/components/ActionPanel/__tests__/theme.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ActionPanel/__tests__/theme.test.tsx b/src/components/ActionPanel/__tests__/theme.test.tsx index 8f6c7d2..1585e51 100644 --- a/src/components/ActionPanel/__tests__/theme.test.tsx +++ b/src/components/ActionPanel/__tests__/theme.test.tsx @@ -7,12 +7,12 @@ import React from 'react'; import {ThemeProvider} from '@gravity-ui/uikit'; import {render} from '@testing-library/react'; -// Sass 1.53 does not expose its bundled declarations through TS bundler resolution. -// @ts-expect-error sass declarations are not resolvable with moduleResolution: bundler -import * as sass from 'sass'; import {ActionPanel} from '../ActionPanel'; +// Sass package declaration resolution differs between local installs and CI. +const sass = require('sass') as {compileString(scss: string): {css: string}}; + it('uses the nearest theme provider instead of an outer light theme', () => { const stylesheet = document.createElement('style'); const scss = readFileSync(join(__dirname, '../ActionPanel.scss'), 'utf8'); From 2bd26ff26d70717956e9f0436577e5a395ce9a27 Mon Sep 17 00:00:00 2001 From: TheMerret Date: Thu, 1 Oct 2026 16:18:45 +0300 Subject: [PATCH 4/9] fix: use Gravity UI theme tokens in ActionPanel --- src/components/ActionPanel/ActionPanel.scss | 32 +++------ src/components/ActionPanel/ActionPanel.tsx | 4 +- .../ActionPanel/__tests__/theme.test.tsx | 68 ------------------- 3 files changed, 11 insertions(+), 93 deletions(-) delete mode 100644 src/components/ActionPanel/__tests__/theme.test.tsx diff --git a/src/components/ActionPanel/ActionPanel.scss b/src/components/ActionPanel/ActionPanel.scss index 43b15dc..ba35010 100644 --- a/src/components/ActionPanel/ActionPanel.scss +++ b/src/components/ActionPanel/ActionPanel.scss @@ -2,15 +2,9 @@ $show_panel_transform: translateX(-50%) translateY(0); $hide_panel_transform: translateX(-50%) translateY(calc(100% + 20px)); - --_--dashkit-action-panel-default-color: #595959cc; - --_--dashkit-action-panel-default-item-dragging-color: #595959; - --_--dashkit-action-panel-default-item-hover-color: var( - --g-color-private-white-200, - rgba(255, 255, 255, 0.2) - ); --_--dashkit-action-panel-color: var( --dashkit-action-panel-color, - var(--_--dashkit-action-panel-default-color) + var(--g-color-base-float-heavy) ); --_--dashkit-action-panel-border-color: var(--dashkit-action-panel-border-color, transparent); --_--dashkit-action-panel-border-radius: var( @@ -59,19 +53,19 @@ --_--dashkit-action-panel-item-color: var(--dashkit-action-panel-item-color, transparent); --_--dashkit-action-panel-item-text-color: var( --dashkit-action-panel-item-text-color, - #fff + var(--g-color-text-light-primary) ); --_--dashkit-action-panel-item-color-hover: var( --dashkit-action-panel-item-color-hover, - var(--_--dashkit-action-panel-default-item-hover-color) + var(--g-color-base-light-simple-hover) ); --_--dashkit-action-panel-item-color-dragging: var( --dashkit-action-panel-item-color-dragging, - var(--_--dashkit-action-panel-default-item-dragging-color) + var(--g-color-base-float-heavy) ); --_--dashkit-action-panel-item-text-color-hover: var( --dashkit-action-panel-item-text-color-hover, - #fff + var(--g-color-text-light-primary) ); min-height: 40px; @@ -81,7 +75,7 @@ gap: 4px; justify-content: center; align-items: center; - border-radius: var(--g-border-radius-xl, 10px); + border-radius: var(--g-border-radius-xl); padding: 0 16px; box-sizing: border-box; white-space: nowrap; @@ -115,7 +109,10 @@ &_dragging:hover { transform: translate(0, 0); background-color: var(--_--dashkit-action-panel-item-color-dragging); - color: var(--dashkit-action-panel-item-text-color-dragging, #fff); + color: var( + --dashkit-action-panel-item-text-color-dragging, + var(--g-color-text-light-primary) + ); } &_dragging-source-disabled, @@ -148,12 +145,3 @@ text-overflow: ellipsis; } } - -.dashkit-action-panel_theme_light, -.dashkit-action-panel_theme_light-hc, -.g-root_theme_light .dashkit-action-panel_theme_unset, -.g-root_theme_light-hc .dashkit-action-panel_theme_unset { - --_--dashkit-action-panel-default-color: #000000b2; - --_--dashkit-action-panel-default-item-dragging-color: #4d4d4d; - --_--dashkit-action-panel-default-item-hover-color: #ffffff12; -} diff --git a/src/components/ActionPanel/ActionPanel.tsx b/src/components/ActionPanel/ActionPanel.tsx index ac72998..17f042b 100644 --- a/src/components/ActionPanel/ActionPanel.tsx +++ b/src/components/ActionPanel/ActionPanel.tsx @@ -1,6 +1,5 @@ import React from 'react'; -import {ThemeContext} from '@gravity-ui/uikit'; import {CSSTransition} from 'react-transition-group'; import {useDnDItemProps} from '../../hooks/useDnDItemProps'; @@ -66,13 +65,12 @@ export const ActionPanelItemContainer = ({item}: {item: ActionPanelItem}) => { }; export const ActionPanel = (props: ActionPanelProps) => { - const theme = React.useContext(ThemeContext)?.themeValue ?? 'unset'; const isDisabled = props.disable ?? false; const isAnimated = props.toggleAnimation ?? false; const nodeRef = React.useRef(null); const content = ( -
+
{props.items.map(({wrapTo, ...item}) => { const key = `dk-action-panel-${item.id}`; const children = ; diff --git a/src/components/ActionPanel/__tests__/theme.test.tsx b/src/components/ActionPanel/__tests__/theme.test.tsx deleted file mode 100644 index 1585e51..0000000 --- a/src/components/ActionPanel/__tests__/theme.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/** @jest-environment jsdom */ - -import {readFileSync} from 'fs'; -import {join} from 'path'; - -import React from 'react'; - -import {ThemeProvider} from '@gravity-ui/uikit'; -import {render} from '@testing-library/react'; - -import {ActionPanel} from '../ActionPanel'; - -// Sass package declaration resolution differs between local installs and CI. -const sass = require('sass') as {compileString(scss: string): {css: string}}; - -it('uses the nearest theme provider instead of an outer light theme', () => { - const stylesheet = document.createElement('style'); - const scss = readFileSync(join(__dirname, '../ActionPanel.scss'), 'utf8'); - stylesheet.textContent = sass.compileString(scss).css; - document.head.appendChild(stylesheet); - - const {container, rerender} = render( - - - - - , - ); - - const panel = container.querySelector('.dashkit-action-panel') as HTMLElement; - expect(panel.classList.contains('dashkit-action-panel_theme_dark')).toBe(true); - const darkStyles = getComputedStyle(panel); - expect(darkStyles.getPropertyValue('--_--dashkit-action-panel-default-color')).toBe( - '#595959cc', - ); - expect( - darkStyles.getPropertyValue('--_--dashkit-action-panel-default-item-dragging-color'), - ).toBe('#595959'); - - rerender( - - - - - , - ); - expect(panel.classList.contains('dashkit-action-panel_theme_light-hc')).toBe(true); - const lightStyles = getComputedStyle(panel); - expect(lightStyles.getPropertyValue('--_--dashkit-action-panel-default-color')).toBe( - '#000000b2', - ); - expect(lightStyles.getPropertyValue('--_--dashkit-action-panel-default-item-hover-color')).toBe( - '#ffffff12', - ); - - // CSS-only theme setups should keep their previous light defaults without a ThemeProvider. - rerender( -
- -
, - ); - const unthemedPanel = container.querySelector('.dashkit-action-panel') as HTMLElement; - expect(unthemedPanel.classList.contains('dashkit-action-panel_theme_unset')).toBe(true); - expect( - getComputedStyle(unthemedPanel).getPropertyValue('--_--dashkit-action-panel-default-color'), - ).toBe('#000000b2'); - stylesheet.remove(); -}); From 8c4012d1c4192462b976e91e0e7bcfec01b3b0e7 Mon Sep 17 00:00:00 2001 From: TheMerret Date: Thu, 1 Oct 2026 18:05:46 +0300 Subject: [PATCH 5/9] fix: respect ActionPanel icon dimensions --- src/components/ActionPanel/ActionPanel.scss | 7 ------- src/components/DashKit/__stories__/CssApiShowcase.tsx | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/ActionPanel/ActionPanel.scss b/src/components/ActionPanel/ActionPanel.scss index ba35010..f169e64 100644 --- a/src/components/ActionPanel/ActionPanel.scss +++ b/src/components/ActionPanel/ActionPanel.scss @@ -125,15 +125,8 @@ &__icon { display: flex; flex: 0 0 auto; - width: 16px; - height: 16px; align-items: center; justify-content: center; - - svg { - width: 16px; - height: 16px; - } } &__title { diff --git a/src/components/DashKit/__stories__/CssApiShowcase.tsx b/src/components/DashKit/__stories__/CssApiShowcase.tsx index 76260ba..bc951fd 100644 --- a/src/components/DashKit/__stories__/CssApiShowcase.tsx +++ b/src/components/DashKit/__stories__/CssApiShowcase.tsx @@ -20,7 +20,7 @@ export const CssApiShowcase: React.FC = () => { () => [ { id: 'chart', - icon: , + icon: , title: 'Chart', className: 'test', qa: 'chart', From 14a0669b851e2ae9f6ea2e9aeba6b64d8d098e37 Mon Sep 17 00:00:00 2001 From: TheMerret Date: Fri, 2 Oct 2026 14:47:16 +0300 Subject: [PATCH 6/9] fix: use brand token for grid placeholder --- src/components/GridItem/GridItem.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/GridItem/GridItem.scss b/src/components/GridItem/GridItem.scss index 2493578..9c2b1d9 100644 --- a/src/components/GridItem/GridItem.scss +++ b/src/components/GridItem/GridItem.scss @@ -94,8 +94,8 @@ } .react-grid-item.react-grid-placeholder { - --_-dashkit-placeholder-color: var(--dashkit-placeholder-color, #027bfe80); - --_-dashkit-placeholder-opacity: var(--dashkit-placeholder-opacity, 0.2); + --_-dashkit-placeholder-color: var(--dashkit-placeholder-color, var(--g-color-base-brand)); + --_-dashkit-placeholder-opacity: var(--dashkit-placeholder-opacity, 0.1); background: var(--_-dashkit-placeholder-color); border-radius: var(--dashkit-grid-item-border-radius, 3px); From 1020aa1adadeecaa47d436a8242db39466be7501 Mon Sep 17 00:00:00 2001 From: TheMerret Date: Fri, 2 Oct 2026 14:54:59 +0300 Subject: [PATCH 7/9] docs: rename customization story and size panel icons --- .../DashKit/__stories__/CssApiShowcase.tsx | 14 +++++++------- .../DashKit/__stories__/DashKit.stories.tsx | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/DashKit/__stories__/CssApiShowcase.tsx b/src/components/DashKit/__stories__/CssApiShowcase.tsx index bc951fd..99315bc 100644 --- a/src/components/DashKit/__stories__/CssApiShowcase.tsx +++ b/src/components/DashKit/__stories__/CssApiShowcase.tsx @@ -20,35 +20,35 @@ export const CssApiShowcase: React.FC = () => { () => [ { id: 'chart', - icon: , + icon: , title: 'Chart', className: 'test', qa: 'chart', }, { id: 'selector', - icon: , + icon: , title: 'Selector', qa: 'selector', }, { id: 'text', - icon: , + icon: , title: 'Text', }, { id: 'header', - icon: , + icon: , title: 'Header', }, { id: 'links', - icon: , + icon: , title: 'Links', }, { id: 'tabs', - icon: , + icon: , title: 'Tabs', }, ], @@ -77,7 +77,7 @@ export const CssApiShowcase: React.FC = () => { --dashkit-placeholder-opacity: 1; }`} - + diff --git a/src/components/DashKit/__stories__/DashKit.stories.tsx b/src/components/DashKit/__stories__/DashKit.stories.tsx index 35a2abe..5a3c675 100644 --- a/src/components/DashKit/__stories__/DashKit.stories.tsx +++ b/src/components/DashKit/__stories__/DashKit.stories.tsx @@ -89,8 +89,8 @@ export const Default = DefaultTemplate.bind({}); const ShowcaseTemplate: Story = () => ; export const Showcase = ShowcaseTemplate.bind({}); -const CssApiShowcaseTemplate: Story = () => ; -export const CSS_API = CssApiShowcaseTemplate.bind({}); +const CustomizationTemplate: Story = () => ; +export const Customization = CustomizationTemplate.bind({}); const DndShowcaseTemplate: Story = () => ; export const DragNDrop = DndShowcaseTemplate.bind({}); From ec6ad62c67ae32a73232ac7098598c4b51806d39 Mon Sep 17 00:00:00 2001 From: TheMerret Date: Fri, 2 Oct 2026 16:31:32 +0300 Subject: [PATCH 8/9] fix: preserve CSS API story ID with customization label --- src/components/DashKit/__stories__/DashKit.stories.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/DashKit/__stories__/DashKit.stories.tsx b/src/components/DashKit/__stories__/DashKit.stories.tsx index 5a3c675..03f7e8b 100644 --- a/src/components/DashKit/__stories__/DashKit.stories.tsx +++ b/src/components/DashKit/__stories__/DashKit.stories.tsx @@ -90,7 +90,8 @@ const ShowcaseTemplate: Story = () => ; export const Showcase = ShowcaseTemplate.bind({}); const CustomizationTemplate: Story = () => ; -export const Customization = CustomizationTemplate.bind({}); +export const CSS_API = CustomizationTemplate.bind({}); +CSS_API.storyName = 'Customization'; const DndShowcaseTemplate: Story = () => ; export const DragNDrop = DndShowcaseTemplate.bind({}); From dfb2b25a399b646b56d9e841b55c196e135321dc Mon Sep 17 00:00:00 2001 From: TheMerret Date: Fri, 2 Oct 2026 16:39:11 +0300 Subject: [PATCH 9/9] fix: use Customization story URL and navigation --- src/components/DashKit/__stories__/DashKit.stories.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/DashKit/__stories__/DashKit.stories.tsx b/src/components/DashKit/__stories__/DashKit.stories.tsx index 03f7e8b..5a3c675 100644 --- a/src/components/DashKit/__stories__/DashKit.stories.tsx +++ b/src/components/DashKit/__stories__/DashKit.stories.tsx @@ -90,8 +90,7 @@ const ShowcaseTemplate: Story = () => ; export const Showcase = ShowcaseTemplate.bind({}); const CustomizationTemplate: Story = () => ; -export const CSS_API = CustomizationTemplate.bind({}); -CSS_API.storyName = 'Customization'; +export const Customization = CustomizationTemplate.bind({}); const DndShowcaseTemplate: Story = () => ; export const DragNDrop = DndShowcaseTemplate.bind({});