diff --git a/.changeset/deep-moments-love.md b/.changeset/deep-moments-love.md new file mode 100644 index 0000000000..89b3174275 --- /dev/null +++ b/.changeset/deep-moments-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Add min-width: 0; by default on every Flex components in Canon to help support truncated texts inside flex elements. diff --git a/.changeset/forty-seas-worry.md b/.changeset/forty-seas-worry.md new file mode 100644 index 0000000000..a88c15df53 --- /dev/null +++ b/.changeset/forty-seas-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Fix styling for the title4 prop on the Heading component in Canon. diff --git a/.changeset/itchy-mirrors-juggle.md b/.changeset/itchy-mirrors-juggle.md new file mode 100644 index 0000000000..6fd7c7543a --- /dev/null +++ b/.changeset/itchy-mirrors-juggle.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications': patch +'@backstage/plugin-scaffolder': patch +--- + +Remove React import form notification and scaffolder plugin diff --git a/.changeset/kind-houses-feel.md b/.changeset/kind-houses-feel.md new file mode 100644 index 0000000000..a4b3ac0d14 --- /dev/null +++ b/.changeset/kind-houses-feel.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Added a render prop to the Button component in Canon to use it as a link. diff --git a/.changeset/purple-times-deny.md b/.changeset/purple-times-deny.md new file mode 100644 index 0000000000..7cf4d2c3c3 --- /dev/null +++ b/.changeset/purple-times-deny.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added appropriate message when global templating function metadata is absent. diff --git a/.changeset/spicy-rivers-notice.md b/.changeset/spicy-rivers-notice.md new file mode 100644 index 0000000000..941f2d257d --- /dev/null +++ b/.changeset/spicy-rivers-notice.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Fixed the `WelcomeTitle` to properly default to the previous value of `inherit` diff --git a/.changeset/stupid-goats-teach.md b/.changeset/stupid-goats-teach.md new file mode 100644 index 0000000000..f90d9373e7 --- /dev/null +++ b/.changeset/stupid-goats-teach.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added `backstage.pluginId` field in `package.json` to all default plugin package templates for the `new` command. diff --git a/canon-docs/src/app/(docs)/components/button/page.mdx b/canon-docs/src/app/(docs)/components/button/page.mdx index e1ecee8920..3554ee39f9 100644 --- a/canon-docs/src/app/(docs)/components/button/page.mdx +++ b/canon-docs/src/app/(docs)/components/button/page.mdx @@ -125,6 +125,20 @@ Here's a view when buttons are disabled. code={``} /> +### As Link + +Here's a view when buttons are rendered as a link. + +} + code={``} +/> + ### Responsive Here's a view when buttons are responsive. diff --git a/canon-docs/src/app/(docs)/components/button/props.ts b/canon-docs/src/app/(docs)/components/button/props.ts index 813dcaa6f0..36b0f73066 100644 --- a/canon-docs/src/app/(docs)/components/button/props.ts +++ b/canon-docs/src/app/(docs)/components/button/props.ts @@ -14,6 +14,12 @@ export const buttonPropDefs: Record = { default: 'medium', responsive: true, }, + render: { + type: 'enum', + values: ['ReactNode'], + responsive: false, + default: ' - ); + const { renderElement } = useRender({ + render, + props: { + className: clsx('canon-Button', className), + ['data-variant']: responsiveVariant, + ['data-size']: responsiveSize, + ...rest, + children: ( + <> + {iconStart && ( + + )} + {children} + {iconEnd && ( + + )} + + ), + }, + refs: [ref, internalRef], + }); + + return renderElement(); }, ); diff --git a/packages/canon/src/components/Button/types.ts b/packages/canon/src/components/Button/types.ts index 4ab62d5683..e91538299e 100644 --- a/packages/canon/src/components/Button/types.ts +++ b/packages/canon/src/components/Button/types.ts @@ -16,14 +16,14 @@ import type { ButtonOwnProps } from './Button.props'; import { ReactElement } from 'react'; +import type { useRender } from '@base-ui-components/react/use-render'; /** * Properties for {@link Button} * * @public */ -export interface ButtonProps - extends Omit, 'children'> { +export interface ButtonProps extends useRender.ComponentProps<'button'> { /** * The size of the button * @defaultValue 'medium' @@ -36,11 +36,6 @@ export interface ButtonProps */ variant?: ButtonOwnProps['variant']; - /** - * The content of the button - */ - children: React.ReactNode; - /** * Optional icon to display at the start of the button */ diff --git a/packages/canon/src/components/Flex/Flex.stories.tsx b/packages/canon/src/components/Flex/Flex.stories.tsx index 91b8dc8fb6..30c527a117 100644 --- a/packages/canon/src/components/Flex/Flex.stories.tsx +++ b/packages/canon/src/components/Flex/Flex.stories.tsx @@ -16,6 +16,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { Flex } from './Flex'; +import { Text } from '../Text'; const meta = { title: 'Layout/Flex', @@ -143,3 +144,24 @@ export const LargeGap: Story = { ), }; + +export const WithTextTruncate: Story = { + render: () => ( + + + + A man looks at a painting in a museum and says, “Brothers and sisters + I have none, but that man's father is my father's son.” Who + is in the painting? + + + + + A man looks at a painting in a museum and says, “Brothers and sisters + I have none, but that man's father is my father's son.” Who + is in the painting? + + + + ), +}; diff --git a/packages/canon/src/components/Flex/styles.css b/packages/canon/src/components/Flex/styles.css index 094edffca7..61fb53d257 100644 --- a/packages/canon/src/components/Flex/styles.css +++ b/packages/canon/src/components/Flex/styles.css @@ -16,4 +16,7 @@ .canon-Flex { display: flex; + + /* This helps when using `truncate` on text inside a flex container */ + min-width: 0; } diff --git a/packages/canon/src/components/Heading/Heading.stories.tsx b/packages/canon/src/components/Heading/Heading.stories.tsx index bb241b5aae..956118ddeb 100644 --- a/packages/canon/src/components/Heading/Heading.stories.tsx +++ b/packages/canon/src/components/Heading/Heading.stories.tsx @@ -46,6 +46,7 @@ export const AllVariants: Story = { Title 2 Title 3 Title 4 + Title 5 ), }; diff --git a/packages/canon/src/components/Heading/styles.css b/packages/canon/src/components/Heading/styles.css index 1644da09df..ce3da94f7f 100644 --- a/packages/canon/src/components/Heading/styles.css +++ b/packages/canon/src/components/Heading/styles.css @@ -43,7 +43,7 @@ } .canon-Heading[data-variant='title4'] { - font-size: var(--canon-font-size-title4); + font-size: var(--canon-font-size-6); font-weight: var(--canon-font-weight-bold); } diff --git a/packages/cli/templates/backend-plugin-module/package.json.hbs b/packages/cli/templates/backend-plugin-module/package.json.hbs index 20d78380ca..c34f7e0646 100644 --- a/packages/cli/templates/backend-plugin-module/package.json.hbs +++ b/packages/cli/templates/backend-plugin-module/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "backend-plugin-module" + "role": "backend-plugin-module", + "pluginId": "{{pluginId}}" }, "scripts": { "start": "backstage-cli package start", diff --git a/packages/cli/templates/backend-plugin/package.json.hbs b/packages/cli/templates/backend-plugin/package.json.hbs index ad3810dff3..d8fcb9a9d6 100644 --- a/packages/cli/templates/backend-plugin/package.json.hbs +++ b/packages/cli/templates/backend-plugin/package.json.hbs @@ -8,7 +8,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "backend-plugin" + "role": "backend-plugin", + "pluginId": "{{pluginId}}" }, "scripts": { "start": "backstage-cli package start", diff --git a/packages/cli/templates/frontend-plugin/package.json.hbs b/packages/cli/templates/frontend-plugin/package.json.hbs index 64888c2d37..1ee8231228 100644 --- a/packages/cli/templates/frontend-plugin/package.json.hbs +++ b/packages/cli/templates/frontend-plugin/package.json.hbs @@ -8,7 +8,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "frontend-plugin" + "role": "frontend-plugin", + "pluginId": "{{pluginId}}" }, "sideEffects": false, "scripts": { diff --git a/packages/cli/templates/plugin-common-library/package.json.hbs b/packages/cli/templates/plugin-common-library/package.json.hbs index 085ccb40dd..117679abc1 100644 --- a/packages/cli/templates/plugin-common-library/package.json.hbs +++ b/packages/cli/templates/plugin-common-library/package.json.hbs @@ -10,7 +10,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "common-library" + "role": "common-library", + "pluginId": "{{pluginId}}" }, "sideEffects": false, "scripts": { diff --git a/packages/cli/templates/plugin-node-library/package.json.hbs b/packages/cli/templates/plugin-node-library/package.json.hbs index 182ef1c93f..876d6414ac 100644 --- a/packages/cli/templates/plugin-node-library/package.json.hbs +++ b/packages/cli/templates/plugin-node-library/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "node-library" + "role": "node-library", + "pluginId": "{{pluginId}}" }, "scripts": { "build": "backstage-cli package build", diff --git a/packages/cli/templates/plugin-web-library/package.json.hbs b/packages/cli/templates/plugin-web-library/package.json.hbs index d104a29305..6924600989 100644 --- a/packages/cli/templates/plugin-web-library/package.json.hbs +++ b/packages/cli/templates/plugin-web-library/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "web-library" + "role": "web-library", + "pluginId": "{{pluginId}}" }, "sideEffects": false, "scripts": { diff --git a/packages/cli/templates/scaffolder-backend-module/package.json.hbs b/packages/cli/templates/scaffolder-backend-module/package.json.hbs index 3866a83672..36c99c7687 100644 --- a/packages/cli/templates/scaffolder-backend-module/package.json.hbs +++ b/packages/cli/templates/scaffolder-backend-module/package.json.hbs @@ -9,7 +9,8 @@ "types": "dist/index.d.ts" }, "backstage": { - "role": "backend-plugin-module" + "role": "backend-plugin-module", + "pluginId": "scaffolder" }, "scripts": { "start": "backstage-cli package start", diff --git a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx index fddd621b92..ee2e8704bd 100644 --- a/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx +++ b/plugins/home/src/homePageComponents/WelcomeTitle/WelcomeTitle.tsx @@ -33,7 +33,7 @@ export type WelcomeTitleLanguageProps = { export const WelcomeTitle = ({ language, - variant, + variant = 'inherit', }: WelcomeTitleLanguageProps) => { const identityApi = useApi(identityApiRef); const alertApi = useApi(alertApiRef); diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx index d8d347c202..f4f6c61493 100644 --- a/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/OriginRow.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import React from 'react'; import { ChannelSetting, isNotificationsEnabledFor, diff --git a/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx b/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx index 5c9c3cbacc..ed2ca8fd05 100644 --- a/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx +++ b/plugins/notifications/src/components/UserNotificationSettingsCard/TopicRow.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import React from 'react'; import { isNotificationsEnabledFor, NotificationSettings, diff --git a/plugins/scaffolder/report-alpha.api.md b/plugins/scaffolder/report-alpha.api.md index 54acadd561..509cfa8497 100644 --- a/plugins/scaffolder/report-alpha.api.md +++ b/plugins/scaffolder/report-alpha.api.md @@ -430,6 +430,7 @@ export const scaffolderTranslationRef: TranslationRef< readonly 'templatingExtensions.content.functions.schema.arguments': 'Arguments'; readonly 'templatingExtensions.content.functions.examples': 'Examples'; readonly 'templatingExtensions.content.functions.notAvailable': 'There are no global template functions defined.'; + readonly 'templatingExtensions.content.functions.metadataAbsent': 'Function metadata unavailable'; readonly 'templatingExtensions.title': 'Templating Extensions'; readonly 'templatingExtensions.subtitle': 'This is the collection of available templating extensions'; readonly 'templatingExtensions.pageTitle': 'Templating Extensions'; diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx index e0c80c27cf..05f3043dd1 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateFilters.tsx @@ -26,7 +26,7 @@ import { ClassNameMap } from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import classNames from 'classnames'; -import React, { ReactElement, useState } from 'react'; +import { cloneElement, Fragment, ReactElement, useState } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { Expanded, RenderSchema, SchemaRenderContext } from '../RenderSchema'; import { ScaffolderUsageExamplesTable } from '../ScaffolderUsageExamplesTable'; @@ -60,7 +60,7 @@ const FilterDetailContent = ({ headings: [], }; return ( - + {filter.description && } @@ -84,7 +84,7 @@ const FilterDetailContent = ({ const [argSchema, required] = inspectFunctionArgSchema(arg); return ( - +
@@ -101,7 +101,7 @@ const FilterDetailContent = ({ }} schema={argSchema} /> - + ); })} @@ -133,7 +133,7 @@ const FilterDetailContent = ({ )} - + ); }; @@ -178,7 +178,7 @@ export const TemplateFilters = ({ > {name} - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx index 329f7b9742..249e2a581d 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplateGlobals.tsx @@ -26,7 +26,7 @@ import { ClassNameMap } from '@material-ui/core/styles/withStyles'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import classNames from 'classnames'; -import React, { ReactElement, useState } from 'react'; +import { cloneElement, Fragment, ReactElement, useState } from 'react'; import { scaffolderTranslationRef } from '../../translation'; import { Expanded, RenderSchema, SchemaRenderContext } from '../RenderSchema'; import { ScaffolderUsageExamplesTable } from '../ScaffolderUsageExamplesTable'; @@ -48,8 +48,11 @@ const FunctionDetailContent = ({ const expanded = useState({}); if (!Object.keys(fn).length) { return ( - - {t('templatingExtensions.content.functions.notAvailable')} + + {t('templatingExtensions.content.functions.metadataAbsent')} ); } @@ -60,7 +63,7 @@ const FunctionDetailContent = ({ headings: [], }; return ( - + {fn.description && } {schema?.arguments?.length && ( @@ -71,7 +74,7 @@ const FunctionDetailContent = ({ const [argSchema, required] = inspectFunctionArgSchema(arg); return ( - +
@@ -89,7 +92,7 @@ const FunctionDetailContent = ({ }} schema={argSchema} /> - + ); })} @@ -121,7 +124,7 @@ const FunctionDetailContent = ({ )} - + ); }; @@ -166,7 +169,7 @@ export const TemplateGlobalFunctions = ({ > {name} - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} @@ -218,7 +221,7 @@ export const TemplateGlobalValues = ({ > {name} - {React.cloneElement(baseLink, { + {cloneElement(baseLink, { to: `${baseLink.props.to}#${fragment}`, })} {gv.description && } diff --git a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx index d3d0eb7370..2c0680b5f8 100644 --- a/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatingExtensionsPage/TemplatingExtensionsPage.test.tsx @@ -315,76 +315,112 @@ describe('TemplatingExtensionsPage', () => { }); }); describe('renders global', () => { - it('renders global functions', async () => { - listTemplatingExtensions.mockResolvedValue({ - ...emptyExtensions, - globals: { - ...emptyExtensions.globals, - functions: { - truthy: { - description: 'evaluate truthiness', - schema: { - arguments: [ - { - title: 'input', - }, - ], - output: { - type: 'boolean', - }, - }, - examples: [ - { - description: 'basic usage', - example: "truthy('foo')", - notes: 'yields `true`', - }, - ], + describe('renders global functions', () => { + it('without metadata', async () => { + listTemplatingExtensions.mockResolvedValue({ + ...emptyExtensions, + globals: { + ...emptyExtensions.globals, + functions: { + anything: {}, }, }, - }, + }); + const { findByTestId, getByRole } = await r(); + + fireEvent.click(within(getByRole('tablist')).getByText('Functions')); + + const functions = await findByTestId('functions'); + + const anything = within(functions).getByTestId('anything'); + const title = within(anything).getByText('anything'); + expect(title).toBeInTheDocument(); + expect(title.id).toBe('function_anything'); + + const link = within(anything).getByRole('link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute( + 'href', + expect.stringMatching(new RegExp(`#${title.id}$`)), + ); + expect( + within(anything).getByTestId('anything.metadataAbsent'), + ).toBeInTheDocument(); }); - const { findByTestId, getByRole } = await r(); + it('with metadata', async () => { + listTemplatingExtensions.mockResolvedValue({ + ...emptyExtensions, + globals: { + ...emptyExtensions.globals, + functions: { + truthy: { + description: 'evaluate truthiness', + schema: { + arguments: [ + { + title: 'input', + }, + ], + output: { + type: 'boolean', + }, + }, + examples: [ + { + description: 'basic usage', + example: "truthy('foo')", + notes: 'yields `true`', + }, + ], + }, + }, + }, + }); + const { findByTestId, getByRole } = await r(); - fireEvent.click(within(getByRole('tablist')).getByText('Functions')); + fireEvent.click(within(getByRole('tablist')).getByText('Functions')); - const functions = await findByTestId('functions'); + const functions = await findByTestId('functions'); - const truthy = within(functions).getByTestId('truthy'); - const title = within(truthy).getByText('truthy'); - expect(title).toBeInTheDocument(); - expect(title.id).toBe('function_truthy'); + const truthy = within(functions).getByTestId('truthy'); + const title = within(truthy).getByText('truthy'); + expect(title).toBeInTheDocument(); + expect(title.id).toBe('function_truthy'); - const link = within(truthy).getByRole('link'); - expect(link).toBeInTheDocument(); - expect(link).toHaveAttribute( - 'href', - expect.stringMatching(new RegExp(`#${title.id}$`)), - ); + const link = within(truthy).getByRole('link'); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute( + 'href', + expect.stringMatching(new RegExp(`#${title.id}$`)), + ); + expect( + within(truthy).queryByTestId('truthy.metadataAbsent'), + ).not.toBeInTheDocument(); - expect( - within(truthy).getByText('evaluate truthiness'), - ).toBeInTheDocument(); + expect( + within(truthy).getByText('evaluate truthiness'), + ).toBeInTheDocument(); - expect(within(truthy).getByText('[0]')).toBeInTheDocument(); - expect( - within(truthy).getByTestId('root_truthy.arg0'), - ).toBeInTheDocument(); - expect( - within(truthy).queryByTestId('root_truthy.arg1'), - ).not.toBeInTheDocument(); - expect( - within(truthy).getByTestId('root_truthy.output'), - ).toBeInTheDocument(); + expect(within(truthy).getByText('[0]')).toBeInTheDocument(); + expect( + within(truthy).getByTestId('root_truthy.arg0'), + ).toBeInTheDocument(); + expect( + within(truthy).queryByTestId('root_truthy.arg1'), + ).not.toBeInTheDocument(); + expect( + within(truthy).getByTestId('root_truthy.output'), + ).toBeInTheDocument(); - const x = within(truthy).getByTestId('examples'); - expect(x).toBeInTheDocument(); - const xd0 = within(x).getByTestId('example_desc0'); - expect(xd0).toBeInTheDocument(); - expect(xd0).toHaveTextContent(/basic usage\s*yields\s*true/); + const x = within(truthy).getByTestId('examples'); + expect(x).toBeInTheDocument(); + const xd0 = within(x).getByTestId('example_desc0'); + expect(xd0).toBeInTheDocument(); + expect(xd0).toHaveTextContent(/basic usage\s*yields\s*true/); - const xc0 = within(x).getByTestId('example_code0'); - expect(within(xc0).getByText("truthy('foo')")).toBeInTheDocument(); + const xc0 = within(x).getByTestId('example_code0'); + expect(within(xc0).getByText("truthy('foo')")).toBeInTheDocument(); + }); }); it('renders global values', async () => { const msvValue = ['foo', 'bar', 'baz']; diff --git a/plugins/scaffolder/src/translation.ts b/plugins/scaffolder/src/translation.ts index fc0f0a6889..78ca15e2ff 100644 --- a/plugins/scaffolder/src/translation.ts +++ b/plugins/scaffolder/src/translation.ts @@ -226,6 +226,7 @@ export const scaffolderTranslationRef = createTranslationRef({ functions: { title: 'Functions', notAvailable: 'There are no global template functions defined.', + metadataAbsent: 'Function metadata unavailable', schema: { arguments: 'Arguments', output: 'Output',