Skip to content

Commit f6fd7a4

Browse files
Merge remote-tracking branch 'upstream/dev' into feat/shadcn-migrate-checkbox
2 parents 3f6ce04 + 08a4bcb commit f6fd7a4

File tree

5 files changed

+442
-1
lines changed

5 files changed

+442
-1
lines changed

.storybook/preview.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ const preview: Preview = {
4242
],
4343
parameters: {
4444
i18n,
45-
actions: { argTypesRegex: "^on[A-Z].*" },
4645
controls: {
4746
matchers: {
4847
color: /(background|color)$/i,
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import { MdChevronRight, MdExpandMore, MdNightlight } from "react-icons/md"
2+
import type { Meta, StoryObj } from "@storybook/react"
3+
4+
import Translation from "@/components/Translation"
5+
6+
import { HStack, VStack } from "../../../src/components/ui/flex"
7+
import { Button, type ButtonVariantProps } from "../buttons/Button"
8+
import { ButtonLink } from "../buttons/Button"
9+
10+
const meta = {
11+
title: "Atoms / Form / ShadCn Buttons",
12+
component: Button,
13+
args: {
14+
children: "What is Ethereum?",
15+
},
16+
} satisfies Meta<typeof Button>
17+
18+
export default meta
19+
20+
type Story = StoryObj<typeof meta>
21+
22+
const variants: ButtonVariantProps["variant"][] = [
23+
"solid",
24+
"outline",
25+
"ghost",
26+
"link",
27+
]
28+
29+
export const StyleVariants: Story = {
30+
render: (args) => (
31+
<VStack className="gap-4">
32+
{variants.map((variant) => (
33+
<HStack key={variant} className="gap-4">
34+
<Button variant={variant} {...args} />
35+
<Button disabled variant={variant} {...args} />
36+
</HStack>
37+
))}
38+
</VStack>
39+
),
40+
}
41+
42+
export const IconVariants: Story = {
43+
render: (args) => (
44+
<HStack>
45+
<VStack>
46+
<Button {...args} />
47+
<Button size="sm" {...args} />
48+
</VStack>
49+
<VStack>
50+
<Button {...args}>
51+
<MdExpandMore />
52+
{args.children}
53+
</Button>
54+
<Button size="sm" {...args}>
55+
<MdExpandMore />
56+
{args.children}
57+
</Button>
58+
</VStack>
59+
<VStack>
60+
<Button {...args}>
61+
{args.children}
62+
<MdChevronRight />
63+
</Button>
64+
<Button size="sm" {...args}>
65+
{args.children}
66+
<MdChevronRight />
67+
</Button>
68+
</VStack>
69+
<VStack>
70+
<Button aria-label="next" {...args}>
71+
<MdChevronRight />
72+
</Button>
73+
<Button aria-label="next" size="sm" {...args}>
74+
<MdChevronRight />
75+
</Button>
76+
</VStack>
77+
</HStack>
78+
),
79+
}
80+
81+
export const MultiLineText: Story = {
82+
args: {
83+
children: "Button label can have two lines",
84+
},
85+
render: (args) => (
86+
<HStack>
87+
<VStack className="max-w-[171px]">
88+
<Button variant="outline" isSecondary {...args} />
89+
<Button variant="outline" size="sm" isSecondary {...args} />
90+
</VStack>
91+
<VStack className="max-w-[171px]">
92+
<Button {...args} />
93+
<Button size="sm" {...args} />
94+
</VStack>
95+
<VStack className="max-w-[209px]">
96+
<Button {...args}>
97+
{args.children}
98+
<MdChevronRight />
99+
</Button>
100+
<Button size="sm" {...args}>
101+
{args.children}
102+
<MdChevronRight />
103+
</Button>
104+
</VStack>
105+
</HStack>
106+
),
107+
}
108+
109+
export const OverrideStyles: Story = {
110+
render: () => (
111+
<>
112+
<p>
113+
Show custom styling examples here for visual testing of overrides from
114+
the theme config
115+
</p>
116+
<VStack>
117+
<Button aria-label="toggle" className="px-1.5">
118+
<MdNightlight />
119+
</Button>
120+
<ButtonLink
121+
className="rounded-full px-0 py-0"
122+
linkProps={{ href: "#" }}
123+
>
124+
<Translation id="get-involved" />
125+
</ButtonLink>
126+
</VStack>
127+
</>
128+
),
129+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { BiCircle } from "react-icons/bi"
2+
import { Stack } from "@chakra-ui/react"
3+
import { Meta, StoryObj } from "@storybook/react"
4+
5+
import ButtonTwoLinesComponent from "../buttons/ButtonTwoLines"
6+
7+
const meta = {
8+
title: "Atoms / Form / ShadCn Buttons / ButtonTwoLines",
9+
component: ButtonTwoLinesComponent,
10+
} satisfies Meta<typeof ButtonTwoLinesComponent>
11+
12+
export default meta
13+
14+
type Story = StoryObj<typeof meta>
15+
16+
export const ButtonTwoLines: Story = {
17+
args: {
18+
componentType: "button",
19+
icon: BiCircle,
20+
mainText: "Main Text",
21+
helperText: "Helper Text",
22+
className: "w-[300px]",
23+
},
24+
render: (args) => (
25+
<Stack spacing="8">
26+
<ButtonTwoLinesComponent {...args} />
27+
<ButtonTwoLinesComponent
28+
{...args}
29+
iconAlignment="end"
30+
size="sm"
31+
reverseTextOrder
32+
/>
33+
</Stack>
34+
),
35+
}

tailwind/ui/buttons/Button.tsx

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import * as React from "react"
2+
import { cva, type VariantProps } from "class-variance-authority"
3+
import { Slot } from "@radix-ui/react-slot"
4+
5+
import { BaseLink, type LinkProps } from "@/components/Link"
6+
7+
import { cn } from "@/lib/utils/cn"
8+
import { type MatomoEventOptions, trackCustomEvent } from "@/lib/utils/matomo"
9+
import { scrollIntoView } from "@/lib/utils/scrollIntoView"
10+
11+
const buttonVariants = cva(
12+
"inline-flex gap-2 items-center justify-center rounded border border-solid border-current text-primary transition focus-visible:outline focus-visible:outline-4 focus-visible:outline-primary-hover focus-visible:-outline-offset-1 disabled:text-disabled disabled:pointer-events-none hover:text-primary-hover [&[data-secondary='true']]:text-body [&>svg]:flex-shrink-0",
13+
{
14+
variants: {
15+
variant: {
16+
solid:
17+
"!text-background bg-primary border-transparent disabled:bg-disabled disabled:text-background hover:text-background hover:bg-primary-hover hover:shadow-button-hover active:shadow-none",
18+
outline: "hover:shadow-button-hover active:shadow-none",
19+
ghost: "border-transparent",
20+
link: "border-transparent font-bold underline py-0 px-1 active:text-primary",
21+
},
22+
size: {
23+
md: "min-h-10.5 px-4 py-2 [&>svg]:text-2xl",
24+
sm: "text-xs min-h-[31px] py-1.5 px-2 [&>svg]:text-md",
25+
},
26+
},
27+
defaultVariants: {
28+
variant: "solid",
29+
size: "md",
30+
},
31+
}
32+
)
33+
34+
export const checkIsSecondary = ({
35+
variant,
36+
isSecondary,
37+
}: {
38+
variant: ButtonVariantProps["variant"]
39+
isSecondary: boolean
40+
}) => {
41+
// These two variants do not have secondary styling, so prevent overrides
42+
return {
43+
"data-secondary":
44+
!["solid", "link"].includes(variant || "solid") && isSecondary,
45+
}
46+
}
47+
48+
type ButtonVariantProps = VariantProps<typeof buttonVariants>
49+
50+
export interface ButtonProps
51+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
52+
ButtonVariantProps {
53+
asChild?: boolean
54+
/**
55+
* Set string value that matches the `id` attribute value used
56+
* on another element in a given page. Selecting the button will then
57+
* trigger a scroll to that element.
58+
*/
59+
toId?: string
60+
/**
61+
* Custom theme prop. If true, `body` color is used instead of
62+
* `primary` color in the theming.
63+
*
64+
* `NOTE`: Does not apply to the `Solid` or `Link` variants
65+
*/
66+
isSecondary?: boolean
67+
customEventOptions?: MatomoEventOptions
68+
}
69+
70+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
71+
(
72+
{
73+
className,
74+
variant,
75+
size,
76+
asChild = false,
77+
isSecondary = false,
78+
onClick,
79+
toId,
80+
customEventOptions,
81+
...props
82+
},
83+
ref
84+
) => {
85+
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement>) => {
86+
toId && scrollIntoView(toId)
87+
customEventOptions && trackCustomEvent(customEventOptions)
88+
89+
onClick?.(e)
90+
}
91+
92+
const Comp = asChild ? Slot : "button"
93+
return (
94+
<Comp
95+
className={cn(buttonVariants({ variant, size, className }))}
96+
ref={ref}
97+
onClick={handleOnClick}
98+
{...checkIsSecondary({
99+
variant,
100+
isSecondary,
101+
})}
102+
{...props}
103+
/>
104+
)
105+
}
106+
)
107+
Button.displayName = "Button"
108+
109+
type ButtonLinkProps = ButtonProps & {
110+
linkProps: LinkProps
111+
customEventOptions?: MatomoEventOptions
112+
}
113+
114+
const ButtonLink = React.forwardRef<HTMLAnchorElement, ButtonLinkProps>(
115+
({ linkProps, customEventOptions, children, ...buttonProps }, ref) => {
116+
const handleClick = () => {
117+
customEventOptions && trackCustomEvent(customEventOptions)
118+
}
119+
return (
120+
<Button asChild {...buttonProps}>
121+
<BaseLink
122+
ref={ref}
123+
activeStyle={{}}
124+
// TODO: Redress this override when migrating the link component
125+
color={
126+
buttonProps.variant === "solid" ? "background.base" : undefined
127+
}
128+
textDecor="none"
129+
_hover={{
130+
textDecor: "none",
131+
}}
132+
{...linkProps}
133+
onClick={handleClick}
134+
>
135+
{children}
136+
</BaseLink>
137+
</Button>
138+
)
139+
}
140+
)
141+
ButtonLink.displayName = "ButtonLink"
142+
143+
export {
144+
Button,
145+
ButtonLink,
146+
type ButtonLinkProps,
147+
type ButtonVariantProps,
148+
buttonVariants,
149+
}

0 commit comments

Comments
 (0)