diff --git a/README.md b/README.md index 463ad91af41..30837055ce8 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ directly in the code. And unit test coverage for the UI components allows us to ### Consumption * [Consuming EUI][consuming] -* [Using EUI with react-router](wiki/react-router.md) +* [Using EUI with react-router](react-router) ### Maintenance / Contributing @@ -88,5 +88,6 @@ directly in the code. And unit test coverage for the UI components allows us to [license]: LICENSE [faq]: FAQ.md -[consuming]: wiki/consuming.md +[consuming]: src-docs/src/views/getting_started/consuming.md +[react-router]: wiki/react-router.md [docs]: https://elastic.github.io/eui/ diff --git a/src-docs/src/components/guide_components.scss b/src-docs/src/components/guide_components.scss index bdf1b4d95fc..5ad7383e622 100644 --- a/src-docs/src/components/guide_components.scss +++ b/src-docs/src/components/guide_components.scss @@ -153,16 +153,7 @@ $guideZLevelHighest: $euiZLevel9 + 1000; .guideDemo__ghostBackground { @if (lightness($euiTextColor) < 50) { - background: $euiColorDarkestShade; - } -} - -.guideDemo__icon { - text-align: center; - - svg, - img { - margin-bottom: $euiSizeS; + background: $euiColorDarkestShade !important; // Override EuiPanel specificity } } @@ -285,3 +276,124 @@ $guideZLevelHighest: $euiZLevel9 + 1000; flex-basis: 22% !important; // sass-lint:disable-line no-important } } + +.guideHomePage__illustrationLightShade { + fill: $euiColorLightShade; +} + +.guideHomePage__illustrationEmptyShade { + fill: $euiColorEmptyShade; +} + +.guideHomePage__illustrationDots, +.guideHomePage__illustrationComponents { + position: absolute; + top: 0; + right: 0; +} + +.guideHomePage__illustrationEffect { + display: block; + height: auto; + position: relative; + width: auto; + + .guideHomePage__illustrationEffectParts { + height: auto; + transform: perspective(1600px); + transform-style: preserve-3d; + transition: all .3s ease-in-out; + width: 100%; + + &:before { + content: ''; + display: block; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + } + } + + .guideHomePage__illustrationEffectPartsDots, + .guideHomePage__illustrationEffectPartsComps { + position: absolute; + left: 0; + top: 0; + transform: translateZ(16px) scale(.9); + transition: all .4s ease; + width: 100%; + height: 100%; + z-index: 1; + } + + .guideHomePage__illustrationTopLeftCorner { + height: 50%; + left: 0; + position: absolute; + top: 0; + width: 50%; + z-index: 300; + + &:hover ~ .guideHomePage__illustrationEffectParts { + transform: perspective(1600px) rotateX(-5deg) rotateY(5deg); + + .guideHomePage__illustrationEffectPartsDots { + transform: translate3d(-.1px, -.1px, 16px) scale(.9); + } + } + } + + .guideHomePage__illustrationTopRightCorner { + height: 50%; + position: absolute; + right: 0; + top: 0; + width: 50%; + z-index: 300; + + &:hover ~ .guideHomePage__illustrationEffectParts { + transform: perspective(1600px) rotateX(-5deg) rotateY(-5deg); + + .guideHomePage__illustrationEffectPartsDots { + transform: translate3d(.1px, -.1px, 16px) scale(.9); + } + } + } + + .guideHomePage__illustrationBottomLeftCorner { + bottom: 0; + height: 50%; + left: 0; + position: absolute; + width: 50%; + z-index: 300; + + &:hover ~ .guideHomePage__illustrationEffectParts { + transform: perspective(1600px) rotateX(5deg) rotateY(5deg); + + .guideHomePage__illustrationEffectPartsDots { + transform: translate3d(-.1px, .1px, 1px) scale(.9); + } + } + } + + .guideHomePage__illustrationBottomRightCorner { + bottom: 0; + height: 50%; + position: absolute; + right: 0; + width: 50%; + z-index: 300; + + &:hover ~ .guideHomePage__illustrationEffectParts { + transform: perspective(1600px) rotateX(5deg) rotateY(-5deg); + + + .guideHomePage__illustrationEffectPartsDots { + transform: translate3d(.1px, .1px, 1px) scale(.9); + } + } + } +} diff --git a/src-docs/src/components/guide_markdown_format/guide_markdown_format.tsx b/src-docs/src/components/guide_markdown_format/guide_markdown_format.tsx new file mode 100644 index 00000000000..c7f2b98e9ad --- /dev/null +++ b/src-docs/src/components/guide_markdown_format/guide_markdown_format.tsx @@ -0,0 +1,53 @@ +import React, { FunctionComponent } from 'react'; +import { + EuiMarkdownFormat, + getDefaultEuiMarkdownProcessingPlugins, +} from '../../../../src/components/markdown_editor'; +import { EuiText } from '../../../../src/components/text'; +import { EuiTitle } from '../../../../src/components/title'; +import { slugify } from '../../../../src/services'; + +export type GuideMarkdownFormatProps = { + children: any; +}; + +export const GuideMarkdownFormat: FunctionComponent = ({ + children, +}) => { + const processingPlugins = getDefaultEuiMarkdownProcessingPlugins(); + const rehype2reactConfig = processingPlugins[1][1]; + + rehype2reactConfig.components.h2 = ({ children }) => { + const id = slugify(children[0]); + + return ( + +

{children}

