Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/eui/changelogs/upcoming/8836.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**New features**

- Added `EuiButtonSplit` component for creating split buttons with a primary action and dropdown menu
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EuiButtonSplit is rendered 1`] = `
<span
class="euiButtonSplit testClass1 testClass2 emotion-euiButtonSplit-euiTestCss"
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-base-primary-leftButton"
type="button"
>
<span
class="emotion-euiButtonDisplayContent"
>
<span
class="eui-textTruncate"
>
Main
</span>
</span>
</button>
<span
class="css-1w8iqkv-rightSpan"
>
<div
class="euiPopover emotion-euiPopover-inline-block"
>
<button
aria-label="More actions"
class="euiButtonIcon emotion-euiButtonIcon-m-base-primary-iconButton"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="arrowDown"
/>
</button>
</div>
</span>
</span>
`;

exports[`EuiButtonSplit props renders with isDisabled 1`] = `
<span
class="euiButtonSplit emotion-euiButtonSplit"
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-isDisabled-base-disabled-leftButton"
disabled=""
type="button"
>
<span
class="emotion-euiButtonDisplayContent"
>
<span
class="eui-textTruncate"
>
Main
</span>
</span>
</button>
<span
class="css-1w8iqkv-rightSpan"
>
<div
class="euiPopover emotion-euiPopover-inline-block"
>
<button
aria-label="More actions"
class="euiButtonIcon emotion-euiButtonIcon-m-base-disabled-isDisabled-iconButton"
disabled=""
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="arrowDown"
/>
</button>
</div>
</span>
</span>
`;

exports[`EuiButtonSplit props text color styling renders non-text color with fill=false (has margin, has left border) 1`] = `
<span
class="euiButtonSplit emotion-euiButtonSplit"
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-base-primary-leftButton"
type="button"
>
<span
class="emotion-euiButtonDisplayContent"
>
<span
class="eui-textTruncate"
>
Main
</span>
</span>
</button>
<span
class="css-1w8iqkv-rightSpan"
>
<div
class="euiPopover emotion-euiPopover-inline-block"
>
<button
aria-label="More actions"
class="euiButtonIcon emotion-euiButtonIcon-m-base-primary-iconButton"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="arrowDown"
/>
</button>
</div>
</span>
</span>
`;

exports[`EuiButtonSplit props text color styling renders text color with fill=false (no margin, no left border) 1`] = `
<span
class="euiButtonSplit emotion-euiButtonSplit"
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-base-text-leftButton"
type="button"
>
<span
class="emotion-euiButtonDisplayContent"
>
<span
class="eui-textTruncate"
>
Main
</span>
</span>
</button>
<span
class="css-1akurmu-rightSpan"
>
<div
class="euiPopover emotion-euiPopover-inline-block"
>
<button
aria-label="More actions"
class="euiButtonIcon emotion-euiButtonIcon-m-base-text-iconButton"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="arrowDown"
/>
</button>
</div>
</span>
</span>
`;

exports[`EuiButtonSplit props text color styling renders text color with fill=true (has margin, has left border) 1`] = `
<span
class="euiButtonSplit emotion-euiButtonSplit"
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-fill-text-leftButton"
type="button"
>
<span
class="emotion-euiButtonDisplayContent"
>
<span
class="eui-textTruncate"
>
Main
</span>
</span>
</button>
<span
class="css-1w8iqkv-rightSpan"
>
<div
class="euiPopover emotion-euiPopover-inline-block"
>
<button
aria-label="More actions"
class="euiButtonIcon emotion-euiButtonIcon-m-fill-text-iconButton"
type="button"
>
<span
aria-hidden="true"
class="euiButtonIcon__icon"
color="inherit"
data-euiicon-type="arrowDown"
/>
</button>
</div>
</span>
</span>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { EuiButtonSplit, EuiButtonSplitProps } from './button_split';
import { EuiListGroup } from '../../list_group';

const meta: Meta<EuiButtonSplitProps> = {
title: 'Navigation/EuiButtonSplit',
component: EuiButtonSplit,
args: {
color: 'text',
fill: false,
size: 's',
isDisabled: false,
buttonProps: {
children: 'Add panel',
onClick: () => alert('Main button clicked!'),
},
iconButtonProps: {
iconType: 'arrowDown',
'aria-label': 'More actions',
},
popoverMenu: (closePopover) => (
<EuiListGroup
style={{ minWidth: 120 }}
listItems={[
{
label: 'Open Lens',
onClick: () => {
alert('Open Lens clicked!');
closePopover();
},
iconType: 'visualizeApp',
},
{
label: 'Open Maps',
onClick: () => {
alert('Open Maps clicked!');
closePopover();
},
iconType: 'gisApp',
},
]}
showToolTips={false}
/>
),
// panelPaddingSize intentionally omitted to show default
},
};

export default meta;
type Story = StoryObj<EuiButtonSplitProps>;

export const Playground: Story = {
args: {},
parameters: {
docs: {
description: {
story: `
This split button demonstrates custom border radius and spacing:
- The right edge of the left button and the left edge of the right button have zero border radius, making them join seamlessly.
- There is a 1px space between the two buttons for visual clarity.

**panelPaddingSize** can be set to control the popover's padding. The default is 'm'. Example with custom padding:

\`\`\`tsx
<EuiButtonSplit
color="primary"
fill
size="m"
buttonProps={{ children: 'Custom padding' }}
iconButtonProps={{ iconType: 'arrowDown', 'aria-label': 'More actions' }}
popoverMenu={<EuiListGroup listItems={[{ label: 'Item' }]} />}
panelPaddingSize="l"
/>
\`\`\`
`,
},
},
},
render: (args) => (
<div style={{ padding: 24 }}>
<EuiButtonSplit {...args} />
</div>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { css } from '@emotion/react';
import { logicalCSS } from '../../../global_styling/functions';

export const euiButtonSplitStyles = () => {
return {
euiButtonSplit: css`
display: inline-flex;
align-items: stretch;
`,
leftButton: css`
${logicalCSS('border-top-right-radius', '0 !important')}
${logicalCSS('border-bottom-right-radius', '0 !important')}
`,
rightSpan: (color: string, fill?: boolean) => css`
display: flex;
align-items: stretch;
${color !== 'text' || fill ? 'margin-left: 1px;' : ''}
`,
iconButton: (color: string, fill?: boolean) => css`
${logicalCSS('border-top-left-radius', '0 !important')}
${logicalCSS('border-bottom-left-radius', '0 !important')}
${color === 'text' && !fill
? logicalCSS('border-left', 'none !important')
: ''}
`,
};
};
Loading