Skip to content

Commit c19625a

Browse files
authored
[EuiButtonGroup] Convert to Emotion (#6841)
* Add support for `element` prop to EuiButtonDisplay - required for deprecation of old component, used by `EuiButtonGroup` + rename `element` usage in `EuiButtonGroupButton` to match existing prefix syntax * Remove unnecessary generated `id` from EuiButtonGroup - `label`s that wrap `input`s don't need a `for` attribute, and buttons really aren't benefiting from randomly generated IDs/data-test-subjs * [EuiButtonGroupButton] Set up & fix focus styles - update focus utils with a new `euiOutline` helper that only exports the outline CSS and not the `:focus-visible` - Button group focus-visible behavior wasn't working as expected - `:has(:focus-visible)` is what's needed for `:focus-within` keyboard behavior, but it needs a Firefox interim workaround * [EuiButtonGroupButton] Convert to non-deprecated `EuiButtonDisplay` internal - New `EuiButtonDisplay` component makes a lot of the old Sass styles unnecessary + add euiCanAnimate * [EuiButtonGroupButton] Convert state styles - Disabled & selected buttons need a custom style + make all selected buttons have a slightly heavier font weight + remove unused(?) z-index CSS * [EuiButtonGroupButton] Convert child content & text CSS * [EuiButtonGroupButton] Convert size-specific styles - handles `border-radius` - handles faux box-shadow borders (with better cleanup) - compressed vs uncompressed styles * [EuiButtonGroup] Convert styles to Emotion - remove unnecessary `box-shadow` affordances - doesn't appear to be a thing anymore in Amsterdam theme - remove `overflow hidden` - doesn't seem to be needed - remove `groupSizeToClassNameMap` * Fix iconOnly padding; fix font weights - compressed font weight is not the same as default button weight - [opinionated] increase uncompressed font weight to make it more obvious which is chosen * Delete button group Sass files * Delete `EuiButtonDisplayDeprecated` There's no longer any components using it in EUI now that `EuiButtonGroup` has been converted to Emotion * Update tests & snapshots - Convert Enzyme to RTL - Reorganize tests slightly - Add tests for ensuring `css` properly merges * Add Storybook playground * changelog
1 parent df49741 commit c19625a

File tree

17 files changed

+1175
-1106
lines changed

17 files changed

+1175
-1106
lines changed

src/components/button/_index.scss

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
@import 'button_content';
22
@import 'button_empty/index';
33
@import 'button_icon/index';
4-
@import 'button_group/index';

src/components/button/button.tsx

Lines changed: 1 addition & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,7 @@
66
* Side Public License, v 1.
77
*/
88

9-
import React, {
10-
forwardRef,
11-
FunctionComponent,
12-
Ref,
13-
CSSProperties,
14-
HTMLAttributes,
15-
ReactNode,
16-
} from 'react';
17-
import classNames from 'classnames';
9+
import React, { FunctionComponent, Ref, ReactNode } from 'react';
1810

1911
import {
2012
CommonProps,
@@ -23,7 +15,6 @@ import {
2315
PropsForButton,
2416
} from '../common';
2517

26-
import { EuiButtonContentDeprecated as EuiButtonContent } from './_button_content_deprecated';
2718
import {
2819
BUTTON_COLORS,
2920
useEuiButtonColorCSS,
@@ -137,135 +128,3 @@ EuiButton.defaultProps = {
137128
size: 'm',
138129
color: 'primary',
139130
};
140-
141-
export type EuiButtonDisplayProps = EuiButtonProps &
142-
HTMLAttributes<HTMLElement> & {
143-
/**
144-
* Provide a valid element to render the element as
145-
*/
146-
element: 'a' | 'button' | 'span' | 'label';
147-
/**
148-
* Provide the component's base class name to build the class list on
149-
*/
150-
baseClassName: string;
151-
};
152-
153-
export const sizeToClassNameMap: { [size in EuiButtonSize]: string | null } = {
154-
s: '--small',
155-
m: null,
156-
};
157-
158-
export const colorToClassNameMap: {
159-
[color in EuiButtonColor]: string | null;
160-
} = {
161-
primary: '--primary',
162-
accent: '--accent',
163-
success: '--success',
164-
warning: '--warning',
165-
danger: '--danger',
166-
ghost: '--ghost',
167-
text: '--text',
168-
};
169-
170-
/**
171-
* *DEPRECATED*
172-
* EuiButtonDisplay is an internal-only component used for displaying
173-
* any element as a button.
174-
* NOTE: This component *must* be below EuiButton in the file and
175-
* EuiButton must also set a displayName for react-docgen-typescript
176-
* to correctly set EuiButton's docgenInfo and display a props table.
177-
* This component has been deprecated in favor of the new EuiButtonDisplay
178-
* that can be found in `src/components/button/button_display/_button_display.tsx`
179-
*/
180-
export const EuiButtonDisplayDeprecated = forwardRef<
181-
HTMLElement,
182-
EuiButtonDisplayProps
183-
>(
184-
(
185-
{
186-
element = 'button',
187-
baseClassName,
188-
children,
189-
className,
190-
iconType,
191-
iconSide = 'left',
192-
color,
193-
size = 'm',
194-
isDisabled,
195-
isLoading,
196-
isSelected,
197-
contentProps,
198-
textProps,
199-
fullWidth,
200-
minWidth,
201-
style,
202-
...rest
203-
},
204-
ref
205-
) => {
206-
const buttonIsDisabled = isButtonDisabled({ isLoading, isDisabled });
207-
208-
const classes = classNames(
209-
baseClassName,
210-
color && colorToClassNameMap[color]
211-
? `${baseClassName}${colorToClassNameMap[color]}`
212-
: undefined,
213-
size && sizeToClassNameMap[size]
214-
? `${baseClassName}${sizeToClassNameMap[size]}`
215-
: null,
216-
fullWidth && `${baseClassName}--fullWidth`,
217-
className
218-
);
219-
220-
/**
221-
* Not changing the content or text class names to match baseClassName yet,
222-
* as it is a major breaking change.
223-
*/
224-
const contentClassNames = classNames(
225-
'euiButton__content',
226-
contentProps && contentProps.className
227-
);
228-
229-
const textClassNames = classNames(
230-
'euiButton__text',
231-
textProps && textProps.className
232-
);
233-
234-
const innerNode = (
235-
<EuiButtonContent
236-
isLoading={isLoading}
237-
iconType={iconType}
238-
iconSide={iconSide}
239-
textProps={{ ...textProps, className: textClassNames }}
240-
{...contentProps}
241-
// className has to come last to override contentProps.className
242-
className={contentClassNames}
243-
>
244-
{children}
245-
</EuiButtonContent>
246-
);
247-
248-
let calculatedStyle: CSSProperties | undefined = style;
249-
if (minWidth !== undefined || minWidth !== null) {
250-
calculatedStyle = {
251-
...calculatedStyle,
252-
// @ts-ignore - deprecated component
253-
minWidth,
254-
};
255-
}
256-
257-
return React.createElement(
258-
element,
259-
{
260-
className: classes,
261-
style: calculatedStyle,
262-
disabled: element === 'button' && buttonIsDisabled,
263-
'aria-pressed': element === 'button' ? isSelected : undefined,
264-
ref,
265-
...rest,
266-
},
267-
innerNode
268-
);
269-
}
270-
);
271-
EuiButtonDisplayDeprecated.displayName = 'EuiButtonDisplay';

src/components/button/button_display/_button_display.test.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { EuiButtonDisplay } from './_button_display';
1515

1616
describe('EuiButtonDisplay', () => {
1717
shouldRenderCustomStyles(<EuiButtonDisplay>Text</EuiButtonDisplay>, {
18-
childProps: ['textProps'],
18+
childProps: ['contentProps', 'textProps'],
1919
});
2020

2121
it('renders', () => {
@@ -54,6 +54,45 @@ describe('EuiButtonDisplay', () => {
5454
});
5555
});
5656

57+
describe('element', () => {
58+
const elements = ['a', 'button', 'span', 'label'] as const;
59+
60+
const getButtonElement = (container: HTMLElement) =>
61+
container.firstChild!.nodeName.toLowerCase();
62+
63+
elements.forEach((element) => {
64+
test(element, () => {
65+
const { container } = render(
66+
<EuiButtonDisplay element={element} className="testing">
67+
Content
68+
</EuiButtonDisplay>
69+
);
70+
71+
expect(getButtonElement(container)).toEqual(element);
72+
});
73+
});
74+
75+
it('always renders a `button` element if disabled', () => {
76+
const { container } = render(
77+
<EuiButtonDisplay element="label" isDisabled>
78+
Content
79+
</EuiButtonDisplay>
80+
);
81+
82+
expect(getButtonElement(container)).toEqual('button');
83+
});
84+
85+
it('always renders an `a` element if a href is passed', () => {
86+
const { container } = render(
87+
<EuiButtonDisplay element="span" href="#">
88+
Content
89+
</EuiButtonDisplay>
90+
);
91+
92+
expect(getButtonElement(container)).toEqual('a');
93+
});
94+
});
95+
5796
describe('disabled behavior', () => {
5897
it('disables hrefs with javascript in them and renders a button instead of a link', () => {
5998
const { container } = render(

src/components/button/button_display/_button_display.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type EuiButtonDisplaySizes = (typeof SIZES)[number];
4444
export interface EuiButtonDisplayCommonProps
4545
extends EuiButtonDisplayContentProps,
4646
CommonProps {
47+
element?: 'a' | 'button' | 'span' | 'label';
4748
children?: ReactNode;
4849
size?: EuiButtonDisplaySizes;
4950
/**
@@ -109,6 +110,8 @@ export function isButtonDisabled({
109110
export const EuiButtonDisplay = forwardRef<HTMLElement, EuiButtonDisplayProps>(
110111
(
111112
{
113+
element: _element = 'button',
114+
type = 'button',
112115
children,
113116
iconType,
114117
iconSide = 'left',
@@ -125,7 +128,6 @@ export const EuiButtonDisplay = forwardRef<HTMLElement, EuiButtonDisplayProps>(
125128
href,
126129
target,
127130
rel,
128-
type = 'button',
129131
style,
130132
...rest
131133
},
@@ -164,7 +166,7 @@ export const EuiButtonDisplay = forwardRef<HTMLElement, EuiButtonDisplayProps>(
164166
</EuiButtonDisplayContent>
165167
);
166168

167-
const element = href && !buttonIsDisabled ? 'a' : 'button';
169+
const element = buttonIsDisabled ? 'button' : href ? 'a' : _element;
168170
let elementProps = {};
169171
// Element-specific attributes
170172
if (element === 'button') {

0 commit comments

Comments
 (0)