+
+ ); + }; + + rehype2reactConfig.components.p = ({ children }) => ( + +

{children}

+
+ ); + + rehype2reactConfig.components.ul = ({ children }) => ( + + + + ); + + rehype2reactConfig.components.ol = ({ children }) => ( + +
    {children}
+
+ ); + + return ( + + {children} + + ); +}; diff --git a/src-docs/src/components/guide_markdown_format/index.ts b/src-docs/src/components/guide_markdown_format/index.ts new file mode 100644 index 00000000000..0b3a3b03939 --- /dev/null +++ b/src-docs/src/components/guide_markdown_format/index.ts @@ -0,0 +1 @@ +export { GuideMarkdownFormat } from './guide_markdown_format'; diff --git a/src-docs/src/components/guide_page/guide_page_header.tsx b/src-docs/src/components/guide_page/guide_page_header.tsx index d0ee296c2c0..e19db37069a 100644 --- a/src-docs/src/components/guide_page/guide_page_header.tsx +++ b/src-docs/src/components/guide_page/guide_page_header.tsx @@ -41,6 +41,19 @@ export const GuidePageHeader: React.FunctionComponent = ({ ); } + function renderVersion() { + const isLocalDev = window.location.host.includes('803'); + + return ( + + {isLocalDev ? 'Local' : `v.${pkg.version}`} + + ); + } + function renderGithub() { const href = 'https://github.com/elastic/eui'; const label = 'EUI GitHub repo'; @@ -131,15 +144,7 @@ export const GuidePageHeader: React.FunctionComponent = ({ theme="dark" sections={[ { - items: [ - renderLogo(), - - v.{pkg.version} - , - ], + items: [renderLogo(), renderVersion()], borders: 'none', }, { diff --git a/src-docs/src/components/guide_rule/_guide_rule_example.scss b/src-docs/src/components/guide_rule/_guide_rule_example.scss index 22971f481bb..53601086201 100644 --- a/src-docs/src/components/guide_rule/_guide_rule_example.scss +++ b/src-docs/src/components/guide_rule/_guide_rule_example.scss @@ -1,58 +1,24 @@ -/** - * 1. Ensure that the borders of the captions line up across the whole example row - * 1b. even if the caption spans multiple lines - */ - .guideRule__example { .guideRule__example__panel { border-bottom: 2px solid; - margin-bottom: $euiSizeS; - flex-grow: 1; /* 1 */ - - &:not(.euiPanel) { - padding-bottom: $euiSize; // only change the bottom padding if it's not an euiPanel - } - } - pre { - margin-bottom: 0; - padding: 0; - } - - .guideRule__caption { - @include euiFontSizeS; - max-height: $euiFontSizeS * $euiLineHeight; /* 1 */ - overflow-y: visible; /* 1 */ - } - - // Coloring - &.guideRule__example--do { - .guideRule__example__panel { - border-bottom-color: $euiColorSuccess; - } - - .guideRule__caption { - color: $euiColorSuccessText; + &--flex { + display: flex; + align-items: center; + justify-content: space-around; } } +} - &.guideRule__example--dont { - .guideRule__example__panel { - border-bottom-color: $euiColorDanger; - } - - .guideRule__caption { - color: $euiColorDanger; - } +.guideRule__example--do { + .guideRule__example__panel { + border-bottom-color: $euiColorSuccess; } +} - &.guideRule__example--frame { - .guideRule__example__panel { - padding: $euiSizeL; - background-color: $euiColorLightestShade; - display: flex; - align-items: center; - justify-content: space-around; - } +.guideRule__example--dont { + .guideRule__example__panel { + border-bottom-color: $euiColorDanger; } } + diff --git a/src-docs/src/components/guide_rule/guide_rule_example.js b/src-docs/src/components/guide_rule/guide_rule_example.js index 33f4a48cbb1..47f85494a72 100644 --- a/src-docs/src/components/guide_rule/guide_rule_example.js +++ b/src-docs/src/components/guide_rule/guide_rule_example.js @@ -1,7 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { EuiFlexItem, EuiPanel } from '../../../../src/components'; +import { + EuiFlexItem, + EuiText, + EuiSplitPanel, +} from '../../../../src/components'; const typeToClassNameMap = { do: 'guideRule__example--do', @@ -10,7 +14,7 @@ const typeToClassNameMap = { const typeToSubtitleTextMap = { do: 'Do', - dont: "Don't", + dont: 'Don’t', }; export const GuideRuleExample = ({ @@ -18,54 +22,70 @@ export const GuideRuleExample = ({ className, type, text, - panel, - frame, minHeight, style, + panelProps, panelStyles, + panelDisplay = 'flex', + panelColor, ...rest }) => { const classes = classNames( 'guideRule__example', typeToClassNameMap[type], - { - 'guideRule__example--frame': frame, - }, className ); - const ChildrenComponent = panel ? EuiPanel : 'div'; - const styles = { ...style, minHeight }; + if (type && !panelColor) { + panelColor = type === 'do' ? 'success' : 'danger'; + } + + const doOrDont = type && typeToSubtitleTextMap[type]; + return ( - - - {children} - -
- {text || typeToSubtitleTextMap[type]} -
+ + +
+ + {children} + + + +

+ {doOrDont && {doOrDont}.} {text} +

+
+
+
+
); }; GuideRuleExample.propTypes = { children: PropTypes.node, - className: PropTypes.string, + className: PropTypes.node, type: PropTypes.string.isRequired, - text: PropTypes.string, - panel: PropTypes.bool, + text: PropTypes.node, minHeight: PropTypes.number, + panelProps: PropTypes.any, }; GuideRuleExample.defaultProps = { type: 'do', - panel: true, }; diff --git a/src-docs/src/components/guide_section/_guide_section.scss b/src-docs/src/components/guide_section/_guide_section.scss index ee602ab2ac5..ae5c3a68abc 100644 --- a/src-docs/src/components/guide_section/_guide_section.scss +++ b/src-docs/src/components/guide_section/_guide_section.scss @@ -1,20 +1,33 @@ .guideSection { & + .guideSection { - margin-top: $euiSizeXL * 2; + margin-top: $euiSizeXL; } } -.guideSection__space { - height: $euiSizeL; -} - .guideSection__tableCodeBlock { padding-left: $euiSizeXS; padding-right: $euiSizeXS; } +.guideSection__propsTableIntro { + padding-left: $euiSizeS; + padding-right: $euiSizeS; +} + .guideSectionExampleCode__link { text-align: right; padding-top: $euiSizeS; padding-bottom: $euiSizeS; } + +.guideSectionTabs { + padding: $euiSizeXS $euiSizeM; + + &--open { + padding-bottom: 0; + } +} + +.guideSectionTabs__tab { + font-weight: $euiFontWeightMedium !important; // sass-lint:disable-line no-important +} diff --git a/src-docs/src/components/guide_section/_utils.js b/src-docs/src/components/guide_section/_utils.js new file mode 100644 index 00000000000..b7a20e4280f --- /dev/null +++ b/src-docs/src/components/guide_section/_utils.js @@ -0,0 +1,44 @@ +import { cleanEuiImports } from '../../services'; + +export const renderJsSourceCode = (code) => { + let renderedCode = code.default + .replace( + /(from )'(..\/)+src\/services(\/?';)/g, + "from '@elastic/eui/lib/services';" + ) + .replace(/(from )'(..\/)+src\/components\/.*?';/g, "from '@elastic/eui';"); + renderedCode = renderedCode.split('\n'); + const linesWithImport = []; + // eslint-disable-next-line guard-for-in + for (const idx in renderedCode) { + const line = renderedCode[idx]; + if (line.includes('import') && line.includes("from '@elastic/eui';")) { + linesWithImport.push(line); + renderedCode[idx] = ''; + } + } + if (linesWithImport.length > 1) { + linesWithImport[0] = linesWithImport[0].replace( + " } from '@elastic/eui';", + ',' + ); + for (let i = 1; i < linesWithImport.length - 1; i++) { + linesWithImport[i] = linesWithImport[i] + .replace('import {', '') + .replace(" } from '@elastic/eui';", ','); + } + linesWithImport[linesWithImport.length - 1] = linesWithImport[ + linesWithImport.length - 1 + ].replace('import {', ''); + } + const newImport = linesWithImport.join(''); + renderedCode.unshift(newImport); + renderedCode = renderedCode.join('\n'); + let len = renderedCode.replace('\n\n\n', '\n\n').length; + while (len < renderedCode.length) { + renderedCode = renderedCode.replace('\n\n\n', '\n\n'); + len = renderedCode.replace('\n\n\n', '\n\n').length; + } + + return cleanEuiImports(renderedCode); +}; diff --git a/src-docs/src/components/guide_section/guide_section.js b/src-docs/src/components/guide_section/guide_section.js deleted file mode 100644 index 373e1351a67..00000000000 --- a/src-docs/src/components/guide_section/guide_section.js +++ /dev/null @@ -1,707 +0,0 @@ -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; - -import { - EuiCode, - EuiCodeBlock, - EuiErrorBoundary, - EuiSpacer, - EuiTab, - EuiTable, - EuiTableBody, - EuiTableHeader, - EuiTableHeaderCell, - EuiTableRow, - EuiTableRowCell, - EuiTabs, - EuiText, - EuiTextColor, - EuiTitle, - EuiLink, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, -} from '../../../../src/components'; - -import { CodeSandboxLink } from '../codesandbox'; - -import { cleanEuiImports } from '../../services'; - -import { extendedTypesInfo } from './guide_section_extends'; - -const slugify = (str) => { - const parts = str - .toLowerCase() - .replace(/[-]+/g, ' ') - .replace(/[^\w^\s]+/g, '') - .replace(/ +/g, ' ') - .split(' '); - return parts.join('-'); -}; - -export const markup = (text) => { - const regex = /(#[a-zA-Z]+)|(`[^`]+`)/g; - return text.split('\n').map((token) => { - const values = token.split(regex).map((token, index) => { - if (!token) { - return ''; - } - if (token.startsWith('#')) { - const id = token.substring(1); - const onClick = () => { - document.getElementById(id).scrollIntoView(); - }; - return ( - - {id} - - ); - } - if (token.startsWith('`')) { - const code = token.substring(1, token.length - 1); - return {code}; - } - if (token.includes('\n')) { - return token - .split('\n') - .map((item) => [item,
]); - } - return token; - }); - return [...values,
]; - }); -}; - -export const humanizeType = (type) => { - if (!type) { - return ''; - } - - let humanizedType; - - switch (type.name) { - case 'enum': - if (Array.isArray(type.value)) { - humanizedType = type.value.map(({ value }) => value).join(', '); - break; - } - humanizedType = type.value; - break; - - case 'union': - if (Array.isArray(type.value)) { - const unionValues = type.value.map(({ name }) => name); - unionValues[unionValues.length - 1] = `or ${ - unionValues[unionValues.length - 1] - }`; - - if (unionValues.length > 2) { - humanizedType = unionValues.join(', '); - } else { - humanizedType = unionValues.join(' '); - } - break; - } - humanizedType = type.value; - break; - - default: - humanizedType = type.name; - } - - return humanizedType; -}; - -const nameToCodeClassMap = { - javascript: 'javascript', - html: 'html', -}; - -const tabDisplayNameMap = { - javascript: 'Demo JS', - html: 'Demo HTML', - snippet: 'Snippet', -}; - -export class GuideSection extends Component { - constructor(props) { - super(props); - - this.componentNames = Object.keys(props.props); - const hasSnippet = 'snippet' in props; - - this.tabs = []; - - if (props.demo) { - this.tabs.push({ - name: 'demo', - displayName: 'Demo', - }); - } - - if (props.source) { - props.source.map((source) => { - this.tabs.push({ - name: - (source.displayName && slugify(source.displayName)) || - tabDisplayNameMap[source.type] || - 'tab', - displayName: - source.displayName || tabDisplayNameMap[source.type] || 'Tab', - isCode: source.type || true, - code: source.code, - }); - }); - } - - if (hasSnippet) { - this.tabs.push({ - name: 'snippet', - displayName: 'Snippet', - isCode: true, - }); - } - - if (this.componentNames.length) { - this.tabs.push({ - name: 'props', - displayName: 'Props', - }); - } - - this.state = { - selectedTab: this.tabs.length > 0 ? this.tabs[0] : undefined, - renderedCode: null, - sortedComponents: {}, - }; - - this.memoScroll = 0; - } - - onSort = (componentName) => { - const { sortedComponents } = this.state; - if ( - !sortedComponents[componentName] || - sortedComponents[componentName] === 'NONE' - ) { - this.setState({ - sortedComponents: { ...sortedComponents, [componentName]: 'ASC' }, - }); - } else if (sortedComponents[componentName] === 'ASC') { - this.setState({ - sortedComponents: { ...sortedComponents, [componentName]: 'DSC' }, - }); - } else { - this.setState({ - sortedComponents: { ...sortedComponents, [componentName]: 'NONE' }, - }); - } - }; - - onSelectedTabChanged = (selectedTab) => { - const { isCode, code } = selectedTab; - let renderedCode = null; - - if (isCode === 'html' || isCode === 'javascript') { - renderedCode = code; - - if (isCode === 'javascript') { - renderedCode = renderedCode.default - .replace( - /(from )'(..\/)+src\/services(\/?';)/g, - "from '@elastic/eui/lib/services';" - ) - .replace( - /(from )'(..\/)+src\/components\/.*?';/g, - "from '@elastic/eui';" - ); - renderedCode = renderedCode.split('\n'); - const linesWithImport = []; - // eslint-disable-next-line guard-for-in - for (const idx in renderedCode) { - const line = renderedCode[idx]; - if ( - line.includes('import') && - line.includes("from '@elastic/eui';") - ) { - linesWithImport.push(line); - renderedCode[idx] = ''; - } - } - if (linesWithImport.length > 1) { - linesWithImport[0] = linesWithImport[0].replace( - " } from '@elastic/eui';", - ',' - ); - for (let i = 1; i < linesWithImport.length - 1; i++) { - linesWithImport[i] = linesWithImport[i] - .replace('import {', '') - .replace(" } from '@elastic/eui';", ','); - } - linesWithImport[linesWithImport.length - 1] = linesWithImport[ - linesWithImport.length - 1 - ].replace('import {', ''); - } - const newImport = linesWithImport.join(''); - renderedCode.unshift(newImport); - renderedCode = renderedCode.join('\n'); - let len = renderedCode.replace('\n\n\n', '\n\n').length; - while (len < renderedCode.length) { - renderedCode = renderedCode.replace('\n\n\n', '\n\n'); - len = renderedCode.replace('\n\n\n', '\n\n').length; - } - renderedCode = cleanEuiImports(renderedCode); - } else if (isCode === 'html') { - renderedCode = code.render(); - } - } - - this.setState({ selectedTab, renderedCode, code }, () => { - if (isCode === 'javascript') { - requestAnimationFrame(() => { - const pre = this.refs.javascript.querySelector('.euiCodeBlock__pre'); - if (!pre) return; - pre.scrollTop = this.memoScroll; - }); - } - }); - }; - - renderTabs() { - return this.tabs.map((tab) => ( - this.onSelectedTabChanged(tab)} - isSelected={tab === this.state.selectedTab} - key={tab.name}> - {tab.displayName} - - )); - } - - renderText() { - const { text } = this.props; - - if (!text) { - return; - } - - return [{text}]; - } - - renderSnippet() { - let { snippet } = this.props; - - if (this.props.source?.find((tab) => tab.type === 'snippet')) { - snippet = this.props.source?.find((tab) => tab.type === 'snippet').code; - } - - if (!snippet) { - return; - } - - let snippetCode; - if (typeof snippet === 'string') { - snippetCode = ( - - - - {snippet} - - - ); - } else { - snippetCode = snippet.map((snip, index) => ( - - - - {snip} - - - )); - } - - return snippetCode; - } - - renderPropsForComponent = (componentName, component) => { - if (!component.__docgenInfo) { - return; - } - - const docgenInfo = Array.isArray(component.__docgenInfo) - ? component.__docgenInfo[0] - : component.__docgenInfo; - const { description, props, extendedInterfaces } = docgenInfo; - - if (!props && !description) { - return; - } - - const { sortedComponents } = this.state; - - const propNames = Object.keys(props); - if ( - sortedComponents[componentName] && - sortedComponents[componentName] !== 'NONE' - ) { - propNames.sort((a, b) => { - if (sortedComponents[componentName] === 'ASC') { - return a < b ? -1 : 1; - } else { - return a < b ? 1 : -1; - } - }); - } - - const rows = propNames.map((propName) => { - const { - description: propDescription = '', - required, - defaultValue, - type, - } = props[propName]; - - const codeBlockProps = { - className: 'guideSection__tableCodeBlock', - paddingSize: 'none', - language: 'ts', - }; - - let humanizedName = ( - {propName} - ); - - if (required) { - humanizedName = ( - - {humanizedName}{' '} - (required) - - ); - } - - const humanizedType = humanizeType(type); - - const functionMatches = [ - ...humanizedType.matchAll(/\([^=]*\) =>\s\w*\)*/g), - ]; - const types = humanizedType.split(/\([^=]*\) =>\s\w*\)*/); - - const typeMarkup = ( - {markup(humanizedType)} - ); - const descriptionMarkup = markup(propDescription); - let defaultValueMarkup = ''; - if (defaultValue) { - defaultValueMarkup = [ - - {defaultValue.value} - , - ]; - if (defaultValue.comment) { - defaultValueMarkup.push(`(${defaultValue.comment})`); - } - } - - let defaultTypeCell = ( - - {typeMarkup} - - ); - if (functionMatches.length > 0) { - const elements = []; - let j = 0; - for (let i = 0; i < types.length; i++) { - if (functionMatches[j]) { - elements.push( - - {types[i]}
-
- ); - elements.push( - - {functionMatches[j][0]}
-
- ); - j++; - } else { - elements.push( - - {types[i]}
-
- ); - } - } - defaultTypeCell = ( - - - {elements} - - - ); - } - - const cells = [ - - {humanizedName} - , - defaultTypeCell, - - {defaultValueMarkup} - , - - {descriptionMarkup} - , - ]; - - return {cells}; - }); - - const extendedTypes = extendedInterfaces - ? extendedInterfaces.filter((type) => !!extendedTypesInfo[type]) - : []; - // if there is an HTMLAttributes type present among others, remove HTMLAttributes - if (extendedTypes.includes('HTMLAttributes') && extendedTypes.length > 1) { - const htmlAttributesIndex = extendedTypes.indexOf('HTMLAttributes'); - extendedTypes.splice(htmlAttributesIndex, 1); - } - const extendedTypesElements = extendedTypes.map((type, index) => ( - - - {extendedTypesInfo[type].name} - - {index + 1 < extendedTypes.length && ', '} - - )); - - let descriptionElement; - - if (description) { - descriptionElement = ( -
- -

