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,