Skip to content

Commit c6d3221

Browse files
committed
add footer visibility prop and toolbarProps
1 parent 5e78524 commit c6d3221

11 files changed

+378
-229
lines changed

packages/eui/changelogs/CHANGELOG_2025.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## [`v106.1.0`](https://github.com/elastic/eui/releases/v106.1.0)
22

3+
- Added `footerProps.visibility` and `toolbarProps.right` props to `EuiMarkdownEditor` for more flexible layout control. ([#8889](https://github.com/elastic/eui/pull/8889))
34
- Added `--euiPushFlyoutOffsetInlineStart` and `--euiPushFlyoutOffsetInlineEnd` global CSS variables set by the `EuiFlyout` in `push` mode. ([#8872](https://github.com/elastic/eui/pull/8872))
45
- Reduced the `min-width` for inputs in `EuiRange` and `EuiDualRange` ([#8866](https://github.com/elastic/eui/pull/8866))
56
- Added `includeSelectorInFocusTrap` prop for `EuiFlyout` ([#8849](https://github.com/elastic/eui/pull/8849))

packages/eui/src/components/markdown_editor/__snapshots__/markdown_editor.test.tsx.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ exports[`EuiMarkdownEditor is rendered 1`] = `
214214
/>
215215
<div
216216
class="euiMarkdownEditorFooter emotion-euiMarkdownEditorFooter"
217+
data-test-subj="euiMarkdownEditorFooter"
217218
>
218219
<div
219220
class="euiMarkdownEditorFooter__actions emotion-euiMarkdownEditorFooter__actions"
@@ -460,6 +461,7 @@ exports[`EuiMarkdownEditor props autoExpandPreview is rendered with false 1`] =
460461
/>
461462
<div
462463
class="euiMarkdownEditorFooter emotion-euiMarkdownEditorFooter"
464+
data-test-subj="euiMarkdownEditorFooter"
463465
>
464466
<div
465467
class="euiMarkdownEditorFooter__actions emotion-euiMarkdownEditorFooter__actions"
@@ -706,6 +708,7 @@ exports[`EuiMarkdownEditor props height is rendered in full mode 1`] = `
706708
/>
707709
<div
708710
class="euiMarkdownEditorFooter emotion-euiMarkdownEditorFooter"
711+
data-test-subj="euiMarkdownEditorFooter"
709712
>
710713
<div
711714
class="euiMarkdownEditorFooter__actions emotion-euiMarkdownEditorFooter__actions"
@@ -952,6 +955,7 @@ exports[`EuiMarkdownEditor props height is rendered with a custom size 1`] = `
952955
/>
953956
<div
954957
class="euiMarkdownEditorFooter emotion-euiMarkdownEditorFooter"
958+
data-test-subj="euiMarkdownEditorFooter"
955959
>
956960
<div
957961
class="euiMarkdownEditorFooter__actions emotion-euiMarkdownEditorFooter__actions"
@@ -1198,6 +1202,7 @@ exports[`EuiMarkdownEditor props maxHeight is rendered with a custom size 1`] =
11981202
/>
11991203
<div
12001204
class="euiMarkdownEditorFooter emotion-euiMarkdownEditorFooter"
1205+
data-test-subj="euiMarkdownEditorFooter"
12011206
>
12021207
<div
12031208
class="euiMarkdownEditorFooter__actions emotion-euiMarkdownEditorFooter__actions"
@@ -1454,6 +1459,7 @@ exports[`EuiMarkdownEditor props readOnly is set to true 1`] = `
14541459
/>
14551460
<div
14561461
class="euiMarkdownEditorFooter emotion-euiMarkdownEditorFooter"
1462+
data-test-subj="euiMarkdownEditorFooter"
14571463
>
14581464
<div
14591465
class="euiMarkdownEditorFooter__actions emotion-euiMarkdownEditorFooter__actions"

packages/eui/src/components/markdown_editor/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
export type { EuiMarkdownEditorProps } from './markdown_editor';
1010
export { EuiMarkdownEditor } from './markdown_editor';
11+
export { EuiMarkdownEditorHelpButton } from './markdown_editor_help_button';
1112
export {
1213
getDefaultEuiMarkdownParsingPlugins,
1314
getDefaultEuiMarkdownProcessingPlugins,

packages/eui/src/components/markdown_editor/markdown_editor.test.tsx

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import React from 'react';
10-
import { fireEvent } from '@testing-library/react';
10+
import { cleanup, fireEvent } from '@testing-library/react';
1111
import { shouldRenderCustomStyles } from '../../test/internal';
1212
import { requiredProps } from '../../test/required_props';
1313
import { render, screen } from '../../test/rtl';
@@ -139,7 +139,7 @@ describe('EuiMarkdownEditor', () => {
139139
});
140140

141141
test('modal with help syntax is rendered', () => {
142-
const { baseElement, getByLabelText } = render(
142+
const { baseElement } = render(
143143
<EuiMarkdownEditor
144144
editorId="editorId"
145145
value=""
@@ -149,7 +149,7 @@ describe('EuiMarkdownEditor', () => {
149149
);
150150
expect(baseElement.querySelector('.euiModal')).not.toBeInTheDocument();
151151

152-
fireEvent.click(getByLabelText('Show markdown help'));
152+
fireEvent.click(screen.getByLabelText('Show markdown help'));
153153

154154
expect(baseElement.querySelector('.euiModal')).toBeInTheDocument();
155155
});
@@ -158,7 +158,7 @@ describe('EuiMarkdownEditor', () => {
158158
const { parsingPlugins, processingPlugins, uiPlugins } =
159159
getDefaultEuiMarkdownPlugins({ exclude: ['tooltip'] });
160160

161-
const { baseElement, getByLabelText } = render(
161+
const { baseElement } = render(
162162
<EuiMarkdownEditor
163163
editorId="editorId"
164164
value=""
@@ -169,7 +169,7 @@ describe('EuiMarkdownEditor', () => {
169169
{...requiredProps}
170170
/>
171171
);
172-
fireEvent.click(getByLabelText('Show markdown help'));
172+
fireEvent.click(screen.getByLabelText('Show markdown help'));
173173

174174
expect(baseElement.querySelector('.euiModal')).not.toBeInTheDocument();
175175
expect(baseElement.querySelector('.euiPopover')).toBeInTheDocument();
@@ -182,12 +182,13 @@ describe('EuiMarkdownEditor', () => {
182182
onChange: jest.fn(),
183183
};
184184

185-
const { getByTestSubject } = render(
186-
<EuiMarkdownEditor {...testProps} {...requiredProps} />
187-
);
185+
render(<EuiMarkdownEditor {...testProps} {...requiredProps} />);
188186

189187
const event = { target: { value: 'sometext' } };
190-
fireEvent.change(getByTestSubject('euiMarkdownEditorTextArea'), event);
188+
fireEvent.change(
189+
screen.getByTestSubject('euiMarkdownEditorTextArea'),
190+
event
191+
);
191192

192193
expect(testProps.onChange).toHaveBeenCalledTimes(1);
193194
expect(testProps.onChange).toHaveBeenLastCalledWith(event.target.value);
@@ -239,11 +240,9 @@ describe('EuiMarkdownEditor', () => {
239240
errors: testMessage,
240241
};
241242

242-
const { getByLabelText } = render(
243-
<EuiMarkdownEditor {...testProps} {...requiredProps} />
244-
);
243+
render(<EuiMarkdownEditor {...testProps} {...requiredProps} />);
245244

246-
expect(getByLabelText('Show errors')).toBeInTheDocument();
245+
expect(screen.getByLabelText('Show errors')).toBeInTheDocument();
247246
});
248247

249248
test('does not render error if error messages are empty', () => {
@@ -254,11 +253,9 @@ describe('EuiMarkdownEditor', () => {
254253
errors: [],
255254
};
256255

257-
const { queryByTestSubject } = render(
258-
<EuiMarkdownEditor {...testProps} {...requiredProps} />
259-
);
256+
render(<EuiMarkdownEditor {...testProps} {...requiredProps} />);
260257

261-
expect(queryByTestSubject('Show errors')).not.toBeInTheDocument();
258+
expect(screen.queryByTestSubject('Show errors')).not.toBeInTheDocument();
262259
});
263260
});
264261

@@ -286,11 +283,9 @@ describe('EuiMarkdownEditor', () => {
286283
document.execCommand = execCommandMock;
287284

288285
beforeEach(() => {
289-
const { getByTestSubject } = render(
290-
<EuiMarkdownEditor {...testProps} {...requiredProps} />
291-
);
286+
render(<EuiMarkdownEditor {...testProps} {...requiredProps} />);
292287

293-
const textarea = getByTestSubject(
288+
const textarea = screen.getByTestSubject(
294289
'euiMarkdownEditorTextArea'
295290
) as HTMLTextAreaElement;
296291
textarea.setSelectionRange(0, 5);
@@ -380,4 +375,61 @@ describe('EuiMarkdownEditor', () => {
380375
);
381376
});
382377
});
378+
describe('toolbar props', () => {
379+
it('shows the custom toolbar component when passed', () => {
380+
const CustomToolbarComponent = () => <div>Custom toolbar</div>;
381+
render(
382+
<EuiMarkdownEditor
383+
onChange={() => null}
384+
value="markdown test"
385+
{...requiredProps}
386+
toolbarProps={{ right: <CustomToolbarComponent /> }}
387+
/>
388+
);
389+
expect(screen.getByText('Custom toolbar')).toBeInTheDocument();
390+
expect(
391+
screen.queryByRole('button', { name: 'Preview' })
392+
).not.toBeInTheDocument();
393+
cleanup();
394+
render(
395+
<EuiMarkdownEditor
396+
onChange={() => null}
397+
value="markdown test"
398+
{...requiredProps}
399+
/>
400+
);
401+
expect(screen.queryByText('Custom toolbar')).not.toBeInTheDocument();
402+
expect(
403+
screen.getByRole('button', { name: 'Preview' })
404+
).toBeInTheDocument();
405+
fireEvent.click(screen.getByRole('button', { name: 'Preview' }));
406+
expect(
407+
screen.getByRole('button', { name: 'Editor' })
408+
).toBeInTheDocument();
409+
});
410+
});
411+
it('should show footer by default and hide when visibility is false', () => {
412+
render(
413+
<EuiMarkdownEditor
414+
onChange={() => null}
415+
value="markdown test"
416+
{...requiredProps}
417+
/>
418+
);
419+
expect(
420+
screen.getByTestSubject('euiMarkdownEditorFooter')
421+
).toBeInTheDocument();
422+
cleanup();
423+
render(
424+
<EuiMarkdownEditor
425+
onChange={() => null}
426+
value="markdown test"
427+
{...requiredProps}
428+
footerProps={{ visibility: false }}
429+
/>
430+
);
431+
expect(
432+
screen.queryByTestSubject('euiMarkdownEditorFooter')
433+
).not.toBeInTheDocument();
434+
});
383435
});

packages/eui/src/components/markdown_editor/markdown_editor.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import React, {
1616
useCallback,
1717
useRef,
1818
forwardRef,
19+
ReactNode,
1920
} from 'react';
2021

2122
import unified, { PluggableList, Processor } from 'unified';
@@ -137,6 +138,14 @@ type CommonMarkdownEditorProps = Omit<
137138
EuiMarkdownFormatProps,
138139
'parsingPluginList' | 'processingPluginList' | 'children'
139140
>;
141+
toolbarProps?: {
142+
/** Renders a custom node instead of the default preview/editor switch on the right side of the toolbar */
143+
right?: ReactNode;
144+
};
145+
footerProps?: {
146+
/** Controls whether the footer is shown, defaults to `true` */
147+
visibility?: boolean;
148+
};
140149
};
141150

142151
export type EuiMarkdownEditorProps = OneOf<
@@ -213,6 +222,8 @@ export const EuiMarkdownEditor = forwardRef<
213222
markdownFormatProps,
214223
placeholder,
215224
readOnly,
225+
toolbarProps,
226+
footerProps = { visibility: true },
216227
...rest
217228
},
218229
ref
@@ -433,6 +444,7 @@ export const EuiMarkdownEditor = forwardRef<
433444
}
434445
viewMode={viewMode}
435446
uiPlugins={toolbarPlugins}
447+
toolbarProps={toolbarProps}
436448
/>
437449

438450
{isPreviewing && (
@@ -481,6 +493,7 @@ export const EuiMarkdownEditor = forwardRef<
481493
selectionEnd: newSelectionPoint,
482494
});
483495
}}
496+
footerProps={footerProps}
484497
uiPlugins={toolbarPlugins}
485498
errors={errors}
486499
hasUnacceptedItems={hasUnacceptedItems}

packages/eui/src/components/markdown_editor/markdown_editor_drop_zone.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ interface EuiMarkdownEditorDropZoneProps extends PropsWithChildren {
3838
setHasUnacceptedItems: (hasUnacceptedItems: boolean) => void;
3939
setEditorFooterHeight: (height: number) => void;
4040
isEditing: boolean;
41+
footerProps?: {
42+
visibility?: boolean;
43+
};
4144
}
4245

4346
const getUnacceptedItems = (
@@ -84,6 +87,7 @@ export const EuiMarkdownEditorDropZone: FunctionComponent<
8487
setHasUnacceptedItems,
8588
setEditorFooterHeight,
8689
isEditing,
90+
footerProps,
8791
} = props;
8892

8993
const classes = classNames('euiMarkdownEditorDropZone');
@@ -217,18 +221,21 @@ export const EuiMarkdownEditorDropZone: FunctionComponent<
217221
return (
218222
<div {...rootProps} css={cssStyles} className={classes}>
219223
{children}
220-
<EuiMarkdownEditorFooter
221-
ref={setEditorFooterRef}
222-
uiPlugins={uiPlugins}
223-
openFiles={() => {
224-
setHasUnacceptedItems(false);
225-
open();
226-
}}
227-
isUploadingFiles={isUploadingFiles}
228-
hasUnacceptedItems={hasUnacceptedItems}
229-
dropHandlers={dropHandlers}
230-
errors={errors}
231-
/>
224+
{footerProps?.visibility && (
225+
<EuiMarkdownEditorFooter
226+
ref={setEditorFooterRef}
227+
uiPlugins={uiPlugins}
228+
openFiles={() => {
229+
setHasUnacceptedItems(false);
230+
open();
231+
}}
232+
isUploadingFiles={isUploadingFiles}
233+
hasUnacceptedItems={hasUnacceptedItems}
234+
dropHandlers={dropHandlers}
235+
errors={errors}
236+
/>
237+
)}
238+
232239
<input {...getInputProps()} />
233240
</div>
234241
);

packages/eui/src/components/markdown_editor/markdown_editor_footer.styles.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,6 @@ export const euiMarkdownEditorFooterStyles = (euiThemeContext: UseEuiTheme) => {
3535
euiMarkdownEditorFooter__uploadError: css`
3636
border-radius: ${borderRadius};
3737
`,
38-
// Override the default button icon width size
39-
euiMarkdownEditorFooter__helpButton: css`
40-
.euiIcon {
41-
${logicalCSS('width', '26px')}
42-
}
43-
`,
4438
euiMarkdownEditorFooter__popover: css`
4539
${logicalCSS('width', '300px')}
4640
`,

0 commit comments

Comments
 (0)