Skip to content

Commit

Permalink
converts EuiCode and EuiCodeBlock to TypeScript (#2835)
Browse files Browse the repository at this point in the history
* fix for setMaxListeners Mutation Notifier

* converts EuiCode and EuiCodeBlock to TypeScript

* adds changelog entry

* undoes String cast per review feedback

* moves prop exports to `index.ts` and removes `index.d.ts`

#2835 (comment)

* uses `keysOf` per review feedback

* uses `HTMLCodeElement` custom type per review feedback

* single source of truth for `FontSize` and `PaddingSize`

* uses `HTMLElement` directly instead of `HTMLCodeElement` alias

* exports entire props definition for EuiCodeBlock

* switches precedence of exported Props for EuiCodeProps per review feedback
  • Loading branch information
dimitropoulos authored Feb 19, 2020
1 parent 0b312d9 commit 11d0a47
Show file tree
Hide file tree
Showing 20 changed files with 145 additions and 163 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 `EuiCode` and `EuiCodeBlock` and to Typescript ([#2835](https://github.com/elastic/eui/pull/2835))
- Converted `EuiFilePicker` to TypeScript ([#2832](https://github.com/elastic/eui/issues/2832))
- Exported `EuiSelectOptionProps` type ([#2830](https://github.com/elastic/eui/pull/2830))
- Added `paperClip` glyph to `EuiIcon` ([#2845](https://github.com/elastic/eui/pull/2845))
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"@types/enzyme": "^3.1.13",
"@types/lodash": "^4.14.116",
"@types/numeral": "^0.0.25",
"@types/react-virtualized": "^9.18.7",
"@types/react-beautiful-dnd": "^10.1.0",
"@types/react-virtualized": "^9.18.7",
"chroma-js": "^2.0.4",
"classnames": "^2.2.5",
"highlight.js": "^9.12.0",
Expand Down Expand Up @@ -85,6 +85,7 @@
"@svgr/core": "5.0.1",
"@svgr/plugin-svgo": "^4.0.3",
"@types/classnames": "^2.2.6",
"@types/highlight.js": "^9.12.3",
"@types/jest": "^24.0.6",
"@types/react": "^16.9.11",
"@types/react-dom": "^16.9.4",
Expand Down
2 changes: 1 addition & 1 deletion scripts/jest/polyfills/mutation_observer.js
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ var MutationNotifier = /** @class */ (function (_super) {
__extends(MutationNotifier, _super);
function MutationNotifier() {
var _this = _super.call(this) || this;
_this.setMaxListeners(150); // bump this as needed - some tests do not perform the unmounting lifecycle
_this.setMaxListeners(294); // bump this as needed - some tests do not perform the unmounting lifecycle
return _this;
}
MutationNotifier.getInstance = function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import { mount, ReactWrapper } from 'enzyme';
import { requiredProps } from '../../test/required_props';

import { EuiCodeBlockImpl } from './_code_block';

function snapshotCodeBlock(component) {
function snapshotCodeBlock(component: ReactWrapper) {
// Get the Portal's sibling and return its html
const renderedHtml = component.find('Portal + *').html();
const container = document.createElement('div');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { Component } from 'react';
import React, { Component, KeyboardEvent, CSSProperties } from 'react';
import { createPortal } from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import hljs from 'highlight.js';

Expand All @@ -15,45 +14,85 @@ import { EuiFocusTrap } from '../focus_trap';
import { keyCodes } from '../../services';
import { EuiI18n } from '../i18n';
import { EuiInnerText } from '../inner_text';
import { keysOf } from '../common';
import { FontSize, PaddingSize } from './code';

const fontSizeToClassNameMap = {
s: 'euiCodeBlock--fontSmall',
m: 'euiCodeBlock--fontMedium',
l: 'euiCodeBlock--fontLarge',
};

export const FONT_SIZES = Object.keys(fontSizeToClassNameMap);
export const FONT_SIZES = keysOf(fontSizeToClassNameMap);

const paddingSizeToClassNameMap = {
const paddingSizeToClassNameMap: { [paddingSize in PaddingSize]: string } = {
none: '',
s: 'euiCodeBlock--paddingSmall',
m: 'euiCodeBlock--paddingMedium',
l: 'euiCodeBlock--paddingLarge',
};

export const PADDING_SIZES = Object.keys(paddingSizeToClassNameMap);
export const PADDING_SIZES = keysOf(paddingSizeToClassNameMap);

interface Props {
className?: string;
fontSize: FontSize;

/**
* Displays the passed code in an inline format. Also removes any margins set.
*/
inline?: boolean;

/**
* Displays an icon button to copy the code snippet to the clipboard.
*/
isCopyable: boolean;

/**
* Sets the syntax highlighting for a specific language
*/
language?: string;
overflowHeight?: number;
paddingSize: PaddingSize;
transparentBackground: boolean;
}

interface State {
isFullScreen: boolean;
}

/**
* This is the base component extended by EuiCode and EuiCodeBlock. These components
* share the same propTypes definition with EuiCodeBlockImpl.
* This is the base component extended by EuiCode and EuiCodeBlock.
* These components share the same propTypes definition with EuiCodeBlockImpl.
*/
export class EuiCodeBlockImpl extends Component {
codeTarget = document.createElement('div');
export class EuiCodeBlockImpl extends Component<Props, State> {
static defaultProps = {
transparentBackground: false,
paddingSize: 'l',
fontSize: 's',
isCopyable: false,
};

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

this.state = {
isFullScreen: false,
};
}

codeTarget = document.createElement('div');
code: HTMLElement | null = null;
codeFullScreen: HTMLElement | null = null;

highlight = () => {
// because React maintains a mapping between its Virtual DOM representation and the actual
// DOM elements (including text nodes), and hljs modifies the DOM structure which leads
// to React updating detached nodes, we render to a document fragment and
// copy from that fragment into the target elements
// (https://github.com/elastic/eui/issues/2322)
/**
* because React maintains a mapping between its Virtual DOM representation and the actual
* DOM elements (including text nodes), and hljs modifies the DOM structure which leads
* to React updating detached nodes, we render to a document fragment and
* copy from that fragment into the target elements
* (https://github.com/elastic/eui/issues/2322)
*/
const html = this.codeTarget.innerHTML;

if (this.code) {
Expand All @@ -64,15 +103,17 @@ export class EuiCodeBlockImpl extends Component {
}

if (this.props.language) {
hljs.highlightBlock(this.code);
if (this.code) {
hljs.highlightBlock(this.code);
}

if (this.codeFullScreen) {
hljs.highlightBlock(this.codeFullScreen);
}
}
};

onKeyDown = event => {
onKeyDown = (event: KeyboardEvent<HTMLElement>) => {
if (event.keyCode === keyCodes.ESCAPE) {
event.preventDefault();
event.stopPropagation();
Expand Down Expand Up @@ -128,7 +169,7 @@ export class EuiCodeBlockImpl extends Component {

const codeClasses = classNames('euiCodeBlock__code', language);

const optionalStyles = {};
const optionalStyles: CSSProperties = {};

if (overflowHeight) {
optionalStyles.maxHeight = overflowHeight;
Expand Down Expand Up @@ -158,14 +199,14 @@ export class EuiCodeBlockImpl extends Component {
);
}

function getCopyButton(textToCopy) {
let copyButton;
const getCopyButton = (textToCopy?: string) => {
let copyButton: JSX.Element | undefined;

if (isCopyable) {
if (isCopyable && textToCopy) {
copyButton = (
<div className="euiCodeBlock__copyButton">
<EuiI18n token="euiCodeBlock.copyButton" default="Copy">
{copyButton => (
{(copyButton: string) => (
<EuiCopy textToCopy={textToCopy}>
{copy => (
<EuiButtonIcon
Expand All @@ -184,9 +225,9 @@ export class EuiCodeBlockImpl extends Component {
}

return copyButton;
}
};

let fullScreenButton;
let fullScreenButton: JSX.Element | undefined;

if (!inline && overflowHeight) {
fullScreenButton = (
Expand All @@ -196,7 +237,7 @@ export class EuiCodeBlockImpl extends Component {
'euiCodeBlock.fullscreenExpand',
]}
defaults={['Collapse', 'Expand']}>
{([fullscreenCollapse, fullscreenExpand]) => (
{([fullscreenCollapse, fullscreenExpand]: string[]) => (
<EuiButtonIcon
className="euiCodeBlock__fullScreenButton"
size="s"
Expand All @@ -212,7 +253,7 @@ export class EuiCodeBlockImpl extends Component {
);
}

function getCodeBlockControls(textToCopy) {
const getCodeBlockControls = (textToCopy?: string) => {
let codeBlockControls;
const copyButton = getCopyButton(textToCopy);

Expand All @@ -226,17 +267,13 @@ export class EuiCodeBlockImpl extends Component {
}

return codeBlockControls;
}
};

const getFullScreenDisplay = codeBlockControls => {
const getFullScreenDisplay = (codeBlockControls?: JSX.Element) => {
let fullScreenDisplay;

if (this.state.isFullScreen) {
{
/*
Force fullscreen to use large font and padding.
*/
}
// Force fullscreen to use large font and padding.
const fullScreenClasses = classNames(
'euiCodeBlock',
fontSizeToClassNameMap[fontSize],
Expand Down Expand Up @@ -299,34 +336,3 @@ export class EuiCodeBlockImpl extends Component {
);
}
}

EuiCodeBlockImpl.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
paddingSize: PropTypes.oneOf(PADDING_SIZES),

/**
* Sets the syntax highlighting for a specific language
*/
language: PropTypes.string,
overflowHeight: PropTypes.number,
fontSize: PropTypes.oneOf(FONT_SIZES),
transparentBackground: PropTypes.bool,

/**
* Displays the passed code in an inline format. Also removes any margins set.
*/
inline: PropTypes.bool,

/**
* Displays an icon button to copy the code snippet to the clipboard.
*/
isCopyable: PropTypes.bool,
};

EuiCodeBlockImpl.defaultProps = {
transparentBackground: false,
paddingSize: 'l',
fontSize: 's',
isCopyable: false,
};
14 changes: 0 additions & 14 deletions src/components/code/code.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import { mount, ReactWrapper } from 'enzyme';
import { requiredProps } from '../../test/required_props';

import { EuiCode } from './code';

function snapshotCodeBlock(component) {
function snapshotCodeBlock(component: ReactWrapper) {
// Get the Portal's sibling and return its html
const renderedHtml = component.find('Portal + *').html();
const container = document.createElement('div');
Expand Down
36 changes: 36 additions & 0 deletions src/components/code/code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { CommonProps } from '../common';

import React, { FunctionComponent, HTMLAttributes } from 'react';

import { EuiCodeBlockImpl } from './_code_block';

export type FontSize = 's' | 'm' | 'l';
export type PaddingSize = 'none' | 's' | 'm' | 'l';

export interface EuiCodeSharedProps {
paddingSize?: PaddingSize;

/**
* Sets the syntax highlighting for a specific language
* @see http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html#language-names-and-aliases
* for options
*/
language?: string;
overflowHeight?: number;
fontSize?: FontSize;
transparentBackground?: boolean;
isCopyable?: boolean;
}

interface Props extends EuiCodeSharedProps {
inline?: true;
}

export type EuiCodeProps = CommonProps & Props & HTMLAttributes<HTMLElement>;

export const EuiCode: FunctionComponent<EuiCodeProps> = ({
inline,
...rest
}) => {
return <EuiCodeBlockImpl inline={true} {...rest} />;
};
14 changes: 0 additions & 14 deletions src/components/code/code_block.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { mount } from 'enzyme';
import { mount, ReactWrapper } from 'enzyme';
import html from 'html';
import { requiredProps } from '../../test/required_props';

import { EuiCodeBlock } from './code_block';
import { FONT_SIZES, PADDING_SIZES } from './_code_block';

function snapshotCodeBlock(component) {
function snapshotCodeBlock(component: ReactWrapper) {
// Get the Portal's sibling and return its html
const renderedHtml = component.find('Portal + *').html();
const container = document.createElement('div');
Expand Down
Loading

0 comments on commit 11d0a47

Please sign in to comment.