-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/custom button #625
base: feature/ant5
Are you sure you want to change the base?
Changes from all commits
ee6d0d7
334431a
1c629d5
ad19491
2a6b563
f24d313
58c7ea1
24fd57e
cd63f17
4cb5dec
eabc972
d2f0662
68cbed4
e7b159c
857baef
c3e87d7
eaf84b3
603b4fa
c9c58b1
69cd8ef
7000cd9
625f288
ca5eba0
54580dd
ace0823
d504830
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
import React, { ReactNode, useState, useEffect, forwardRef } from "react"; | ||
import { Button as AntButton, Tooltip } from "antd"; | ||
import styled, { css, RuleSet } from "styled-components"; | ||
import type { ButtonProps as AntButtonProps } from "antd"; | ||
import { | ||
NAV_BAR_TOOLTIP_OFFSET, | ||
TOOLTIP_COLOR, | ||
TOOLTIP_DELAY, | ||
} from "../../constants"; | ||
import { ButtonClass, TooltipPlacement } from "../../constants/interfaces"; | ||
|
||
interface CustomButtonProps extends Omit<AntButtonProps, "type" | "variant"> { | ||
variant?: ButtonClass; | ||
titleText?: string; | ||
icon?: ReactNode; | ||
onClick?: () => void; | ||
} | ||
|
||
interface TooltipText { | ||
defaultText: string; | ||
disabledText?: string; | ||
} | ||
|
||
interface TooltipButtonProps extends CustomButtonProps { | ||
tooltipText: TooltipText; | ||
tooltipPlacement?: TooltipPlacement; | ||
} | ||
|
||
const baseStyles = css` | ||
font-family: ${(props) => props.theme.typography}; | ||
border-radius: 3px; | ||
height: 32px; | ||
padding: 6px 16px; | ||
font-size: 14px; | ||
align-items: center; | ||
gap: 8px; | ||
cursor: pointer; | ||
|
||
&:disabled { | ||
cursor: not-allowed; | ||
} | ||
|
||
&:focus-visible { | ||
outline: 1px solid ${({ theme }) => theme.colors.lightPurpleBg}; | ||
outline-offset: 1px; | ||
} | ||
`; | ||
|
||
const generateButtonStyles = ( | ||
variant: "primary" | "secondary", | ||
theme: "light" | "dark" | ||
) => css` | ||
${({ | ||
theme: { | ||
colors: { button }, | ||
}, | ||
}) => { | ||
const buttonTheme = button[variant][theme]; | ||
const { background, text, border, hover, active, disabled } = | ||
buttonTheme; | ||
|
||
return css` | ||
background-color: ${background}; | ||
border: 1px solid ${variant === "primary" ? background : border}; | ||
color: ${text}; | ||
|
||
&&& { | ||
&:hover:not(:disabled) { | ||
background-color: ${hover.background}; | ||
border-color: ${hover.background}; | ||
color: ${hover.text}; | ||
} | ||
|
||
&:active:not(:disabled) { | ||
background-color: ${hover.background}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I feel like I'd expect this to be |
||
border-color: ${active.background}; | ||
color: ${active.text}; | ||
} | ||
|
||
&:disabled { | ||
background-color: ${disabled.background}; | ||
border-color: ${disabled.background}; | ||
color: ${disabled.text}; | ||
} | ||
} | ||
`; | ||
}} | ||
`; | ||
|
||
const actionStyles = css` | ||
${({ | ||
theme: { | ||
colors: { | ||
button: { action }, | ||
}, | ||
}, | ||
}) => css` | ||
background: ${action.background}; | ||
border: none; | ||
color: ${action.text}; | ||
padding: 6px 8px; | ||
min-width: auto; | ||
|
||
&&& { | ||
&:hover:not(:disabled) { | ||
color: ${action.hover.background}; | ||
} | ||
|
||
&:active:not(:disabled) { | ||
border: ${action.active.text}; | ||
color: ${action.active.text}; | ||
} | ||
|
||
&:disabled { | ||
color: ${action.disabled.text}; | ||
} | ||
} | ||
`} | ||
`; | ||
|
||
const variantStyles: Record<ButtonClass, RuleSet<object>> = { | ||
[ButtonClass.LightPrimary]: generateButtonStyles("primary", "light"), | ||
[ButtonClass.LightSecondary]: generateButtonStyles("secondary", "light"), | ||
[ButtonClass.DarkPrimary]: generateButtonStyles("primary", "dark"), | ||
[ButtonClass.DarkSecondary]: generateButtonStyles("secondary", "dark"), | ||
[ButtonClass.Action]: actionStyles, | ||
}; | ||
|
||
const StyledButton = styled(AntButton)<CustomButtonProps>` | ||
${baseStyles} | ||
${({ variant = ButtonClass.LightPrimary }) => variantStyles[variant]} | ||
`; | ||
|
||
export const CustomButton = forwardRef<HTMLButtonElement, CustomButtonProps>( | ||
( | ||
{ | ||
children, | ||
variant = ButtonClass.LightPrimary, | ||
titleText, | ||
icon, | ||
onClick, | ||
disabled, | ||
...props | ||
}, | ||
ref | ||
) => { | ||
return ( | ||
<StyledButton | ||
ref={ref} | ||
variant={variant} | ||
onClick={onClick} | ||
disabled={disabled} | ||
{...props} | ||
> | ||
{titleText || children} {icon} | ||
</StyledButton> | ||
); | ||
} | ||
); | ||
|
||
CustomButton.displayName = "CustomButton"; | ||
|
||
export const TooltipButton: React.FC<TooltipButtonProps> = ({ | ||
tooltipText = { defaultText: "", disabledText: "" }, | ||
tooltipPlacement, | ||
disabled = false, | ||
...buttonProps | ||
}) => { | ||
const tooltipRenderText = disabled | ||
? tooltipText.disabledText | ||
: tooltipText.defaultText; | ||
const [tooltipVisible, setTooltipVisible] = useState(false); | ||
|
||
const handleMouseEnter = (e: React.MouseEvent<HTMLElement>) => { | ||
setTooltipVisible(true); | ||
buttonProps.onMouseEnter?.(e); | ||
}; | ||
|
||
const handleMouseLeave = (e: React.MouseEvent<HTMLElement>) => { | ||
setTooltipVisible(false); | ||
buttonProps.onMouseLeave?.(e); | ||
}; | ||
|
||
return ( | ||
<div | ||
className="inline-block" | ||
onMouseEnter={handleMouseEnter} | ||
onMouseLeave={handleMouseLeave} | ||
> | ||
<Tooltip | ||
placement={tooltipPlacement} | ||
title={tooltipRenderText} | ||
color={TOOLTIP_COLOR} | ||
mouseEnterDelay={TOOLTIP_DELAY} | ||
align={{ targetOffset: NAV_BAR_TOOLTIP_OFFSET }} | ||
trigger={["hover", "focus"]} | ||
open={tooltipVisible} | ||
> | ||
<div> | ||
<CustomButton {...buttonProps} disabled={disabled} /> | ||
</div> | ||
</Tooltip> | ||
</div> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeesh. Which brand of CSS magic does this triple ampersand belong to? Does this mean "parent-parent-parent"..?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No! It's both better and worse than that.
In
styled-components
this just directly raises the specificity of the current selector. Yes, this is arbitrary and smells a bit like using!important
but for me its a needed workaround for quality of life when stylingantd
.Because some
antd
components apply many different class names, change element nesting, and even changes which base html element is rendered for different components, it can be tricky to ensure that our styles will be consistently applied. This solves that in a brute force kind of way, but is better than using!important
because these triple applied class hashes CAN be overridden by something of greater selector specificity, whereas!important
ignores the specificity hierarchy entirely.When our styles don't really map onto ant's opinions very well, I prefer to just sort of veto all their styling by using this triple ampersand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I sort of it get it now.
Does there come a point where we eschew the ant buttons altogether and just roll our own from scratch 🤔 ? I'd be curious as to when the team would consider that to be worth it.
This isn't a request for changes though! Feel free to resolve.