live message tailing (#1672)
* live tailing * addind test case * fixing useffect array deps * adding test cases for select * adding test cases for filters * deleting unused code * adding test case for filter
This commit is contained in:
parent
e85e1aafa1
commit
39359bb9a9
9 changed files with 227 additions and 1510 deletions
|
@ -1,5 +1,13 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
interface MessageLoadingProps {
|
||||
isLive: boolean;
|
||||
}
|
||||
|
||||
interface MessageLoadingSpinnerProps {
|
||||
isFetching: boolean;
|
||||
}
|
||||
|
||||
export const FiltersWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -83,3 +91,36 @@ export const MetricsIcon = styled.div`
|
|||
padding-right: 6px;
|
||||
height: 12px;
|
||||
`;
|
||||
|
||||
export const MessageLoading = styled.div<MessageLoadingProps>`
|
||||
color: ${({ theme }) => theme.heading.h3.color};
|
||||
font-size: ${({ theme }) => theme.heading.h3.fontSize};
|
||||
display: ${(props) => (props.isLive ? 'flex' : 'none')};
|
||||
justify-content: space-around;
|
||||
width: 250px;
|
||||
`;
|
||||
|
||||
export const StopLoading = styled.div`
|
||||
color: ${({ theme }) => theme.pageLoader.borderColor};
|
||||
font-size: ${({ theme }) => theme.heading.h3.fontSize};
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const MessageLoadingSpinner = styled.div<MessageLoadingSpinnerProps>`
|
||||
display: ${(props) => (props.isFetching ? 'block' : 'none')};
|
||||
border: 3px solid ${({ theme }) => theme.pageLoader.borderColor};
|
||||
border-bottom: 3px solid ${({ theme }) => theme.pageLoader.borderBottomColor};
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation: spin 1.3s linear infinite;
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -53,8 +53,9 @@ const SeekTypeOptions = [
|
|||
{ value: SeekType.TIMESTAMP, label: 'Timestamp' },
|
||||
];
|
||||
const SeekDirectionOptions = [
|
||||
{ value: SeekDirection.FORWARD, label: 'Oldest First' },
|
||||
{ value: SeekDirection.BACKWARD, label: 'Newest First' },
|
||||
{ value: SeekDirection.FORWARD, label: 'Oldest First', isLive: false },
|
||||
{ value: SeekDirection.BACKWARD, label: 'Newest First', isLive: false },
|
||||
{ value: SeekDirection.TAILING, label: 'Live Mode', isLive: true },
|
||||
];
|
||||
|
||||
const Filters: React.FC<FiltersProps> = ({
|
||||
|
@ -100,7 +101,6 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
(searchParams.get('seekDirection') as SeekDirection) ||
|
||||
SeekDirection.FORWARD
|
||||
);
|
||||
|
||||
const isSeekTypeControlVisible = React.useMemo(
|
||||
() => selectedPartitions.length > 0,
|
||||
[selectedPartitions]
|
||||
|
@ -167,14 +167,21 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
search: `?${qs}`,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [seekDirection]);
|
||||
|
||||
const toggleSeekDirection = (val: string) => {
|
||||
const nextSeekDirectionValue =
|
||||
val === SeekDirection.FORWARD
|
||||
? SeekDirection.FORWARD
|
||||
: SeekDirection.BACKWARD;
|
||||
setSeekDirection(nextSeekDirectionValue);
|
||||
switch (val) {
|
||||
case SeekDirection.FORWARD:
|
||||
setSeekDirection(SeekDirection.FORWARD);
|
||||
break;
|
||||
case SeekDirection.BACKWARD:
|
||||
setSeekDirection(SeekDirection.BACKWARD);
|
||||
break;
|
||||
case SeekDirection.TAILING:
|
||||
setSeekDirection(SeekDirection.TAILING);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
const handleSSECancel = () => {
|
||||
|
@ -228,6 +235,7 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
}, [
|
||||
clusterName,
|
||||
topicName,
|
||||
seekDirection,
|
||||
location,
|
||||
setIsFetching,
|
||||
resetMessages,
|
||||
|
@ -268,6 +276,7 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
selectSize="M"
|
||||
minWidth="100px"
|
||||
options={SeekTypeOptions}
|
||||
disabled={seekDirection === SeekDirection.TAILING}
|
||||
/>
|
||||
{currentSeekType === SeekType.OFFSET ? (
|
||||
<Input
|
||||
|
@ -276,7 +285,9 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
inputSize="M"
|
||||
value={offset}
|
||||
className="offset-selector"
|
||||
placeholder="Offset"
|
||||
onChange={({ target: { value } }) => setOffset(value)}
|
||||
disabled={seekDirection === SeekDirection.TAILING}
|
||||
/>
|
||||
) : (
|
||||
<DatePicker
|
||||
|
@ -287,6 +298,7 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
dateFormat="MMMM d, yyyy HH:mm"
|
||||
className="date-picker"
|
||||
placeholderText="Select timestamp"
|
||||
disabled={seekDirection === SeekDirection.TAILING}
|
||||
/>
|
||||
)}
|
||||
</S.SeekTypeSelectorWrapper>
|
||||
|
@ -331,10 +343,27 @@ const Filters: React.FC<FiltersProps> = ({
|
|||
value={seekDirection}
|
||||
minWidth="120px"
|
||||
options={SeekDirectionOptions}
|
||||
isLive={seekDirection === SeekDirection.TAILING}
|
||||
/>
|
||||
</div>
|
||||
<S.FiltersMetrics>
|
||||
<p style={{ fontSize: 14 }}>{isFetching && phaseMessage}</p>
|
||||
<p style={{ fontSize: 14 }}>
|
||||
{seekDirection !== SeekDirection.TAILING &&
|
||||
isFetching &&
|
||||
phaseMessage}
|
||||
</p>
|
||||
<S.MessageLoading isLive={seekDirection === SeekDirection.TAILING}>
|
||||
<S.MessageLoadingSpinner isFetching={isFetching} />
|
||||
Loading messages.
|
||||
<S.StopLoading
|
||||
onClick={() => {
|
||||
setSeekDirection(SeekDirection.FORWARD);
|
||||
setIsFetching(false);
|
||||
}}
|
||||
>
|
||||
Stop loading
|
||||
</S.StopLoading>
|
||||
</S.MessageLoading>
|
||||
<S.Metric title="Elapsed Time">
|
||||
<S.MetricsIcon>
|
||||
<i className="far fa-clock" />
|
||||
|
|
|
@ -3,8 +3,11 @@ import Filters, {
|
|||
FiltersProps,
|
||||
} from 'components/Topics/Topic/Details/Messages/Filters/Filters';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
const setupWrapper = (props?: Partial<FiltersProps>) => (
|
||||
const setupWrapper = (props?: Partial<FiltersProps>) =>
|
||||
render(
|
||||
<Filters
|
||||
clusterName="test-cluster"
|
||||
topicName="test-topic"
|
||||
|
@ -18,16 +21,85 @@ const setupWrapper = (props?: Partial<FiltersProps>) => (
|
|||
setIsFetching={jest.fn()}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
);
|
||||
describe('Filters component', () => {
|
||||
it('matches the snapshot', () => {
|
||||
const component = render(setupWrapper());
|
||||
expect(component.baseElement).toMatchSnapshot();
|
||||
it('renders component', () => {
|
||||
setupWrapper();
|
||||
});
|
||||
describe('when fetching', () => {
|
||||
it('matches the snapshot', () => {
|
||||
const component = render(setupWrapper({ isFetching: true }));
|
||||
expect(component.baseElement).toMatchSnapshot();
|
||||
it('shows cancel button while fetching', () => {
|
||||
setupWrapper({ isFetching: true });
|
||||
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
describe('when fetching is over', () => {
|
||||
it('shows submit button while fetching is over', () => {
|
||||
setupWrapper();
|
||||
expect(screen.getByText('Submit')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
describe('Input elements', () => {
|
||||
it('search input', () => {
|
||||
setupWrapper();
|
||||
const SearchInput = screen.getByPlaceholderText('Search');
|
||||
expect(SearchInput).toBeInTheDocument();
|
||||
expect(SearchInput).toHaveValue('');
|
||||
userEvent.type(SearchInput, 'Hello World!');
|
||||
expect(SearchInput).toHaveValue('Hello World!');
|
||||
});
|
||||
it('offset input', () => {
|
||||
setupWrapper();
|
||||
const OffsetInput = screen.getByPlaceholderText('Offset');
|
||||
expect(OffsetInput).toBeInTheDocument();
|
||||
expect(OffsetInput).toHaveValue('');
|
||||
userEvent.type(OffsetInput, 'Hello World!');
|
||||
expect(OffsetInput).toHaveValue('Hello World!');
|
||||
});
|
||||
it('timestamp input', () => {
|
||||
setupWrapper();
|
||||
const seekTypeSelect = screen.getAllByRole('listbox');
|
||||
const option = screen.getAllByRole('option');
|
||||
userEvent.click(seekTypeSelect[0]);
|
||||
userEvent.selectOptions(seekTypeSelect[0], ['Timestamp']);
|
||||
expect(option[0]).toHaveTextContent('Timestamp');
|
||||
const TimestampInput = screen.getByPlaceholderText('Select timestamp');
|
||||
expect(TimestampInput).toBeInTheDocument();
|
||||
expect(TimestampInput).toHaveValue('');
|
||||
userEvent.type(TimestampInput, 'Hello World!');
|
||||
expect(TimestampInput).toHaveValue('Hello World!');
|
||||
expect(screen.getByText('Submit')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
describe('Select elements', () => {
|
||||
it('seekType select', () => {
|
||||
setupWrapper();
|
||||
const seekTypeSelect = screen.getAllByRole('listbox');
|
||||
const option = screen.getAllByRole('option');
|
||||
expect(option[0]).toHaveTextContent('Offset');
|
||||
userEvent.click(seekTypeSelect[0]);
|
||||
userEvent.selectOptions(seekTypeSelect[0], ['Timestamp']);
|
||||
expect(option[0]).toHaveTextContent('Timestamp');
|
||||
expect(screen.getByText('Submit')).toBeInTheDocument();
|
||||
});
|
||||
it('seekDirection select', () => {
|
||||
setupWrapper();
|
||||
const seekDirectionSelect = screen.getAllByRole('listbox');
|
||||
const option = screen.getAllByRole('option');
|
||||
userEvent.click(seekDirectionSelect[1]);
|
||||
userEvent.selectOptions(seekDirectionSelect[1], ['Newest First']);
|
||||
expect(option[1]).toHaveTextContent('Newest First');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when live mode is active', () => {
|
||||
it('stop loading', () => {
|
||||
setupWrapper();
|
||||
const StopLoading = screen.getByText('Stop loading');
|
||||
expect(StopLoading).toBeInTheDocument();
|
||||
userEvent.click(StopLoading);
|
||||
const option = screen.getAllByRole('option');
|
||||
expect(option[1]).toHaveTextContent('Oldest First');
|
||||
expect(screen.getByText('Submit')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,10 +5,14 @@ interface Props {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
const SVGWrapper = styled.i`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const LiveIcon: React.FC<Props> = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<i>
|
||||
<SVGWrapper data-testid="liveIcon">
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
|
@ -19,7 +23,7 @@ const LiveIcon: React.FC<Props> = () => {
|
|||
<circle cx="8" cy="8" r="7" fill={theme.icons.liveIcon.circleBig} />
|
||||
<circle cx="8" cy="8" r="4" fill={theme.icons.liveIcon.circleSmall} />
|
||||
</svg>
|
||||
</i>
|
||||
</SVGWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ export const Select = styled.ul<Props>`
|
|||
position: relative;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: ${(props) => (props.isLive ? '5px' : '0')};
|
||||
align-items: center;
|
||||
height: ${(props) => (props.selectSize === 'M' ? '32px' : '40px')};
|
||||
border: 1px
|
||||
|
@ -26,7 +27,7 @@ export const Select = styled.ul<Props>`
|
|||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
width: fit-content;
|
||||
padding-left: ${(props) => (props.isLive ? '36px' : '12px')};
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
color: ${({ theme, disabled }) =>
|
||||
disabled ? theme.select.color.disabled : theme.select.color.normal};
|
||||
|
@ -38,8 +39,8 @@ export const Select = styled.ul<Props>`
|
|||
background-repeat: no-repeat !important;
|
||||
background-position-x: calc(100% - 8px) !important;
|
||||
background-position-y: 55% !important;
|
||||
|
||||
&:hover {
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
&:hover:enabled {
|
||||
color: ${(props) => props.theme.select.color.hover};
|
||||
border-color: ${(props) => props.theme.select.borderColor.hover};
|
||||
}
|
||||
|
@ -51,7 +52,6 @@ export const Select = styled.ul<Props>`
|
|||
&:disabled {
|
||||
color: ${(props) => props.theme.select.color.disabled};
|
||||
border-color: ${(props) => props.theme.select.borderColor.disabled};
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -71,7 +71,6 @@ export const OptionList = styled.ul`
|
|||
z-index: 10;
|
||||
max-width: 300px;
|
||||
min-width: 100%;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
-webkit-appearance: none;
|
||||
width: 7px;
|
||||
|
@ -89,10 +88,12 @@ export const OptionList = styled.ul`
|
|||
`;
|
||||
|
||||
export const Option = styled.li<OptionProps>`
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 10px 12px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
gap: 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) => props.theme.select.backgroundColor.hover};
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface SelectOption {
|
|||
label: string | number;
|
||||
value: string | number;
|
||||
disabled?: boolean;
|
||||
isLive?: boolean;
|
||||
}
|
||||
|
||||
const Select: React.FC<SelectProps> = ({
|
||||
|
@ -53,10 +54,12 @@ const Select: React.FC<SelectProps> = ({
|
|||
if (onChange) onChange(option.value);
|
||||
setShowOptions(false);
|
||||
};
|
||||
React.useEffect(() => {
|
||||
setSelectedOption(value);
|
||||
}, [isLive, value]);
|
||||
|
||||
return (
|
||||
<div ref={selectContainerRef}>
|
||||
{isLive && <LiveIcon />}
|
||||
<S.Select
|
||||
role="listbox"
|
||||
selectSize={selectSize}
|
||||
|
@ -66,6 +69,7 @@ const Select: React.FC<SelectProps> = ({
|
|||
onKeyDown={showOptionsHandler}
|
||||
{...props}
|
||||
>
|
||||
{isLive && <LiveIcon />}
|
||||
<S.SelectedOption role="option" tabIndex={0}>
|
||||
{options.find(
|
||||
(option) => option.value === (defaultValue || selectedOption)
|
||||
|
@ -82,6 +86,7 @@ const Select: React.FC<SelectProps> = ({
|
|||
tabIndex={0}
|
||||
role="option"
|
||||
>
|
||||
{option.isLive && <LiveIcon />}
|
||||
{option.label}
|
||||
</S.Option>
|
||||
))}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import Select, { SelectProps } from 'components/common/Select/Select';
|
||||
import React from 'react';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('react-hook-form', () => ({
|
||||
useFormContext: () => ({
|
||||
|
@ -8,26 +10,47 @@ jest.mock('react-hook-form', () => ({
|
|||
}),
|
||||
}));
|
||||
|
||||
const setupWrapper = (props?: Partial<SelectProps>) => (
|
||||
<Select name="test" {...props} />
|
||||
);
|
||||
const options = [
|
||||
{ label: 'test-label1', value: 'test-value1' },
|
||||
{ label: 'test-label2', value: 'test-value2' },
|
||||
];
|
||||
|
||||
const renderComponent = (props?: Partial<SelectProps>) =>
|
||||
render(<Select name="test" {...props} />);
|
||||
|
||||
describe('Custom Select', () => {
|
||||
it('renders component', () => {
|
||||
renderComponent();
|
||||
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
||||
});
|
||||
it('show select options when select is being clicked', () => {
|
||||
renderComponent({
|
||||
options,
|
||||
});
|
||||
expect(screen.getByRole('option')).toBeInTheDocument();
|
||||
userEvent.click(screen.getByRole('listbox'));
|
||||
expect(screen.getAllByRole('option')).toHaveLength(3);
|
||||
});
|
||||
it('checking select option change', () => {
|
||||
renderComponent({
|
||||
options,
|
||||
});
|
||||
userEvent.click(screen.getByRole('listbox'));
|
||||
userEvent.selectOptions(screen.getByRole('listbox'), ['test-label1']);
|
||||
expect(screen.getByRole('option')).toHaveTextContent('test-label1');
|
||||
});
|
||||
|
||||
describe('when non-live', () => {
|
||||
it('matches the snapshot', () => {
|
||||
const component = render(setupWrapper());
|
||||
expect(component.baseElement).toMatchSnapshot();
|
||||
it('there is not live icon', () => {
|
||||
renderComponent({ isLive: false });
|
||||
expect(screen.queryByTestId('liveIcon')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when live', () => {
|
||||
it('matches the snapshot', () => {
|
||||
const component = render(
|
||||
setupWrapper({
|
||||
isLive: true,
|
||||
})
|
||||
);
|
||||
expect(component.baseElement).toMatchSnapshot();
|
||||
it('there is live icon', () => {
|
||||
renderComponent({ isLive: true });
|
||||
expect(screen.getByTestId('liveIcon')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Custom Select when live matches the snapshot 1`] = `
|
||||
<body>
|
||||
.c0 {
|
||||
position: relative;
|
||||
list-style: none;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
border: 1px #ABB5BA solid;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
padding-left: 36px;
|
||||
padding-right: 16px;
|
||||
color: #171A1C;
|
||||
min-width: auto;
|
||||
background-image: url('data:image/svg+xml,%3Csvg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 1L5 5L9 1" stroke="%23454F54"/%3E%3C/svg%3E%0A') !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position-x: calc(100% - 8px) !important;
|
||||
background-position-y: 55% !important;
|
||||
}
|
||||
|
||||
.c0:hover {
|
||||
color: #171A1C;
|
||||
border-color: #73848C;
|
||||
}
|
||||
|
||||
.c0:focus {
|
||||
outline: none;
|
||||
color: #171A1C;
|
||||
border-color: #454F54;
|
||||
}
|
||||
|
||||
.c0:disabled {
|
||||
color: #ABB5BA;
|
||||
border-color: #E3E6E8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
padding-right: 16px;
|
||||
list-style-position: inside;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<i>
|
||||
<svg
|
||||
fill="none"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle
|
||||
cx="8"
|
||||
cy="8"
|
||||
fill="#FAD1D1"
|
||||
r="7"
|
||||
/>
|
||||
<circle
|
||||
cx="8"
|
||||
cy="8"
|
||||
fill="#E51A1A"
|
||||
r="4"
|
||||
/>
|
||||
</svg>
|
||||
</i>
|
||||
<ul
|
||||
class="c0"
|
||||
name="test"
|
||||
role="listbox"
|
||||
>
|
||||
<li
|
||||
class="c1"
|
||||
role="option"
|
||||
tabindex="0"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
||||
|
||||
exports[`Custom Select when non-live matches the snapshot 1`] = `
|
||||
.c0 {
|
||||
position: relative;
|
||||
list-style: none;
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
border: 1px #ABB5BA solid;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
width: -webkit-fit-content;
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
padding-left: 12px;
|
||||
padding-right: 16px;
|
||||
color: #171A1C;
|
||||
min-width: auto;
|
||||
background-image: url('data:image/svg+xml,%3Csvg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 1L5 5L9 1" stroke="%23454F54"/%3E%3C/svg%3E%0A') !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position-x: calc(100% - 8px) !important;
|
||||
background-position-y: 55% !important;
|
||||
}
|
||||
|
||||
.c0:hover {
|
||||
color: #171A1C;
|
||||
border-color: #73848C;
|
||||
}
|
||||
|
||||
.c0:focus {
|
||||
outline: none;
|
||||
color: #171A1C;
|
||||
border-color: #454F54;
|
||||
}
|
||||
|
||||
.c0:disabled {
|
||||
color: #ABB5BA;
|
||||
border-color: #E3E6E8;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
padding-right: 16px;
|
||||
list-style-position: inside;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<div>
|
||||
<ul
|
||||
class="c0"
|
||||
name="test"
|
||||
role="listbox"
|
||||
>
|
||||
<li
|
||||
class="c1"
|
||||
role="option"
|
||||
tabindex="0"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
`;
|
Loading…
Add table
Reference in a new issue