{markup(description)}

-
- -
- ); - } - - let table; - - if (rows.length) { - table = ( - - - { - this.onSort(componentName); - }} - isSorted={ - sortedComponents[componentName] && - sortedComponents[componentName] !== 'NONE' - } - isSortAscending={ - sortedComponents[componentName] && - sortedComponents[componentName] === 'ASC' - } - style={{ Width: '20%' }}> - Prop - - - - Type - - - - Default - - - - Note - - - - {rows} - - ); - } - - return [ - , - - - -

{componentName}

-
-
- {extendedTypesElements.length > 0 && ( - - -

[ extends {extendedTypesElements} ]

-
-
- )} -
, - , - descriptionElement, - table, - ]; - }; - - renderProps() { - const { props } = this.props; - return this.componentNames - .map((componentName) => - this.renderPropsForComponent(componentName, props[componentName]) - ) - .reduce((a, b) => a.concat(b), []); // Flatten the resulting array - } - - renderChrome() { - let title; - - if (this.props.title) { - title = ( - - -

{this.props.title}

-
- -
- ); - } - return ( -
-
- {title} - {this.renderText()} -
- - {this.tabs.length > 0 && ( - <> - - {this.renderTabs()} - - )} -
- ); - } - - renderCode(name) { - const euiCodeBlock = ( - - {this.state.renderedCode} - - ); - - const divProps = { - key: name, - ref: name, - }; - - const memoScrollUtility = () => { - const pre = this.refs.javascript.querySelector('.euiCodeBlock__pre'); - this.memoScroll = pre.scrollTop; - }; - - if (name === 'javascript') { - return ( -
- {euiCodeBlock} - {name === 'javascript' - ? this.renderCodeSandBoxButton(this.state.code) - : null} -
- ); - } - - return
{euiCodeBlock}
; - } - - renderContent() { - if (typeof this.state.selectedTab === 'undefined') { - return; - } - - if (this.state.selectedTab.name === 'snippet') { - return {this.renderSnippet()}; - } - - if (this.state.selectedTab.isCode) { - return ( - - {this.renderCode(this.state.selectedTab.isCode)} - - ); - } - - if (this.state.selectedTab.name === 'props') { - return {this.renderProps()}; - } - - return ( - -
-
- {this.props.demo} -
- - ); - } - - renderCodeSandBoxButton(code) { - if (!code) { - return; - } - - return ( - - - Try out this demo on Code Sandbox - - - ); - } - - render() { - const chrome = this.renderChrome(); - - return ( -
- {chrome} - {this.renderContent()} - {this.props.extraContent} -
- ); - } -} - -GuideSection.propTypes = { - title: PropTypes.string, - id: PropTypes.string, - source: PropTypes.array, - snippet: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - children: PropTypes.any, - routes: PropTypes.object.isRequired, - props: PropTypes.object, -}; - -GuideSection.defaultProps = { - props: {}, -}; diff --git a/src-docs/src/components/guide_section/guide_section.tsx b/src-docs/src/components/guide_section/guide_section.tsx new file mode 100644 index 00000000000..0b741233f9d --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section.tsx @@ -0,0 +1,182 @@ +import React, { FunctionComponent, ReactNode, useState } from 'react'; + +import { EuiErrorBoundary } from '../../../../src/components/error_boundary'; +import { EuiText } from '../../../../src/components/text'; +import { EuiSwitch } from '../../../../src/components/form'; + +import { slugify } from '../../../../src/services/string/slugify'; + +// @ts-ignore Not TS yet +import playgroundService from '../../services/playground/playground'; + +import { GuideSectionExample } from './guide_section_parts/guide_section_example'; +import { GuideSectionExampleText } from './guide_section_parts/guide_section_text'; +import { + GuideSectionExampleTabs, + GuideSectionExampleTabsProps, +} from './guide_section_parts/guide_section_tabs'; +import { GuideSectionPropsDescription } from './guide_section_parts/guide_section_props_description'; + +export interface GuideSection { + id?: string; + title?: string; + text?: ReactNode; + source?: any[]; + demo?: ReactNode; + demoPanelProps?: GuideSectionExample['demoPanelProps']; + props?: object; + playground?: any; + ghostBackground?: boolean; + wrapText?: boolean; + snippet?: string | string[]; +} + +export const GuideSectionCodeTypesMap = { + JS: { + name: 'demoJS', + displayName: 'Demo JS', + }, + SNIPPET: { + name: 'snippet', + displayName: 'Snippet', + }, + PROPS: { + name: 'props', + displayName: 'Props', + }, +}; + +export const GuideSection: FunctionComponent = ({ + id, + title, + text, + demo, + source = [], + props = {}, + playground, + ghostBackground, + wrapText = true, + demoPanelProps, + snippet, +}) => { + const [renderingPlayground, setRenderingPlayground] = useState(false); + + const renderTabs = () => { + const hasSnippet = !!snippet; + + // Don't duplicate in case this function is run multiple times + if (hasSnippet && !source.find((tab) => tab.name === 'snippet')) { + source.push({ + ...GuideSectionCodeTypesMap.SNIPPET, + snippets: snippet, + }); + } + + const hasPropsTabAlready = source.find((tab) => tab.name === 'props'); + + if ( + Object.keys(props).length && + !hasPropsTabAlready // Don't duplicate in case this function is run multiple times + ) { + source.push({ + ...GuideSectionCodeTypesMap.PROPS, + props: props, + }); + } + + const tabs: GuideSectionExampleTabsProps['tabs'] = []; + + if (source) { + source.map((source) => { + // Forever skipping the HTML tab + if (source.type === 'HTML') return; + tabs.push({ + // @ts-ignore Complicated + ...GuideSectionCodeTypesMap[source.type], + // Make sure the `name` is unique in case there are multiple source languages + name: source.displayName + ? slugify(source.displayName) + : // @ts-ignore Complicated + GuideSectionCodeTypesMap[source.type].name, + disabled: renderingPlayground, + ...source, + }); + }); + } + + return tabs.length ? ( + + ) : undefined; + }; + + const renderPlaygroundToggle = () => { + const isPlaygroundUnsupported = + typeof window !== 'undefined' && + typeof document !== 'undefined' && + !!window.MSInputMethodContext && + // @ts-ignore doesn't exist? + !!document.documentMode; + + if (!isPlaygroundUnsupported && !!playground) { + return ( + { + setRenderingPlayground((rendering) => !rendering); + }} + checked={renderingPlayground} + compressed + label={ + + Playground + + } + /> + ); + } + }; + + const renderPlayground = () => { + const { config, setGhostBackground, playgroundClassName } = playground(); + + const description = ( + + ); + + return playgroundService({ + config, + setGhostBackground, + playgroundClassName, + playgroundToggle: renderPlaygroundToggle(), + tabs: renderTabs(), + description, + }); + }; + + return ( +
+ + {text} + + + {renderingPlayground && renderPlayground()} + {!renderingPlayground && demo && ( + +
{demo}
+ + } + tabs={renderTabs()} + ghostBackground={ghostBackground} + demoPanelProps={demoPanelProps} + /> + )} +
+ ); +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_code.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_code.tsx new file mode 100644 index 00000000000..b2fc48fa16d --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_code.tsx @@ -0,0 +1,43 @@ +import React, { FunctionComponent, useState, useEffect } from 'react'; +import { EuiCodeBlock } from '../../../../../src/components/code'; +import { EuiButtonEmpty } from '../../../../../src/components/button'; +// @ts-ignore Not TS +import { CodeSandboxLink } from '../../codesandbox'; +// @ts-ignore Not TS +import { renderJsSourceCode } from '../_utils'; + +export type GuideSectionExampleCode = { + code: any; +}; + +export const GuideSectionExampleCode: FunctionComponent = ({ + code, +}) => { + const [codeToRender, setCodeToRender] = useState(); + + useEffect(() => { + setCodeToRender(renderJsSourceCode(code)); + return () => { + setCodeToRender(undefined); + }; + }, [code]); + + const codeSandboxLink = ( + + + Try out this demo on Code Sandbox + + + ); + + return ( + <> + + {codeToRender} + + {codeSandboxLink} + + ); +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_example.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_example.tsx new file mode 100644 index 00000000000..605a8649262 --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_example.tsx @@ -0,0 +1,39 @@ +import React, { FunctionComponent, ReactNode } from 'react'; +import classNames from 'classnames'; +import { EuiSplitPanel } from '../../../../../src/components/panel'; +import { _EuiSplitPanelInnerProps } from '../../../../../src/components/panel/split_panel/'; + +export interface GuideSectionExample { + example: ReactNode; + tabs?: ReactNode; + /** Forces display of a certain content (playground props table) */ + tabContent?: ReactNode; + ghostBackground?: boolean; + demoPanelProps?: _EuiSplitPanelInnerProps; +} + +export const GuideSectionExample: FunctionComponent = ({ + example, + tabs, + ghostBackground = false, + tabContent, + demoPanelProps, +}) => { + const classes = classNames(demoPanelProps?.className, { + guideDemo__ghostBackground: ghostBackground, + }); + + return ( + + + {example} + + {(tabs || tabContent) && ( + + {tabs} + {tabContent} + + )} + + ); +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_props_description.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_props_description.tsx new file mode 100644 index 00000000000..de94ee41891 --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_props_description.tsx @@ -0,0 +1,85 @@ +import React, { FunctionComponent, Fragment } from 'react'; +import { EuiLink } from '../../../../../src/components/link'; +import { EuiSpacer } from '../../../../../src/components/spacer'; +import { EuiText } from '../../../../../src/components/text'; +import { EuiHorizontalRule } from '../../../../../src/components/horizontal_rule'; +import { EuiFlexGroup, EuiFlexItem } from '../../../../../src/components/flex'; +import { EuiTitle } from '../../../../../src/components/title'; + +// @ts-ignore not TS +import { extendedTypesInfo } from '../guide_section_extends'; +// @ts-ignore not TS +import { markup } from '../../../services/playground/knobs'; + +export type GuideSectionPropsDescription = { + componentName: any; + component: any; +}; + +export const GuideSectionPropsDescription: FunctionComponent = ({ + componentName, + component, +}) => { + const docgenInfo = Array.isArray(component.__docgenInfo) + ? component.__docgenInfo[0] + : component.__docgenInfo; + + const { description, extendedInterfaces } = docgenInfo; + + const extendedTypes = extendedInterfaces + ? extendedInterfaces.filter((type: any) => !!extendedTypesInfo[type]) + : []; + + // if there is an HTMLAttributes type present among others, remove HTMLAttributes + if (extendedTypes.includes('HTMLAttributes') && extendedTypes.length > 1) { + const htmlAttributesIndex = extendedTypes.indexOf('HTMLAttributes'); + extendedTypes.splice(htmlAttributesIndex, 1); + } + + const extendedTypesElements = extendedTypes.map((type: any, index: any) => ( + + + {extendedTypesInfo[type].name} + + {index + 1 < extendedTypes.length && ', '} + + )); + + let descriptionElement; + + if (description) { + descriptionElement = ( + <> + +

