Create a page limit component
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2022 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 React, { ComponentType } from 'react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef, MockSearchApi } from '../../api';
|
||||
import { SearchContextProvider } from '../../context';
|
||||
|
||||
import { SearchResultLimiter } from './SearchResultLimiter';
|
||||
|
||||
export default {
|
||||
title: 'Plugins/Search/SearchResultLimiter',
|
||||
component: SearchResultLimiter,
|
||||
decorators: [
|
||||
(Story: ComponentType<{}>) => (
|
||||
<TestApiProvider apis={[[searchApiRef, new MockSearchApi()]]}>
|
||||
<SearchContextProvider>
|
||||
<Grid container direction="row">
|
||||
<Grid item xs={12}>
|
||||
<Story />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
return <SearchResultLimiter />;
|
||||
};
|
||||
|
||||
export const CustomLabel = () => {
|
||||
return <SearchResultLimiter label="Results limit:" />;
|
||||
};
|
||||
|
||||
export const CustomOptions = () => {
|
||||
return <SearchResultLimiter options={[5, 10, 20]} />;
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2022 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 React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
|
||||
|
||||
import { searchApiRef } from '../../api';
|
||||
import { SearchContextProvider } from '../../context';
|
||||
|
||||
import { SearchResultLimiter } from './SearchResultLimiter';
|
||||
|
||||
const query = jest.fn().mockResolvedValue({ results: [] });
|
||||
|
||||
describe('SearchResultLimiter', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('Renders without exploding', async () => {
|
||||
await renderWithEffects(
|
||||
<TestApiProvider apis={[[searchApiRef, { query }]]}>
|
||||
<SearchContextProvider>
|
||||
<SearchResultLimiter />
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Results per page:')).toBeInTheDocument();
|
||||
expect(screen.getByText('25')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Define default options', async () => {
|
||||
await renderWithEffects(
|
||||
<TestApiProvider apis={[[searchApiRef, { query }]]}>
|
||||
<SearchContextProvider>
|
||||
<SearchResultLimiter />
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByText('25'));
|
||||
|
||||
const options = screen.getAllByRole('option');
|
||||
expect(options).toHaveLength(4);
|
||||
expect(options[0]).toHaveTextContent('10');
|
||||
expect(options[1]).toHaveTextContent('25');
|
||||
expect(options[2]).toHaveTextContent('50');
|
||||
expect(options[3]).toHaveTextContent('100');
|
||||
});
|
||||
|
||||
it('Set page limit in the context', async () => {
|
||||
await renderWithEffects(
|
||||
<TestApiProvider apis={[[searchApiRef, { query }]]}>
|
||||
<SearchContextProvider>
|
||||
<SearchResultLimiter />
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByText('25'));
|
||||
|
||||
await userEvent.click(screen.getByText('10'));
|
||||
|
||||
expect(query).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pageLimit: 10,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('Accept custom label', async () => {
|
||||
const label = 'Custom label';
|
||||
await renderWithEffects(
|
||||
<TestApiProvider apis={[[searchApiRef, { query }]]}>
|
||||
<SearchContextProvider>
|
||||
<SearchResultLimiter label={label} />
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(label)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Accept custom options', async () => {
|
||||
await renderWithEffects(
|
||||
<TestApiProvider apis={[[searchApiRef, { query }]]}>
|
||||
<SearchContextProvider>
|
||||
<SearchResultLimiter options={[5, 10, 20, 25]} />
|
||||
</SearchContextProvider>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByText('25'));
|
||||
|
||||
const options = screen.getAllByRole('option');
|
||||
expect(options).toHaveLength(4);
|
||||
expect(options[0]).toHaveTextContent('5');
|
||||
expect(options[1]).toHaveTextContent('10');
|
||||
expect(options[2]).toHaveTextContent('20');
|
||||
expect(options[3]).toHaveTextContent('25');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2022 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 React, { ReactNode, ChangeEvent, useCallback } from 'react';
|
||||
import {
|
||||
Box,
|
||||
InputBase,
|
||||
MenuItem,
|
||||
Select,
|
||||
Typography,
|
||||
useTheme,
|
||||
} from '@material-ui/core';
|
||||
import { useSearch } from '../../context';
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultLimiterBase}.
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultLimiterBaseProps = {
|
||||
id?: string;
|
||||
className?: string;
|
||||
/**
|
||||
* A label for the combobox.
|
||||
*/
|
||||
label?: ReactNode;
|
||||
/**
|
||||
* The combobox labels, defaults to 10, 25, 50 and 100.
|
||||
*/
|
||||
options?: number[];
|
||||
/**
|
||||
* Combobox selected option, defaults to 25;
|
||||
*/
|
||||
value?: number;
|
||||
/**
|
||||
* The callback handler called when the selected option changed.
|
||||
*/
|
||||
onChange?: (value: number) => void;
|
||||
};
|
||||
|
||||
const DEFAULT_PAGE_LIMIT = 25;
|
||||
|
||||
/**
|
||||
* A component for selecting the number of results per page.
|
||||
* @param props - See {@link SearchResultLimiterBaseProps}.
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultLimiterBase = (
|
||||
props: SearchResultLimiterBaseProps,
|
||||
) => {
|
||||
const {
|
||||
id = 'search-result-limiter',
|
||||
className,
|
||||
label = 'Results per page:',
|
||||
options = [10, 50, 100],
|
||||
value = DEFAULT_PAGE_LIMIT,
|
||||
onChange = () => {},
|
||||
} = props;
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: ChangeEvent<{ value: unknown }>) => {
|
||||
const newValue = e.target.value;
|
||||
if (typeof newValue === 'number') {
|
||||
onChange(newValue);
|
||||
}
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={className}
|
||||
display="inline-grid"
|
||||
gridGap={theme.spacing(0.5)}
|
||||
gridAutoFlow="column"
|
||||
alignItems="center"
|
||||
>
|
||||
<Typography id={`${id}-label`} variant="body2">
|
||||
{label}
|
||||
</Typography>
|
||||
<Select
|
||||
id={`${id}-select`}
|
||||
labelId={`${id}-label`}
|
||||
variant="standard"
|
||||
input={<InputBase />}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{[...new Set([DEFAULT_PAGE_LIMIT, ...options])]
|
||||
.sort((a, b) => a - b)
|
||||
.map(option => (
|
||||
<MenuItem key={option} value={option}>
|
||||
{option}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for {@link SearchResultLimiter}.
|
||||
* @public
|
||||
*/
|
||||
export type SearchResultLimiterProps = Omit<
|
||||
SearchResultLimiterBaseProps,
|
||||
'value' | 'onChange'
|
||||
>;
|
||||
|
||||
/**
|
||||
* A component for setting the search context page limit.
|
||||
* @param props - See {@link SearchResultLimiterProps}.
|
||||
* @public
|
||||
*/
|
||||
export const SearchResultLimiter = (props: SearchResultLimiterProps) => {
|
||||
const { pageLimit, setPageLimit } = useSearch();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newPageLimit: number) => {
|
||||
setPageLimit(newPageLimit);
|
||||
},
|
||||
[setPageLimit],
|
||||
);
|
||||
|
||||
return (
|
||||
<SearchResultLimiterBase
|
||||
{...props}
|
||||
value={pageLimit}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
export * from './SearchResultLimiter';
|
||||
@@ -20,6 +20,7 @@ export * from './SearchAutocomplete';
|
||||
export * from './SearchFilter';
|
||||
export * from './SearchResult';
|
||||
export * from './SearchResultPager';
|
||||
export * from './SearchResultLimiter';
|
||||
export * from './SearchResultList';
|
||||
export * from './SearchResultGroup';
|
||||
export * from './DefaultResultListItem';
|
||||
|
||||
Reference in New Issue
Block a user