Skip to content

Commit

Permalink
feat: Add PropTable component to EUI docusaurus theme (#7932)
Browse files Browse the repository at this point in the history
  • Loading branch information
tkajtoch authored Jul 30, 2024
1 parent a2935a8 commit 1ff2db5
Show file tree
Hide file tree
Showing 10 changed files with 832 additions and 30 deletions.
599 changes: 599 additions & 0 deletions .yarn/patches/infima-npm-0.2.0-alpha.43-8d3b77b44d.patch

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"react-focus-lock": "2.9.5",
"@babel/core": "^7.21.8",
"jsdom": "24.1.0",
"@types/jsdom@npm:^20.0.0": "patch:@types/jsdom@npm%3A20.0.1#~/.yarn/patches/@types-jsdom-npm-20.0.1-5bb899e006.patch"
"@types/jsdom@npm:^20.0.0": "patch:@types/jsdom@npm%3A20.0.1#~/.yarn/patches/@types-jsdom-npm-20.0.1-5bb899e006.patch",
"infima@npm:0.2.0-alpha.43": "patch:infima@npm%3A0.2.0-alpha.43#~/.yarn/patches/infima-npm-0.2.0-alpha.43-8d3b77b44d.patch"
},
"packageManager": "[email protected]"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Fragment, useMemo } from 'react';
import { EuiLink, EuiText } from '@elastic/eui';
import { ProcessedComponent } from '@elastic/eui-docgen';
import { extendedTypesInfo } from './extended_types_info';

export interface PropTableExtendedTypesProps {
definition: ProcessedComponent;
}

