Merge branch 'master' into canon-styles
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': minor
|
||||
---
|
||||
|
||||
`SimpleStepper` back button now works with `activeStep` property set higher than 0
|
||||
@@ -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.
|
||||
@@ -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 ✨
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
storybook-static
|
||||
|
||||
# CSS output
|
||||
css
|
||||
/css
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
@@ -93,4 +93,62 @@ describe('<Select />', () => {
|
||||
const input = getByTestId('custom-select');
|
||||
expect(input.textContent).toBe('All results');
|
||||
});
|
||||
|
||||
it('should not open dropdown when deleting Chip component from Select', () => {
|
||||
const items = [
|
||||
{ value: 'test_1', label: 'test 1' },
|
||||
{ value: 'test_2', label: 'test 2' },
|
||||
];
|
||||
|
||||
const handleChange = jest.fn();
|
||||
|
||||
const { getByTestId, queryByText } = render(
|
||||
<Select
|
||||
label="Default"
|
||||
items={items}
|
||||
multiple
|
||||
selected={['test_1']} // Creates Chip Component initially
|
||||
onChange={handleChange}
|
||||
placeholder="All results"
|
||||
/>,
|
||||
);
|
||||
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 ? (
|
||||
<Chip
|
||||
data-testid="chip"
|
||||
key={item?.value}
|
||||
label={item?.label}
|
||||
clickable
|
||||
deleteIcon={
|
||||
<CancelIcon
|
||||
data-testid="cancel-icon"
|
||||
onMouseDown={event => event.stopPropagation()}
|
||||
/>
|
||||
}
|
||||
onDelete={handleDelete(selectedValue)}
|
||||
className={classes.chip}
|
||||
/>
|
||||
|
||||
@@ -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(
|
||||
<Stepper activeStep={2}>
|
||||
<Step title="Step 0" data-testid="step0">
|
||||
<div>step0</div>
|
||||
</Step>
|
||||
<Step title="Step 1" data-testid="step1">
|
||||
<div>step1</div>
|
||||
</Step>
|
||||
<Step title="Step 2" data-testid="step2">
|
||||
<div>step2</div>
|
||||
</Step>
|
||||
</Stepper>,
|
||||
);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -32,6 +32,7 @@ type InternalState = {
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
export const VerticalStepperContext = React.createContext<InternalState>({
|
||||
stepperLength: 0,
|
||||
stepIndex: 0,
|
||||
@@ -50,7 +51,17 @@ export interface StepperProps {
|
||||
export function SimpleStepper(props: PropsWithChildren<StepperProps>) {
|
||||
const { children, elevated, onStepChange, activeStep = 0 } = props;
|
||||
const [stepIndex, setStepIndex] = useState<number>(activeStep);
|
||||
const [stepHistory, setStepHistory] = useState<number[]>([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<number[]>(
|
||||
inOrderRecreatedStepHistory,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setStepIndex(activeStep);
|
||||
|
||||
Reference in New Issue
Block a user