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/error-message": "^2.0.0",
|
||||||
"@hookform/resolvers": "^2.7.1",
|
"@hookform/resolvers": "^2.7.1",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@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",
|
"@tanstack/react-query": "^4.0.5",
|
||||||
"@testing-library/react": "^13.2.0",
|
"@testing-library/react": "^13.2.0",
|
||||||
"@types/testing-library__jest-dom": "^5.14.5",
|
"@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
|
'@jest/types': ^28.1.1
|
||||||
'@openapitools/openapi-generator-cli': ^2.5.1
|
'@openapitools/openapi-generator-cli': ^2.5.1
|
||||||
'@reduxjs/toolkit': ^1.8.3
|
'@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
|
'@tanstack/react-query': ^4.0.5
|
||||||
'@testing-library/dom': ^8.11.1
|
'@testing-library/dom': ^8.11.1
|
||||||
'@testing-library/jest-dom': ^5.16.4
|
'@testing-library/jest-dom': ^5.16.4
|
||||||
|
@ -97,7 +97,7 @@ dependencies:
|
||||||
'@hookform/error-message': 2.0.0_l2dcsysovzdujulgxvsen7vbsm
|
'@hookform/error-message': 2.0.0_l2dcsysovzdujulgxvsen7vbsm
|
||||||
'@hookform/resolvers': 2.8.9_react-hook-form@7.6.9
|
'@hookform/resolvers': 2.8.9_react-hook-form@7.6.9
|
||||||
'@reduxjs/toolkit': 1.8.3_ctm756ikdwcjcvyfxxwskzbr6q
|
'@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
|
'@tanstack/react-query': 4.0.5_ef5jwxihqo6n7gxfmzogljlgcm
|
||||||
'@testing-library/react': 13.2.0_ef5jwxihqo6n7gxfmzogljlgcm
|
'@testing-library/react': 13.2.0_ef5jwxihqo6n7gxfmzogljlgcm
|
||||||
'@types/testing-library__jest-dom': 5.14.5
|
'@types/testing-library__jest-dom': 5.14.5
|
||||||
|
@ -2352,14 +2352,6 @@ packages:
|
||||||
reselect: 4.1.5
|
reselect: 4.1.5
|
||||||
dev: false
|
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:
|
/@rushstack/eslint-patch/1.1.3:
|
||||||
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
resolution: {integrity: sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -2377,6 +2369,18 @@ packages:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sinonjs/commons': 1.8.3
|
'@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:
|
/@tanstack/query-core/4.0.5:
|
||||||
resolution: {integrity: sha512-QOJ2gLbwlf8p0487pMey6vv8EF5X2ib1zINayaD7mb9/LibUtXmZ12uJgTqcnjgNY/4tWZn5qJnEk2ePG5AVGA==}
|
resolution: {integrity: sha512-QOJ2gLbwlf8p0487pMey6vv8EF5X2ib1zINayaD7mb9/LibUtXmZ12uJgTqcnjgNY/4tWZn5qJnEk2ePG5AVGA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -6752,6 +6756,16 @@ packages:
|
||||||
react: 18.1.0
|
react: 18.1.0
|
||||||
dev: false
|
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:
|
/react/18.1.0:
|
||||||
resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==}
|
resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
|
@ -7,11 +7,9 @@ import {
|
||||||
} from 'lib/hooks/api/kafkaConnect';
|
} from 'lib/hooks/api/kafkaConnect';
|
||||||
import useAppParams from 'lib/hooks/useAppParams';
|
import useAppParams from 'lib/hooks/useAppParams';
|
||||||
import { RouterParamsClusterConnectConnector } from 'lib/paths';
|
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 getTagColor from 'components/common/Tag/getTagColor';
|
||||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||||
|
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||||
|
|
||||||
const Tasks: React.FC = () => {
|
const Tasks: React.FC = () => {
|
||||||
const routerProps = useAppParams<RouterParamsClusterConnectConnector>();
|
const routerProps = useAppParams<RouterParamsClusterConnectConnector>();
|
||||||
|
@ -50,7 +48,7 @@ const Tasks: React.FC = () => {
|
||||||
<td>{task.status.trace || 'null'}</td>
|
<td>{task.status.trace || 'null'}</td>
|
||||||
<td style={{ width: '5%' }}>
|
<td style={{ width: '5%' }}>
|
||||||
<div>
|
<div>
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
<Dropdown>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() => restartTaskHandler(task.id?.task)}
|
onClick={() => restartTaskHandler(task.id?.task)}
|
||||||
danger
|
danger
|
||||||
|
|
|
@ -3,15 +3,13 @@ import { FullConnectorInfo } from 'generated-sources';
|
||||||
import { clusterConnectConnectorPath, clusterTopicPath } from 'lib/paths';
|
import { clusterConnectConnectorPath, clusterTopicPath } from 'lib/paths';
|
||||||
import { ClusterName } from 'redux/interfaces';
|
import { ClusterName } from 'redux/interfaces';
|
||||||
import { Link, NavLink } from 'react-router-dom';
|
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 ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||||
import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.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 getTagColor from 'components/common/Tag/getTagColor';
|
||||||
import useModal from 'lib/hooks/useModal';
|
import useModal from 'lib/hooks/useModal';
|
||||||
import { useDeleteConnector } from 'lib/hooks/api/kafkaConnect';
|
import { useDeleteConnector } from 'lib/hooks/api/kafkaConnect';
|
||||||
|
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||||
|
|
||||||
import * as S from './List.styled';
|
import * as S from './List.styled';
|
||||||
|
|
||||||
|
@ -79,7 +77,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div>
|
<div>
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right up>
|
<Dropdown>
|
||||||
<DropdownItem onClick={setOpen} danger>
|
<DropdownItem onClick={setOpen} danger>
|
||||||
Remove Connector
|
Remove Connector
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
|
@ -9,11 +9,8 @@ import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||||
import ClusterContext from 'components/contexts/ClusterContext';
|
import ClusterContext from 'components/contexts/ClusterContext';
|
||||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
|
||||||
import * as Metrics from 'components/common/Metrics';
|
import * as Metrics from 'components/common/Metrics';
|
||||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
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 groupBy from 'lodash/groupBy';
|
||||||
import { Table } from 'components/common/table/Table/Table.styled';
|
import { Table } from 'components/common/table/Table/Table.styled';
|
||||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||||
|
@ -26,6 +23,7 @@ import {
|
||||||
getAreConsumerGroupDetailsFulfilled,
|
getAreConsumerGroupDetailsFulfilled,
|
||||||
} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
} from 'redux/reducers/consumerGroups/consumerGroupsSlice';
|
||||||
import getTagColor from 'components/common/Tag/getTagColor';
|
import getTagColor from 'components/common/Tag/getTagColor';
|
||||||
|
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||||
|
|
||||||
import ListItem from './ListItem';
|
import ListItem from './ListItem';
|
||||||
|
|
||||||
|
@ -72,7 +70,7 @@ const Details: React.FC = () => {
|
||||||
<div>
|
<div>
|
||||||
<PageHeading text={consumerGroupID}>
|
<PageHeading text={consumerGroupID}>
|
||||||
{!isReadOnly && (
|
{!isReadOnly && (
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
<Dropdown>
|
||||||
<DropdownItem onClick={onResetOffsets}>Reset offset</DropdownItem>
|
<DropdownItem onClick={onResetOffsets}>Reset offset</DropdownItem>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() => setIsConfirmationModalVisible(true)}
|
onClick={() => setIsConfirmationModalVisible(true)}
|
||||||
|
|
|
@ -10,9 +10,6 @@ import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationM
|
||||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||||
import { Button } from 'components/common/Button/Button';
|
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 { Table } from 'components/common/table/Table/Table.styled';
|
||||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
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 { TableTitle } from 'components/common/table/TableTitle/TableTitle.styled';
|
||||||
import useAppParams from 'lib/hooks/useAppParams';
|
import useAppParams from 'lib/hooks/useAppParams';
|
||||||
import { schemasApiClient } from 'lib/api';
|
import { schemasApiClient } from 'lib/api';
|
||||||
|
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||||
|
|
||||||
import LatestVersionItem from './LatestVersion/LatestVersionItem';
|
import LatestVersionItem from './LatestVersion/LatestVersionItem';
|
||||||
import SchemaVersion from './SchemaVersion/SchemaVersion';
|
import SchemaVersion from './SchemaVersion/SchemaVersion';
|
||||||
|
@ -101,7 +99,7 @@ const Details: React.FC = () => {
|
||||||
>
|
>
|
||||||
Edit Schema
|
Edit Schema
|
||||||
</Button>
|
</Button>
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
<Dropdown>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() => setDeleteSchemaConfirmationVisible(true)}
|
onClick={() => setDeleteSchemaConfirmationVisible(true)}
|
||||||
danger
|
danger
|
||||||
|
|
|
@ -6,11 +6,8 @@ import {
|
||||||
} from 'generated-sources';
|
} from 'generated-sources';
|
||||||
import { useAppDispatch } from 'lib/hooks/redux';
|
import { useAppDispatch } from 'lib/hooks/redux';
|
||||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||||
import DropdownItem from 'components/common/Dropdown/DropdownItem';
|
|
||||||
import { TableCellProps } from 'components/common/SmartTable/TableColumn';
|
import { TableCellProps } from 'components/common/SmartTable/TableColumn';
|
||||||
import { TopicWithDetailedInfo } from 'redux/interfaces';
|
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 ClusterContext from 'components/contexts/ClusterContext';
|
||||||
import * as S from 'components/Topics/List/List.styled';
|
import * as S from 'components/Topics/List/List.styled';
|
||||||
import { ClusterNameRoute } from 'lib/paths';
|
import { ClusterNameRoute } from 'lib/paths';
|
||||||
|
@ -22,6 +19,7 @@ import {
|
||||||
recreateTopic,
|
recreateTopic,
|
||||||
} from 'redux/reducers/topics/topicsSlice';
|
} from 'redux/reducers/topics/topicsSlice';
|
||||||
import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
|
import { clearTopicMessages } from 'redux/reducers/topicMessages/topicMessagesSlice';
|
||||||
|
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||||
|
|
||||||
interface TopicsListParams {
|
interface TopicsListParams {
|
||||||
clusterName: string;
|
clusterName: string;
|
||||||
|
@ -68,8 +66,10 @@ const ActionsCell: React.FC<
|
||||||
|
|
||||||
const isHidden = internal || isReadOnly || !hovered;
|
const isHidden = internal || isReadOnly || !hovered;
|
||||||
|
|
||||||
const deleteTopicHandler = () =>
|
const deleteTopicHandler = () => {
|
||||||
dispatch(deleteTopic({ clusterName, topicName: name }));
|
dispatch(deleteTopic({ clusterName, topicName: name }));
|
||||||
|
closeDeleteTopicModal();
|
||||||
|
};
|
||||||
|
|
||||||
const clearTopicMessagesHandler = () => {
|
const clearTopicMessagesHandler = () => {
|
||||||
dispatch(clearTopicMessages({ clusterName, topicName: name }));
|
dispatch(clearTopicMessages({ clusterName, topicName: name }));
|
||||||
|
@ -86,20 +86,20 @@ const ActionsCell: React.FC<
|
||||||
<>
|
<>
|
||||||
<S.ActionsContainer>
|
<S.ActionsContainer>
|
||||||
{!isHidden && (
|
{!isHidden && (
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
<Dropdown>
|
||||||
{cleanUpPolicy === CleanUpPolicy.DELETE && (
|
{cleanUpPolicy === CleanUpPolicy.DELETE && (
|
||||||
<DropdownItem onClick={openClearMessagesModal} danger>
|
<DropdownItem onClick={openClearMessagesModal} danger>
|
||||||
Clear Messages
|
Clear Messages
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
)}
|
)}
|
||||||
|
<DropdownItem onClick={openRecreateTopicModal} danger>
|
||||||
|
Recreate Topic
|
||||||
|
</DropdownItem>
|
||||||
{isTopicDeletionAllowed && (
|
{isTopicDeletionAllowed && (
|
||||||
<DropdownItem onClick={openDeleteTopicModal} danger>
|
<DropdownItem onClick={openDeleteTopicModal} danger>
|
||||||
Remove Topic
|
Remove Topic
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
)}
|
)}
|
||||||
<DropdownItem onClick={openRecreateTopicModal} danger>
|
|
||||||
Recreate Topic
|
|
||||||
</DropdownItem>
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
)}
|
)}
|
||||||
</S.ActionsContainer>
|
</S.ActionsContainer>
|
||||||
|
|
|
@ -1,12 +1,5 @@
|
||||||
import styled from 'styled-components';
|
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' })<{
|
export const ReplicaCell = styled.span.attrs({ 'aria-label': 'replica-info' })<{
|
||||||
leader?: boolean;
|
leader?: boolean;
|
||||||
}>`
|
}>`
|
||||||
|
|
|
@ -13,18 +13,19 @@ import ClusterContext from 'components/contexts/ClusterContext';
|
||||||
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
|
||||||
import PageHeading from 'components/common/PageHeading/PageHeading';
|
import PageHeading from 'components/common/PageHeading/PageHeading';
|
||||||
import { Button } from 'components/common/Button/Button';
|
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 styled from 'styled-components';
|
||||||
import Navbar from 'components/common/Navigation/Navbar.styled';
|
import Navbar from 'components/common/Navigation/Navbar.styled';
|
||||||
import * as S from 'components/Topics/Topic/Details/Details.styled';
|
import { useAppSelector } from 'lib/hooks/redux';
|
||||||
import { useAppDispatch, useAppSelector } from 'lib/hooks/redux';
|
|
||||||
import {
|
import {
|
||||||
getIsTopicDeletePolicy,
|
getIsTopicDeletePolicy,
|
||||||
getIsTopicInternal,
|
getIsTopicInternal,
|
||||||
} from 'redux/reducers/topics/selectors';
|
} from 'redux/reducers/topics/selectors';
|
||||||
import useAppParams from 'lib/hooks/useAppParams';
|
import useAppParams from 'lib/hooks/useAppParams';
|
||||||
|
import {
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
DropdownItemHint,
|
||||||
|
} from 'components/common/Dropdown';
|
||||||
|
|
||||||
import OverviewContainer from './Overview/OverviewContainer';
|
import OverviewContainer from './Overview/OverviewContainer';
|
||||||
import TopicConsumerGroupsContainer from './ConsumerGroups/TopicConsumerGroupsContainer';
|
import TopicConsumerGroupsContainer from './ConsumerGroups/TopicConsumerGroupsContainer';
|
||||||
|
@ -32,7 +33,6 @@ import SettingsContainer from './Settings/SettingsContainer';
|
||||||
import Messages from './Messages/Messages';
|
import Messages from './Messages/Messages';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isDeleted: boolean;
|
|
||||||
deleteTopic: (payload: {
|
deleteTopic: (payload: {
|
||||||
clusterName: ClusterName;
|
clusterName: ClusterName;
|
||||||
topicName: TopicName;
|
topicName: TopicName;
|
||||||
|
@ -55,7 +55,6 @@ const HeaderControlsWrapper = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Details: React.FC<Props> = ({
|
const Details: React.FC<Props> = ({
|
||||||
isDeleted,
|
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
recreateTopic,
|
recreateTopic,
|
||||||
clearTopicMessages,
|
clearTopicMessages,
|
||||||
|
@ -65,15 +64,14 @@ const Details: React.FC<Props> = ({
|
||||||
const isInternal = useAppSelector((state) =>
|
const isInternal = useAppSelector((state) =>
|
||||||
getIsTopicInternal(state, topicName)
|
getIsTopicInternal(state, topicName)
|
||||||
);
|
);
|
||||||
|
|
||||||
const isDeletePolicy = useAppSelector((state) =>
|
const isDeletePolicy = useAppSelector((state) =>
|
||||||
getIsTopicDeletePolicy(state, topicName)
|
getIsTopicDeletePolicy(state, topicName)
|
||||||
);
|
);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const { isReadOnly, isTopicDeletionAllowed } =
|
const { isReadOnly, isTopicDeletionAllowed } =
|
||||||
React.useContext(ClusterContext);
|
React.useContext(ClusterContext);
|
||||||
|
|
||||||
const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] =
|
const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] =
|
||||||
React.useState(false);
|
React.useState(false);
|
||||||
const [isClearTopicConfirmationVisible, setClearTopicConfirmationVisible] =
|
const [isClearTopicConfirmationVisible, setClearTopicConfirmationVisible] =
|
||||||
|
@ -82,13 +80,11 @@ const Details: React.FC<Props> = ({
|
||||||
isRecreateTopicConfirmationVisible,
|
isRecreateTopicConfirmationVisible,
|
||||||
setRecreateTopicConfirmationVisible,
|
setRecreateTopicConfirmationVisible,
|
||||||
] = React.useState(false);
|
] = React.useState(false);
|
||||||
const deleteTopicHandler = () => deleteTopic({ clusterName, topicName });
|
const deleteTopicHandler = () => {
|
||||||
|
deleteTopic({ clusterName, topicName });
|
||||||
React.useEffect(() => {
|
setDeleteTopicConfirmationVisible(false);
|
||||||
if (isDeleted) {
|
navigate('../..');
|
||||||
navigate('../..');
|
};
|
||||||
}
|
|
||||||
}, [isDeleted, clusterName, dispatch, navigate]);
|
|
||||||
|
|
||||||
const clearTopicMessagesHandler = () => {
|
const clearTopicMessagesHandler = () => {
|
||||||
clearTopicMessages({ clusterName, topicName });
|
clearTopicMessages({ clusterName, topicName });
|
||||||
|
@ -124,39 +120,37 @@ const Details: React.FC<Props> = ({
|
||||||
<Route
|
<Route
|
||||||
index
|
index
|
||||||
element={
|
element={
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
<Dropdown>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() => navigate(clusterTopicEditRelativePath)}
|
onClick={() => navigate(clusterTopicEditRelativePath)}
|
||||||
>
|
>
|
||||||
Edit settings
|
Edit settings
|
||||||
<S.DropdownExtraMessage>
|
<DropdownItemHint>
|
||||||
Pay attention! This operation has
|
Pay attention! This operation has
|
||||||
<br />
|
<br />
|
||||||
especially important consequences.
|
especially important consequences.
|
||||||
</S.DropdownExtraMessage>
|
</DropdownItemHint>
|
||||||
|
</DropdownItem>
|
||||||
|
<DropdownItem
|
||||||
|
disabled={!isDeletePolicy}
|
||||||
|
onClick={() => setClearTopicConfirmationVisible(true)}
|
||||||
|
danger
|
||||||
|
>
|
||||||
|
Clear messages
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
{isDeletePolicy && (
|
|
||||||
<DropdownItem
|
|
||||||
onClick={() => setClearTopicConfirmationVisible(true)}
|
|
||||||
danger
|
|
||||||
>
|
|
||||||
Clear messages
|
|
||||||
</DropdownItem>
|
|
||||||
)}
|
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() => setRecreateTopicConfirmationVisible(true)}
|
onClick={() => setRecreateTopicConfirmationVisible(true)}
|
||||||
danger
|
danger
|
||||||
>
|
>
|
||||||
Recreate Topic
|
Recreate Topic
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
{isTopicDeletionAllowed && (
|
<DropdownItem
|
||||||
<DropdownItem
|
disabled={!isTopicDeletionAllowed}
|
||||||
onClick={() => setDeleteTopicConfirmationVisible(true)}
|
onClick={() => setDeleteTopicConfirmationVisible(true)}
|
||||||
danger
|
danger
|
||||||
>
|
>
|
||||||
Remove topic
|
Remove Topic
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
)}
|
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -214,14 +208,11 @@ const Details: React.FC<Props> = ({
|
||||||
</Navbar>
|
</Navbar>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<OverviewContainer />} />
|
<Route index element={<OverviewContainer />} />
|
||||||
|
|
||||||
<Route path={clusterTopicMessagesRelativePath} element={<Messages />} />
|
<Route path={clusterTopicMessagesRelativePath} element={<Messages />} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={clusterTopicSettingsRelativePath}
|
path={clusterTopicSettingsRelativePath}
|
||||||
element={<SettingsContainer />}
|
element={<SettingsContainer />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={clusterTopicConsumerGroupsRelativePath}
|
path={clusterTopicConsumerGroupsRelativePath}
|
||||||
element={<TopicConsumerGroupsContainer />}
|
element={<TopicConsumerGroupsContainer />}
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { TopicMessage } from 'generated-sources';
|
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 useDataSaver from 'lib/hooks/useDataSaver';
|
||||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
|
||||||
import MessageToggleIcon from 'components/common/Icons/MessageToggleIcon';
|
import MessageToggleIcon from 'components/common/Icons/MessageToggleIcon';
|
||||||
import IconButtonWrapper from 'components/common/Icons/IconButtonWrapper';
|
import IconButtonWrapper from 'components/common/Icons/IconButtonWrapper';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||||
|
|
||||||
import MessageContent from './MessageContent/MessageContent';
|
import MessageContent from './MessageContent/MessageContent';
|
||||||
import * as S from './MessageContent/MessageContent.styled';
|
import * as S from './MessageContent/MessageContent.styled';
|
||||||
|
@ -76,7 +74,7 @@ const Message: React.FC<Props> = ({
|
||||||
</StyledDataCell>
|
</StyledDataCell>
|
||||||
<td style={{ width: '5%' }}>
|
<td style={{ width: '5%' }}>
|
||||||
{vEllipsisOpen && (
|
{vEllipsisOpen && (
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
<Dropdown>
|
||||||
<DropdownItem onClick={copyToClipboard}>
|
<DropdownItem onClick={copyToClipboard}>
|
||||||
Copy to clipboard
|
Copy to clipboard
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Partition, Replica } from 'generated-sources';
|
import { Partition, Replica } from 'generated-sources';
|
||||||
import { ClusterName, TopicName } from 'redux/interfaces';
|
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 ClusterContext from 'components/contexts/ClusterContext';
|
||||||
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
|
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
|
||||||
import { Table } from 'components/common/table/Table/Table.styled';
|
import { Table } from 'components/common/table/Table/Table.styled';
|
||||||
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
|
||||||
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
|
||||||
import * as Metrics from 'components/common/Metrics';
|
import * as Metrics from 'components/common/Metrics';
|
||||||
import { Tag } from 'components/common/Tag/Tag.styled';
|
import { Tag } from 'components/common/Tag/Tag.styled';
|
||||||
import { useAppSelector } from 'lib/hooks/redux';
|
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 { ReplicaCell } from 'components/Topics/Topic/Details/Details.styled';
|
||||||
import { RouteParamsClusterTopic } from 'lib/paths';
|
import { RouteParamsClusterTopic } from 'lib/paths';
|
||||||
import useAppParams from 'lib/hooks/useAppParams';
|
import useAppParams from 'lib/hooks/useAppParams';
|
||||||
|
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
clearTopicMessages(params: {
|
clearTopicMessages(params: {
|
||||||
|
@ -135,7 +133,7 @@ const Overview: React.FC<Props> = ({ clearTopicMessages }) => {
|
||||||
<td>{partition.offsetMax - partition.offsetMin}</td>
|
<td>{partition.offsetMax - partition.offsetMin}</td>
|
||||||
<td style={{ width: '5%' }}>
|
<td style={{ width: '5%' }}>
|
||||||
{!internal && !isReadOnly && cleanUpPolicy === 'DELETE' ? (
|
{!internal && !isReadOnly && cleanUpPolicy === 'DELETE' ? (
|
||||||
<Dropdown label={<VerticalElipsisIcon />} right>
|
<Dropdown>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
clearTopicMessages({
|
clearTopicMessages({
|
||||||
|
|
|
@ -80,7 +80,7 @@ describe('Overview', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when it has internal flag', () => {
|
describe('when it has internal flag', () => {
|
||||||
it('does not render the Action button a Topic', () => {
|
it('renders the Action button for Topic', () => {
|
||||||
setupComponent(
|
setupComponent(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
|
@ -90,7 +90,7 @@ describe('Overview', () => {
|
||||||
cleanUpPolicy: CleanUpPolicy.DELETE,
|
cleanUpPolicy: CleanUpPolicy.DELETE,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
expect(screen.getAllByRole('menu')[0]).toBeInTheDocument();
|
expect(screen.getAllByLabelText('Dropdown Toggle').length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not render Partitions', () => {
|
it('does not render Partitions', () => {
|
||||||
|
|
|
@ -46,7 +46,6 @@ describe('Details', () => {
|
||||||
deleteTopic={mockDelete}
|
deleteTopic={mockDelete}
|
||||||
recreateTopic={mockRecreateTopic}
|
recreateTopic={mockRecreateTopic}
|
||||||
clearTopicMessages={mockClearTopicMessages}
|
clearTopicMessages={mockClearTopicMessages}
|
||||||
isDeleted={false}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</WithRoute>
|
</WithRoute>
|
||||||
|
@ -83,7 +82,6 @@ describe('Details', () => {
|
||||||
deleteTopic={mockDelete}
|
deleteTopic={mockDelete}
|
||||||
recreateTopic={mockRecreateTopic}
|
recreateTopic={mockRecreateTopic}
|
||||||
clearTopicMessages={mockClearTopicMessages}
|
clearTopicMessages={mockClearTopicMessages}
|
||||||
isDeleted={false}
|
|
||||||
/>
|
/>
|
||||||
</ClusterContext.Provider>
|
</ClusterContext.Provider>
|
||||||
);
|
);
|
||||||
|
@ -95,8 +93,7 @@ describe('Details', () => {
|
||||||
describe('when remove topic modal is open', () => {
|
describe('when remove topic modal is open', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setupComponent();
|
setupComponent();
|
||||||
|
const openModalButton = screen.getAllByText('Remove Topic')[0];
|
||||||
const openModalButton = screen.getAllByText('Remove topic')[0];
|
|
||||||
userEvent.click(openModalButton);
|
userEvent.click(openModalButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -156,7 +153,13 @@ describe('Details', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirects to the correct route if topic is deleted', () => {
|
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('../..');
|
expect(mockNavigate).toHaveBeenCalledWith('../..');
|
||||||
});
|
});
|
||||||
|
|
|
@ -170,7 +170,7 @@ const SendMessage: React.FC = () => {
|
||||||
aria-labelledby="selectPartitionOptions"
|
aria-labelledby="selectPartitionOptions"
|
||||||
name={name}
|
name={name}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
minWidth="100%"
|
minWidth="100px"
|
||||||
options={selectPartitionOptions}
|
options={selectPartitionOptions}
|
||||||
value={selectPartitionOptions[0].value}
|
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`
|
import '@szhsin/react-menu/dist/core.css';
|
||||||
display: flex;
|
|
||||||
align-self: center;
|
const menuShow = keyframes`
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
const menuHide = keyframes`
|
||||||
export const Trigger = styled.button.attrs({
|
to {
|
||||||
type: 'button',
|
opacity: 0;
|
||||||
ariaHaspopup: 'true',
|
|
||||||
ariaControls: 'dropdown-menu',
|
|
||||||
})`
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
display: flex;
|
|
||||||
align-items: 'center';
|
|
||||||
justify-content: 'center';
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Item = styled.a.attrs({
|
export const Dropdown = styled(ControlledMenu)(
|
||||||
href: '#end',
|
({ theme: { dropdown } }) => css`
|
||||||
role: 'menuitem',
|
// container for the menu items
|
||||||
type: 'button',
|
${menuSelector.name} {
|
||||||
})<{ $isDanger: boolean }>`
|
border: 1px solid ${dropdown.borderColor};
|
||||||
color: ${({ $isDanger, theme }) =>
|
box-shadow: 0px 4px 16px ${dropdown.shadow};
|
||||||
$isDanger ? theme.dropdown.color : 'initial'};
|
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 { MenuProps } from '@szhsin/react-menu';
|
||||||
import cx from 'classnames';
|
import React, { PropsWithChildren, useRef } from 'react';
|
||||||
import React, { PropsWithChildren, useMemo, useState } from 'react';
|
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
|
||||||
|
import useModal from 'lib/hooks/useModal';
|
||||||
|
|
||||||
import * as S from './Dropdown.styled';
|
import * as S from './Dropdown.styled';
|
||||||
|
|
||||||
export interface DropdownProps {
|
interface DropdownProps extends PropsWithChildren<Partial<MenuProps>> {
|
||||||
label: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
right?: boolean;
|
|
||||||
up?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Dropdown: React.FC<PropsWithChildren<DropdownProps>> = ({
|
const Dropdown: React.FC<DropdownProps> = ({ label, children }) => {
|
||||||
label,
|
const ref = useRef(null);
|
||||||
right,
|
const { isOpen, setClose, setOpen } = useModal(false);
|
||||||
up,
|
|
||||||
children,
|
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
|
||||||
}) => {
|
e.preventDefault();
|
||||||
const [active, setActive] = useState<boolean>(false);
|
|
||||||
const [wrapperRef] = useOutsideClickRef(() => setActive(false));
|
|
||||||
const onClick = (e: React.MouseEvent) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setActive(!active);
|
setOpen();
|
||||||
};
|
};
|
||||||
|
|
||||||
const classNames = useMemo(
|
|
||||||
() =>
|
|
||||||
cx('dropdown', {
|
|
||||||
'is-active': active,
|
|
||||||
'is-right': right,
|
|
||||||
'is-up': up,
|
|
||||||
}),
|
|
||||||
[active, right, up]
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames} ref={wrapperRef}>
|
<>
|
||||||
<S.TriggerWrapper>
|
<S.DropdownButton
|
||||||
<S.Trigger onClick={onClick}>{label}</S.Trigger>
|
onClick={handleClick}
|
||||||
</S.TriggerWrapper>
|
ref={ref}
|
||||||
<div className="dropdown-menu" id="dropdown-menu" role="menu">
|
aria-label="Dropdown Toggle"
|
||||||
<div className="dropdown-content has-text-left">{children}</div>
|
>
|
||||||
</div>
|
{label || <VerticalElipsisIcon />}
|
||||||
</div>
|
</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 React, { PropsWithChildren } from 'react';
|
||||||
|
import { ClickEvent, MenuItem, MenuItemProps } from '@szhsin/react-menu';
|
||||||
|
|
||||||
import * as S from './Dropdown.styled';
|
import * as S from './Dropdown.styled';
|
||||||
|
|
||||||
interface DropdownItemProps {
|
interface DropdownItemProps extends PropsWithChildren<MenuItemProps> {
|
||||||
onClick(): void;
|
|
||||||
danger?: boolean;
|
danger?: boolean;
|
||||||
|
onClick?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DropdownItem: React.FC<PropsWithChildren<DropdownItemProps>> = ({
|
const DropdownItem: React.FC<DropdownItemProps> = ({
|
||||||
onClick,
|
onClick,
|
||||||
danger,
|
danger,
|
||||||
children,
|
children,
|
||||||
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const onClickHandler = (e: React.MouseEvent) => {
|
const handleClick = (e: ClickEvent) => {
|
||||||
e.preventDefault();
|
if (!onClick) return;
|
||||||
e.stopPropagation();
|
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
e.stopPropagation = true;
|
||||||
|
e.syntheticEvent.stopPropagation();
|
||||||
onClick();
|
onClick();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<S.Item
|
<MenuItem onClick={handleClick} {...rest}>
|
||||||
$isDanger={!!danger}
|
{danger ? <S.DangerItem>{children}</S.DangerItem> : children}
|
||||||
onClick={onClickHandler}
|
</MenuItem>
|
||||||
className="dropdown-item is-link"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</S.Item>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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';
|
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
|
|
||||||
// Base
|
// Base
|
||||||
@import "bulma/sass/base/minireset";
|
@import "./minireset";
|
||||||
@import "bulma/sass/base/generic";
|
@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=Roboto+Mono:wght@400;500&display=swap');
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter: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],
|
breadcrumb: Colors.neutral[30],
|
||||||
connectEditWarning: Colors.yellow[10],
|
connectEditWarning: Colors.yellow[10],
|
||||||
dropdown: {
|
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: {
|
ksqlDb: {
|
||||||
query: {
|
query: {
|
||||||
|
|
Loading…
Add table
Reference in a new issue