mirror of
https://github.com/apache/superset.git
synced 2026-04-24 18:44:53 +00:00
test: Adds storybook and tests to PopoverDropdown component (#13547)
* test: Adds storybook and tests to PopoverDropdown component * Changes to use gridUnit and theme hook
This commit is contained in:
committed by
GitHub
parent
e61f5a924b
commit
be8f8d9c1f
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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, { useState } from 'react';
|
||||
import PopoverDropdown, { PopoverDropdownProps, OptionProps } from '.';
|
||||
|
||||
export default {
|
||||
title: 'PopoverDropdown',
|
||||
includeStories: ['InteractivePopoverDropdown'],
|
||||
};
|
||||
|
||||
export const OPTIONS: OptionProps[] = [
|
||||
{ label: 'Option A', value: 'A' },
|
||||
{ label: 'Option B', value: 'B' },
|
||||
{ label: 'Option C', value: 'C' },
|
||||
];
|
||||
|
||||
type ElementType = 'default' | 'button';
|
||||
|
||||
type Props = PopoverDropdownProps & {
|
||||
buttonType: ElementType;
|
||||
optionType: ElementType;
|
||||
};
|
||||
|
||||
export const InteractivePopoverDropdown = (props: Props) => {
|
||||
const { value, buttonType, optionType, ...rest } = props;
|
||||
const [currentValue, setCurrentValue] = useState(value);
|
||||
|
||||
const newElementHandler = (type: ElementType) => ({
|
||||
label,
|
||||
value,
|
||||
}: OptionProps) => {
|
||||
if (type === 'button') {
|
||||
return (
|
||||
<button type="button" key={value}>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
return <span>{label}</span>;
|
||||
};
|
||||
|
||||
return (
|
||||
<PopoverDropdown
|
||||
{...rest}
|
||||
value={currentValue}
|
||||
renderButton={newElementHandler(buttonType)}
|
||||
renderOption={newElementHandler(optionType)}
|
||||
onChange={selected => setCurrentValue(selected as string)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
InteractivePopoverDropdown.argTypes = {
|
||||
buttonType: {
|
||||
defaultValue: 'default',
|
||||
control: { type: 'radio', options: ['default', 'button'] },
|
||||
},
|
||||
optionType: {
|
||||
defaultValue: 'default',
|
||||
control: { type: 'radio', options: ['default', 'button'] },
|
||||
},
|
||||
value: {
|
||||
defaultValue: OPTIONS[0].value,
|
||||
table: { disable: true },
|
||||
},
|
||||
options: {
|
||||
defaultValue: OPTIONS,
|
||||
table: { disable: true },
|
||||
},
|
||||
};
|
||||
|
||||
InteractivePopoverDropdown.story = {
|
||||
parameters: {
|
||||
knobs: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 { render, screen } from 'spec/helpers/testing-library';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import PopoverDropdown, {
|
||||
PopoverDropdownProps,
|
||||
OptionProps,
|
||||
} from 'src/components/PopoverDropdown';
|
||||
|
||||
const defaultProps: PopoverDropdownProps = {
|
||||
id: 'popover-dropdown',
|
||||
options: [
|
||||
{ label: 'Option 1', value: '1' },
|
||||
{ label: 'Option 2', value: '2' },
|
||||
],
|
||||
value: '1',
|
||||
renderButton: (option: OptionProps) => <span>{option.label}</span>,
|
||||
renderOption: (option: OptionProps) => <div>{option.label}</div>,
|
||||
onChange: jest.fn(),
|
||||
};
|
||||
|
||||
test('renders with default props', () => {
|
||||
render(<PopoverDropdown {...defaultProps} />);
|
||||
expect(screen.getByRole('button')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button')).toHaveTextContent('Option 1');
|
||||
});
|
||||
|
||||
test('renders the menu on click', () => {
|
||||
render(<PopoverDropdown {...defaultProps} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders with custom button', () => {
|
||||
render(
|
||||
<PopoverDropdown
|
||||
{...defaultProps}
|
||||
renderButton={({ label, value }: OptionProps) => (
|
||||
<button type="button" key={value}>
|
||||
{`Custom ${label}`}
|
||||
</button>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText('Custom Option 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders with custom option', () => {
|
||||
render(
|
||||
<PopoverDropdown
|
||||
{...defaultProps}
|
||||
renderOption={({ label, value }: OptionProps) => (
|
||||
<button type="button" key={value}>
|
||||
{`Custom ${label}`}
|
||||
</button>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByText('Custom Option 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('triggers onChange', () => {
|
||||
render(<PopoverDropdown {...defaultProps} />);
|
||||
userEvent.click(screen.getByRole('button'));
|
||||
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
||||
userEvent.click(screen.getByText('Option 2'));
|
||||
expect(defaultProps.onChange).toHaveBeenCalled();
|
||||
});
|
||||
118
superset-frontend/src/components/PopoverDropdown/index.tsx
Normal file
118
superset-frontend/src/components/PopoverDropdown/index.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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 cx from 'classnames';
|
||||
import { styled, useTheme } from '@superset-ui/core';
|
||||
import { Dropdown, Menu } from 'src/common/components';
|
||||
import Icon from 'src/components/Icon';
|
||||
|
||||
export interface OptionProps {
|
||||
value: string;
|
||||
label: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type OnChangeHandler = (key: React.Key) => void;
|
||||
export type RenderElementHandler = (option: OptionProps) => JSX.Element;
|
||||
|
||||
export interface PopoverDropdownProps {
|
||||
id: string;
|
||||
options: OptionProps[];
|
||||
onChange: OnChangeHandler;
|
||||
value: string;
|
||||
renderButton?: RenderElementHandler;
|
||||
renderOption?: RenderElementHandler;
|
||||
}
|
||||
|
||||
interface HandleSelectProps {
|
||||
key: React.Key;
|
||||
}
|
||||
|
||||
const MenuItem = styled(Menu.Item)`
|
||||
&.ant-menu-item {
|
||||
height: auto;
|
||||
line-height: 1.4;
|
||||
|
||||
padding-top: ${({ theme }) => theme.gridUnit}px;
|
||||
padding-bottom: ${({ theme }) => theme.gridUnit}px;
|
||||
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.colors.grayscale.light3};
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: ${({ theme }) => theme.typography.weights.bold};
|
||||
background: ${({ theme }) => theme.colors.grayscale.light2};
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-menu-item-selected {
|
||||
color: unset;
|
||||
}
|
||||
`;
|
||||
|
||||
const PopoverDropdown = (props: PopoverDropdownProps) => {
|
||||
const {
|
||||
value,
|
||||
options,
|
||||
onChange,
|
||||
renderButton = (option: OptionProps) => option.label,
|
||||
renderOption = (option: OptionProps) => (
|
||||
<div className={option.className}>{option.label}</div>
|
||||
),
|
||||
} = props;
|
||||
|
||||
const theme = useTheme();
|
||||
const selected = options.find(opt => opt.value === value);
|
||||
return (
|
||||
<Dropdown
|
||||
trigger={['click']}
|
||||
overlayStyle={{ zIndex: theme.zIndex.max }}
|
||||
overlay={
|
||||
<Menu onClick={({ key }: HandleSelectProps) => onChange(key)}>
|
||||
{options.map(option => (
|
||||
<MenuItem
|
||||
id="menu-item"
|
||||
key={option.value}
|
||||
className={cx('dropdown-item', {
|
||||
active: option.value === value,
|
||||
})}
|
||||
>
|
||||
{renderOption(option)}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
}
|
||||
>
|
||||
<div role="button" css={{ display: 'flex', alignItems: 'center' }}>
|
||||
{selected && renderButton(selected)}
|
||||
<Icon name="caret-down" css={{ marginTop: theme.gridUnit }} />
|
||||
</div>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopoverDropdown;
|
||||
Reference in New Issue
Block a user