export const PropTableExtendedTypes = ({ definition }: PropTableExtendedTypesProps) => {
const extendedTypes = useMemo(() => {
const types = definition.extends.filter(
(type) => extendedTypesInfo.hasOwnProperty(type.displayName),
);

if (types.every((type) => type.displayName.indexOf('HTMLAttributes') > -1)) {
const htmlAttributesIndex = types.findIndex((type) => type.displayName === 'HTMLAttributes');
if (htmlAttributesIndex > -1 && types.length > 1) {
types.splice(htmlAttributesIndex, 1);
}
}

return types;
}, [definition.extends]);

if (!extendedTypes.length) {
return null;
}

return (
<EuiText size="s">
Extends{' '}
{extendedTypes.map((type, index) => (
<Fragment key={index}>
<EuiLink href={extendedTypesInfo[type.displayName as keyof typeof extendedTypesInfo].url}>
{type.displayName}
</EuiLink>
{extendedTypes.length - 1 > index && ', '}
</Fragment>
))}
</EuiText>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const extendedTypesInfo = {
// HTMLAttributes is removed from display if any of the following elements also exist
HTMLAttributes: {
name: 'HTMLElement',
url: 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement',
},
SelectHTMLAttributes: {
name: 'HTMLSelectElement',
url: 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement',
},
TextareaHTMLAttributes: {
name: 'HTMLTextAreaElement',
url: 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement',
},
InputHTMLAttributes: {
name: 'HTMLInputElement',
url: 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement',
},
AnchorHTMLAttributes: {
name: 'HTMLAnchorElement',
url: 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement',
},
ButtonHTMLAttributes: {
name: 'HTMLButtonElement',
url: 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLButtonElement',
},
};
151 changes: 126 additions & 25 deletions packages/docusaurus-theme/src/components/prop_table/prop_table.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,86 @@
import { EuiBasicTable, EuiMarkdownFormat, EuiBasicTableColumn, EuiTextColor, EuiCode } from '@elastic/eui';
import {
EuiBasicTable,
EuiMarkdownFormat,
EuiBasicTableColumn,
EuiTextColor,
EuiFlexGroup,
EuiCode,
UseEuiTheme,
EuiTitle,
useEuiMemoizedStyles, EuiLink,
} from '@elastic/eui';
import { ProcessedComponent, ProcessedComponentProp } from '@elastic/eui-docgen';
import { useMemo } from 'react';
import { useCallback, useMemo } from 'react';
import { css } from '@emotion/react';
import { PropTableExtendedTypes } from './extended_types';

export interface PropTableProps {
definition: ProcessedComponent;
propHeadingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
}

const getPropId = (prop: ProcessedComponentProp, componentName: string) => (
`${encodeURIComponent(componentName)}-prop-${prop.name}`
);

const getPropTableStyles = ({ euiTheme }: UseEuiTheme) => ({
propTable: css`
margin-block: ${euiTheme.size.xl};
`,
header: css`
// Increased specificity is needed here to override default
// content heading styles
&& h1,
&& h2,
&& h3,
&& h4,
&& h5,
&& h6 {
margin-block: 0;
}
`,
table: css`
vertical-align: top;
`,
propName: css`
font-family: ${euiTheme.font.familyCode};
font-weight: ${euiTheme.font.weight.semiBold};
`,
description: css`
p:first-child {
margin-block-start: 0;
}
`,
required: css`
font-family: ${euiTheme.font.familyCode};
color: ${euiTheme.colors.dangerText};
`,
type: css`
font-weight: ${euiTheme.font.weight.semiBold};
`,
tableNameLink: css`
display: none;
margin-inline-start: ${euiTheme.size.xs};
`,
tableRow: css`
scroll-margin-block-start: calc(var(--ifm-navbar-height) + 0.5rem);
&:hover .propLink {
display: inline-block;
}
`,
});

export const PropTable = ({
definition,
propHeadingLevel: PropHeadingLevel = 'h3',
headingLevel: HeadingLevel = 'h3',
}: PropTableProps) => {
const styles = useEuiMemoizedStyles(getPropTableStyles);

const tableItems = useMemo<Array<ProcessedComponentProp>>(
() => Object.values(definition.props),
() => Object.values(definition.props).sort(
(a, b) => +b.isRequired - +a.isRequired
),
[definition.props],
);

Expand All @@ -21,53 +89,86 @@ export const PropTable = ({
{
field: 'name',
name: 'Prop',
render(value: ProcessedComponentProp['name']) {
width: "150",
render(value: ProcessedComponentProp['name'], prop: ProcessedComponentProp) {
return (
<PropHeadingLevel>{value}</PropHeadingLevel>
<span css={styles.propName}>
{value}
<EuiLink
href={`#${getPropId(prop, definition.displayName)}`}
css={styles.tableNameLink}
className="propLink"
aria-label={`Direct link to the ${prop.name} prop`}
title={`Direct link to the ${prop.name} prop`}
>
#
</EuiLink>
</span>
);
},
},
{
field: 'description',
name: 'Description and Type',
name: 'Description and type',
render(value: ProcessedComponentProp['description'], prop: ProcessedComponentProp) {
return (
<div>
<EuiFlexGroup direction="column" alignItems="flexStart" gutterSize="s">
{value?.trim() && (
<EuiMarkdownFormat>{value}</EuiMarkdownFormat>
<EuiMarkdownFormat css={styles.description}>{value}</EuiMarkdownFormat>
)}
{prop.type && (
<>
Type: <EuiCode>{prop.type.name}</EuiCode>
</>
<span css={styles.type}>
Type: {' '}
<EuiCode language="ts">
{prop.type.raw || prop.type.name}
</EuiCode>
</span>
)}
</div>
</EuiFlexGroup>
);
}
},
{
field: 'defaultValue',
name: 'Default value',
width: "120",
render(value: ProcessedComponentProp['defaultValue'], prop: ProcessedComponentProp) {
if (prop.isRequired) {
return <EuiTextColor>Required</EuiTextColor>
if (prop.isRequired && !value?.trim().length) {
return <EuiTextColor css={styles.required}>Required</EuiTextColor>
}

const finalValue = (!!value && typeof value === 'object' &&
(value as object).hasOwnProperty('value'))
? (value as { value: string; }).value
: '';

return !!finalValue && <EuiCode>{finalValue}</EuiCode>;
return value && <EuiCode>{value}</EuiCode>;
},
}
]),
[],
);

const rowProps = useCallback((item: ProcessedComponentProp) => ({
id: getPropId(item, definition.displayName),
css: styles.tableRow,
}), [definition.displayName]);

return (
<div>
<EuiBasicTable width="100%" items={tableItems} columns={columns} compressed />
</div>
<EuiFlexGroup
aria-label={`Component properties table for ${definition.displayName}`}
gutterSize="s"
direction="column"
css={styles.propTable}
>
<header css={styles.header}>
<EuiTitle size="m">
<HeadingLevel>{definition.displayName}</HeadingLevel>
</EuiTitle>
<PropTableExtendedTypes definition={definition} />
</header>
<EuiBasicTable
css={styles.table}
width="100%"
items={tableItems}
columns={columns}
rowProps={rowProps}
/>
</EuiFlexGroup>
);
};
24 changes: 23 additions & 1 deletion packages/docusaurus-theme/src/theme/DocRoot/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { css } from '@emotion/react';
import { useDocsSidebar } from '@docusaurus/theme-common/internal';
import useIsBrowser from '@docusaurus/useIsBrowser';
import BackToTopButton from '@theme-original/BackToTopButton';
import type { Props } from '@theme-original/DocRoot/Layout';
import DocRootLayoutSidebar from '@theme-original/DocRoot/Layout/Sidebar';
Expand All @@ -20,8 +21,29 @@ const styles = {
};

export default function DocRootLayout({ children }: Props): JSX.Element {
const isBrowser = useIsBrowser();
const sidebar = useDocsSidebar();
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);

// Replicate browser hash scroll behavior to trigger it after the MDX content
// is rendered. Timeout = 0 should do the job here just fine as the effect
// will get executed at next render cycle when all elements are (hopefully)
// already in the DOM.
useEffect(() => {
if (!isBrowser) {
return;
}

if (window.location.hash) {
setTimeout(() => {
const element = document.getElementById(
window.location.hash.substring(1),
);
element?.scrollIntoView(true);
}, 0);
}
}, [isBrowser]);

return (
<div css={styles.docsWrapper}>
<BackToTopButton />
Expand Down
2 changes: 1 addition & 1 deletion packages/eui-docgen/src/process_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface ProcessedComponent {
/**
* Component props
*/
props: Record<string, any>;
props: Record<string, ProcessedComponentProp>;
/**
* A list of all types the props type is extending from
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ yarn
Before you run scripts, it's mandatory to build all local dependency packages:

```shell
yarn workspaces foreach -Rpt --from @elastic/eui-website run build
yarn workspaces foreach -Rpti --from @elastic/eui-website run build
```

### Running the development server
Expand Down
4 changes: 3 additions & 1 deletion packages/website/docs/docgen_demo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import ButtonDocgen from '@elastic/eui-docgen/dist/components/button/button.json';

## EuiButton props
import BasicTableDocgen from '@elastic/eui-docgen/dist/components/basic_table/basic_table.json';

<PropTable definition={ButtonDocgen.EuiButton} />

<PropTable definition={BasicTableDocgen.EuiBasicTable} />
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -22082,6 +22082,13 @@ __metadata:
languageName: node
linkType: hard

"infima@patch:infima@npm%3A0.2.0-alpha.43#~/.yarn/patches/infima-npm-0.2.0-alpha.43-8d3b77b44d.patch":
version: 0.2.0-alpha.43
resolution: "infima@patch:infima@npm%3A0.2.0-alpha.43#~/.yarn/patches/infima-npm-0.2.0-alpha.43-8d3b77b44d.patch::version=0.2.0-alpha.43&hash=4d36f6"
checksum: 10c0/e1f7599330df8fc7d3eca0605eda161c31891dc2260247b852eee031f2f005d392e50041f307a5910188e7528beacc7a2be9bfa434516ec5d095d338f1cd69ad
languageName: node
linkType: hard

"inflight@npm:^1.0.4":
version: 1.0.6
resolution: "inflight@npm:1.0.6"
Expand Down

0 comments on commit 1ff2db5

Please sign in to comment.