mirror of https://github.com/portainer/portainer
fix(ui): Fixed react-select TooManyResultsSelector filter and improved scrolling (#1024)
parent
c9d18b614b
commit
06f6bcc340
|
@ -0,0 +1,330 @@
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
|
||||||
|
import selectEvent from '@/react/test-utils/react-select';
|
||||||
|
|
||||||
|
import { Select } from './ReactSelect';
|
||||||
|
|
||||||
|
describe('ReactSelect', () => {
|
||||||
|
const mockOptions = [
|
||||||
|
{ value: 'option1', label: 'Option 1' },
|
||||||
|
{ value: 'option2', label: 'Option 2' },
|
||||||
|
{ value: 'option3', label: 'Option 3' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockGroupedOptions = [
|
||||||
|
{
|
||||||
|
label: 'Group 1',
|
||||||
|
options: [
|
||||||
|
{ value: 'g1-option1', label: 'Group 1 Option 1' },
|
||||||
|
{ value: 'g1-option2', label: 'Group 1 Option 2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Group 2',
|
||||||
|
options: [
|
||||||
|
{ value: 'g2-option1', label: 'Group 2 Option 1' },
|
||||||
|
{ value: 'g2-option2', label: 'Group 2 Option 2' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Select component', () => {
|
||||||
|
it('should apply the correct size class', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
size="sm"
|
||||||
|
data-cy="test-select"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectContainer = container.querySelector(
|
||||||
|
'.portainer-selector-root'
|
||||||
|
);
|
||||||
|
expect(selectContainer).toHaveClass('sm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should apply custom className', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
className="custom-class"
|
||||||
|
data-cy="test-select"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectContainer = container.querySelector(
|
||||||
|
'.portainer-selector-root'
|
||||||
|
);
|
||||||
|
expect(selectContainer).toHaveClass('custom-class');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle onChange event', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const handleChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
onChange={handleChange}
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
await selectEvent.select(input, 'Option 2', { user });
|
||||||
|
|
||||||
|
expect(handleChange).toHaveBeenCalledWith(
|
||||||
|
mockOptions[1],
|
||||||
|
expect.objectContaining({ action: 'select-option' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multi-select', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const handleChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
onChange={handleChange}
|
||||||
|
isMulti
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
await selectEvent.select(input, 'Option 1', { user });
|
||||||
|
await selectEvent.select(input, 'Option 2', { user });
|
||||||
|
|
||||||
|
expect(handleChange).toHaveBeenCalledTimes(2);
|
||||||
|
expect(handleChange).toHaveBeenLastCalledWith(
|
||||||
|
[mockOptions[0], mockOptions[1]],
|
||||||
|
expect.objectContaining({ action: 'select-option' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render with grouped options', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockGroupedOptions}
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
await selectEvent.openMenu(input, { user });
|
||||||
|
|
||||||
|
expect(screen.getByText('Group 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Group 2')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Group 1 Option 1')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle disabled state', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
isDisabled
|
||||||
|
data-cy="test-select"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectContainer = container.querySelector(
|
||||||
|
'.portainer-selector-root'
|
||||||
|
);
|
||||||
|
expect(selectContainer).toHaveClass('portainer-selector--is-disabled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle loading state', () => {
|
||||||
|
const { container } = render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
isLoading
|
||||||
|
data-cy="test-select"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadingIndicator = container.querySelector(
|
||||||
|
'.portainer-selector__loading-indicator'
|
||||||
|
);
|
||||||
|
expect(loadingIndicator).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should clear selection', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const handleChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
onChange={handleChange}
|
||||||
|
isClearable
|
||||||
|
value={mockOptions[0]}
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
await selectEvent.clearFirst(input, { user });
|
||||||
|
|
||||||
|
expect(handleChange).toHaveBeenCalledWith(
|
||||||
|
null,
|
||||||
|
expect.objectContaining({ action: 'clear' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty options array', () => {
|
||||||
|
render(<Select id="test-select" options={[]} data-cy="test-select" />);
|
||||||
|
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
expect(input).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined options', () => {
|
||||||
|
render(
|
||||||
|
<Select id="test-select" options={undefined} data-cy="test-select" />
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
expect(input).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Component integration', () => {
|
||||||
|
it('should switch between regular and paginated select based on options count', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
// First render with few options - should use regular Select
|
||||||
|
const { rerender } = render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
let input = screen.getByRole('combobox');
|
||||||
|
await user.click(input);
|
||||||
|
|
||||||
|
// Regular select should render all 3 options immediately
|
||||||
|
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Option 3')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Close menu
|
||||||
|
await user.keyboard('{Escape}');
|
||||||
|
|
||||||
|
// Now rerender with many options - should switch to TooManyResultsSelector
|
||||||
|
const manyOptions = Array.from({ length: 1001 }, (_, i) => ({
|
||||||
|
value: `option${i}`,
|
||||||
|
label: `Option ${i}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={manyOptions}
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
input = screen.getByRole('combobox');
|
||||||
|
await user.click(input);
|
||||||
|
|
||||||
|
// Paginated select should only render first page (100 items max)
|
||||||
|
// Check that first few options are present
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Option 0')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Count total rendered options - should be limited to PAGE_SIZE (100)
|
||||||
|
// React-select uses divs with class portainer-selector__option for options
|
||||||
|
const renderedOptions = document.querySelectorAll(
|
||||||
|
'.portainer-selector__option'
|
||||||
|
);
|
||||||
|
expect(renderedOptions.length).toBeLessThanOrEqual(100);
|
||||||
|
expect(renderedOptions.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Verify that options beyond page size are NOT rendered
|
||||||
|
expect(screen.queryByText('Option 999')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render creatable mode when isCreatable prop is true', async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const handleChange = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={mockOptions}
|
||||||
|
isCreatable
|
||||||
|
onChange={handleChange}
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
// Type a new value that doesn't exist in options
|
||||||
|
await user.type(input, 'Brand New Option');
|
||||||
|
|
||||||
|
// Should show create option (may appear in multiple places)
|
||||||
|
await waitFor(() => {
|
||||||
|
const createOptions = screen.getAllByText(/Create "Brand New Option"/);
|
||||||
|
expect(createOptions.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve props when switching to TooManyResultsSelector', () => {
|
||||||
|
const handleChange = vi.fn();
|
||||||
|
const manyOptions = Array.from({ length: 1001 }, (_, i) => ({
|
||||||
|
value: `option${i}`,
|
||||||
|
label: `Option ${i}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<Select
|
||||||
|
id="test-select"
|
||||||
|
options={manyOptions}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder="Select an option"
|
||||||
|
isSearchable
|
||||||
|
isClearable
|
||||||
|
data-cy="test-select"
|
||||||
|
inputId="test-input"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should use TooManyResultsSelector for large datasets
|
||||||
|
const selectContainer = container.querySelector(
|
||||||
|
'.portainer-selector-root'
|
||||||
|
);
|
||||||
|
expect(selectContainer).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Should preserve data-cy attribute
|
||||||
|
const input = screen.getByRole('combobox');
|
||||||
|
expect(input).toHaveAttribute('data-cy', 'test-select');
|
||||||
|
|
||||||
|
// Should preserve id
|
||||||
|
expect(input).toHaveAttribute('id', 'test-input');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,9 +1,10 @@
|
||||||
import ReactSelectCreatable, {
|
import ReactSelectCreatable, {
|
||||||
CreatableProps as ReactSelectCreatableProps,
|
CreatableProps as ReactSelectCreatableProps,
|
||||||
} from 'react-select/creatable';
|
} from 'react-select/creatable';
|
||||||
import ReactSelectAsync, {
|
import {
|
||||||
AsyncProps as ReactSelectAsyncProps,
|
AsyncPaginate as ReactSelectAsyncPaginate,
|
||||||
} from 'react-select/async';
|
AsyncPaginateProps as ReactSelectAsyncPaginateProps,
|
||||||
|
} from 'react-select-async-paginate';
|
||||||
import ReactSelect, {
|
import ReactSelect, {
|
||||||
components,
|
components,
|
||||||
GroupBase,
|
GroupBase,
|
||||||
|
@ -18,6 +19,9 @@ import ReactSelectType from 'react-select/dist/declarations/src/Select';
|
||||||
import './ReactSelect.css';
|
import './ReactSelect.css';
|
||||||
import { AutomationTestingProps } from '@/types';
|
import { AutomationTestingProps } from '@/types';
|
||||||
|
|
||||||
|
const PAGE_SIZE = 100;
|
||||||
|
const MAX_OPTIONS_WITHOUT_PAGINATION = 1000;
|
||||||
|
|
||||||
interface DefaultOption {
|
interface DefaultOption {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -86,7 +90,7 @@ export function Select<
|
||||||
Group
|
Group
|
||||||
>(dataCy, componentsProp);
|
>(dataCy, componentsProp);
|
||||||
|
|
||||||
if ((options?.length || 0) > 1000) {
|
if ((options?.length || 0) > MAX_OPTIONS_WITHOUT_PAGINATION) {
|
||||||
return (
|
return (
|
||||||
<TooManyResultsSelector
|
<TooManyResultsSelector
|
||||||
size={size}
|
size={size}
|
||||||
|
@ -143,7 +147,7 @@ export function Async<
|
||||||
className,
|
className,
|
||||||
size,
|
size,
|
||||||
...props
|
...props
|
||||||
}: ReactSelectAsyncProps<Option, IsMulti, Group> & {
|
}: ReactSelectAsyncPaginateProps<Option, Group, unknown, IsMulti> & {
|
||||||
size?: 'sm' | 'md';
|
size?: 'sm' | 'md';
|
||||||
} & AutomationTestingProps) {
|
} & AutomationTestingProps) {
|
||||||
const { 'data-cy': dataCy, components: componentsProp, ...rest } = props;
|
const { 'data-cy': dataCy, components: componentsProp, ...rest } = props;
|
||||||
|
@ -155,7 +159,7 @@ export function Async<
|
||||||
>(dataCy, componentsProp);
|
>(dataCy, componentsProp);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactSelectAsync
|
<ReactSelectAsyncPaginate
|
||||||
className={clsx(className, 'portainer-selector-root', size)}
|
className={clsx(className, 'portainer-selector-root', size)}
|
||||||
classNamePrefix="portainer-selector"
|
classNamePrefix="portainer-selector"
|
||||||
components={memoizedComponents}
|
components={memoizedComponents}
|
||||||
|
@ -173,22 +177,29 @@ export function TooManyResultsSelector<
|
||||||
options,
|
options,
|
||||||
isLoading,
|
isLoading,
|
||||||
getOptionValue,
|
getOptionValue,
|
||||||
|
getOptionLabel,
|
||||||
isItemVisible = (item, search) =>
|
isItemVisible = (item, search) =>
|
||||||
!!getOptionValue?.(item).toLowerCase().includes(search.toLowerCase()),
|
search.trim() === '' ||
|
||||||
|
!!getOptionLabel?.(item).toLowerCase().includes(search.toLowerCase()),
|
||||||
...props
|
...props
|
||||||
}: RegularProps<Option, IsMulti, Group> & {
|
}: RegularProps<Option, IsMulti, Group> & {
|
||||||
isItemVisible?: (item: Option, search: string) => boolean;
|
isItemVisible?: (item: Option, search: string) => boolean;
|
||||||
}) {
|
}) {
|
||||||
const defaultOptions = useMemo(() => options?.slice(0, 100), [options]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Async
|
<Async
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
getOptionValue={getOptionValue}
|
getOptionValue={getOptionValue}
|
||||||
loadOptions={(search: string) =>
|
loadOptions={(
|
||||||
filterOptions<Option, Group>(options, isItemVisible, search)
|
search: string,
|
||||||
|
loadedOptions: OptionsOrGroups<Option, Group> | undefined
|
||||||
|
) =>
|
||||||
|
filterOptions<Option, Group>(
|
||||||
|
options,
|
||||||
|
isItemVisible,
|
||||||
|
search,
|
||||||
|
loadedOptions
|
||||||
|
)
|
||||||
}
|
}
|
||||||
defaultOptions={defaultOptions}
|
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
@ -201,17 +212,21 @@ function filterOptions<
|
||||||
>(
|
>(
|
||||||
options: OptionsOrGroups<Option, Group> | undefined,
|
options: OptionsOrGroups<Option, Group> | undefined,
|
||||||
isItemVisible: (item: Option, search: string) => boolean,
|
isItemVisible: (item: Option, search: string) => boolean,
|
||||||
search: string
|
search: string,
|
||||||
): Promise<OptionsOrGroups<Option, Group> | undefined> {
|
loadedOptions?: OptionsOrGroups<Option, Group>
|
||||||
return Promise.resolve<OptionsOrGroups<Option, Group> | undefined>(
|
) {
|
||||||
options
|
const filteredOptions = options?.filter((item) =>
|
||||||
?.filter((item) =>
|
isGroup(item)
|
||||||
isGroup(item)
|
? item.options.some((ni) => isItemVisible(ni, search))
|
||||||
? item.options.some((ni) => isItemVisible(ni, search))
|
: isItemVisible(item, search)
|
||||||
: isItemVisible(item, search)
|
|
||||||
)
|
|
||||||
.slice(0, 100)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const offset = loadedOptions?.length ?? 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
options: filteredOptions?.slice(offset, offset + PAGE_SIZE) ?? [],
|
||||||
|
hasMore: (filteredOptions?.length ?? 0) > offset + PAGE_SIZE,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGroup<
|
function isGroup<
|
||||||
|
|
|
@ -125,6 +125,7 @@
|
||||||
"react-is": "^17.0.2",
|
"react-is": "^17.0.2",
|
||||||
"react-json-view-lite": "^1.2.1",
|
"react-json-view-lite": "^1.2.1",
|
||||||
"react-select": "^5.2.1",
|
"react-select": "^5.2.1",
|
||||||
|
"react-select-async-paginate": "^0.7.11",
|
||||||
"sanitize-html": "^2.8.1",
|
"sanitize-html": "^2.8.1",
|
||||||
"spinkit": "^2.0.1",
|
"spinkit": "^2.0.1",
|
||||||
"strip-ansi": "^6.0.0",
|
"strip-ansi": "^6.0.0",
|
||||||
|
|
49
yarn.lock
49
yarn.lock
|
@ -4808,6 +4808,11 @@
|
||||||
"@sagold/json-pointer" "^5.1.2"
|
"@sagold/json-pointer" "^5.1.2"
|
||||||
ebnf "^1.9.1"
|
ebnf "^1.9.1"
|
||||||
|
|
||||||
|
"@seznam/compose-react-refs@^1.0.6":
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@seznam/compose-react-refs/-/compose-react-refs-1.0.6.tgz#6ec4e70bdd6e32f8e70b4100f27267cf306bd8df"
|
||||||
|
integrity sha512-izzOXQfeQLonzrIQb8u6LQ8dk+ymz3WXTIXjvOlTXHq6sbzROg3NWU+9TTAOpEoK9Bth24/6F/XrfHJ5yR5n6Q==
|
||||||
|
|
||||||
"@shikijs/core@1.29.2":
|
"@shikijs/core@1.29.2":
|
||||||
version "1.29.2"
|
version "1.29.2"
|
||||||
resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.29.2.tgz#9c051d3ac99dd06ae46bd96536380c916e552bf3"
|
resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.29.2.tgz#9c051d3ac99dd06ae46bd96536380c916e552bf3"
|
||||||
|
@ -6992,6 +6997,11 @@
|
||||||
loupe "^3.1.2"
|
loupe "^3.1.2"
|
||||||
tinyrainbow "^1.2.0"
|
tinyrainbow "^1.2.0"
|
||||||
|
|
||||||
|
"@vtaits/use-lazy-ref@^0.1.4":
|
||||||
|
version "0.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vtaits/use-lazy-ref/-/use-lazy-ref-0.1.4.tgz#6befc141f4b29f97022259b00c4a5b6c482fe953"
|
||||||
|
integrity sha512-pdHe8k2WLIm8ccVfNw3HzeTCkifKKjVQ3hpiM7/rMynCp8nev715wrY2RCYnbeowNvekWqpGdHtrWKfCDocC6g==
|
||||||
|
|
||||||
"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5":
|
"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5":
|
||||||
version "1.11.5"
|
version "1.11.5"
|
||||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c"
|
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c"
|
||||||
|
@ -12868,6 +12878,11 @@ klona@^2.0.6:
|
||||||
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
|
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
|
||||||
integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
|
integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
|
||||||
|
|
||||||
|
krustykrab@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/krustykrab/-/krustykrab-1.1.0.tgz#2b77faf06da9a43fe740799ac73fc2e8b6b515b0"
|
||||||
|
integrity sha512-xpX9MPbw+nJseewe6who9Oq46RQwrBfps+dO/N4fSjJhsf2+y4XWC2kz46oBGX8yzMHyYJj35ug0X5s5yxB6tA==
|
||||||
|
|
||||||
kubernetes-types@^1.30.0:
|
kubernetes-types@^1.30.0:
|
||||||
version "1.30.0"
|
version "1.30.0"
|
||||||
resolved "https://registry.yarnpkg.com/kubernetes-types/-/kubernetes-types-1.30.0.tgz#f686cacb08ffc5f7e89254899c2153c723420116"
|
resolved "https://registry.yarnpkg.com/kubernetes-types/-/kubernetes-types-1.30.0.tgz#f686cacb08ffc5f7e89254899c2153c723420116"
|
||||||
|
@ -15514,6 +15529,18 @@ react-remove-scroll@^2.6.3:
|
||||||
use-callback-ref "^1.3.3"
|
use-callback-ref "^1.3.3"
|
||||||
use-sidecar "^1.1.3"
|
use-sidecar "^1.1.3"
|
||||||
|
|
||||||
|
react-select-async-paginate@^0.7.11:
|
||||||
|
version "0.7.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-select-async-paginate/-/react-select-async-paginate-0.7.11.tgz#737b3fef1beb23dab82c7d2b90059c7b823aae1d"
|
||||||
|
integrity sha512-AjtCLPMk5DLNgygwQprEPC0gfVIjkou+QYvXM+2gm/LeRpY1Gv5KNT79EYB37H1uMCrwA+HL9BY7OtlaNWtYNg==
|
||||||
|
dependencies:
|
||||||
|
"@seznam/compose-react-refs" "^1.0.6"
|
||||||
|
"@vtaits/use-lazy-ref" "^0.1.4"
|
||||||
|
krustykrab "^1.1.0"
|
||||||
|
sleep-promise "^9.1.0"
|
||||||
|
use-is-mounted-ref "^1.5.0"
|
||||||
|
use-latest "^1.3.0"
|
||||||
|
|
||||||
react-select@^5.2.1:
|
react-select@^5.2.1:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.1.tgz#416c25c6b79b94687702374e019c4f2ed9d159d6"
|
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.1.tgz#416c25c6b79b94687702374e019c4f2ed9d159d6"
|
||||||
|
@ -16554,6 +16581,11 @@ slash@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
|
resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
|
||||||
integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
|
integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
|
||||||
|
|
||||||
|
sleep-promise@^9.1.0:
|
||||||
|
version "9.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sleep-promise/-/sleep-promise-9.1.0.tgz#101ebe65700bcd184709da95d960967b02b79d03"
|
||||||
|
integrity sha512-UHYzVpz9Xn8b+jikYSD6bqvf754xL2uBUzDFwiU6NcdZeifPr6UfgU43xpkPu67VMS88+TI2PSI7Eohgqf2fKA==
|
||||||
|
|
||||||
slice-ansi@^5.0.0:
|
slice-ansi@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
|
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a"
|
||||||
|
@ -17908,6 +17940,23 @@ use-callback-ref@^1.3.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.0.0"
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
use-is-mounted-ref@^1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-is-mounted-ref/-/use-is-mounted-ref-1.5.0.tgz#d737e7b30f1bbbaca594f21cdd2621dc52ae8180"
|
||||||
|
integrity sha512-p5FksHf/ospZUr5KU9ese6u3jp9fzvZ3wuSb50i0y6fdONaHWgmOqQtxR/PUcwi6hnhQDbNxWSg3eTK3N6m+dg==
|
||||||
|
|
||||||
|
use-isomorphic-layout-effect@^1.1.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz#2f11a525628f56424521c748feabc2ffcc962fce"
|
||||||
|
integrity sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==
|
||||||
|
|
||||||
|
use-latest@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.3.0.tgz#549b9b0d4c1761862072f0899c6f096eb379137a"
|
||||||
|
integrity sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==
|
||||||
|
dependencies:
|
||||||
|
use-isomorphic-layout-effect "^1.1.1"
|
||||||
|
|
||||||
use-resize-observer@^9.1.0:
|
use-resize-observer@^9.1.0:
|
||||||
version "9.1.0"
|
version "9.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c"
|
resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-9.1.0.tgz#14735235cf3268569c1ea468f8a90c5789fc5c6c"
|
||||||
|
|
Loading…
Reference in New Issue