Skip to content

Commit

Permalink
Translate EuiTabs to TS (#2717)
Browse files Browse the repository at this point in the history
* Translate EuiTabs to TS

* EuiTabs: Export Props types and insert temporary type for FocusEvent

* EuiTabs: Remove defaultProps and use types derived from object

* EuiTabs: remove propTypes from EuiTabs

* EuiTabs: move PropTypes docblocks into Props types

* EuiTabs: run ESLint

* EuiTabs: update jest snapshots

* EuiTabs: Revert name change and update exports

* Add changelog entry

* Manually remove parentheses added by Eslint
  • Loading branch information
junlarsen authored and chandlerprall committed Jan 21, 2020
1 parent c9dcfb3 commit d5f1340
Show file tree
Hide file tree
Showing 14 changed files with 147 additions and 164 deletions.
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

0 comments on commit d5f1340

Please sign in to comment.