mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-07 05:53:46 +00:00
ui: glossary badge feature feedback (#13562)
This commit is contained in:
parent
1c5a6e9425
commit
6111e62466
@ -24,9 +24,11 @@ export type SelectOption = {
|
|||||||
|
|
||||||
export interface AsyncSelectListProps {
|
export interface AsyncSelectListProps {
|
||||||
mode?: 'multiple';
|
mode?: 'multiple';
|
||||||
|
className?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
debounceTimeout?: number;
|
debounceTimeout?: number;
|
||||||
defaultValue?: string[];
|
defaultValue?: string[];
|
||||||
|
initialData?: SelectOption[];
|
||||||
onChange?: (option: DefaultOptionType | DefaultOptionType[]) => void;
|
onChange?: (option: DefaultOptionType | DefaultOptionType[]) => void;
|
||||||
fetchOptions: (
|
fetchOptions: (
|
||||||
search: string,
|
search: string,
|
||||||
|
|||||||
@ -10,17 +10,28 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { Select, SelectProps, Space, Tooltip, Typography } from 'antd';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
import { DefaultOptionType } from 'antd/lib/select';
|
import {
|
||||||
|
Select,
|
||||||
|
SelectProps,
|
||||||
|
Space,
|
||||||
|
TagProps,
|
||||||
|
Tooltip,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { debounce, isEmpty } from 'lodash';
|
import { debounce, isEmpty, isUndefined } from 'lodash';
|
||||||
import React, { FC, useCallback, useMemo, useState } from 'react';
|
import { CustomTagProps } from 'rc-select/lib/BaseSelect';
|
||||||
|
import React, { FC, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import Loader from '../../components/Loader/Loader';
|
import Loader from '../../components/Loader/Loader';
|
||||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||||
|
import { TAG_START_WITH } from '../../constants/Tag.constants';
|
||||||
import { Paging } from '../../generated/type/paging';
|
import { Paging } from '../../generated/type/paging';
|
||||||
|
import { TagLabel } from '../../generated/type/tagLabel';
|
||||||
import Fqn from '../../utils/Fqn';
|
import Fqn from '../../utils/Fqn';
|
||||||
import { tagRender } from '../../utils/TagsUtils';
|
import { getTagDisplay, tagRender } from '../../utils/TagsUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
|
import TagsV1 from '../Tag/TagsV1/TagsV1.component';
|
||||||
import {
|
import {
|
||||||
AsyncSelectListProps,
|
AsyncSelectListProps,
|
||||||
SelectOption,
|
SelectOption,
|
||||||
@ -31,6 +42,8 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
fetchOptions,
|
fetchOptions,
|
||||||
debounceTimeout = 800,
|
debounceTimeout = 800,
|
||||||
|
initialData,
|
||||||
|
className,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -39,6 +52,7 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
|
|||||||
const [searchValue, setSearchValue] = useState<string>('');
|
const [searchValue, setSearchValue] = useState<string>('');
|
||||||
const [paging, setPaging] = useState<Paging>({} as Paging);
|
const [paging, setPaging] = useState<Paging>({} as Paging);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const selectedTagsRef = useRef<SelectOption[]>(initialData ?? []);
|
||||||
|
|
||||||
const loadOptions = useCallback(
|
const loadOptions = useCallback(
|
||||||
async (value: string) => {
|
async (value: string) => {
|
||||||
@ -84,7 +98,11 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
|
|||||||
className="text-grey-muted m-0 p-0">
|
className="text-grey-muted m-0 p-0">
|
||||||
{parts.join(FQN_SEPARATOR_CHAR)}
|
{parts.join(FQN_SEPARATOR_CHAR)}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
<Typography.Text ellipsis>{lastPartOfTag}</Typography.Text>
|
<Typography.Text
|
||||||
|
ellipsis
|
||||||
|
style={{ color: tag.data?.style?.color }}>
|
||||||
|
{lastPartOfTag}
|
||||||
|
</Typography.Text>
|
||||||
</Space>
|
</Space>
|
||||||
),
|
),
|
||||||
value: tag.value,
|
value: tag.value,
|
||||||
@ -124,15 +142,56 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const customTagRender = (data: CustomTagProps) => {
|
||||||
|
const selectedTag = selectedTagsRef.current.find(
|
||||||
|
(tag) => tag.value === data.label
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isUndefined(selectedTag?.data)) {
|
||||||
|
return tagRender(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { label, onClose } = data;
|
||||||
|
const tagLabel = getTagDisplay(label as string);
|
||||||
|
|
||||||
|
const onPreventMouseDown = (event: React.MouseEvent<HTMLSpanElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const tagProps = {
|
||||||
|
closable: true,
|
||||||
|
closeIcon: (
|
||||||
|
<CloseOutlined
|
||||||
|
className="p-r-xs"
|
||||||
|
data-testid="remove-tags"
|
||||||
|
height={8}
|
||||||
|
width={8}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
'data-testid': `selected-tag-${tagLabel}`,
|
||||||
|
onClose,
|
||||||
|
onMouseDown: onPreventMouseDown,
|
||||||
|
} as TagProps;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TagsV1
|
||||||
|
startWith={TAG_START_WITH.SOURCE_ICON}
|
||||||
|
tag={selectedTag?.data as TagLabel}
|
||||||
|
tagProps={tagProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleChange: SelectProps['onChange'] = (values: string[], options) => {
|
const handleChange: SelectProps['onChange'] = (values: string[], options) => {
|
||||||
const selectedValues = values.map((value) => {
|
const selectedValues = values.map((value) => {
|
||||||
const data = (options as DefaultOptionType[]).find(
|
const data = (options as SelectOption[]).find(
|
||||||
(option) => option.value === value
|
(option) => option.value === value
|
||||||
);
|
);
|
||||||
|
|
||||||
return data ?? { value, label: value };
|
return data ?? { value, label: value };
|
||||||
});
|
});
|
||||||
|
selectedTagsRef.current = selectedValues;
|
||||||
onChange?.(selectedValues);
|
onChange?.(selectedValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -147,7 +206,7 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
|
|||||||
notFoundContent={isLoading ? <Loader size="small" /> : null}
|
notFoundContent={isLoading ? <Loader size="small" /> : null}
|
||||||
optionLabelProp="label"
|
optionLabelProp="label"
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
tagRender={tagRender}
|
tagRender={customTagRender}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
setSearchValue('');
|
setSearchValue('');
|
||||||
@ -160,6 +219,7 @@ const AsyncSelectList: FC<AsyncSelectListProps> = ({
|
|||||||
{...props}>
|
{...props}>
|
||||||
{tagOptions.map(({ label, value, displayName, data }) => (
|
{tagOptions.map(({ label, value, displayName, data }) => (
|
||||||
<Select.Option
|
<Select.Option
|
||||||
|
className={className}
|
||||||
data={data}
|
data={data}
|
||||||
data-testid={`tag-${value}`}
|
data-testid={`tag-${value}`}
|
||||||
key={label}
|
key={label}
|
||||||
|
|||||||
@ -33,6 +33,7 @@ interface Props {
|
|||||||
openEntityInNewPage?: boolean;
|
openEntityInNewPage?: boolean;
|
||||||
gutter?: 'default' | 'large';
|
gutter?: 'default' | 'large';
|
||||||
serviceName: string;
|
serviceName: string;
|
||||||
|
titleColor?: string;
|
||||||
badge?: React.ReactNode;
|
badge?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +47,7 @@ export const EntityHeader = ({
|
|||||||
gutter = 'default',
|
gutter = 'default',
|
||||||
serviceName,
|
serviceName,
|
||||||
badge,
|
badge,
|
||||||
|
titleColor,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@ -60,6 +62,7 @@ export const EntityHeader = ({
|
|||||||
|
|
||||||
<EntityHeaderTitle
|
<EntityHeaderTitle
|
||||||
badge={badge}
|
badge={badge}
|
||||||
|
color={titleColor}
|
||||||
deleted={entityData.deleted}
|
deleted={entityData.deleted}
|
||||||
displayName={entityData.displayName}
|
displayName={entityData.displayName}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import React, { useMemo } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { ReactComponent as IconExternalLink } from '../../../assets/svg/external-link-grey.svg';
|
import { ReactComponent as IconExternalLink } from '../../../assets/svg/external-link-grey.svg';
|
||||||
|
import { TEXT_COLOR } from '../../../constants/Color.constants';
|
||||||
import { ROUTES } from '../../../constants/constants';
|
import { ROUTES } from '../../../constants/constants';
|
||||||
import { stringToHTML } from '../../../utils/StringsUtils';
|
import { stringToHTML } from '../../../utils/StringsUtils';
|
||||||
import { EntityHeaderTitleProps } from './EntityHeaderTitle.interface';
|
import { EntityHeaderTitleProps } from './EntityHeaderTitle.interface';
|
||||||
@ -32,6 +33,7 @@ const EntityHeaderTitle = ({
|
|||||||
badge,
|
badge,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
className,
|
className,
|
||||||
|
color,
|
||||||
}: EntityHeaderTitleProps) => {
|
}: EntityHeaderTitleProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -63,8 +65,9 @@ const EntityHeaderTitle = ({
|
|||||||
<Typography.Text
|
<Typography.Text
|
||||||
className="m-b-0 d-block entity-header-display-name text-lg font-semibold"
|
className="m-b-0 d-block entity-header-display-name text-lg font-semibold"
|
||||||
data-testid="entity-header-display-name"
|
data-testid="entity-header-display-name"
|
||||||
ellipsis={{ tooltip: true }}>
|
ellipsis={{ tooltip: true }}
|
||||||
{stringToHTML(displayName || name)}
|
style={{ color: color ?? TEXT_COLOR }}>
|
||||||
|
{stringToHTML(displayName ?? name)}
|
||||||
{openEntityInNewPage && (
|
{openEntityInNewPage && (
|
||||||
<IconExternalLink
|
<IconExternalLink
|
||||||
className="anticon vertical-baseline m-l-xss"
|
className="anticon vertical-baseline m-l-xss"
|
||||||
|
|||||||
@ -16,6 +16,7 @@ export interface EntityHeaderTitleProps {
|
|||||||
name: string;
|
name: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
|
color?: string;
|
||||||
openEntityInNewPage?: boolean;
|
openEntityInNewPage?: boolean;
|
||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
serviceName: string;
|
serviceName: string;
|
||||||
|
|||||||
@ -492,6 +492,11 @@ const GlossaryHeader = ({
|
|||||||
entityType={EntityType.GLOSSARY_TERM}
|
entityType={EntityType.GLOSSARY_TERM}
|
||||||
icon={icon}
|
icon={icon}
|
||||||
serviceName=""
|
serviceName=""
|
||||||
|
titleColor={
|
||||||
|
isGlossary
|
||||||
|
? undefined
|
||||||
|
: (selectedData as GlossaryTerm).style?.color
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col flex="360px">
|
<Col flex="360px">
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import {
|
|||||||
getRequestTagsPath,
|
getRequestTagsPath,
|
||||||
getUpdateTagsPath,
|
getUpdateTagsPath,
|
||||||
} from '../../../utils/TasksUtils';
|
} from '../../../utils/TasksUtils';
|
||||||
|
import { SelectOption } from '../../AsyncSelectList/AsyncSelectList.interface';
|
||||||
import TagSelectForm from '../TagsSelectForm/TagsSelectForm.component';
|
import TagSelectForm from '../TagsSelectForm/TagsSelectForm.component';
|
||||||
import TagsV1 from '../TagsV1/TagsV1.component';
|
import TagsV1 from '../TagsV1/TagsV1.component';
|
||||||
import TagsViewer from '../TagsViewer/TagsViewer';
|
import TagsViewer from '../TagsViewer/TagsViewer';
|
||||||
@ -74,11 +75,17 @@ const TagsContainerV2 = ({
|
|||||||
showAddTagButton,
|
showAddTagButton,
|
||||||
selectedTagsInternal,
|
selectedTagsInternal,
|
||||||
isHoriZontalLayout,
|
isHoriZontalLayout,
|
||||||
|
initialOptions,
|
||||||
} = useMemo(
|
} = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
isGlossaryType: tagType === TagSource.Glossary,
|
isGlossaryType: tagType === TagSource.Glossary,
|
||||||
showAddTagButton: permission && isEmpty(tags?.[tagType]),
|
showAddTagButton: permission && isEmpty(tags?.[tagType]),
|
||||||
selectedTagsInternal: tags?.[tagType].map(({ tagFQN }) => tagFQN),
|
selectedTagsInternal: tags?.[tagType].map(({ tagFQN }) => tagFQN),
|
||||||
|
initialOptions: tags?.[tagType].map((data) => ({
|
||||||
|
label: data.tagFQN,
|
||||||
|
value: data.tagFQN,
|
||||||
|
data,
|
||||||
|
})) as SelectOption[],
|
||||||
isHoriZontalLayout: layoutType === LayoutType.HORIZONTAL,
|
isHoriZontalLayout: layoutType === LayoutType.HORIZONTAL,
|
||||||
}),
|
}),
|
||||||
[tagType, permission, tags?.[tagType], tags, layoutType]
|
[tagType, permission, tags?.[tagType], tags, layoutType]
|
||||||
@ -207,6 +214,7 @@ const TagsContainerV2 = ({
|
|||||||
defaultValue={selectedTagsInternal ?? []}
|
defaultValue={selectedTagsInternal ?? []}
|
||||||
fetchApi={fetchAPI}
|
fetchApi={fetchAPI}
|
||||||
placeholder={getTagPlaceholder(isGlossaryType)}
|
placeholder={getTagPlaceholder(isGlossaryType)}
|
||||||
|
tagData={initialOptions}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onSubmit={handleSave}
|
onSubmit={handleSave}
|
||||||
/>
|
/>
|
||||||
@ -218,6 +226,7 @@ const TagsContainerV2 = ({
|
|||||||
fetchAPI,
|
fetchAPI,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
handleSave,
|
handleSave,
|
||||||
|
initialOptions,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleTagsTask = (hasTags: boolean) => {
|
const handleTagsTask = (hasTags: boolean) => {
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { Button, Col, Form, Row, Space } from 'antd';
|
|||||||
import { useForm } from 'antd/lib/form/Form';
|
import { useForm } from 'antd/lib/form/Form';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import AsyncSelectList from '../../../components/AsyncSelectList/AsyncSelectList';
|
import AsyncSelectList from '../../../components/AsyncSelectList/AsyncSelectList';
|
||||||
|
import './tag-select-fom.style.less';
|
||||||
import { TagsSelectFormProps } from './TagsSelectForm.interface';
|
import { TagsSelectFormProps } from './TagsSelectForm.interface';
|
||||||
|
|
||||||
const TagSelectForm = ({
|
const TagSelectForm = ({
|
||||||
@ -23,6 +24,7 @@ const TagSelectForm = ({
|
|||||||
placeholder,
|
placeholder,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onCancel,
|
onCancel,
|
||||||
|
tagData,
|
||||||
}: TagsSelectFormProps) => {
|
}: TagsSelectFormProps) => {
|
||||||
const [form] = useForm();
|
const [form] = useForm();
|
||||||
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
const [isSubmitLoading, setIsSubmitLoading] = useState(false);
|
||||||
@ -62,7 +64,9 @@ const TagSelectForm = ({
|
|||||||
<Col className="gutter-row" span={24}>
|
<Col className="gutter-row" span={24}>
|
||||||
<Form.Item noStyle name="tags">
|
<Form.Item noStyle name="tags">
|
||||||
<AsyncSelectList
|
<AsyncSelectList
|
||||||
|
className="tag-select-box"
|
||||||
fetchOptions={fetchApi}
|
fetchOptions={fetchApi}
|
||||||
|
initialData={tagData}
|
||||||
mode="multiple"
|
mode="multiple"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { Paging } from '../../../generated/type/paging';
|
|||||||
export type TagsSelectFormProps = {
|
export type TagsSelectFormProps = {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
defaultValue: string[];
|
defaultValue: string[];
|
||||||
|
tagData?: SelectOption[];
|
||||||
onChange?: (value: string[]) => void;
|
onChange?: (value: string[]) => void;
|
||||||
onSubmit: (option: DefaultOptionType | DefaultOptionType[]) => Promise<void>;
|
onSubmit: (option: DefaultOptionType | DefaultOptionType[]) => Promise<void>;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
@import (reference) '/src/styles/variables.less';
|
||||||
|
|
||||||
|
.tag-select-box.ant-select-item-option-selected {
|
||||||
|
background-color: @grey-1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
@ -36,6 +36,7 @@ const TagsV1 = ({
|
|||||||
className,
|
className,
|
||||||
showOnlyName = false,
|
showOnlyName = false,
|
||||||
isVersionPage = false,
|
isVersionPage = false,
|
||||||
|
tagProps,
|
||||||
}: TagsV1Props) => {
|
}: TagsV1Props) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const color = useMemo(
|
const color = useMemo(
|
||||||
@ -121,7 +122,8 @@ const TagsV1 = ({
|
|||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
ellipsis
|
ellipsis
|
||||||
className="m-0 tags-label"
|
className="m-0 tags-label"
|
||||||
data-testid={`tag-${tag.tagFQN}`}>
|
data-testid={`tag-${tag.tagFQN}`}
|
||||||
|
style={{ color: tag.style?.color }}>
|
||||||
{tagName}
|
{tagName}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
</div>
|
</div>
|
||||||
@ -137,10 +139,11 @@ const TagsV1 = ({
|
|||||||
data-testid="tags"
|
data-testid="tags"
|
||||||
style={
|
style={
|
||||||
color
|
color
|
||||||
? { backgroundColor: reduceColorOpacity(color, 0.1) }
|
? { backgroundColor: reduceColorOpacity(color, 0.05) }
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onClick={() => redirectLink()}>
|
onClick={redirectLink}
|
||||||
|
{...tagProps}>
|
||||||
{tagContent}
|
{tagContent}
|
||||||
</Tag>
|
</Tag>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { TagProps } from 'antd';
|
||||||
import { TAG_START_WITH } from '../../../constants/Tag.constants';
|
import { TAG_START_WITH } from '../../../constants/Tag.constants';
|
||||||
import { TagLabel } from '../../../generated/type/tagLabel';
|
import { TagLabel } from '../../../generated/type/tagLabel';
|
||||||
|
|
||||||
@ -20,4 +21,5 @@ export type TagsV1Props = {
|
|||||||
showOnlyName?: boolean;
|
showOnlyName?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
isVersionPage?: boolean;
|
isVersionPage?: boolean;
|
||||||
|
tagProps?: TagProps;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,3 +17,4 @@ export const GREEN_3_OPACITY = '#48ca9e30';
|
|||||||
export const YELLOW_2 = '#ffbe0e';
|
export const YELLOW_2 = '#ffbe0e';
|
||||||
export const RED_3 = '#f24822';
|
export const RED_3 = '#f24822';
|
||||||
export const PURPLE_2 = '#7147e8';
|
export const PURPLE_2 = '#7147e8';
|
||||||
|
export const TEXT_COLOR = '#292929';
|
||||||
|
|||||||
@ -373,7 +373,7 @@ a[href].link-text-grey,
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
color: @text-color !important;
|
color: @text-color;
|
||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -56,8 +56,15 @@ export const getCommonColumns = (): ColumnsType<Tag> => [
|
|||||||
key: 'name',
|
key: 'name',
|
||||||
width: 200,
|
width: 200,
|
||||||
render: (_, record) => (
|
render: (_, record) => (
|
||||||
<Space>
|
<Space align="center">
|
||||||
<Typography.Text>{record.name}</Typography.Text>
|
{record.style?.iconURL && (
|
||||||
|
<img data-testid="tag-icon" src={record.style.iconURL} width={16} />
|
||||||
|
)}
|
||||||
|
<Typography.Text
|
||||||
|
className="m-b-0"
|
||||||
|
style={{ color: record.style?.color }}>
|
||||||
|
{record.name}
|
||||||
|
</Typography.Text>
|
||||||
{record.disabled ? (
|
{record.disabled ? (
|
||||||
<Badge
|
<Badge
|
||||||
className="m-l-xs badge-grey"
|
className="m-l-xs badge-grey"
|
||||||
|
|||||||
@ -243,11 +243,12 @@ describe('Tests for CommonUtils', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should reduce color opacity by the given value', () => {
|
it('should reduce color opacity by the given value', () => {
|
||||||
expect(reduceColorOpacity('#0000FF', 0)).toBe('#0000FFFF');
|
expect(reduceColorOpacity('#0000FF', 0)).toBe('rgba(0, 0, 255, 0)');
|
||||||
expect(reduceColorOpacity('#00FF00', 0.25)).toBe('#00FF0040');
|
expect(reduceColorOpacity('#00FF00', 0.25)).toBe('rgba(0, 255, 0, 0.25)');
|
||||||
expect(reduceColorOpacity('#FF0000', 0.5)).toBe('#FF000080');
|
expect(reduceColorOpacity('#FF0000', 0.5)).toBe('rgba(255, 0, 0, 0.5)');
|
||||||
expect(reduceColorOpacity('#FF0000', 0.75)).toBe('#FF0000BF');
|
expect(reduceColorOpacity('#FF0000', 0.75)).toBe('rgba(255, 0, 0, 0.75)');
|
||||||
expect(reduceColorOpacity('#FF0000', -0.5)).toBe('#FF00000');
|
expect(reduceColorOpacity('#FF0000', -0.5)).toBe('rgba(255, 0, 0, -0.5)');
|
||||||
|
expect(reduceColorOpacity('#FF0000', 0.05)).toBe('rgba(255, 0, 0, 0.05)');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return base64 encoded string for input text', () => {
|
it('should return base64 encoded string for input text', () => {
|
||||||
|
|||||||
@ -782,14 +782,18 @@ export const getIsErrorMatch = (error: AxiosError, key: string): boolean => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param color have color code
|
* @param color hex have color code
|
||||||
* @param opacity take opacity how much to reduce it
|
* @param opacity take opacity how much to reduce it
|
||||||
* @returns hex color string
|
* @returns hex color string
|
||||||
*/
|
*/
|
||||||
export const reduceColorOpacity = (color: string, opacity: number): string => {
|
export const reduceColorOpacity = (hex: string, opacity: number): string => {
|
||||||
const _opacity = Math.round(Math.min(Math.max(opacity || 1, 0), 1) * 255);
|
hex = hex.replace(/^#/, ''); // Remove the "#" if it's there
|
||||||
|
hex = hex.length === 3 ? hex.replace(/./g, '$&$&') : hex; // Expand short hex to full hex format
|
||||||
|
const [red, green, blue] = [0, 2, 4].map((i) =>
|
||||||
|
parseInt(hex.slice(i, i + 2), 16)
|
||||||
|
); // Parse hex values
|
||||||
|
|
||||||
return color + _opacity.toString(16).toUpperCase();
|
return `rgba(${red}, ${green}, ${blue}, ${opacity})`; // Create RGBA color
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getEntityDetailLink = (
|
export const getEntityDetailLink = (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user