{markup(description)}

+
+ + + ); + } + + return ( + <> + + +
+ + + +

{componentName}

+
+
+ {extendedTypesElements.length > 0 && ( + + +

[ extends {extendedTypesElements} ]

+
+
+ )} +
+ + {descriptionElement} +
+ + ); +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_props_table.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_props_table.tsx new file mode 100644 index 00000000000..bccd36828bf --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_props_table.tsx @@ -0,0 +1,48 @@ +import React, { FunctionComponent } from 'react'; +import { useView } from 'react-view'; + +import { GuideSectionPropsDescription } from './guide_section_props_description'; + +// @ts-ignore not TS +import Knobs from '../../../services/playground/knobs'; +// @ts-ignore not TS +import { propUtilityForPlayground } from '../../../services/playground'; + +export type GuideSectionPropsTable = { + componentName: any; + component: any; +}; + +export const GuideSectionPropsTable: FunctionComponent = ({ + componentName, + component, +}) => { + const docgenInfo = Array.isArray(component.__docgenInfo) + ? component.__docgenInfo[0] + : component.__docgenInfo; + + const { props } = docgenInfo; + + return ( +
+ + +
+ ); +}; + +const PlaygroundProps: FunctionComponent = ({ config, isPlayground }) => { + const params = useView(config); + + return ; +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_snippets.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_snippets.tsx new file mode 100644 index 00000000000..69d8958469c --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_snippets.tsx @@ -0,0 +1,31 @@ +import React, { FunctionComponent } from 'react'; +import { EuiCodeBlock } from '../../../../../src/components/code'; +import { EuiSpacer } from '../../../../../src/components/spacer'; + +export type GuideSectionSnippets = { + snippets: string | string[]; +}; + +export const GuideSectionSnippets: FunctionComponent = ({ + snippets, +}) => { + let snippetCode; + if (typeof snippets === 'string') { + snippetCode = ( + + {snippets} + + ); + } else { + snippetCode = snippets.map((snip, index) => ( + + + {snip} + + {index < snippets.length - 1 && } + + )); + } + + return {snippetCode}; +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_tabs.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_tabs.tsx new file mode 100644 index 00000000000..5d12884ea56 --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_tabs.tsx @@ -0,0 +1,131 @@ +import React, { FunctionComponent, useState, ReactNode } from 'react'; +import classNames from 'classnames'; +import { + EuiTabs, + EuiTab, + EuiTabProps, +} from '../../../../../src/components/tabs'; +import { EuiErrorBoundary } from '../../../../../src/components/error_boundary'; +import { EuiHorizontalRule } from '../../../../../src/components/horizontal_rule'; +import { GuideSectionSnippets } from './guide_section_snippets'; +import { GuideSectionExampleCode } from './guide_section_code'; +import { GuideSectionPropsTable } from './guide_section_props_table'; +import { EuiFlexGroup, EuiFlexItem } from '../../../../../src/components/flex'; +import { ExclusiveUnion } from '../../../../../src/components/common'; + +export type GuideSectionExampleTabCodeType = GuideSectionExampleCode; +export type GuideSectionExampleTabSnippetType = GuideSectionSnippets; +export type GuideSectionExampleTabPropsTableType = { + props: any; +}; + +export type GuideSectionExampleTabType = EuiTabProps & + ExclusiveUnion< + GuideSectionExampleTabCodeType, + ExclusiveUnion< + GuideSectionExampleTabSnippetType, + GuideSectionExampleTabPropsTableType + > + > & { + displayName: string; + name: string; + }; + +export type GuideSectionExampleTabsProps = { + tabs: GuideSectionExampleTabType[]; + /** Renders any content to the right of the tabs (playground toggle) */ + rightSideControl?: ReactNode; +}; + +export const GuideSectionExampleTabs: FunctionComponent = ({ + tabs, + rightSideControl, +}) => { + const [selectedTabId, setSelectedTabId] = useState(''); + + const onSelectedTabChanged = (id: string) => { + if (id === selectedTabId) { + setSelectedTabId(''); + } else { + setSelectedTabId(id); + } + }; + + const tabClasses = classNames('guideSectionTabs', { + 'guideSectionTabs--open': selectedTabId, + }); + + const renderTabs = () => { + return ( + + {tabs.map((tab, index) => { + const { displayName, code, name, props, snippets, ...rest } = tab; + + return ( + onSelectedTabChanged(name)} + isSelected={name === selectedTabId} + key={index}> + {tab.displayName} + + ); + })} + + ); + }; + + const renderContent = () => { + if (!selectedTabId) return null; + + const selectedTab = tabs.find((tab) => tab.name === selectedTabId); + + // SNIPPET + if (selectedTab && selectedTab.snippets) { + return ( + + + + + ); + // SOURCE CODE BLOCK + } else if (selectedTab && selectedTab.code) { + return ( + + + + + ); + // PROPS TABLE + } else if (selectedTab && selectedTab.props) { + const components = Object.keys(selectedTab.props); + + return components.map((component) => ( + + + + )); + } + }; + + return ( + <> + + {renderTabs()} + {rightSideControl} + + {selectedTabId && renderContent()} + + ); +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/guide_section_text.tsx b/src-docs/src/components/guide_section/guide_section_parts/guide_section_text.tsx new file mode 100644 index 00000000000..8637a13eba0 --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/guide_section_text.tsx @@ -0,0 +1,46 @@ +import React, { FunctionComponent, ReactNode } from 'react'; +import { EuiSpacer } from '../../../../../src/components/spacer'; +import { EuiTitle } from '../../../../../src/components/title'; +import { EuiText } from '../../../../../src/components/text'; + +export const LANGUAGES = ['javascript', 'html'] as const; + +type GuideSectionExampleText = { + title?: ReactNode; + children?: ReactNode; + wrapText?: boolean; +}; + +export const GuideSectionExampleText: FunctionComponent = ({ + title, + children, + wrapText = true, +}) => { + let titleNode; + + if (title) { + titleNode = ( + <> + + +

{title}

+
+ + + ); + } + + let textNode = children; + + if (children && wrapText) { + textNode = {children}; + } + + return ( + <> + {titleNode} + {textNode} + {(titleNode || textNode) && } + + ); +}; diff --git a/src-docs/src/components/guide_section/guide_section_parts/index.ts b/src-docs/src/components/guide_section/guide_section_parts/index.ts new file mode 100644 index 00000000000..7b5559c93de --- /dev/null +++ b/src-docs/src/components/guide_section/guide_section_parts/index.ts @@ -0,0 +1 @@ +export { GuideSectionSnippets } from './guide_section_snippets'; diff --git a/src-docs/src/components/guide_section/guide_section_types.js b/src-docs/src/components/guide_section/guide_section_types.js index ad0b93c736e..c149ab412c6 100644 --- a/src-docs/src/components/guide_section/guide_section_types.js +++ b/src-docs/src/components/guide_section/guide_section_types.js @@ -1,5 +1,4 @@ export const GuideSectionTypes = { - JS: 'javascript', - HTML: 'html', - SNIPPET: 'snippet', + JS: 'JS', + HTML: 'HTML', }; diff --git a/src-docs/src/components/guide_section/index.js b/src-docs/src/components/guide_section/index.js deleted file mode 100644 index 9e6038f7df5..00000000000 --- a/src-docs/src/components/guide_section/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { GuideSectionContainer as GuideSection } from './guide_section_container'; - -export { GuideSectionTypes } from './guide_section_types'; diff --git a/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx b/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx index 8253e46205f..bb6ca5cf1ee 100644 --- a/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx +++ b/src-docs/src/components/guide_theme_selector/guide_theme_selector.tsx @@ -91,7 +91,7 @@ const GuideThemeSelectorComponent: React.FunctionComponent - {location.host === 'localhost:8030' && ( + {location.host.includes('803') && ( <>
diff --git a/src-docs/src/components/index.js b/src-docs/src/components/index.js index d5df86905f8..f202a572b56 100644 --- a/src-docs/src/components/index.js +++ b/src-docs/src/components/index.js @@ -5,8 +5,12 @@ export { GuideRuleDescription, } from './guide_rule'; +export { GuideMarkdownFormat } from './guide_markdown_format'; + export { GuidePage, GuidePageChrome } from './guide_page'; -export { GuideSection, GuideSectionTypes } from './guide_section'; +export { GuideSectionContainer as GuideSection } from './guide_section/guide_section_container'; + +export { GuideSectionTypes } from './guide_section/guide_section_types'; export { ThemeProvider, ThemeContext } from './with_theme'; diff --git a/src-docs/src/images/button_empty.svg b/src-docs/src/images/button_empty.svg new file mode 100644 index 00000000000..fed86f6cdc2 --- /dev/null +++ b/src-docs/src/images/button_empty.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src-docs/src/images/button_left.svg b/src-docs/src/images/button_left.svg new file mode 100644 index 00000000000..f01b8176b79 --- /dev/null +++ b/src-docs/src/images/button_left.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/button_placement.png b/src-docs/src/images/button_placement.png deleted file mode 100644 index 8d36b298310..00000000000 Binary files a/src-docs/src/images/button_placement.png and /dev/null differ diff --git a/src-docs/src/images/button_placement.svg b/src-docs/src/images/button_placement.svg new file mode 100644 index 00000000000..de800d02cf8 --- /dev/null +++ b/src-docs/src/images/button_placement.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/button_popover.svg b/src-docs/src/images/button_popover.svg new file mode 100644 index 00000000000..2a4e22868bf --- /dev/null +++ b/src-docs/src/images/button_popover.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/button_right.svg b/src-docs/src/images/button_right.svg new file mode 100644 index 00000000000..73b3bfbf22e --- /dev/null +++ b/src-docs/src/images/button_right.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/button_table.svg b/src-docs/src/images/button_table.svg new file mode 100644 index 00000000000..30b389540a4 --- /dev/null +++ b/src-docs/src/images/button_table.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/button_types.svg b/src-docs/src/images/button_types.svg new file mode 100644 index 00000000000..344d109390c --- /dev/null +++ b/src-docs/src/images/button_types.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/button_types_bad.svg b/src-docs/src/images/button_types_bad.svg new file mode 100644 index 00000000000..f18586a23d1 --- /dev/null +++ b/src-docs/src/images/button_types_bad.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/form-row--00.png b/src-docs/src/images/form-row--00.png deleted file mode 100644 index 97448893e03..00000000000 Binary files a/src-docs/src/images/form-row--00.png and /dev/null differ diff --git a/src-docs/src/images/form-row--01.png b/src-docs/src/images/form-row--01.png deleted file mode 100644 index 771ffcfa1aa..00000000000 Binary files a/src-docs/src/images/form-row--01.png and /dev/null differ diff --git a/src-docs/src/images/form-row--01.svg b/src-docs/src/images/form-row--01.svg new file mode 100644 index 00000000000..2acfb91dd4e --- /dev/null +++ b/src-docs/src/images/form-row--01.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/form-row--02.png b/src-docs/src/images/form-row--02.png deleted file mode 100644 index 904961c81eb..00000000000 Binary files a/src-docs/src/images/form-row--02.png and /dev/null differ diff --git a/src-docs/src/images/form-row--02.svg b/src-docs/src/images/form-row--02.svg new file mode 100644 index 00000000000..3f94dce4f0e --- /dev/null +++ b/src-docs/src/images/form-row--02.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/form-row--03.png b/src-docs/src/images/form-row--03.png deleted file mode 100644 index 147285b2db7..00000000000 Binary files a/src-docs/src/images/form-row--03.png and /dev/null differ diff --git a/src-docs/src/images/form-row--03.svg b/src-docs/src/images/form-row--03.svg new file mode 100644 index 00000000000..b9ba5e0f95f --- /dev/null +++ b/src-docs/src/images/form-row--03.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/form-row--04.png b/src-docs/src/images/form-row--04.png deleted file mode 100644 index 16cc8b15eb4..00000000000 Binary files a/src-docs/src/images/form-row--04.png and /dev/null differ diff --git a/src-docs/src/images/form-row--04.svg b/src-docs/src/images/form-row--04.svg new file mode 100644 index 00000000000..3ee8b6b40e6 --- /dev/null +++ b/src-docs/src/images/form-row--04.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/form-row--05.png b/src-docs/src/images/form-row--05.png deleted file mode 100644 index 661a6df6694..00000000000 Binary files a/src-docs/src/images/form-row--05.png and /dev/null differ diff --git a/src-docs/src/images/form-row--05.svg b/src-docs/src/images/form-row--05.svg new file mode 100644 index 00000000000..a98eff4a2f6 --- /dev/null +++ b/src-docs/src/images/form-row--05.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/form-row--06.png b/src-docs/src/images/form-row--06.png deleted file mode 100644 index 07c6a7769cb..00000000000 Binary files a/src-docs/src/images/form-row--06.png and /dev/null differ diff --git a/src-docs/src/images/form-row--06.svg b/src-docs/src/images/form-row--06.svg new file mode 100644 index 00000000000..7d798167614 --- /dev/null +++ b/src-docs/src/images/form-row--06.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-docs/src/images/form-row--toggle.gif b/src-docs/src/images/form-row--toggle.gif index 2564b2894d9..0b3b2ca7c21 100644 Binary files a/src-docs/src/images/form-row--toggle.gif and b/src-docs/src/images/form-row--toggle.gif differ diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index a6d2a0af694..be5011b5e58 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -1,4 +1,5 @@ import React, { createElement, Fragment } from 'react'; +import { slugify } from '../../src/services'; import { createHashHistory } from 'history'; @@ -9,6 +10,7 @@ import { EuiErrorBoundary } from '../../src/components'; import { playgroundCreator } from './services/playground'; // Guidelines +// const GettingStarted = require('!!raw-loader!./views/guidelines/getting_started.md'); import AccessibilityGuidelines from './views/guidelines/accessibility'; @@ -228,19 +230,6 @@ import { ElasticChartsCategoryExample } from './views/elastic_charts/category_ex import { ElasticChartsSparklinesExample } from './views/elastic_charts/sparklines_example'; import { ElasticChartsPieExample } from './views/elastic_charts/pie_example'; -/** - * Lowercases input and replaces spaces with hyphens: - * e.g. 'GridView Example' -> 'gridview-example' - */ -const slugify = (str) => { - const parts = str - .toLowerCase() - .replace(/[-]+/g, ' ') - .replace(/[^\w^\s]+/g, '') - .replace(/ +/g, ' ') - .split(' '); - return parts.join('-'); -}; const createExample = (example, customTitle) => { if (!example) { @@ -266,12 +255,13 @@ const createExample = (example, customTitle) => { guidelines, } = example; sections.forEach((section) => { - section.id = slugify(section.title || title); + section.id = section.title ? slugify(section.title) : undefined; }); - const renderedSections = sections.map((section) => + const renderedSections = sections.map((section, index) => createElement(GuideSection, { - key: section.title || title, + // Using index as the key because not all require a `title` + key: index, ...section, }) ); @@ -309,10 +299,34 @@ const createExample = (example, customTitle) => { }; }; +// const createMarkdownExample = (example, title) => { +// const headings = example.default.match(/^(##) (.*)/gm); + +// const sections = headings.map((heading) => { +// const title = heading.replace('## ', ''); + +// return { id: slugify(title), title: title }; +// }); + +// return { +// name: title, +// component: () => ( +// +// +// {example.default} +// +// +// ), +// sections: sections, +// }; +// }; + const navigation = [ { name: 'Guidelines', items: [ + // TODO uncomment when EuiMarkdownFormat has a better text formatting + // createMarkdownExample(GettingStarted, 'Getting started'), createExample(AccessibilityGuidelines, 'Accessibility'), { name: 'Colors', @@ -322,10 +336,7 @@ const navigation = [ name: 'Sass', component: SassGuidelines, }, - { - name: 'Writing', - component: WritingGuidelines, - }, + createExample(WritingGuidelines, 'Writing'), ], }, { diff --git a/src-docs/src/services/playground/_playground_compiler.scss b/src-docs/src/services/playground/_playground_compiler.scss index 0771394014b..8440e28d784 100644 --- a/src-docs/src/services/playground/_playground_compiler.scss +++ b/src-docs/src/services/playground/_playground_compiler.scss @@ -1,14 +1,5 @@ -.playgroundCompiler { +.playgroundWrapper { > div { - justify-content: left !important; // sass-lint:disable-line no-important display: block !important; // sass-lint:disable-line no-important } } - -.playgroundCompiler__ghostBackground { - padding: 0 $euiSize; - - @if (lightness($euiTextColor) < 50) { - background: $euiColorDarkestShade; - } -} diff --git a/src-docs/src/services/playground/knobs.js b/src-docs/src/services/playground/knobs.js index 614a03a386f..a5676ec7f83 100644 --- a/src-docs/src/services/playground/knobs.js +++ b/src-docs/src/services/playground/knobs.js @@ -1,6 +1,9 @@ import React, { useState, useEffect } from 'react'; import { assertUnreachable, PropTypes } from 'react-view'; +import { useIsWithinBreakpoints } from '../../../../src/services/hooks'; import { + EuiTitle, + EuiCodeBlock, EuiSpacer, EuiSwitch, EuiRadioGroup, @@ -18,12 +21,83 @@ import { EuiTextColor, EuiTextArea, EuiFormRow, + EuiLink, + EuiText, + EuiPanel, } from '../../../../src/components/'; -import { - humanizeType, - markup, -} from '../../components/guide_section/guide_section'; +export const markup = (text) => { + const regex = /(#[a-zA-Z]+)|(`[^`]+`)/g; + return text.split('\n').map((token) => { + const values = token.split(regex).map((token, index) => { + if (!token) { + return ''; + } + if (token.startsWith('#')) { + const id = token.substring(1); + const onClick = () => { + document.getElementById(id).scrollIntoView(); + }; + return ( + + {id} + + ); + } + if (token.startsWith('`')) { + const code = token.substring(1, token.length - 1); + return {code}; + } + if (token.includes('\n')) { + return token + .split('\n') + .map((item) => [item,
]); + } + return token; + }); + return [...values,
]; + }); +}; + +export const humanizeType = (type) => { + if (!type) { + return ''; + } + + let humanizedType; + + switch (type.name) { + case 'enum': + if (Array.isArray(type.value)) { + humanizedType = type.value.map(({ value }) => value).join(', '); + break; + } + humanizedType = type.value; + break; + + case 'union': + if (Array.isArray(type.value)) { + const unionValues = type.value.map(({ name }) => name); + unionValues[unionValues.length - 1] = `or ${ + unionValues[unionValues.length - 1] + }`; + + if (unionValues.length > 2) { + humanizedType = unionValues.join(', '); + } else { + humanizedType = unionValues.join(' '); + } + break; + } + humanizedType = type.value; + break; + + default: + humanizedType = type.name; + } + + return humanizedType; +}; const getTooltip = (description, type, name) => ( @@ -58,6 +132,7 @@ const Knob = ({ custom, state, hidden, + helpText, }) => { const [error, setError] = useState(errorMsg); @@ -91,6 +166,7 @@ const Knob = ({ 0} error={error} + helpText={helpText} fullWidth> 0} error={error} fullWidth - helpText={custom && custom.helpText}> + helpText={ + <> + {helpText} + {custom && custom.helpText && ( + <> +
+ {custom.helpText} + + )} + + }> + 0} + error={error}> - {error &&
error {error}
} - +
); case PropTypes.Enum: @@ -168,6 +257,7 @@ const Knob = ({ let valueKey = val || defaultValue; + // When would numberOfOptions ever be less than 1? if (numberOfOptions < 1) { if (valueKey && !valueKey.includes('__')) { valueKey = `${valueKey}__${name}`; @@ -201,6 +291,7 @@ const Knob = ({ return ( 0} + helpText={helpText} error={error} fullWidth> { - return ( - <> - {knobNames.map((name, idx) => { - let humanizedType; - - if ( - state[name].custom && - state[name].custom.origin && - state[name].custom.origin.type - ) - humanizedType = humanizeType(state[name].custom.origin.type); - - const typeMarkup = humanizedType && ( - - {markup(humanizedType)} - - ); +const KnobColumn = ({ state, knobNames, error, set, isPlayground }) => { + return knobNames.map((name, idx) => { + const codeBlockProps = { + className: 'guideSection__tableCodeBlock', + paddingSize: 'none', + language: 'ts', + }; + + /** + * TS Type + */ + let humanizedType; + + if ( + state[name].custom && + state[name].custom.origin && + state[name].custom.origin.type + ) + humanizedType = humanizeType(state[name].custom.origin.type); + + let typeMarkup; + + if (humanizedType) { + typeMarkup = humanizedType && ( + {markup(humanizedType)} + ); - let humanizedName = ( - {name} - ); + const functionMatches = [ + ...humanizedType.matchAll(/\([^=]*\) =>\s\w*\)*/g), + ]; - if ( - state[name].custom && - state[name].custom.origin && - state[name].custom.origin.required - ) { - humanizedName = ( - - {humanizedName}{' '} - (required) - - ); - } + const types = humanizedType.split(/\([^=]*\) =>\s\w*\)*/); - let defaultValueMarkup; - - if ( - state[name].custom && - state[name].custom.origin && - state[name].custom.origin.defaultValue - ) { - defaultValueMarkup = ( - - - {state[name].custom.origin.defaultValue.value} - - - ); + if (functionMatches.length > 0) { + const elements = []; + let j = 0; + for (let i = 0; i < types.length; i++) { + if (functionMatches[j]) { + elements.push(
{types[i]}
); + elements.push( +
{functionMatches[j][0]}
+ ); + j++; + } else { + elements.push(
{types[i]}
); + } } - - return ( - - - {humanizedName} - {state[name].description && ( - <> -
- <>{markup(state[name].description)} - - )} -
- - {typeMarkup} - - - {defaultValueMarkup} - - - -
+ typeMarkup = ( + {elements} ); - })} - - ); + } + } + + /** + * Prop name + */ + let humanizedName = {name}; + + if ( + state[name].custom && + state[name].custom.origin && + state[name].custom.origin.required + ) { + humanizedName = ( + <> + {humanizedName} (required) + + ); + } + + /** + * Default value + */ + let defaultValueMarkup; + if ( + // !isPlayground && + state[name].custom && + state[name].custom.origin && + state[name].custom.origin.defaultValue + ) { + const defaultValue = state[name].custom.origin.defaultValue; + defaultValueMarkup = ( + + {isPlayground && 'Default: '} + {defaultValue.value} + {defaultValue.comment && ( + <> +
({defaultValue.comment}) + + )} +
+ ); + } + + return ( + + +
+ + {humanizedName} + + {state[name].description && ( + <> + + +

{markup(state[name].description)}

+
+ + )} +
+
+ +
{typeMarkup}
+
+ + {isPlayground ? ( + +
+ ); + }); }; -const columns = [ - { - field: 'prop', - name: 'Prop', - sortable: true, - 'data-test-subj': 'PropCell', - }, - { - field: 'type', - name: 'Type', - }, - { - field: 'default', - name: 'Default', - }, - { - field: 'modify', - name: 'Modify', - }, -]; - -const Knobs = ({ state, set, error }) => { +const Knobs = ({ state, set, error, isPlayground = true }) => { + const isMobile = useIsWithinBreakpoints(['xs', 's']); const knobNames = Object.keys(state); + const columns = [ + { + field: 'prop', + name: 'Prop', + }, + { + field: 'type', + name: 'Type', + }, + ]; + + columns.push({ + field: isPlayground ? 'modify' : 'default', + name: isPlayground ? 'Modify' : 'Default value', + width: 200, + }); + return ( - - - {columns.map(({ name }, id) => { - return {name}; - })} - - - - - - + + + + {columns.map(({ name, width }, id) => { + return ( + + {name} + + ); + })} + + + + + + + ); }; diff --git a/src-docs/src/services/playground/playground.js b/src-docs/src/services/playground/playground.js index 58eb52e47be..dfd90dfe97f 100644 --- a/src-docs/src/services/playground/playground.js +++ b/src-docs/src/services/playground/playground.js @@ -3,10 +3,22 @@ import classNames from 'classnames'; import format from 'html-format'; import { useView, Compiler, Placeholder } from 'react-view'; -import { EuiSpacer, EuiTitle, EuiCodeBlock } from '../../../../src/components'; +import { + EuiSpacer, + EuiCodeBlock, + EuiErrorBoundary, + EuiTitle, +} from '../../../../src/components'; import Knobs from './knobs'; +import { GuideSectionExample } from '../../components/guide_section/guide_section_parts/guide_section_example'; -export default ({ config, setGhostBackground, playgroundClassName }) => { +export default ({ + config, + setGhostBackground, + playgroundClassName, + description, + tabs, +}) => { const getSnippet = (code) => { let regex = /return \(([\S\s]*?)(;)$/gm; let newCode = code.match(regex); @@ -53,37 +65,48 @@ export default ({ config, setGhostBackground, playgroundClassName }) => { } }, [params.knobProps]); - const compilerClasses = classNames( - 'playgroundCompiler', - { - playgroundCompiler__ghostBackground: isGhost, - }, - playgroundClassName - ); - return ( - - -

{config.componentName}

-
- -
- -
- - - - {getSnippet(params.editorProps.code)} - - - - - -
+ +
+ +
+ + + {getSnippet(params.editorProps.code)} + + + } + tabs={tabs} + tabContent={ + <> + {description ? ( + description + ) : ( +
+ +

{config.componentName}

+
+
+ )} + + + + + } + /> ); }; diff --git a/src-docs/src/views/accordion/accordion_example.js b/src-docs/src/views/accordion/accordion_example.js index af8b650f5fb..e33c4567188 100644 --- a/src-docs/src/views/accordion/accordion_example.js +++ b/src-docs/src/views/accordion/accordion_example.js @@ -5,12 +5,7 @@ import { renderToHtml } from '../../services'; import { GuideSectionTypes } from '../../components'; -import { - EuiAccordion, - EuiCode, - EuiCallOut, - EuiSpacer, -} from '../../../../src/components'; +import { EuiAccordion, EuiCode, EuiCallOut } from '../../../../src/components'; import { accordionConfig } from './playground'; @@ -146,8 +141,6 @@ export const AccordionExample = { groups.

- - ), sections: [ @@ -186,6 +179,7 @@ export const AccordionExample = { props: { EuiAccordion }, snippet: accordionSnippet, demo: , + playground: accordionConfig, }, { title: 'Arrow display', @@ -409,5 +403,4 @@ export const AccordionExample = { demo: , }, ], - playground: accordionConfig, }; diff --git a/src-docs/src/views/accordion/playground.js b/src-docs/src/views/accordion/playground.js index 8465d35bd5b..e20204bc1fd 100644 --- a/src-docs/src/views/accordion/playground.js +++ b/src-docs/src/views/accordion/playground.js @@ -1,5 +1,6 @@ import { PropTypes } from 'react-view'; import { EuiAccordion, EuiText } from '../../../../src/components/'; +import { htmlIdGenerator } from '../../../../src/services'; import { propUtilityForPlayground, createOptionalEnum, @@ -19,6 +20,11 @@ export const accordionConfig = () => { type: PropTypes.String, }; + propsToUse.id = { + ...propsToUse.id, + value: htmlIdGenerator('generated')(), + }; + propsToUse.children = { value: `

diff --git a/src-docs/src/views/beacon/beacon_example.js b/src-docs/src/views/beacon/beacon_example.js index c02abbb582f..67ae2b04598 100644 --- a/src-docs/src/views/beacon/beacon_example.js +++ b/src-docs/src/views/beacon/beacon_example.js @@ -38,7 +38,7 @@ export const BeaconExample = { props: { EuiBeacon }, snippet: beaconSnippet, demo: , + playground: beaconConfig, }, ], - playground: beaconConfig, }; diff --git a/src-docs/src/views/button/button.js b/src-docs/src/views/button/button.js index a36cf1fedf5..364cb9f628f 100644 --- a/src-docs/src/views/button/button.js +++ b/src-docs/src/views/button/button.js @@ -8,7 +8,7 @@ import { export default () => (

- + {}}>Primary @@ -32,7 +32,7 @@ export default () => ( - + {}}> Secondary @@ -58,7 +58,7 @@ export default () => ( - + {}}> Warning @@ -84,7 +84,7 @@ export default () => ( - + {}}> Danger @@ -110,7 +110,7 @@ export default () => ( - + {}}> Text @@ -136,7 +136,7 @@ export default () => ( - + {}}> Disabled diff --git a/src-docs/src/views/button/button_empty.js b/src-docs/src/views/button/button_empty.js index 16c1a7eb93b..01c0fbe4fd7 100644 --- a/src-docs/src/views/button/button_empty.js +++ b/src-docs/src/views/button/button_empty.js @@ -11,7 +11,7 @@ const buttons = ['primary', 'success', 'warning', 'danger', 'text', 'disabled']; export default () => (
{buttons.map((value) => ( - <> + ( - + ))} diff --git a/src-docs/src/views/button/button_example.js b/src-docs/src/views/button/button_example.js index 484c84e5bb0..71db8832282 100644 --- a/src-docs/src/views/button/button_example.js +++ b/src-docs/src/views/button/button_example.js @@ -11,7 +11,7 @@ import { EuiCode, EuiButtonGroup, EuiCallOut, - EuiText, + EuiTitle, } from '../../../../src/components'; import { EuiButtonGroupOptionProps } from '!!prop-loader!../../../../src/components/button/button_group/button_group'; @@ -141,6 +141,14 @@ const buttonToggleSnippet = [ import ButtonGroup from './button_group'; const buttonGroupSource = require('!!raw-loader!./button_group'); const buttonGroupHtml = renderToHtml(ButtonGroup); + +import ButtonGroupIcons from './button_group_icon'; +const buttonGroupIconsSource = require('!!raw-loader!./button_group_icon'); +const buttonGroupIconsHtml = renderToHtml(ButtonGroupIcons); + +import ButtonGroupCompressed from './button_group_compressed'; +const buttonGroupCompressedSource = require('!!raw-loader!./button_group_compressed'); +const buttonGroupCompressedHtml = renderToHtml(ButtonGroupCompressed); const buttonGroupSnippet = [ ` {}} />`, +]; +const buttonGroupIconsSnippet = [ ` -

- EuiButton comes in two styles. The{' '} - fill style should be reserved for the main action and - limited in number for a single page. Be sure to read the full{' '} - button usage guidelines. -

- - ), sections: [ { source: [ @@ -195,9 +195,18 @@ export const ButtonExample = { code: buttonHtml, }, ], + text: ( +

+ EuiButton comes in two styles. The{' '} + fill style should be reserved for the main action + and limited in number for a single page. Be sure to read the full{' '} + button usage guidelines. +

+ ), props: { EuiButton }, snippet: buttonSnippet, demo: