diff --git a/.changeset/poor-roses-hang.md b/.changeset/poor-roses-hang.md new file mode 100644 index 0000000000..028df6a7c6 --- /dev/null +++ b/.changeset/poor-roses-hang.md @@ -0,0 +1,7 @@ +--- +'@backstage/core-components': patch +--- + +Make the `options.titleFormat` prop of `` apply to all keys including nested ones. Previously, this option would only apply to the root keys of the `metadata` prop. + +Document and improve the props of ``. Previously, the `options` prop was `any`. diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index c76843e50e..d03d51f4fd 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -120,10 +120,10 @@ export type BottomLinkProps = { onClick?: (event: React_2.MouseEvent) => void; }; -// Warning: (ae-forgotten-export) The symbol "Props_19" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Props_18" needs to be exported by the entry point index.d.ts // // @public -export function Breadcrumbs(props: Props_19): React_2.JSX.Element; +export function Breadcrumbs(props: Props_18): React_2.JSX.Element; // @public (undocumented) export type BreadcrumbsClickableTextClassKey = 'root'; @@ -177,11 +177,11 @@ export interface CodeSnippetProps { text: string; } -// Warning: (ae-forgotten-export) The symbol "Props_13" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Props_12" needs to be exported by the entry point index.d.ts // // @public export function Content( - props: PropsWithChildren, + props: PropsWithChildren, ): React_2.JSX.Element; // Warning: (ae-forgotten-export) The symbol "ContentHeaderProps" needs to be exported by the entry point index.d.ts @@ -455,10 +455,10 @@ export function GitHubIcon(props: IconComponentProps): React_2.JSX.Element; // @public (undocumented) export function GroupIcon(props: IconComponentProps): React_2.JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props_14" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Props_13" needs to be exported by the entry point index.d.ts // // @public -export function Header(props: PropsWithChildren): React_2.JSX.Element; +export function Header(props: PropsWithChildren): React_2.JSX.Element; // @public (undocumented) export function HeaderActionMenu( @@ -565,10 +565,10 @@ export type IconLinkVerticalProps = { // @public (undocumented) export type IdentityProviders = ('guest' | 'custom' | SignInProviderConfig)[]; -// Warning: (ae-forgotten-export) The symbol "Props_15" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Props_14" needs to be exported by the entry point index.d.ts // // @public -export function InfoCard(props: Props_15): JSX.Element; +export function InfoCard(props: Props_14): JSX.Element; // @public (undocumented) export type InfoCardClassKey = @@ -796,11 +796,11 @@ export function OverflowTooltip(props: Props_9): React_2.JSX.Element; // @public (undocumented) export type OverflowTooltipClassKey = 'container'; -// Warning: (ae-forgotten-export) The symbol "Props_16" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Props_15" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Page" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function Page(props: Props_16): React_2.JSX.Element; +export function Page(props: Props_15): React_2.JSX.Element; // Warning: (ae-missing-release-tag) "PageClassKey" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1126,11 +1126,11 @@ export type SidebarSubmenuProps = { children: ReactNode; }; -// Warning: (ae-forgotten-export) The symbol "Props_17" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Props_16" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "SignInPage" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function SignInPage(props: Props_17): React_2.JSX.Element; +export function SignInPage(props: Props_16): React_2.JSX.Element; // Warning: (ae-missing-release-tag) "SignInPageClassKey" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1223,11 +1223,10 @@ export function StatusWarning( props: PropsWithChildren<{}>, ): React_2.JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props_12" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "StructuredMetadataTable" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export function StructuredMetadataTable(props: Props_12): React_2.JSX.Element; +export function StructuredMetadataTable( + props: StructuredMetadataTableProps, +): React_2.JSX.Element; // Warning: (ae-missing-release-tag) "StructuredMetadataTableListClassKey" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1239,6 +1238,20 @@ export type StructuredMetadataTableListClassKey = 'root'; // @public (undocumented) export type StructuredMetadataTableNestedListClassKey = 'root'; +// @public (undocumented) +export interface StructuredMetadataTableProps { + // (undocumented) + dense?: boolean; + // (undocumented) + metadata: { + [key: string]: any; + }; + // (undocumented) + options?: { + titleFormat?: (key: string) => string; + }; +} + // @public (undocumented) export type SubmenuOptions = { drawerWidthClosed?: number; @@ -1306,12 +1319,12 @@ export type Tab = { >; }; -// Warning: (ae-forgotten-export) The symbol "Props_18" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Props_17" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "TabbedCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export function TabbedCard( - props: PropsWithChildren, + props: PropsWithChildren, ): React_2.JSX.Element; // Warning: (ae-missing-release-tag) "TabbedCardClassKey" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx index f48e959073..17d01090a7 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx @@ -71,3 +71,19 @@ export const NotDenseTable = () => ( ); + +export const WithoutKeyFormatting = () => ( + + +
+ key }} + /> +
+
+
+); diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx index db7c53897d..4eb05e715e 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx @@ -115,4 +115,60 @@ describe('', () => { } }); }); + + describe('Title formatting', () => { + const metadata = { + testA: 'stuff', + testB: { testC: 'stuff' }, + testD: [{ testE: 'stuff' }], + }; + + it('should make keys human readable', async () => { + const rendered = render(); + expect(rendered.queryByText(/^Test A/)).toBeInTheDocument(); + expect(rendered.queryByText(/^Test B/)).toBeInTheDocument(); + expect(rendered.queryByText(/^Test C/)).toBeInTheDocument(); + expect(rendered.queryByText(/^Test D/)).toBeInTheDocument(); + expect(rendered.queryByText(/^Test E/)).toBeInTheDocument(); + }); + + it('should be possible to disable it', async () => { + const rendered = render( + key }} + />, + ); + expect(rendered.queryByText(/^testA/)).toBeInTheDocument(); + expect(rendered.queryByText(/^testB/)).toBeInTheDocument(); + expect(rendered.queryByText(/^testC/)).toBeInTheDocument(); + expect(rendered.queryByText(/^testD/)).toBeInTheDocument(); + expect(rendered.queryByText(/^testE/)).toBeInTheDocument(); + }); + + it('should be customizable', async () => { + const spongeBobCase = (key: string) => + key + .split('') + .map((letter, index) => { + if (index % 2 === 0) { + return letter.toLocaleLowerCase('en-US'); + } + return letter.toLocaleUpperCase('en-US'); + }) + .join(''); + + const rendered = render( + , + ); + expect(rendered.queryByText(/^tEsTa/)).toBeInTheDocument(); + expect(rendered.queryByText(/^tEsTb/)).toBeInTheDocument(); + expect(rendered.queryByText(/^tEsTc/)).toBeInTheDocument(); + expect(rendered.queryByText(/^tEsTd/)).toBeInTheDocument(); + expect(rendered.queryByText(/^tEsTe/)).toBeInTheDocument(); + }); + }); }); diff --git a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx index 2b18f6ad19..3099edf9d8 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx @@ -65,9 +65,11 @@ const StyledNestedList = withStyles(nestedListStyle, { {children} )); -function renderList(list: Array, nested?: boolean) { +function renderList(list: Array, options: Options, nested: boolean) { const values = list.map((item: any, index: number) => ( - {toValue(item)} + + {toValue(item, options, nested)} + )); return nested ? ( {values} @@ -78,19 +80,15 @@ function renderList(list: Array, nested?: boolean) { function renderMap( map: { [key: string]: any }, - nested?: boolean, - options?: any, + options: Options, + nested: boolean, ) { const values = Object.keys(map).map(key => { - const value = toValue(map[key], true); - const fmtKey = - options && options.titleFormat - ? options.titleFormat(key) - : startCase(key); + const value = toValue(map[key], options, true); return ( - {`${fmtKey}: `} + {`${options.titleFormat(key)}: `} {value} @@ -106,8 +104,8 @@ function renderMap( function toValue( value: ReactElement | object | Array | boolean, - options?: any, - nested?: boolean, + options: Options, + nested: boolean, ) { if (React.isValidElement(value)) { return {value}; @@ -118,7 +116,7 @@ function toValue( } if (Array.isArray(value)) { - return renderList(value, nested); + return renderList(value, options, nested); } if (typeof value === 'boolean') { @@ -131,8 +129,8 @@ function toValue( ); } -const ItemValue = ({ value, options }: { value: any; options: any }) => ( - {toValue(value, options)} +const ItemValue = ({ value, options }: { value: any; options: Options }) => ( + {toValue(value, options, false)} ); const TableItem = ({ @@ -142,35 +140,44 @@ const TableItem = ({ }: { title: string; value: any; - options: any; + options: Options; }) => { return ( - + ); }; -function mapToItems(info: { [key: string]: string }, options: any) { +function mapToItems(info: { [key: string]: string }, options: Options) { return Object.keys(info).map(key => ( )); } -type Props = { +/** @public */ +export interface StructuredMetadataTableProps { metadata: { [key: string]: any }; dense?: boolean; - options?: any; -}; + options?: { + /** + * Function to format the keys from the `metadata` object. Defaults to + * startCase from the lodash library. + * @param key - A key within the `metadata` + * @returns Formatted key + */ + titleFormat?: (key: string) => string; + }; +} -export function StructuredMetadataTable(props: Props) { - const { metadata, dense = true, options } = props; - const metadataItems = mapToItems(metadata, options || {}); +type Options = Required>; + +/** @public */ +export function StructuredMetadataTable(props: StructuredMetadataTableProps) { + const { metadata, dense = true, options = {} } = props; + const metadataItems = mapToItems(metadata, { + titleFormat: startCase, + ...options, + }); return {metadataItems}; } diff --git a/packages/core-components/src/components/StructuredMetadataTable/index.tsx b/packages/core-components/src/components/StructuredMetadataTable/index.tsx index 855df31e2f..e45b4a862f 100644 --- a/packages/core-components/src/components/StructuredMetadataTable/index.tsx +++ b/packages/core-components/src/components/StructuredMetadataTable/index.tsx @@ -20,7 +20,10 @@ export type { MetadataTableListClassKey, MetadataTableListItemClassKey, } from './MetadataTable'; -export { StructuredMetadataTable } from './StructuredMetadataTable'; +export { + StructuredMetadataTable, + type StructuredMetadataTableProps, +} from './StructuredMetadataTable'; export type { StructuredMetadataTableListClassKey, StructuredMetadataTableNestedListClassKey,