Skip to content

Commit

Permalink
♻️(frontend) simplify button primitive
Browse files Browse the repository at this point in the history
In object-oriented terms, the previous implementation violated the Liskov
Substitution Principle. Props between these two components (Button and Link)
were not substitutable.

This led to TypeScript errors and increased overall complexity without
significant DX gains. To address this, the LinkButton has been extracted
into a dedicated component.
  • Loading branch information
lebaudantoine committed Oct 9, 2024
1 parent 211ba33 commit 1875a39
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 19 deletions.
6 changes: 3 additions & 3 deletions src/frontend/src/components/Feedback.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Button } from '@/primitives'
import { css } from '@/styled-system/css'
import { RiExternalLinkLine } from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import { LinkButton } from '@/primitives'

export const Feedback = () => {
const { t } = useTranslation()
return (
<Button
<LinkButton
href="https://grist.incubateur.net/o/docs/forms/1YrfNP1QSSy8p2gCxMFnSf/4"
variant="success"
target="_blank"
Expand All @@ -20,6 +20,6 @@ export const Feedback = () => {
className={css({ marginLeft: 0.5 })}
aria-hidden="true"
/>
</Button>
</LinkButton>
)
}
17 changes: 1 addition & 16 deletions src/frontend/src/primitives/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {
Button as RACButton,
type ButtonProps as RACButtonsProps,
Link,
LinkProps,
} from 'react-aria-components'
import { type RecipeVariantProps } from '@/styled-system/css'
import { buttonRecipe, type ButtonRecipe } from './buttonRecipe'
Expand All @@ -12,25 +10,12 @@ export type ButtonProps = RecipeVariantProps<ButtonRecipe> &
RACButtonsProps &
TooltipWrapperProps

type LinkButtonProps = RecipeVariantProps<ButtonRecipe> &
LinkProps &
TooltipWrapperProps

type ButtonOrLinkProps = ButtonProps | LinkButtonProps

export const Button = ({
tooltip,
tooltipType = 'instant',
...props
}: ButtonOrLinkProps) => {
}: ButtonProps) => {
const [variantProps, componentProps] = buttonRecipe.splitVariantProps(props)
if ((props as LinkButtonProps).href !== undefined) {
return (
<TooltipWrapper tooltip={tooltip} tooltipType={tooltipType}>
<Link className={buttonRecipe(variantProps)} {...componentProps} />
</TooltipWrapper>
)
}

return (
<TooltipWrapper tooltip={tooltip} tooltipType={tooltipType}>
Expand Down
22 changes: 22 additions & 0 deletions src/frontend/src/primitives/LinkButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Link, LinkProps } from 'react-aria-components'
import { type RecipeVariantProps } from '@/styled-system/css'
import { buttonRecipe, type ButtonRecipe } from './buttonRecipe'
import { TooltipWrapper, type TooltipWrapperProps } from './TooltipWrapper'

type LinkButtonProps = RecipeVariantProps<ButtonRecipe> &
LinkProps &
TooltipWrapperProps

export const LinkButton = ({
tooltip,
tooltipType = 'instant',
...props
}: LinkButtonProps) => {
const [variantProps, componentProps] = buttonRecipe.splitVariantProps(props)

return (
<TooltipWrapper tooltip={tooltip} tooltipType={tooltipType}>
<Link className={buttonRecipe(variantProps)} {...componentProps} />
</TooltipWrapper>
)
}
1 change: 1 addition & 0 deletions src/frontend/src/primitives/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export { Badge } from './Badge'
export { Bold } from './Bold'
export { Box } from './Box'
export { Button } from './Button'
export { LinkButton } from './LinkButton'
export { useCloseDialog } from './useCloseDialog'
export { Dialog, type DialogProps } from './Dialog'
export { Div } from './Div'
Expand Down

0 comments on commit 1875a39

Please sign in to comment.