Skip to content

Commit

Permalink
Revert "feat: EuiFlexGroup and EuiFlexItem component prop type im…
Browse files Browse the repository at this point in the history
…provements (#7688)"

This reverts commit f8c9aec.
  • Loading branch information
cee-chen committed May 1, 2024
1 parent 04f0a17 commit f3765f8
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 191 deletions.
9 changes: 3 additions & 6 deletions src-docs/src/views/flex/flex_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export const FlexExample = {
),
},
{
title: 'Override output component type',
title: 'Spans instead of divs',
source: [
{
type: GuideSectionTypes.JS,
Expand All @@ -255,12 +255,9 @@ export const FlexExample = {
],
text: (
<p>
Pass the <EuiCode>component</EuiCode> property to{' '}
Specify <EuiCode>component=&ldquo;span&rdquo;</EuiCode> on{' '}
<strong>EuiFlexGroup</strong> and/or <strong>EuiFlexItem</strong> to
change the rendered component type from the default{' '}
<EuiCode>div</EuiCode>. It can be any valid React component type like
a tag name string such as <EuiCode>div</EuiCode>
or <EuiCode>span</EuiCode> or a React component.
change from the default <EuiCode>div</EuiCode>.
</p>
),
snippet: componentSpanSnippet,
Expand Down
30 changes: 19 additions & 11 deletions src/components/flex/flex_group.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
* Side Public License, v 1.
*/

import React, { JSX } from 'react';
import React from 'react';
import { requiredProps } from '../../test';
import { shouldRenderCustomStyles } from '../../test/internal';
import {
shouldRenderCustomStyles,
testOnReactVersion,
} from '../../test/internal';
import { render } from '../../test/rtl';

import {
Expand All @@ -17,6 +20,7 @@ import {
ALIGN_ITEMS,
JUSTIFY_CONTENTS,
DIRECTIONS,
COMPONENT_TYPES,
} from './flex_group';

describe('EuiFlexGroup', () => {
Expand Down Expand Up @@ -94,21 +98,25 @@ describe('EuiFlexGroup', () => {
});

describe('component', () => {
['div', 'span'].forEach((value) => {
COMPONENT_TYPES.forEach((value) => {
test(`${value} is rendered`, () => {
const { container } = render(
<EuiFlexGroup component={value as keyof JSX.IntrinsicElements} />
);
const { container } = render(<EuiFlexGroup component={value} />);

expect(container.firstChild!.nodeName).toEqual(value.toUpperCase());
});
});

test('custom component is rendered', () => {
const component = () => <span>Custom component test</span>;
const { getByText } = render(<EuiFlexGroup component={component} />);
expect(getByText('Custom component test')).toBeInTheDocument();
});
// React 18 throws a false error on test unmount for components w/ ref callbacks
// that throw in a `useEffect`. Note: This only affects the test env, not prod
// @see https://github.com/facebook/react/issues/25675#issuecomment-1363957941
// TODO: Remove `testOnReactVersion` once the above bug is fixed
testOnReactVersion(['16', '17'])(
`invalid component types throw an error`,
() => {
// @ts-expect-error intentionally passing an invalid value
expect(() => render(<EuiFlexGroup component="h2" />)).toThrow();
}
);
});
});
});
152 changes: 72 additions & 80 deletions src/components/flex/flex_group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,7 @@
* Side Public License, v 1.
*/

import React, {
ComponentPropsWithoutRef,
ComponentType,
ElementType,
ForwardedRef,
forwardRef,
FunctionComponent,
Ref,
} from 'react';
import React, { HTMLAttributes, Ref, forwardRef, useEffect } from 'react';
import classNames from 'classnames';
import { CommonProps } from '../common';

Expand Down Expand Up @@ -51,80 +43,80 @@ export const DIRECTIONS = [
] as const;
type FlexGroupDirection = (typeof DIRECTIONS)[number];

type ComponentPropType = ElementType<CommonProps>;
export const COMPONENT_TYPES = ['div', 'span'] as const;
type FlexGroupComponentType = (typeof COMPONENT_TYPES)[number];

export type EuiFlexGroupProps<TComponent extends ComponentPropType = 'div'> =
ComponentPropsWithoutRef<TComponent> & {
alignItems?: FlexGroupAlignItems;
/**
* Customize the component type that is rendered.
*
* It can be any valid React component type like a tag name string
* such as `'div'` or `'span'`, a React component (a function, a class,
* or an exotic component like `memo()`).
*
* `<EuiFlexGroup>` accepts and forwards all extra props to the custom
* component.
*
* @example
* // Renders a <button> element
* <EuiFlexGroup component="button">
* Submit form
* </EuiFlexGroup>
* @default "div"
*/
component?: TComponent;
direction?: FlexGroupDirection;
gutterSize?: EuiFlexGroupGutterSize;
justifyContent?: FlexGroupJustifyContent;
responsive?: boolean;
wrap?: boolean;
};
export interface EuiFlexGroupProps
extends CommonProps,
HTMLAttributes<HTMLDivElement | HTMLSpanElement> {
alignItems?: FlexGroupAlignItems;
component?: FlexGroupComponentType;
direction?: FlexGroupDirection;
gutterSize?: EuiFlexGroupGutterSize;
justifyContent?: FlexGroupJustifyContent;
responsive?: boolean;
wrap?: boolean;
}

const EuiFlexGroupInternal = <TComponent extends ComponentPropType>(
{
className,
component = 'div' as TComponent,
gutterSize = 'l',
alignItems = 'stretch',
responsive = true,
justifyContent = 'flexStart',
direction = 'row',
wrap = false,
...rest
}: EuiFlexGroupProps<TComponent>,
ref: ForwardedRef<TComponent>
) => {
const styles = useEuiMemoizedStyles(euiFlexGroupStyles);
const cssStyles = [
styles.euiFlexGroup,
responsive && !direction.includes('column') && styles.responsive,
wrap && styles.wrap,
styles.gutterSizes[gutterSize],
styles.justifyContent[justifyContent],
styles.alignItems[alignItems],
styles.direction[direction],
];

const classes = classNames('euiFlexGroup', className);
export const EuiFlexGroup = forwardRef<
HTMLDivElement | HTMLSpanElement,
EuiFlexGroupProps
>(
(
{
children,
className,
gutterSize = 'l',
alignItems = 'stretch',
responsive = true,
justifyContent = 'flexStart',
direction = 'row',
wrap = false,
component = 'div',
...rest
},
ref: Ref<HTMLDivElement> | Ref<HTMLSpanElement>
) => {
const styles = useEuiMemoizedStyles(euiFlexGroupStyles);
const cssStyles = [
styles.euiFlexGroup,
responsive && !direction.includes('column') && styles.responsive,
wrap && styles.wrap,
styles.gutterSizes[gutterSize],
styles.justifyContent[justifyContent],
styles.alignItems[alignItems],
styles.direction[direction],
];

// Cast the resolved component prop type to ComponentType to help TS
// process multiple infers and the overall type complexity.
// This might not be needed in TypeScript 5
const Component = component as ComponentType<CommonProps & typeof rest>;
const classes = classNames('euiFlexGroup', className);

return <Component {...rest} ref={ref} className={classes} css={cssStyles} />;
};
useEffect(() => {
if (!COMPONENT_TYPES.includes(component)) {
throw new Error(
`${component} is not a valid element type. Use \`div\` or \`span\`.`
);
}
}, [component]);

// Cast forwardRef return type to work with the generic TComponent type
// and not fallback to implicit any typing
export const EuiFlexGroup = forwardRef(EuiFlexGroupInternal) as <
TComponent extends ComponentPropType
>(
props: EuiFlexGroupProps<TComponent> & {
ref?: Ref<typeof EuiFlexGroupInternal>;
return component === 'span' ? (
<span
css={cssStyles}
className={classes}
ref={ref as Ref<HTMLSpanElement>}
{...rest}
>
{children}
</span>
) : (
<div
css={cssStyles}
className={classes}
ref={ref as Ref<HTMLDivElement>}
{...rest}
>
{children}
</div>
);
}
) => ReturnType<typeof EuiFlexGroupInternal>;

// Cast is required here because of the cast above
(EuiFlexGroup as FunctionComponent).displayName = 'EuiFlexGroup';
);
EuiFlexGroup.displayName = 'EuiFlexGroup';
41 changes: 9 additions & 32 deletions src/components/flex/flex_item.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@
* Side Public License, v 1.
*/

import React, { JSX } from 'react';
import React from 'react';
import {
requiredProps,
startThrowingReactWarnings,
stopThrowingReactWarnings,
} from '../../test';
import {
shouldRenderCustomStyles,
testOnReactVersion,
} from '../../test/internal';
import { shouldRenderCustomStyles } from '../../test/internal';
import { render } from '../../test/rtl';

import { EuiFlexItem } from './flex_item';
Expand All @@ -32,23 +29,10 @@ describe('EuiFlexItem', () => {
expect(container.firstChild).toMatchSnapshot();
});

describe('component', () => {
['div', 'span'].forEach((value) => {
test(`${value} is rendered`, () => {
const { container } = render(
<EuiFlexItem component={value as keyof JSX.IntrinsicElements} />
);

expect(container.firstChild?.nodeName).toEqual(value.toUpperCase());
});
});
it('renders as the passed component element', () => {
const { container } = render(<EuiFlexItem component="span" />);

test('custom component is rendered', () => {
const component = () => <span>Custom component test</span>;
const { getByText } = render(<EuiFlexItem component={component} />);

expect(getByText('Custom component test')).toBeInTheDocument();
});
expect(container.firstChild?.nodeName).toEqual('SPAN');
});

describe('grow', () => {
Expand Down Expand Up @@ -94,16 +78,9 @@ describe('EuiFlexItem', () => {
});
});

// React 18 throws a false error on test unmount for components w/ ref callbacks
// that throw in a `useEffect`. Note: This only affects the test env, not prod
// @see https://github.com/facebook/react/issues/25675#issuecomment-1363957941
// TODO: Remove `testOnReactVersion` once the above bug is fixed
testOnReactVersion(['16', '17'])(
`invalid component types throw an error`,
() => {
// @ts-expect-error intentionally passing an invalid value
expect(() => render(<EuiFlexItem grow={11} />)).toThrow();
}
);
it('throws an error for invalid values', () => {
// @ts-expect-error testing invalid type
expect(() => render(<EuiFlexItem grow={11} />)).toThrowError();
});
});
});
Loading

0 comments on commit f3765f8

Please sign in to comment.