Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate EuiTabs to TS #2717

Merged
merged 11 commits into from
Jan 21, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)

- Converted `EuiTabs` to TypeScript ([#2717](https://github.com/elastic/eui/pull/2717))
- Converted `EuiFormRow` to TypeScript ([#2712](https://github.com/elastic/eui/pull/2712))
- Updated `logoAPM`, `logoSecurity` and `logoEnterpriseSearch`. Added `logoWorkplaceSearch` and `logoObservability` ([#2769](https://github.com/elastic/eui/pull/2769))
- Converted `EuiFilterButton` to TypeScript ([#2761](https://github.com/elastic/eui/pull/2761))
Expand Down
1 change: 0 additions & 1 deletion src/components/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
/// <reference path="./date_picker/index.d.ts" />
/// <reference path="./form/index.d.ts" />
/// <reference path="./modal/index.d.ts" />
/// <reference path="./tabs/index.d.ts" />

declare module '@elastic/eui' {
// @ts-ignore
Expand Down
43 changes: 0 additions & 43 deletions src/components/tabs/index.d.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/components/tabs/index.js

This file was deleted.

7 changes: 7 additions & 0 deletions src/components/tabs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { EuiTab } from './tab';
export { EuiTabs, EuiTabsProps } from './tabs';
export {
EuiTabbedContent,
EuiTabbedContentTab,
EuiTabbedContentProps,
} from './tabbed_content';
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,7 @@ exports[`EuiTabbedContent behavior when uncontrolled, the selected tab should up
<div
onFocus={[Function]}
>
<EuiTabs
display="default"
expand={false}
size="m"
>
<EuiTabs>
<div
className="euiTabs"
role="tablist"
Expand Down
1 change: 0 additions & 1 deletion src/components/tabs/tabbed_content/index.js

This file was deleted.

5 changes: 5 additions & 0 deletions src/components/tabs/tabbed_content/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
EuiTabbedContent,
EuiTabbedContentTab,
EuiTabbedContentProps,
} from './tabbed_content';
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { render, mount } from 'enzyme';
import sinon from 'sinon';
import { requiredProps, findTestSubject } from '../../../test';

import { EuiTabbedContent, AUTOFOCUS } from './tabbed_content';
Expand Down Expand Up @@ -38,13 +37,13 @@ describe('EuiTabbedContent', () => {
describe('props', () => {
describe('onTabClick', () => {
test('is called when a tab is clicked', () => {
const onTabClickHandler = sinon.stub();
const onTabClickHandler = jest.fn();
const component = mount(
<EuiTabbedContent onTabClick={onTabClickHandler} tabs={tabs} />
);
findTestSubject(component, 'kibanaTab').simulate('click');
sinon.assert.calledOnce(onTabClickHandler);
sinon.assert.calledWith(onTabClickHandler, kibanaTab);
expect(onTabClickHandler).toBeCalledTimes(1);
expect(onTabClickHandler).toBeCalledWith(kibanaTab);
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,80 @@
import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';
import React, { Component, createRef, HTMLAttributes, ReactNode } from 'react';

import { htmlIdGenerator } from '../../../services';

import { EuiTabs, DISPLAYS, SIZES } from '../tabs';
import { EuiTabs, EuiTabsDisplaySizes, EuiTabsSizes } from '../tabs';
import { EuiTab } from '../tab';
import { CommonProps } from '../../common';

const makeId = htmlIdGenerator();

export const AUTOFOCUS = ['initial', 'selected'];
/**
* Marked as const so type is `['initial', 'selected']` instead of `string[]`
*/
export const AUTOFOCUS = ['initial', 'selected'] as const;

export class EuiTabbedContent extends Component {
static propTypes = {
className: PropTypes.string,
export interface EuiTabbedContentTab {
id: string;
name: string;
content: ReactNode;
}

interface EuiTabbedContentState {
selectedTabId: string | undefined;
inFocus: boolean;
}

export type EuiTabbedContentProps = CommonProps &
HTMLAttributes<HTMLDivElement> & {
/**
* When tabbing into the tabs, set the focus on `initial` for the first tab,
* or `selected` for the currently selected tab. Best use case is for inside of
* overlay content like popovers or flyouts.
*/
autoFocus?: 'initial' | 'selected';
/**
* Choose `default` or alternative `condensed` display styles
*/
display: PropTypes.oneOf(DISPLAYS),
display?: EuiTabsDisplaySizes;
/**
* Evenly stretches each tab to fill the horizontal space
*/
expand: PropTypes.bool,
expand?: boolean;
/**
* Use this prop to set the initially selected tab while letting the tabbed content component
* control selection state internally
*/
initialSelectedTab: PropTypes.object,
onTabClick: PropTypes.func,
initialSelectedTab?: EuiTabbedContentTab;
onTabClick?: (selectedTab: EuiTabbedContentTab) => void;
/**
* Use this prop if you want to control selection state within the owner component
*/
selectedTab: PropTypes.object,
/**
* When tabbing into the tabs, set the focus on `initial` for the first tab,
* or `selected` for the currently selected tab. Best use case is for inside of
* overlay content like popovers or flyouts.
*/
autoFocus: PropTypes.oneOf(AUTOFOCUS),
size: PropTypes.oneOf(SIZES),
selectedTab?: EuiTabbedContentTab;
size?: EuiTabsSizes;
/**
* Each tab needs id and content properties, so we can associate it with its panel for accessibility.
* The name property is also required to display to the user.
*/
tabs: PropTypes.arrayOf(
PropTypes.shape({
content: PropTypes.node.isRequired,
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})
).isRequired,
tabs: EuiTabbedContentTab[];
};

export class EuiTabbedContent extends Component<
EuiTabbedContentProps,
EuiTabbedContentState
> {
static defaultProps = {
autoFocus: 'initial',
};

constructor(props) {
private readonly rootId = makeId();

private readonly divRef = createRef<HTMLDivElement>();

constructor(props: EuiTabbedContentProps) {
super(props);

const { initialSelectedTab, selectedTab, tabs } = props;

this.rootId = makeId();
this.divRef = createRef();

// Only track selection state if it's not controlled externally.
let selectedTabId;
if (!selectedTab) {
Expand All @@ -76,13 +92,21 @@ export class EuiTabbedContent extends Component {
// IE11 doesn't support the `relatedTarget` event property for blur events
// but does add it for focusout. React doesn't support `onFocusOut` so here we are.
if (this.divRef.current) {
this.divRef.current.addEventListener('focusout', this.removeFocus);
// Current short-term solution for event listener (see https://github.com/elastic/eui/pull/2717)
this.divRef.current.addEventListener(
'focusout' as 'blur',
this.removeFocus
);
}
}

componentWillUnmount() {
if (this.divRef.current) {
this.divRef.current.removeEventListener('focusout', this.removeFocus);
// Current short-term solution for event listener (see https://github.com/elastic/eui/pull/2717)
this.divRef.current.removeEventListener(
'focusout' as 'blur',
this.removeFocus
);
}
}

Expand All @@ -91,24 +115,27 @@ export class EuiTabbedContent extends Component {
// Must wait for setState to finish before calling `.focus()`
// as the focus call triggers a blur on the first tab
this.setState({ inFocus: true }, () => {
const targetTab = this.divRef.current.querySelector(
const targetTab: HTMLDivElement | null = this.divRef.current!.querySelector(
`#${this.state.selectedTabId}`
);
targetTab.focus();
targetTab!.focus();
});
}
};

removeFocus = blurEvent => {
// todo: figure out type for blurEvent
removeFocus = (blurEvent: FocusEvent) => {
// only set inFocus to false if the wrapping div doesn't contain the now-focusing element
if (blurEvent.currentTarget.contains(blurEvent.relatedTarget) === false) {
const currentTarget = blurEvent.currentTarget! as HTMLElement;
const relatedTarget = blurEvent.relatedTarget! as HTMLElement;
if (currentTarget.contains(relatedTarget) === false) {
this.setState({
inFocus: false,
});
}
};

onTabClick = selectedTab => {
onTabClick = (selectedTab: EuiTabbedContentTab) => {
const { onTabClick, selectedTab: externalSelectedTab } = this.props;

if (onTabClick) {
Expand Down Expand Up @@ -138,9 +165,11 @@ export class EuiTabbedContent extends Component {
// Allow the consumer to control tab selection.
const selectedTab =
externalSelectedTab ||
tabs.find(tab => tab.id === this.state.selectedTabId);
tabs.find(
(tab: EuiTabbedContentTab) => tab.id === this.state.selectedTabId
);

const { content: selectedTabContent, id: selectedTabId } = selectedTab;
const { content: selectedTabContent, id: selectedTabId } = selectedTab!;

return (
<div
Expand All @@ -149,7 +178,7 @@ export class EuiTabbedContent extends Component {
{...rest}
onFocus={this.initializeFocus}>
<EuiTabs expand={expand} display={display} size={size}>
{tabs.map(tab => {
{tabs.map((tab: EuiTabbedContentTab) => {
const {
id,
name,
Expand Down Expand Up @@ -179,7 +208,3 @@ export class EuiTabbedContent extends Component {
);
}
}

EuiTabbedContent.defaultProps = {
autoFocus: 'initial',
};
63 changes: 0 additions & 63 deletions src/components/tabs/tabs.js

This file was deleted.

File renamed without changes.
Loading