Skip to content

Commit 2a3780d

Browse files
Kiryousclaude
andauthored
[One Workflow] Fix Actions menu row layout after EUI 116 upgrade (#271409)
Closes elastic/security-team#17599 ## Summary EUI 116 ([#260949](#260949)) routes `EuiSelectableListItem` through the shared `EuiListItemLayout` (EUI [#9532](elastic/eui#9532)). The new layout (a) adds `padding-block: 6px` on `.euiSelectableListItem__text` and `gap: 8px` on `.euiSelectableListItem__content`, (b) drops the between-row `border-bottom` that EUI 114/115 set on every non-last item, and (c) shrinks the `.euiSelectableList__groupLabel` horizontal padding from `12px` to `4px`. `renderActionOption` in the workflows Actions menu builds a custom icon + title + subtitle layout and owns its own spacing, so the new padding/gap stacked on top — rows grew from ~64 px to 76 px and the subtitle shifted. The dropped row border also broke the divider lines the design relies on, and the group label / header no longer sat on the same left edge as the items. The fix scopes a few CSS overrides to the menu's `EuiSelectable`: - `__content { gap: 0 }` - `__text { padding-block: 0 }` - `.euiSelectableListItem:not(:last-of-type) { border-bottom: thin }` — restores the row divider - `.euiSelectableList__groupLabel { padding-inline: 16px }` — aligns "Add step" with the icon column - `header { padding-inline: 16px }` (was `padding: 12px`) — aligns the popover title and search with the items Also gated the "Add step" group label on having at least one matching action, so searches with no step matches no longer leave an orphan label (matching the existing behavior of the Commands and Jump-to-a-step groups, per designer feedback). After the fix, "Actions menu" title, search input, "Add step" label, and item icons all sit at the same `x = 16` from the popover edge — matching the [Figma design](https://www.figma.com/design/bVapoDOKB46hm0pSQXp9nA/One-Workflow--2?node-id=9838-34071). Item height settles at 65 px with 1 px row borders. ## Before / after | Current main (bug) | With fix | |---|---| | ![before](https://pub-6b50802d113345dea50c783e8280b53e.r2.dev/artifacts/20260528/pokm12gw-sb_before_main.webp) | ![after](https://pub-6b50802d113345dea50c783e8280b53e.r2.dev/artifacts/20260528/6xla69h8-sb_after_main_clean.webp) | ### Empty search state When a search has no matching steps, EuiSelectable's "doesn't match any options" message shows instead of an orphan "Add step" label: ![empty](https://pub-6b50802d113345dea50c783e8280b53e.r2.dev/artifacts/20260528/7yjujiv6-sb_no_match.webp) ## Test plan - [ ] Open the workflows YAML editor, press `⌘K`, confirm rows are tight (~65 px), divider lines visible between items, subtitle sits directly under the title next to the icon - [ ] Confirm title, search input, "Add step" label, and item icons all sit at the same left edge - [ ] Click into a connector group (e.g. Triggers) — nested view renders the same way with dividers - [ ] Type `Steps: ` to enter the virtualized full-catalog mode — items render correctly without overlapping - [ ] Type a query that doesn't match any step — confirm no orphan "Add step" label - [ ] No regression in the existing `ActionsMenu` Jest tests --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent a067907 commit 2a3780d

3 files changed

Lines changed: 59 additions & 25 deletions

File tree

src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/use_display_options.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,24 @@ describe('buildDisplayOptions', () => {
4343
};
4444

4545
it('shows Add step + Commands sections when no search is active', () => {
46-
const result = buildDisplayOptions(base);
46+
const result = buildDisplayOptions({ ...base, options: [makeAction('a', 'A')] });
4747
expect(groupLabels(result)).toEqual(['Add step', 'Commands']);
48-
expect(dataKinds(result)).toEqual(['command', 'command']);
48+
expect(dataKinds(result)).toEqual(['action', 'command', 'command']);
49+
});
50+
51+
it('hides Add step section when no actions match the search', () => {
52+
const result = buildDisplayOptions({ ...base, options: [], searchTerm: 'zzz' });
53+
expect(groupLabels(result)).not.toContain('Add step');
54+
});
55+
56+
it('hides Add step section in Steps: mode when no actions match', () => {
57+
const result = buildDisplayOptions({
58+
...base,
59+
options: [],
60+
searchTerm: `${STEPS_PREFIX}zzz`,
61+
});
62+
expect(groupLabels(result)).not.toContain('Add step');
63+
expect(result).toHaveLength(0);
4964
});
5065

5166
it('returns action items directly when inside a sub-group', () => {

src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/lib/use_display_options.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -91,37 +91,41 @@ export function buildDisplayOptions({
9191
}
9292

9393
if (isStepsMode) {
94+
if (options.length > 0) {
95+
result.push({
96+
label: i18n.translate('workflows.actionsMenu.addStepGroupLabel', {
97+
defaultMessage: 'Add step',
98+
}),
99+
isGroupLabel: true,
100+
});
101+
for (const opt of options) {
102+
result.push({ label: opt.label, data: { menuItem: { kind: 'action', action: opt } } });
103+
}
104+
}
105+
return result;
106+
}
107+
108+
const visibleOptions = hasSearch ? options.slice(0, MAX_VISIBLE_STEPS) : options;
109+
if (visibleOptions.length > 0) {
94110
result.push({
95111
label: i18n.translate('workflows.actionsMenu.addStepGroupLabel', {
96112
defaultMessage: 'Add step',
97113
}),
98114
isGroupLabel: true,
99115
});
100-
for (const opt of options) {
116+
for (const opt of visibleOptions) {
101117
result.push({ label: opt.label, data: { menuItem: { kind: 'action', action: opt } } });
102118
}
103-
return result;
104-
}
105-
106-
result.push({
107-
label: i18n.translate('workflows.actionsMenu.addStepGroupLabel', {
108-
defaultMessage: 'Add step',
109-
}),
110-
isGroupLabel: true,
111-
});
112-
const visibleOptions = hasSearch ? options.slice(0, MAX_VISIBLE_STEPS) : options;
113-
for (const opt of visibleOptions) {
114-
result.push({ label: opt.label, data: { menuItem: { kind: 'action', action: opt } } });
115-
}
116119

117-
if (hasSearch && options.length > MAX_VISIBLE_STEPS) {
118-
result.push({
119-
label: i18n.translate('workflows.actionsMenu.viewAllSteps', {
120-
defaultMessage: 'View all steps to add',
121-
}),
122-
className: 'compactOption',
123-
data: { menuItem: { kind: 'nav', target: 'viewAll' } },
124-
});
120+
if (hasSearch && options.length > MAX_VISIBLE_STEPS) {
121+
result.push({
122+
label: i18n.translate('workflows.actionsMenu.viewAllSteps', {
123+
defaultMessage: 'View all steps to add',
124+
}),
125+
className: 'compactOption',
126+
data: { menuItem: { kind: 'nav', target: 'viewAll' } },
127+
});
128+
}
125129
}
126130

127131
const filteredCmds = (commands ?? []).filter(

src/platform/plugins/shared/workflows_management/public/features/actions_menu_popover/ui/actions_menu.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,12 +417,26 @@ const componentStyles = {
417417
'& .euiSelectableListItem.compactOption': {
418418
paddingBlock: euiTheme.size.s,
419419
},
420+
// EUI 116 routes EuiSelectableListItem through EuiListItemLayout, which
421+
// adds gap on __content and vertical padding on __text and drops the
422+
// between-row border. renderActionOption owns its own spacing, so zero
423+
// the new gap/padding out and re-add the row border to match the design.
424+
'& .euiSelectableListItem__content': {
425+
gap: 0,
426+
},
427+
'& .euiSelectableListItem__text': {
428+
paddingBlock: 0,
429+
},
430+
'& .euiSelectableListItem:not(:last-of-type)': {
431+
borderBottom: euiTheme.border.thin,
432+
},
420433
'& .euiSelectableList': {
421434
maxHeight: '420px',
422435
overflowY: 'auto',
423436
},
424437
'& .euiSelectableList__groupLabel': {
425438
borderBottom: euiTheme.border.thin,
439+
paddingInline: '16px',
426440
},
427441
'& .euiSelectableList__groupLabel ~ .euiSelectableList__groupLabel': {
428442
marginTop: '24px',
@@ -435,7 +449,8 @@ const componentStyles = {
435449
}),
436450
header: ({ euiTheme }: UseEuiTheme) =>
437451
css({
438-
padding: euiTheme.size.m,
452+
paddingBlock: euiTheme.size.m,
453+
paddingInline: '16px',
439454
}),
440455
actionOption: css({
441456
gap: '12px',

0 commit comments

Comments
 (0)