diff --git a/.changeset/beige-chairs-suffer.md b/.changeset/beige-chairs-suffer.md new file mode 100644 index 0000000000..c642b70cbb --- /dev/null +++ b/.changeset/beige-chairs-suffer.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': minor +--- + +`SimpleStepper` back button now works with `activeStep` property set higher than 0 diff --git a/.changeset/metal-crabs-agree.md b/.changeset/metal-crabs-agree.md new file mode 100644 index 0000000000..c5a2d1102d --- /dev/null +++ b/.changeset/metal-crabs-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': minor +--- + +Declared CancelIcon explicitly on Chip component inside Select.tsx to disable onMouseDown event by default that creates the flaw of re-opening select component when user tries to remove a selected filter. diff --git a/canon-docs/src/app/(docs)/page.mdx b/canon-docs/src/app/(docs)/page.mdx index b2abdefb12..862127ec7b 100644 --- a/canon-docs/src/app/(docs)/page.mdx +++ b/canon-docs/src/app/(docs)/page.mdx @@ -24,8 +24,7 @@ Install Canon using a package manager. Import the global CSS file at the root of your application. ```tsx -import '@backstage/canon/css/core.css'; -import '@backstage/canon/css/components.css'; +import '@backstage/canon/css/styles.css'; ``` ### 3. Start building ✨ diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 5a9044842f..9444dc9b6e 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -92,10 +92,11 @@ the source code hosting provider. Notice that instead of the `dir:` prefix, the Note, just as it's possible to specify a subdirectory with the `dir:` prefix, you can also provide a path to a non-root directory inside the repository which -contains the `mkdocs.yml` file and `docs/` directory. +contains the `mkdocs.yml` file and `docs/` directory. It is important that it is +suffixed with a '/' in order for relative path resolution to work consistently. e.g. -`url:https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/examples/documented-component` +`url:https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/examples/documented-component/` ### Why is URL Reader faster than a git clone? diff --git a/packages/canon/.gitignore b/packages/canon/.gitignore index e7c52ff306..b200b44847 100644 --- a/packages/canon/.gitignore +++ b/packages/canon/.gitignore @@ -2,4 +2,4 @@ storybook-static # CSS output -css +/css diff --git a/packages/canon/.storybook/preview.tsx b/packages/canon/.storybook/preview.tsx index e278bb011f..b1bfa2dc88 100644 --- a/packages/canon/.storybook/preview.tsx +++ b/packages/canon/.storybook/preview.tsx @@ -1,10 +1,7 @@ import React from 'react'; import type { Preview, ReactRenderer } from '@storybook/react'; import { withThemeByDataAttribute } from '@storybook/addon-themes'; - -// Canon specific styles -import '../src/css/core.css'; -import '../src/css/components.css'; +import '../src/css/styles.css'; const preview: Preview = { parameters: { diff --git a/packages/canon/scripts/build-css.mjs b/packages/canon/scripts/build-css.mjs index b2782e090e..80f566ac7e 100644 --- a/packages/canon/scripts/build-css.mjs +++ b/packages/canon/scripts/build-css.mjs @@ -31,6 +31,7 @@ const componentsDir = 'src/components'; const cssFiles = [ { path: `${cssDir}/core.css`, newName: 'core.css' }, { path: `${cssDir}/components.css`, newName: 'components.css' }, + { path: `${cssDir}/styles.css`, newName: 'styles.css' }, ]; // Components files diff --git a/packages/canon/src/css/styles.css b/packages/canon/src/css/styles.css new file mode 100644 index 0000000000..f5d623389f --- /dev/null +++ b/packages/canon/src/css/styles.css @@ -0,0 +1,18 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import './core.css'; +@import './components.css'; diff --git a/packages/core-components/src/components/Select/Select.test.tsx b/packages/core-components/src/components/Select/Select.test.tsx index b83ac68d5b..505152db5f 100644 --- a/packages/core-components/src/components/Select/Select.test.tsx +++ b/packages/core-components/src/components/Select/Select.test.tsx @@ -93,4 +93,62 @@ describe(', + ); + + // Verify Chip component exist + const chip = getByTestId('chip'); + expect(chip).toBeInTheDocument(); + expect(chip.textContent).toContain('test 1'); + + // Find cancel icon + const cancelIcon = getByTestId('cancel-icon'); + expect(cancelIcon).toBeInTheDocument(); + + // Verify dropdown is initially closed + expect(queryByText('test 2')).not.toBeInTheDocument(); + + // Fire mouseDown on Chip's CancelIcon that tests if onMouseDown={event => event.stopPropagation()} is working properly + fireEvent.mouseDown(cancelIcon); + + // Verify dropdown is closed after mouseDown on Chip's CancelIcon + expect(queryByText('test 2')).not.toBeInTheDocument(); + + // Delete the Chip + fireEvent.click(cancelIcon); + + // Verify dropdown is still closed after the removal of Chip + expect(queryByText('test 2')).not.toBeInTheDocument(); + + // Verify Chip is removed + expect(chip).not.toBeInTheDocument(); + expect(queryByText('test 1')).not.toBeInTheDocument(); + + // Verify we can still open the dropdown with a click on the select + const selectInput = getByTestId('select'); + expect(selectInput.textContent).toBe('All results'); + + // Simulate click on select + fireEvent.mouseDown(within(selectInput).getByRole('button')); + + // Now dropdown should be open + expect(queryByText('test 2')).toBeInTheDocument(); + }); }); diff --git a/packages/core-components/src/components/Select/Select.tsx b/packages/core-components/src/components/Select/Select.tsx index 6b5174820b..7a862678b8 100644 --- a/packages/core-components/src/components/Select/Select.tsx +++ b/packages/core-components/src/components/Select/Select.tsx @@ -28,6 +28,7 @@ import { withStyles, } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; +import CancelIcon from '@material-ui/icons/Cancel'; import React, { useEffect, useState } from 'react'; import ClosedDropdown from './static/ClosedDropdown'; @@ -222,9 +223,16 @@ export function SelectComponent(props: SelectProps) { const item = items.find(el => el.value === selectedValue); return item ? ( event.stopPropagation()} + /> + } onDelete={handleDelete(selectedValue)} className={classes.chip} /> diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx index 73baff8db2..e4093f07d2 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx @@ -173,4 +173,32 @@ describe('Stepper', () => { fireEvent.click(getTextInSlide(rendered, 2)('Back') as Node); expect(rendered.getByText('step1')).toBeInTheDocument(); }); + + it('Handles onBack action properly when activeStep is higher than 0', async () => { + const rendered = await renderInTestApp( + + +
step0
+
+ +
step1
+
+ +
step2
+
+
, + ); + + fireEvent.click(getTextInSlide(rendered, 2)('Back') as Node); + expect(rendered.getByText('step1')).toBeInTheDocument(); + + fireEvent.click(getTextInSlide(rendered, 1)('Back') as Node); + expect(rendered.getByText('step0')).toBeInTheDocument(); + + fireEvent.click(getTextInSlide(rendered, 0)('Next') as Node); + expect(rendered.getByText('step1')).toBeInTheDocument(); + + fireEvent.click(getTextInSlide(rendered, 1)('Next') as Node); + expect(rendered.getByText('step2')).toBeInTheDocument(); + }); }); diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx index 4a1a4ab919..b0b6c1afd0 100644 --- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx +++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx @@ -32,6 +32,7 @@ type InternalState = { }; const noop = () => {}; + export const VerticalStepperContext = React.createContext({ stepperLength: 0, stepIndex: 0, @@ -50,7 +51,17 @@ export interface StepperProps { export function SimpleStepper(props: PropsWithChildren) { const { children, elevated, onStepChange, activeStep = 0 } = props; const [stepIndex, setStepIndex] = useState(activeStep); - const [stepHistory, setStepHistory] = useState([0]); + /* + Recreates the stepHistory array based on the activeStep + to make sure the handleBack function of the Footer works when activeStep is higher than 0 + */ + const inOrderRecreatedStepHistory = Array.from( + { length: activeStep + 1 }, + (_, i) => i, + ); + const [stepHistory, setStepHistory] = useState( + inOrderRecreatedStepHistory, + ); useEffect(() => { setStepIndex(activeStep);