refactor(Button): Upgrade Button component to Antd5 (#31623)

This commit is contained in:
alexandrusoare 2025-01-09 12:04:59 +02:00 committed by GitHub
parent 840a920aba
commit 9cd3a8d5b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 114 additions and 56 deletions

View File

@ -106,7 +106,7 @@ export const databasesPage = {
alertMessage: '.antd5-alert-message',
errorField: '[role="alert"]',
uploadJson: '[title="Upload JSON file"]',
chooseFile: '[class="ant-btn input-upload-btn"]',
chooseFile: '[class="antd5-btn input-upload-btn"]',
additionalParameters: '[name="query_input"]',
sqlAlchemyUriInput: dataTestLocator('sqlalchemy-uri-input'),
advancedTab: '#rc-tabs-0-tab-2',
@ -148,7 +148,7 @@ export const sqlLabView = {
examplesMenuItem: '[title="examples"]',
tableInput: ':nth-child(4) > .select > :nth-child(1)',
sqlEditor: '#brace-editor textarea',
saveAsButton: '.SaveQuery > .ant-btn',
saveAsButton: '.SaveQuery > .antd5-btn',
saveAsModal: {
footer: '.antd5-modal-footer',
queryNameInput: 'input[class^="ant-input"]',
@ -195,7 +195,7 @@ export const savedQuery = {
export const annotationLayersView = {
emptyDescription: {
description: '.ant-empty-description',
addAnnotationLayerButton: '.ant-empty-footer > .ant-btn',
addAnnotationLayerButton: '.ant-empty-footer > .antd5-btn',
},
modal: {
content: {
@ -434,7 +434,7 @@ export const dashboardListView = {
newDashboardButton: '.css-yff34v',
},
importModal: {
selectFileButton: '.ant-upload > .ant-btn > span',
selectFileButton: '.ant-upload > .antd5-btn > span',
importButton: dataTestLocator('modal-confirm-button'),
},
header: {
@ -588,7 +588,7 @@ export const exploreView = {
rowsContainer: dataTestLocator('table-content-rows'),
},
confirmModal: {
okButton: '.antd5-modal-confirm-btns .ant-btn-primary',
okButton: '.antd5-modal-confirm-btns .antd5-btn-primary',
},
},
visualizationTypeModal: {

View File

@ -37,7 +37,7 @@ const SaveDatasetActionButton = ({
const StyledDropdownButton = styled(
DropdownButton as FC<DropdownButtonProps>,
)`
&.ant-dropdown-button button.ant-btn.ant-btn-default {
&.ant-dropdown-button button.antd5-btn.antd5-btn-default {
font-weight: ${theme.gridUnit * 150};
background-color: ${theme.colors.primary.light4};
color: ${theme.colors.primary.dark1};

View File

@ -26,10 +26,10 @@ import {
import { mix } from 'polished';
import cx from 'classnames';
import { Button as AntdButton } from 'antd';
import { Button as AntdButton } from 'antd-v5';
import { useTheme } from '@superset-ui/core';
import { Tooltip, TooltipProps } from 'src/components/Tooltip';
import { ButtonProps as AntdButtonProps } from 'antd/lib/button';
import { ButtonProps as AntdButtonProps } from 'antd-v5/lib/button';
export type OnClickHandler = MouseEventHandler<HTMLElement>;
@ -56,6 +56,25 @@ export type ButtonProps = Omit<AntdButtonProps, 'css'> &
showMarginRight?: boolean;
};
const decideType = (buttonStyle: ButtonStyle) => {
const typeMap: Record<
ButtonStyle,
'primary' | 'default' | 'dashed' | 'link'
> = {
primary: 'primary',
danger: 'primary',
warning: 'primary',
success: 'primary',
secondary: 'default',
default: 'default',
tertiary: 'dashed',
dashed: 'dashed',
link: 'link',
};
return typeMap[buttonStyle];
};
export default function Button(props: ButtonProps) {
const {
tooltip,
@ -73,7 +92,7 @@ export default function Button(props: ButtonProps) {
const theme = useTheme();
const { colors, transitionTiming, borderRadius, typography } = theme;
const { primary, grayscale, success, warning, error } = colors;
const { primary, grayscale, success, warning } = colors;
let height = 32;
let padding = 18;
@ -85,25 +104,19 @@ export default function Button(props: ButtonProps) {
padding = 10;
}
let backgroundColor = primary.light4;
let backgroundColorHover = mix(0.1, primary.base, primary.light4);
let backgroundColorActive = mix(0.25, primary.base, primary.light4);
let backgroundColor;
let backgroundColorHover;
let backgroundColorActive;
let backgroundColorDisabled = grayscale.light2;
let color = primary.dark1;
let colorHover = color;
let color;
let colorHover;
let borderWidth = 0;
let borderStyle = 'none';
let borderColor = 'transparent';
let borderColorHover = 'transparent';
let borderColor;
let borderColorHover;
let borderColorDisabled = 'transparent';
if (buttonStyle === 'primary') {
backgroundColor = primary.base;
backgroundColorHover = primary.dark1;
backgroundColorActive = mix(0.2, grayscale.dark2, primary.dark1);
color = grayscale.light5;
colorHover = color;
} else if (buttonStyle === 'tertiary' || buttonStyle === 'dashed') {
if (buttonStyle === 'tertiary' || buttonStyle === 'dashed') {
backgroundColor = grayscale.light5;
backgroundColorHover = grayscale.light5;
backgroundColorActive = grayscale.light5;
@ -114,10 +127,6 @@ export default function Button(props: ButtonProps) {
borderColorHover = primary.light1;
borderColorDisabled = grayscale.light2;
} else if (buttonStyle === 'danger') {
backgroundColor = error.base;
backgroundColorHover = mix(0.1, grayscale.light5, error.base);
backgroundColorActive = mix(0.2, grayscale.dark2, error.base);
color = grayscale.light5;
colorHover = color;
} else if (buttonStyle === 'warning') {
backgroundColor = warning.base;
@ -135,7 +144,7 @@ export default function Button(props: ButtonProps) {
backgroundColor = 'transparent';
backgroundColorHover = 'transparent';
backgroundColorActive = 'transparent';
colorHover = primary.base;
color = primary.dark1;
}
const element = children as ReactElement;
@ -149,10 +158,14 @@ export default function Button(props: ButtonProps) {
const firstChildMargin =
showMarginRight && renderedChildren.length > 1 ? theme.gridUnit * 2 : 0;
const effectiveButtonStyle: ButtonStyle = buttonStyle ?? 'default';
const button = (
<AntdButton
href={disabled ? undefined : href}
disabled={disabled}
type={decideType(effectiveButtonStyle)}
danger={effectiveButtonStyle === 'danger'}
className={cx(
className,
'superset-button',
@ -179,12 +192,18 @@ export default function Button(props: ButtonProps) {
borderColor,
borderRadius,
color,
backgroundColor,
'&:hover': {
color: colorHover,
backgroundColor: backgroundColorHover,
borderColor: borderColorHover,
},
background: backgroundColor,
...(colorHover || backgroundColorHover || borderColorHover
? {
[`&.superset-button.superset-button-${buttonStyle}:hover`]: {
...(colorHover && { color: colorHover }),
...(backgroundColorHover && {
background: backgroundColorHover,
}),
...(borderColorHover && { borderColor: borderColorHover }),
},
}
: {}),
'&:active': {
color,
backgroundColor: backgroundColorActive,
@ -206,7 +225,7 @@ export default function Button(props: ButtonProps) {
'& + .superset-button': {
marginLeft: theme.gridUnit * 2,
},
'& > :first-of-type': {
'& > span > :first-of-type': {
marginRight: firstChildMargin,
},
}}

View File

@ -23,8 +23,8 @@ import { styled } from '@superset-ui/core';
import { kebabCase } from 'lodash';
const StyledDropdownButton = styled.div`
.ant-btn-group {
button.ant-btn {
.antd5-btn-group {
button.antd5-btn {
background-color: ${({ theme }) => theme.colors.primary.dark1};
border-color: transparent;
color: ${({ theme }) => theme.colors.grayscale.light5};

View File

@ -41,10 +41,10 @@ export interface DropDownSelectableProps extends Pick<MenuProps, 'onSelect'> {
}
const StyledDropdownButton = styled(DropdownButton as FC<DropdownButtonProps>)`
button.ant-btn:first-of-type {
button.antd5-btn:first-of-type {
display: none;
}
> button.ant-btn:nth-of-type(2) {
> button.antd5-btn:nth-of-type(2) {
display: inline-flex;
background-color: transparent !important;
height: unset;

View File

@ -17,8 +17,7 @@
* under the License.
*/
import { styled } from '@superset-ui/core';
import Button from 'src/components/Button';
import { ButtonProps as AntdButtonProps } from 'antd/lib/button';
import Button, { ButtonProps as AntdButtonProps } from 'src/components/Button';
import Icons from 'src/components/Icons';
import LinesEllipsis from 'react-lines-ellipsis';

View File

@ -35,7 +35,7 @@ export const menuTriggerStyles = (theme: SupersetTheme) => css`
padding: 0;
border: 1px solid ${theme.colors.primary.dark2};
&.ant-btn > span.anticon {
&.antd5-btn > span.anticon {
line-height: 0;
transition: inherit;
}

View File

@ -45,7 +45,7 @@ const AddButtonContainer = styled.div`
padding-bottom: 1px;
}
.ant-btn > .anticon + span {
.antd5-btn > .anticon + span {
margin-left: 0;
}
`}

View File

@ -97,7 +97,7 @@ export const MenuTrigger = styled(Button)`
padding: 0;
border: 1px solid ${theme.colors.primary.dark2};
&.ant-btn > span.anticon {
&.antd5-btn > span.anticon {
line-height: 0;
transition: inherit;
}

View File

@ -624,7 +624,7 @@ describe('DatabaseModal', () => {
];
visibleComponents.forEach(component => {
expect(component).toBeVisible();
expect(component).toBeInTheDocument();
});
});
@ -781,7 +781,7 @@ describe('DatabaseModal', () => {
enableRowExpansionCheckbox,
];
visibleComponents.forEach(component => {
expect(component).toBeVisible();
expect(component).toBeInTheDocument();
});
invisibleComponents.forEach(component => {
expect(component).not.toBeVisible();
@ -849,7 +849,7 @@ describe('DatabaseModal', () => {
];
visibleComponents.forEach(component => {
expect(component).toBeVisible();
expect(component).toBeInTheDocument();
});
});
@ -929,7 +929,7 @@ describe('DatabaseModal', () => {
// ---------- Assertions ----------
visibleComponents.forEach(component => {
expect(component).toBeVisible();
expect(component).toBeInTheDocument();
});
invisibleComponents.forEach(component => {
expect(component).not.toBeVisible();
@ -1137,7 +1137,7 @@ describe('DatabaseModal', () => {
expect(await screen.findByText(/step 2 of 2/i)).toBeInTheDocument();
const sqlAlchemyFormStepText = screen.getByText(/step 2 of 2/i);
expect(sqlAlchemyFormStepText).toBeVisible();
expect(sqlAlchemyFormStepText).toBeInTheDocument();
});
describe('SQL Alchemy form flow', () => {
@ -1293,7 +1293,7 @@ describe('DatabaseModal', () => {
expect(await screen.findByText(/step 2 of 2/i)).toBeInTheDocument();
const SSHTunnelingToggle = screen.getByTestId('ssh-tunnel-switch');
expect(SSHTunnelingToggle).toBeVisible();
expect(SSHTunnelingToggle).toBeInTheDocument();
const SSHTunnelServerAddressInput = screen.queryByTestId(
'ssh-tunnel-server_address-input',
);
@ -1325,26 +1325,26 @@ describe('DatabaseModal', () => {
const SSHTunnelUsePasswordInput = screen.getByTestId(
'ssh-tunnel-use_password-radio',
);
expect(SSHTunnelUsePasswordInput).toBeVisible();
expect(SSHTunnelUsePasswordInput).toBeInTheDocument();
const SSHTunnelUsePrivateKeyInput = screen.getByTestId(
'ssh-tunnel-use_private_key-radio',
);
expect(SSHTunnelUsePrivateKeyInput).toBeVisible();
expect(SSHTunnelUsePrivateKeyInput).toBeInTheDocument();
const SSHTunnelPasswordInput = screen.getByTestId(
'ssh-tunnel-password-input',
);
// By default, we use Password as login method
expect(SSHTunnelPasswordInput).toBeVisible();
expect(SSHTunnelPasswordInput).toBeInTheDocument();
// Change the login method to use private key
userEvent.click(SSHTunnelUsePrivateKeyInput);
const SSHTunnelPrivateKeyInput = screen.getByTestId(
'ssh-tunnel-private_key-input',
);
expect(SSHTunnelPrivateKeyInput).toBeVisible();
expect(SSHTunnelPrivateKeyInput).toBeInTheDocument();
const SSHTunnelPrivateKeyPasswordInput = screen.getByTestId(
'ssh-tunnel-private_key_password-input',
);
expect(SSHTunnelPrivateKeyPasswordInput).toBeVisible();
expect(SSHTunnelPrivateKeyPasswordInput).toBeInTheDocument();
});
});
});

View File

@ -118,7 +118,7 @@ export const StyledLayoutFooter = styled.div`
`;
export const HeaderComponentStyles = styled.div`
.ant-btn {
.antd5-btn {
span {
margin-right: 0;
}

View File

@ -19,6 +19,7 @@
import { type ThemeConfig } from 'antd-v5';
import { theme as supersetTheme } from 'src/preamble';
import { mix } from 'polished';
import { lightAlgorithm } from './light';
export enum ThemeType {
@ -72,6 +73,45 @@ const baseConfig: ThemeConfig = {
Badge: {
paddingXS: supersetTheme.gridUnit * 2,
},
Button: {
defaultBg: supersetTheme.colors.primary.light4,
defaultHoverBg: mix(
0.1,
supersetTheme.colors.primary.base,
supersetTheme.colors.primary.light4,
),
defaultActiveBg: mix(
0.25,
supersetTheme.colors.primary.base,
supersetTheme.colors.primary.light4,
),
defaultColor: supersetTheme.colors.primary.dark1,
defaultHoverColor: supersetTheme.colors.primary.dark1,
defaultBorderColor: 'transparent',
defaultHoverBorderColor: 'transparent',
colorPrimaryHover: supersetTheme.colors.primary.dark1,
colorPrimaryActive: mix(
0.2,
supersetTheme.colors.grayscale.dark2,
supersetTheme.colors.primary.dark1,
),
primaryColor: supersetTheme.colors.grayscale.light5,
colorPrimaryTextHover: supersetTheme.colors.grayscale.light5,
colorError: supersetTheme.colors.error.base,
colorErrorHover: mix(
0.1,
supersetTheme.colors.grayscale.light5,
supersetTheme.colors.error.base,
),
colorErrorBg: mix(
0.2,
supersetTheme.colors.grayscale.dark2,
supersetTheme.colors.error.base,
),
dangerColor: supersetTheme.colors.grayscale.light5,
colorLinkHover: supersetTheme.colors.primary.base,
linkHoverBg: 'transparent',
},
Card: {
paddingLG: supersetTheme.gridUnit * 6,
fontWeightStrong: supersetTheme.typography.weights.medium,