New Dropdown component (#2355)
* New Dropdown component * Cleanup * Styling * Minireset
This commit is contained in:
parent
bff27f1b5b
commit
70414d2279
24 changed files with 219 additions and 278 deletions
|
@ -11,7 +11,7 @@
|
|||
"@hookform/error-message": "^2.0.0",
|
||||
"@hookform/resolvers": "^2.7.1",
|
||||
"@reduxjs/toolkit": "^1.8.3",
|
||||
"@rooks/use-outside-click-ref": "^4.10.1",
|
||||
"@szhsin/react-menu": "^3.1.1",
|
||||
"@tanstack/react-query": "^4.0.5",
|
||||
"@testing-library/react": "^13.2.0",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
|
|
34
kafka-ui-react-app/pnpm-lock.yaml
generated
34
kafka-ui-react-app/pnpm-lock.yaml
generated
|
@ -13,7 +13,7 @@ specifiers:
|
|||
'@jest/types': ^28.1.1
|
||||
'@openapitools/openapi-generator-cli': ^2.5.1
|
||||
'@reduxjs/toolkit': ^1.8.3
|
||||
'@rooks/use-outside-click-ref': ^4.10.1
|
||||
'@szhsin/react-menu': ^3.1.1
|
||||
'@tanstack/react-query': ^4.0.5
|
||||
'@testing-library/dom': ^8.11.1
|
||||
'@testing-library/jest-dom': ^5.16.4
|
||||
|
@ -97,7 +97,7 @@ dependencies:
|
|||
'@hookform/error-message': 2.0.0_l2dcsysovzdujulgxvsen7vbsm
|
||||
'@hookform/resolvers': 2.8.9_react-hook-form@7.6.9
|
||||
'@reduxjs/toolkit': 1.8.3_ctm756ikdwcjcvyfxxwskzbr6q
|
||||
'@rooks/use-outside-click-ref': 4.11.2_react@18.1.0
|
||||
'@szhsin/react-menu': 3.1.1_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
'@tanstack/react-query': 4.0.5_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
'@testing-library/react': 13.2.0_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
'@types/testing-library__jest-dom': 5.14.5
|
||||
|
@ -2352,14 +2352,6 @@ packages:
|
|||
reselect: 4.1.5
|
||||
dev: false
|
||||
|
||||
/@rooks/use-outside-click-ref/4.11.2_react@18.1.0:
|
||||
resolution: {integrity: sha512-w2bCW69zcpLh0KmN/odAuBsQ3sps+73KEu7zMOi0o4YMfDo+tXcqwlTJiLYysd0BEoQC9pNIklzZmI9zZep69g==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
dependencies:
|
||||
react: 18.1.0
|
||||
dev: false
|
||||
|
||||
/@rushstack/eslint-patch/1.1.3:
|
||||
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
||||
dev: true
|
||||
|
@ -2377,6 +2369,18 @@ packages:
|
|||
dependencies:
|
||||
'@sinonjs/commons': 1.8.3
|
||||
|
||||
/@szhsin/react-menu/3.1.1_ef5jwxihqo6n7gxfmzogljlgcm:
|
||||
resolution: {integrity: sha512-IdHLyH61M+KqjTrvqglKo7JnbC0GIkg4OCtlXBxQPEjx/ecR5g0Iycqm+SG3rObEoniLZEz32iJkefve/LAHMA==}
|
||||
peerDependencies:
|
||||
react: '>=16.14.0'
|
||||
react-dom: '>=16.14.0'
|
||||
dependencies:
|
||||
prop-types: 15.8.1
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
react-transition-state: 1.1.4_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
dev: false
|
||||
|
||||
/@tanstack/query-core/4.0.5:
|
||||
resolution: {integrity: sha512-QOJ2gLbwlf8p0487pMey6vv8EF5X2ib1zINayaD7mb9/LibUtXmZ12uJgTqcnjgNY/4tWZn5qJnEk2ePG5AVGA==}
|
||||
dev: false
|
||||
|
@ -6752,6 +6756,16 @@ packages:
|
|||
react: 18.1.0
|
||||
dev: false
|
||||
|
||||
/react-transition-state/1.1.4_ef5jwxihqo6n7gxfmzogljlgcm:
|
||||
resolution: {integrity: sha512-6nQLWWx95gYazCm6OdtD1zGbRiirvVXPrDtHAGsYb4xs9spMM7bA8Vx77KCpjL8PJ8qz1lXFGz2PTboCSvt7iw==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
dependencies:
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
dev: false
|
||||
|
||||
/react/18.1.0:
|
||||
resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
|
@ -7,11 +7,9 @@ import {
|
|||
} from 'lib/hooks/api/kafkaConnect';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { RouterParamsClusterConnectConnector } from 'lib/paths';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import getTagColor from 'components/common/Tag/getTagColor';
|
||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||
|
||||
const Tasks: React.FC = () => {
|
||||
const routerProps = useAppParams<RouterParamsClusterConnectConnector>();
|
||||
|
@ -50,7 +48,7 @@ const Tasks: React.FC = () => {
|
|||
<td>{task.status.trace || 'null'}</td>
|
||||
<td style={{ width: '5%' }}>
|
||||
<div>
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<Dropdown>
|
||||
<DropdownItem
|
||||
onClick={() => restartTaskHandler(task.id?.task)}
|
||||
danger
|
||||
|
|
|
@ -3,15 +3,13 @@ import { FullConnectorInfo } from 'generated-sources';
|
|||
import { clusterConnectConnectorPath, clusterTopicPath } from 'lib/paths';
|
||||
import { ClusterName } from 'redux/interfaces';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||
import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.styled';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import getTagColor from 'components/common/Tag/getTagColor';
|
||||
import useModal from 'lib/hooks/useModal';
|
||||
import { useDeleteConnector } from 'lib/hooks/api/kafkaConnect';
|
||||
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||
|
||||
import * as S from './List.styled';
|
||||
|
||||
|
@ -79,7 +77,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<Dropdown label={<VerticalElipsisIcon />} right up>
|
||||
<Dropdown>
|
||||
<DropdownItem onClick={setOpen} danger>
|
||||
Remove Connector
|
||||
</DropdownItem>
|
||||
|
|
|
@ -9,11 +9,8 @@ import PageLoader from 'components/common/PageLoader/PageLoader';
|
|||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import * as Metrics from 'components/common/Metrics';
|
||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
|
@ -26,6 +23,7 @@ import {
|
|||
getAreConsumerGroupDetailsFulfilled,
|
||||
} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||
import getTagColor from 'components/common/Tag/getTagColor';
|
||||
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||
|
||||
import ListItem from './ListItem';
|
||||
|
||||
|
@ -72,7 +70,7 @@ const Details: React.FC = () => {
|
|||
<div>
|
||||
<PageHeading text={consumerGroupID}>
|
||||
{!isReadOnly && (
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<Dropdown>
|
||||
<DropdownItem onClick={onResetOffsets}>Reset offset</DropdownItem>
|
||||
<DropdownItem
|
||||
onClick={() => setIsConfirmationModalVisible(true)}
|
||||
|
|
|
@ -10,9 +10,6 @@ import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationM
|
|||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import { Button } from 'components/common/Button/Button';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
||||
|
@ -31,6 +28,7 @@ import { resetLoaderById } from 'redux/reducers/loader/loaderSlice';
|
|||
import { TableTitle } from 'components/common/table/TableTitle/TableTitle.styled';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { schemasApiClient } from 'lib/api';
|
||||
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||
|
||||
import LatestVersionItem from './LatestVersion/LatestVersionItem';
|
||||
import SchemaVersion from './SchemaVersion/SchemaVersion';
|
||||
|
@ -101,7 +99,7 @@ const Details: React.FC = () => {
|
|||
>
|
||||
Edit Schema
|
||||
</Button>
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<Dropdown>
|
||||
<DropdownItem
|
||||
onClick={() => setDeleteSchemaConfirmationVisible(true)}
|
||||
danger
|
||||
|
|
|
@ -6,11 +6,8 @@ import {
|
|||
} from 'generated-sources';
|
||||
import { useAppDispatch } from 'lib/hooks/redux';
|
||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import { TableCellProps } from 'components/common/SmartTable/TableColumn';
|
||||
import { TopicWithDetailedInfo } from 'redux/interfaces';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import * as S from 'components/Topics/List/List.styled';
|
||||
import { ClusterNameRoute } from 'lib/paths';
|
||||
|
@ -22,6 +19,7 @@ import {
|
|||
recreateTopic,
|
||||
} from 'redux/reducers/topics/topicsSlice';
|
||||
import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
|
||||
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||
|
||||
interface TopicsListParams {
|
||||
clusterName: string;
|
||||
|
@ -68,8 +66,10 @@ const ActionsCell: React.FC<
|
|||
|
||||
const isHidden = internal || isReadOnly || !hovered;
|
||||
|
||||
const deleteTopicHandler = () =>
|
||||
const deleteTopicHandler = () => {
|
||||
dispatch(deleteTopic({ clusterName, topicName: name }));
|
||||
closeDeleteTopicModal();
|
||||
};
|
||||
|
||||
const clearTopicMessagesHandler = () => {
|
||||
dispatch(clearTopicMessages({ clusterName, topicName: name }));
|
||||
|
@ -86,20 +86,20 @@ const ActionsCell: React.FC<
|
|||
<>
|
||||
<S.ActionsContainer>
|
||||
{!isHidden && (
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<Dropdown>
|
||||
{cleanUpPolicy === CleanUpPolicy.DELETE && (
|
||||
<DropdownItem onClick={openClearMessagesModal} danger>
|
||||
Clear Messages
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem onClick={openRecreateTopicModal} danger>
|
||||
Recreate Topic
|
||||
</DropdownItem>
|
||||
{isTopicDeletionAllowed && (
|
||||
<DropdownItem onClick={openDeleteTopicModal} danger>
|
||||
Remove Topic
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem onClick={openRecreateTopicModal} danger>
|
||||
Recreate Topic
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
)}
|
||||
</S.ActionsContainer>
|
||||
|
|
|
@ -1,12 +1,5 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
export const DropdownExtraMessage = styled.div`
|
||||
color: ${({ theme }) => theme.topicMetaData.color.label};
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
export const ReplicaCell = styled.span.attrs({ 'aria-label': 'replica-info' })<{
|
||||
leader?: boolean;
|
||||
}>`
|
||||
|
|
|
@ -13,18 +13,19 @@ import ClusterContext from 'components/contexts/ClusterContext';
|
|||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||
import { Button } from 'components/common/Button/Button';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import styled from 'styled-components';
|
||||
import Navbar from 'components/common/Navigation/Navbar.styled';
|
||||
import * as S from 'components/Topics/Topic/Details/Details.styled';
|
||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
import {
|
||||
getIsTopicDeletePolicy,
|
||||
getIsTopicInternal,
|
||||
} from 'redux/reducers/topics/selectors';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownItemHint,
|
||||
} from 'components/common/Dropdown';
|
||||
|
||||
import OverviewContainer from './Overview/OverviewContainer';
|
||||
import TopicConsumerGroupsContainer from './ConsumerGroups/TopicConsumerGroupsContainer';
|
||||
|
@ -32,7 +33,6 @@ import SettingsContainer from './Settings/SettingsContainer';
|
|||
import Messages from './Messages/Messages';
|
||||
|
||||
interface Props {
|
||||
isDeleted: boolean;
|
||||
deleteTopic: (payload: {
|
||||
clusterName: ClusterName;
|
||||
topicName: TopicName;
|
||||
|
@ -55,7 +55,6 @@ const HeaderControlsWrapper = styled.div`
|
|||
`;
|
||||
|
||||
const Details: React.FC<Props> = ({
|
||||
isDeleted,
|
||||
deleteTopic,
|
||||
recreateTopic,
|
||||
clearTopicMessages,
|
||||
|
@ -65,15 +64,14 @@ const Details: React.FC<Props> = ({
|
|||
const isInternal = useAppSelector((state) =>
|
||||
getIsTopicInternal(state, topicName)
|
||||
);
|
||||
|
||||
const isDeletePolicy = useAppSelector((state) =>
|
||||
getIsTopicDeletePolicy(state, topicName)
|
||||
);
|
||||
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const { isReadOnly, isTopicDeletionAllowed } =
|
||||
React.useContext(ClusterContext);
|
||||
|
||||
const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] =
|
||||
React.useState(false);
|
||||
const [isClearTopicConfirmationVisible, setClearTopicConfirmationVisible] =
|
||||
|
@ -82,13 +80,11 @@ const Details: React.FC<Props> = ({
|
|||
isRecreateTopicConfirmationVisible,
|
||||
setRecreateTopicConfirmationVisible,
|
||||
] = React.useState(false);
|
||||
const deleteTopicHandler = () => deleteTopic({ clusterName, topicName });
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isDeleted) {
|
||||
const deleteTopicHandler = () => {
|
||||
deleteTopic({ clusterName, topicName });
|
||||
setDeleteTopicConfirmationVisible(false);
|
||||
navigate('../..');
|
||||
}
|
||||
}, [isDeleted, clusterName, dispatch, navigate]);
|
||||
};
|
||||
|
||||
const clearTopicMessagesHandler = () => {
|
||||
clearTopicMessages({ clusterName, topicName });
|
||||
|
@ -124,39 +120,37 @@ const Details: React.FC<Props> = ({
|
|||
<Route
|
||||
index
|
||||
element={
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<Dropdown>
|
||||
<DropdownItem
|
||||
onClick={() => navigate(clusterTopicEditRelativePath)}
|
||||
>
|
||||
Edit settings
|
||||
<S.DropdownExtraMessage>
|
||||
<DropdownItemHint>
|
||||
Pay attention! This operation has
|
||||
<br />
|
||||
especially important consequences.
|
||||
</S.DropdownExtraMessage>
|
||||
</DropdownItemHint>
|
||||
</DropdownItem>
|
||||
{isDeletePolicy && (
|
||||
<DropdownItem
|
||||
disabled={!isDeletePolicy}
|
||||
onClick={() => setClearTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Clear messages
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem
|
||||
onClick={() => setRecreateTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Recreate Topic
|
||||
</DropdownItem>
|
||||
{isTopicDeletionAllowed && (
|
||||
<DropdownItem
|
||||
disabled={!isTopicDeletionAllowed}
|
||||
onClick={() => setDeleteTopicConfirmationVisible(true)}
|
||||
danger
|
||||
>
|
||||
Remove topic
|
||||
Remove Topic
|
||||
</DropdownItem>
|
||||
)}
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
|
@ -214,14 +208,11 @@ const Details: React.FC<Props> = ({
|
|||
</Navbar>
|
||||
<Routes>
|
||||
<Route index element={<OverviewContainer />} />
|
||||
|
||||
<Route path={clusterTopicMessagesRelativePath} element={<Messages />} />
|
||||
|
||||
<Route
|
||||
path={clusterTopicSettingsRelativePath}
|
||||
element={<SettingsContainer />}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={clusterTopicConsumerGroupsRelativePath}
|
||||
element={<TopicConsumerGroupsContainer />}
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import React from 'react';
|
||||
import dayjs from 'dayjs';
|
||||
import { TopicMessage } from 'generated-sources';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import useDataSaver from 'lib/hooks/useDataSaver';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import MessageToggleIcon from 'components/common/Icons/MessageToggleIcon';
|
||||
import IconButtonWrapper from 'components/common/Icons/IconButtonWrapper';
|
||||
import styled from 'styled-components';
|
||||
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||
|
||||
import MessageContent from './MessageContent/MessageContent';
|
||||
import * as S from './MessageContent/MessageContent.styled';
|
||||
|
@ -76,7 +74,7 @@ const Message: React.FC<Props> = ({
|
|||
</StyledDataCell>
|
||||
<td style={{ width: '5%' }}>
|
||||
{vEllipsisOpen && (
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<Dropdown>
|
||||
<DropdownItem onClick={copyToClipboard}>
|
||||
Copy to clipboard
|
||||
</DropdownItem>
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import React from 'react';
|
||||
import { Partition, Replica } from 'generated-sources';
|
||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
||||
import Dropdown from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import ClusterContext from 'components/contexts/ClusterContext';
|
||||
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
|
||||
import { Table } from 'components/common/table/Table/Table.styled';
|
||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import * as Metrics from 'components/common/Metrics';
|
||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||
import { useAppSelector } from 'lib/hooks/redux';
|
||||
|
@ -15,6 +12,7 @@ import { getTopicByName } from 'redux/reducers/topics/selectors';
|
|||
import { ReplicaCell } from 'components/Topics/Topic/Details/Details.styled';
|
||||
import { RouteParamsClusterTopic } from 'lib/paths';
|
||||
import useAppParams from 'lib/hooks/useAppParams';
|
||||
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||
|
||||
export interface Props {
|
||||
clearTopicMessages(params: {
|
||||
|
@ -135,7 +133,7 @@ const Overview: React.FC<Props> = ({ clearTopicMessages }) => {
|
|||
<td>{partition.offsetMax - partition.offsetMin}</td>
|
||||
<td style={{ width: '5%' }}>
|
||||
{!internal && !isReadOnly && cleanUpPolicy === 'DELETE' ? (
|
||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
||||
<Dropdown>
|
||||
<DropdownItem
|
||||
onClick={() =>
|
||||
clearTopicMessages({
|
||||
|
|
|
@ -80,7 +80,7 @@ describe('Overview', () => {
|
|||
});
|
||||
|
||||
describe('when it has internal flag', () => {
|
||||
it('does not render the Action button a Topic', () => {
|
||||
it('renders the Action button for Topic', () => {
|
||||
setupComponent(
|
||||
{},
|
||||
{
|
||||
|
@ -90,7 +90,7 @@ describe('Overview', () => {
|
|||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||
}
|
||||
);
|
||||
expect(screen.getAllByRole('menu')[0]).toBeInTheDocument();
|
||||
expect(screen.getAllByLabelText('Dropdown Toggle').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('does not render Partitions', () => {
|
||||
|
|
|
@ -46,7 +46,6 @@ describe('Details', () => {
|
|||
deleteTopic={mockDelete}
|
||||
recreateTopic={mockRecreateTopic}
|
||||
clearTopicMessages={mockClearTopicMessages}
|
||||
isDeleted={false}
|
||||
{...props}
|
||||
/>
|
||||
</WithRoute>
|
||||
|
@ -83,7 +82,6 @@ describe('Details', () => {
|
|||
deleteTopic={mockDelete}
|
||||
recreateTopic={mockRecreateTopic}
|
||||
clearTopicMessages={mockClearTopicMessages}
|
||||
isDeleted={false}
|
||||
/>
|
||||
</ClusterContext.Provider>
|
||||
);
|
||||
|
@ -95,8 +93,7 @@ describe('Details', () => {
|
|||
describe('when remove topic modal is open', () => {
|
||||
beforeEach(() => {
|
||||
setupComponent();
|
||||
|
||||
const openModalButton = screen.getAllByText('Remove topic')[0];
|
||||
const openModalButton = screen.getAllByText('Remove Topic')[0];
|
||||
userEvent.click(openModalButton);
|
||||
});
|
||||
|
||||
|
@ -156,7 +153,13 @@ describe('Details', () => {
|
|||
});
|
||||
|
||||
it('redirects to the correct route if topic is deleted', () => {
|
||||
setupComponent({ isDeleted: true });
|
||||
setupComponent();
|
||||
|
||||
const deleteTopicButton = screen.getByText(/Remove topic/i);
|
||||
userEvent.click(deleteTopicButton);
|
||||
|
||||
const submitDeleteButton = screen.getByText(/Submit/i);
|
||||
userEvent.click(submitDeleteButton);
|
||||
|
||||
expect(mockNavigate).toHaveBeenCalledWith('../..');
|
||||
});
|
||||
|
|
|
@ -170,7 +170,7 @@ const SendMessage: React.FC = () => {
|
|||
aria-labelledby="selectPartitionOptions"
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
minWidth="100%"
|
||||
minWidth="100px"
|
||||
options={selectPartitionOptions}
|
||||
value={selectPartitionOptions[0].value}
|
||||
/>
|
||||
|
|
|
@ -1,30 +1,73 @@
|
|||
import styled from 'styled-components';
|
||||
import styled, { css, keyframes } from 'styled-components';
|
||||
import { ControlledMenu } from '@szhsin/react-menu';
|
||||
import { menuSelector, menuItemSelector } from '@szhsin/react-menu/style-utils';
|
||||
|
||||
export const TriggerWrapper = styled.div`
|
||||
display: flex;
|
||||
align-self: center;
|
||||
import '@szhsin/react-menu/dist/core.css';
|
||||
|
||||
const menuShow = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Trigger = styled.button.attrs({
|
||||
type: 'button',
|
||||
ariaHaspopup: 'true',
|
||||
ariaControls: 'dropdown-menu',
|
||||
})`
|
||||
background: transparent;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: 'center';
|
||||
justify-content: 'center';
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
const menuHide = keyframes`
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Item = styled.a.attrs({
|
||||
href: '#end',
|
||||
role: 'menuitem',
|
||||
type: 'button',
|
||||
})<{ $isDanger: boolean }>`
|
||||
color: ${({ $isDanger, theme }) =>
|
||||
$isDanger ? theme.dropdown.color : 'initial'};
|
||||
export const Dropdown = styled(ControlledMenu)(
|
||||
({ theme: { dropdown } }) => css`
|
||||
// container for the menu items
|
||||
${menuSelector.name} {
|
||||
border: 1px solid ${dropdown.borderColor};
|
||||
box-shadow: 0px 4px 16px ${dropdown.shadow};
|
||||
padding: 8px 0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background-color: ${dropdown.backgroundColor};
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
${menuSelector.stateOpening} {
|
||||
animation: ${menuShow} 0.15s ease-out;
|
||||
}
|
||||
|
||||
// NOTE: animation-fill-mode: forwards is required to
|
||||
// prevent flickering with React 18 createRoot()
|
||||
${menuSelector.stateClosing} {
|
||||
animation: ${menuHide} 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
${menuItemSelector.name} {
|
||||
padding: 6px 16px;
|
||||
min-width: 150px;
|
||||
background-color: ${dropdown.item.backgroundColor.default};
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
${menuItemSelector.hover} {
|
||||
background-color: ${dropdown.item.backgroundColor.hover};
|
||||
}
|
||||
|
||||
${menuItemSelector.disabled} {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const DropdownButton = styled.button`
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const DangerItem = styled.div`
|
||||
color: ${({ theme: { dropdown } }) => dropdown.item.color.danger};
|
||||
`;
|
||||
|
||||
export const DropdownItemHint = styled.div`
|
||||
color: ${({ theme }) => theme.topicMetaData.color.label};
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
`;
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
import useOutsideClickRef from '@rooks/use-outside-click-ref';
|
||||
import cx from 'classnames';
|
||||
import React, { PropsWithChildren, useMemo, useState } from 'react';
|
||||
import { MenuProps } from '@szhsin/react-menu';
|
||||
import React, { PropsWithChildren, useRef } from 'react';
|
||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||
import useModal from 'lib/hooks/useModal';
|
||||
|
||||
import * as S from './Dropdown.styled';
|
||||
|
||||
export interface DropdownProps {
|
||||
label: React.ReactNode;
|
||||
right?: boolean;
|
||||
up?: boolean;
|
||||
interface DropdownProps extends PropsWithChildren<Partial<MenuProps>> {
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Dropdown: React.FC<PropsWithChildren<DropdownProps>> = ({
|
||||
label,
|
||||
right,
|
||||
up,
|
||||
children,
|
||||
}) => {
|
||||
const [active, setActive] = useState<boolean>(false);
|
||||
const [wrapperRef] = useOutsideClickRef(() => setActive(false));
|
||||
const onClick = (e: React.MouseEvent) => {
|
||||
const Dropdown: React.FC<DropdownProps> = ({ label, children }) => {
|
||||
const ref = useRef(null);
|
||||
const { isOpen, setClose, setOpen } = useModal(false);
|
||||
|
||||
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setActive(!active);
|
||||
setOpen();
|
||||
};
|
||||
|
||||
const classNames = useMemo(
|
||||
() =>
|
||||
cx('dropdown', {
|
||||
'is-active': active,
|
||||
'is-right': right,
|
||||
'is-up': up,
|
||||
}),
|
||||
[active, right, up]
|
||||
);
|
||||
return (
|
||||
<div className={classNames} ref={wrapperRef}>
|
||||
<S.TriggerWrapper>
|
||||
<S.Trigger onClick={onClick}>{label}</S.Trigger>
|
||||
</S.TriggerWrapper>
|
||||
<div className="dropdown-menu" id="dropdown-menu" role="menu">
|
||||
<div className="dropdown-content has-text-left">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<S.DropdownButton
|
||||
onClick={handleClick}
|
||||
ref={ref}
|
||||
aria-label="Dropdown Toggle"
|
||||
>
|
||||
{label || <VerticalElipsisIcon />}
|
||||
</S.DropdownButton>
|
||||
<S.Dropdown
|
||||
anchorRef={ref}
|
||||
state={isOpen ? 'open' : 'closed'}
|
||||
onMouseLeave={setClose}
|
||||
onClose={setClose}
|
||||
align="end"
|
||||
direction="bottom"
|
||||
offsetY={10}
|
||||
viewScroll="auto"
|
||||
>
|
||||
{children}
|
||||
</S.Dropdown>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
const DropdownDivider: React.FC = () => <hr className="dropdown-divider" />;
|
||||
|
||||
export default DropdownDivider;
|
|
@ -1,31 +1,32 @@
|
|||
import React, { PropsWithChildren } from 'react';
|
||||
import { ClickEvent, MenuItem, MenuItemProps } from '@szhsin/react-menu';
|
||||
|
||||
import * as S from './Dropdown.styled';
|
||||
|
||||
interface DropdownItemProps {
|
||||
onClick(): void;
|
||||
interface DropdownItemProps extends PropsWithChildren<MenuItemProps> {
|
||||
danger?: boolean;
|
||||
onClick?(): void;
|
||||
}
|
||||
|
||||
const DropdownItem: React.FC<PropsWithChildren<DropdownItemProps>> = ({
|
||||
const DropdownItem: React.FC<DropdownItemProps> = ({
|
||||
onClick,
|
||||
danger,
|
||||
children,
|
||||
...rest
|
||||
}) => {
|
||||
const onClickHandler = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const handleClick = (e: ClickEvent) => {
|
||||
if (!onClick) return;
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
e.stopPropagation = true;
|
||||
e.syntheticEvent.stopPropagation();
|
||||
onClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<S.Item
|
||||
$isDanger={!!danger}
|
||||
onClick={onClickHandler}
|
||||
className="dropdown-item is-link"
|
||||
>
|
||||
{children}
|
||||
</S.Item>
|
||||
<MenuItem onClick={handleClick} {...rest}>
|
||||
{danger ? <S.DangerItem>{children}</S.DangerItem> : children}
|
||||
</MenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
import React from 'react';
|
||||
import Dropdown, { DropdownProps } from 'components/common/Dropdown/Dropdown';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import DropdownDivider from 'components/common/Dropdown/DropdownDivider';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
const dummyLable = 'My Test Label';
|
||||
const dummyChildren = (
|
||||
<>
|
||||
<DropdownItem onClick={jest.fn()}>Child 1</DropdownItem>
|
||||
<DropdownItem onClick={jest.fn()}>Child 2</DropdownItem>
|
||||
<DropdownDivider />
|
||||
<DropdownItem onClick={jest.fn()}>Child 3</DropdownItem>
|
||||
</>
|
||||
);
|
||||
|
||||
describe('Dropdown', () => {
|
||||
const setupWrapper = (
|
||||
props: Partial<DropdownProps> = {},
|
||||
children: React.ReactNode = undefined
|
||||
) => (
|
||||
<Dropdown label={dummyLable} {...props}>
|
||||
{children}
|
||||
</Dropdown>
|
||||
);
|
||||
|
||||
it('renders Dropdown with initial props', () => {
|
||||
const wrapper = render(setupWrapper()).baseElement;
|
||||
expect(wrapper.querySelector('.dropdown')).toBeTruthy();
|
||||
|
||||
expect(wrapper.querySelector('.dropdown.is-active')).toBeFalsy();
|
||||
expect(wrapper.querySelector('.dropdown.is-right')).toBeFalsy();
|
||||
expect(wrapper.querySelector('.dropdown.is-up')).toBeFalsy();
|
||||
|
||||
expect(wrapper.querySelector('.dropdown-content')).toBeTruthy();
|
||||
expect(wrapper.querySelector('.dropdown-content')).toHaveTextContent('');
|
||||
});
|
||||
|
||||
it('renders custom children', () => {
|
||||
const wrapper = render(setupWrapper({}, dummyChildren)).baseElement;
|
||||
expect(wrapper.querySelector('.dropdown-content')).toBeTruthy();
|
||||
expect(wrapper.querySelectorAll('.dropdown-item').length).toEqual(3);
|
||||
expect(wrapper.querySelectorAll('.dropdown-divider').length).toEqual(1);
|
||||
});
|
||||
|
||||
it('renders dropdown with a right-aligned menu', () => {
|
||||
const wrapper = render(setupWrapper({ right: true })).baseElement;
|
||||
expect(wrapper.querySelector('.dropdown.is-right')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders dropdown with a popup menu', () => {
|
||||
const wrapper = render(setupWrapper({ up: true })).baseElement;
|
||||
expect(wrapper.querySelector('.dropdown.is-up')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('handles click', () => {
|
||||
const wrapper = render(setupWrapper()).baseElement;
|
||||
const button = screen.getByText('My Test Label');
|
||||
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(wrapper.querySelector('.dropdown.is-active')).toBeFalsy();
|
||||
|
||||
userEvent.click(button);
|
||||
expect(wrapper.querySelector('.dropdown.is-active')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('to be in the document', () => {
|
||||
render(
|
||||
setupWrapper(
|
||||
{
|
||||
right: true,
|
||||
up: true,
|
||||
},
|
||||
dummyChildren
|
||||
)
|
||||
);
|
||||
expect(screen.getByRole('menu')).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
import React from 'react';
|
||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
||||
import { render } from 'lib/testHelpers';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
const onClick = jest.fn();
|
||||
|
||||
describe('DropdownItem', () => {
|
||||
it('to be in the document', () => {
|
||||
render(<DropdownItem onClick={jest.fn()}>Item 1</DropdownItem>);
|
||||
expect(screen.getByText('Item 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles Click', () => {
|
||||
render(<DropdownItem onClick={onClick}>Item 1</DropdownItem>);
|
||||
userEvent.click(screen.getByText('Item 1'));
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import { DropdownItemHint } from './Dropdown.styled';
|
||||
import Dropdown from './Dropdown';
|
||||
import DropdownItem from './DropdownItem';
|
||||
|
||||
export { Dropdown, DropdownItem, DropdownItemHint };
|
|
@ -1,12 +1,9 @@
|
|||
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||
|
||||
// Base
|
||||
@import "bulma/sass/base/minireset";
|
||||
@import "./minireset";
|
||||
@import "bulma/sass/base/generic";
|
||||
|
||||
// Components
|
||||
@import "bulma/sass/components/dropdown";
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500&display=swap');
|
||||
|
||||
|
|
1
kafka-ui-react-app/src/theme/minireset.css
Normal file
1
kafka-ui-react-app/src/theme/minireset.css
Normal file
|
@ -0,0 +1 @@
|
|||
/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */html,body,p,ol,ul,li,dl,dt,dd,blockquote,figure,fieldset,legend,textarea,pre,iframe,hr,h1,h2,h3,h4,h5,h6{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal}ul{list-style:none}button,input,select{margin:0}html{box-sizing:border-box}*,*::before,*::after{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}
|
|
@ -77,7 +77,18 @@ const theme = {
|
|||
breadcrumb: Colors.neutral[30],
|
||||
connectEditWarning: Colors.yellow[10],
|
||||
dropdown: {
|
||||
color: Colors.red[50],
|
||||
backgroundColor: Colors.neutral[0],
|
||||
borderColor: Colors.neutral[5],
|
||||
shadow: Colors.transparency[20],
|
||||
item: {
|
||||
color: {
|
||||
danger: Colors.red[60],
|
||||
},
|
||||
backgroundColor: {
|
||||
default: Colors.neutral[0],
|
||||
hover: Colors.neutral[5],
|
||||
},
|
||||
},
|
||||
},
|
||||
ksqlDb: {
|
||||
query: {
|
||||
|
|
Loading…
Add table
Reference in a new issue