Skip to content

Commit 205e087

Browse files
fix(ButtonTwoLines): split renders of button and link
1 parent 5ed6b53 commit 205e087

File tree

4 files changed

+155
-60
lines changed

4 files changed

+155
-60
lines changed
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 type { Meta, StoryObj } from "@storybook/react"
3+
4+
import { ButtonLinkTwoLines as ButtonLinkTwoLinesComponent } from "../buttons/ButtonTwoLines"
5+
import { Stack } from "../flex"
6+
7+
const meta = {
8+
title: "Atoms / Form / Buttons / ButtonTwoLines",
9+
component: ButtonLinkTwoLinesComponent,
10+
} satisfies Meta<typeof ButtonLinkTwoLinesComponent>
11+
12+
export default meta
13+
14+
type Story = StoryObj<typeof meta>
15+
16+
export const ButtonLinkTwoLines: Story = {
17+
args: {
18+
icon: BiCircle,
19+
mainText: "Main Text",
20+
helperText: "Helper Text",
21+
className: "w-[300px]",
22+
href: "#",
23+
},
24+
render: (args) => (
25+
<Stack className="gap-8">
26+
<ButtonLinkTwoLinesComponent {...args} />
27+
<ButtonLinkTwoLinesComponent
28+
{...args}
29+
iconAlignment="end"
30+
size="sm"
31+
reverseTextOrder
32+
/>
33+
</Stack>
34+
),
35+
}

src/components/ui/__stories__/ButtonTwoLines.stories.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { BiCircle } from "react-icons/bi"
2-
import { Stack } from "@chakra-ui/react"
32
import { Meta, StoryObj } from "@storybook/react"
43

5-
import ButtonTwoLinesComponent from "../buttons/ButtonTwoLines"
4+
import { ButtonTwoLines as ButtonTwoLinesComponent } from "../buttons/ButtonTwoLines"
5+
import { Stack } from "../flex"
66

77
const meta = {
88
title: "Atoms / Form / Buttons / ButtonTwoLines",
@@ -15,14 +15,13 @@ type Story = StoryObj<typeof meta>
1515

1616
export const ButtonTwoLines: Story = {
1717
args: {
18-
componentType: "button",
1918
icon: BiCircle,
2019
mainText: "Main Text",
2120
helperText: "Helper Text",
2221
className: "w-[300px]",
2322
},
2423
render: (args) => (
25-
<Stack spacing="8">
24+
<Stack className="gap-8">
2625
<ButtonTwoLinesComponent {...args} />
2726
<ButtonTwoLinesComponent
2827
{...args}

src/components/ui/buttons/Button.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,9 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
126126
)
127127
Button.displayName = "Button"
128128

129-
type ButtonLinkProps = LinkProps &
129+
type ButtonLinkProps = Omit<LinkProps, "href"> &
130130
Pick<ButtonProps, "size" | "variant" | "isSecondary"> & {
131+
href: string
131132
buttonProps?: Omit<ButtonProps, "size" | "variant">
132133
customEventOptions?: MatomoEventOptions
133134
}

src/components/ui/buttons/ButtonTwoLines.tsx

Lines changed: 115 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -36,76 +36,103 @@ type CommonProps = {
3636

3737
type OmittedTypes = "variant" | "size" | "children"
3838

39-
type ButtonTypeProps = CommonProps &
40-
Omit<ButtonProps, OmittedTypes> & {
41-
componentType: "button"
42-
}
39+
type ButtonTwoLinesProps = Omit<ButtonProps, OmittedTypes> & CommonProps
4340

44-
type ButtonLinkTypeProps = CommonProps &
45-
Omit<ButtonLinkProps, OmittedTypes> & {
46-
componentType: "link"
47-
}
48-
49-
type ButtonTwoLinesProps = ButtonTypeProps | ButtonLinkTypeProps
50-
51-
const ButtonTwoLines = ({
52-
iconAlignment = "start",
41+
/**
42+
* Button that renders two styled lines of text
43+
*/
44+
export const ButtonTwoLines = ({
5345
className,
46+
iconAlignment = "start",
5447
size = "md",
5548
...props
5649
}: ButtonTwoLinesProps) => {
5750
const isIconLeft = ["left", "start"].includes(iconAlignment)
5851

59-
const commonClassStyles = cn(
60-
isIconLeft ? "text-start justify-start" : "text-end justify-end",
61-
size === "md" ? "py-4" : "py-2",
62-
className
52+
const [childProps, ownProps] = createSplitProps<ButtonTwoLinesProps>()(
53+
{ ...props, isIconLeft },
54+
[
55+
"reverseTextOrder",
56+
"mainText",
57+
"helperText",
58+
"variant",
59+
"icon",
60+
"isIconLeft",
61+
"isSecondary",
62+
"size",
63+
]
6364
)
6465

65-
if (props.componentType === "link") {
66-
const { buttonProps, ...rest } = props
67-
return (
68-
<ButtonLink
69-
className={commonClassStyles}
70-
// size={size}
71-
buttonProps={buttonProps}
72-
{...rest}
73-
>
74-
<ChildContent
75-
{...rest}
76-
size={size}
77-
isSecondary={buttonProps?.isSecondary}
78-
isIconLeft={isIconLeft}
79-
/>
80-
</ButtonLink>
81-
)
82-
}
8366
return (
84-
<Button className={commonClassStyles} size={size} {...props}>
85-
<ChildContent {...props} size={size} isIconLeft={isIconLeft} />
67+
<Button
68+
className={cn(
69+
isIconLeft ? "justify-start text-start" : "justify-end text-end",
70+
size === "md" ? "py-4" : "py-2",
71+
className
72+
)}
73+
{...ownProps}
74+
>
75+
<ChildContent {...childProps} />
8676
</Button>
8777
)
8878
}
8979

90-
export default ButtonTwoLines
91-
92-
const ChildContent = (
93-
props: Omit<ButtonTwoLinesProps, "iconAlignment" | "buttonProps"> & {
94-
isIconLeft: boolean
95-
isSecondary?: boolean
96-
}
97-
) => {
98-
const {
99-
reverseTextOrder = false,
100-
size,
101-
mainText,
102-
helperText,
103-
icon: Icon,
104-
isIconLeft,
105-
isSecondary,
106-
variant,
107-
} = props
80+
type ButtonLinkTwoLinesProps = Omit<ButtonLinkProps, OmittedTypes> & CommonProps
81+
82+
/**
83+
* ButtonLink that renders two styled lines of text
84+
*/
85+
export const ButtonLinkTwoLines = ({
86+
className,
87+
iconAlignment = "start",
88+
size = "md",
89+
...props
90+
}: ButtonLinkTwoLinesProps) => {
91+
const isIconLeft = ["left", "start"].includes(iconAlignment)
92+
93+
const [childProps, ownProps] = createSplitProps<ButtonLinkTwoLinesProps>()(
94+
{ ...props, isIconLeft },
95+
[
96+
"reverseTextOrder",
97+
"mainText",
98+
"helperText",
99+
"variant",
100+
"icon",
101+
"isIconLeft",
102+
"isSecondary",
103+
"size",
104+
]
105+
)
106+
107+
return (
108+
<ButtonLink
109+
className={cn(
110+
isIconLeft ? "justify-start text-start" : "justify-end text-end",
111+
size === "md" ? "py-4" : "py-2",
112+
className
113+
)}
114+
{...ownProps}
115+
>
116+
<ChildContent {...childProps} />
117+
</ButtonLink>
118+
)
119+
}
120+
121+
type ChildContentProps = Omit<CommonProps, "iconAlignment"> & {
122+
isIconLeft: boolean
123+
isSecondary?: boolean
124+
}
108125

126+
const ChildContent = ({
127+
helperText,
128+
icon: Icon,
129+
mainText,
130+
reverseTextOrder = false,
131+
size,
132+
variant,
133+
isIconLeft,
134+
isSecondary,
135+
}: ChildContentProps) => {
109136
const ButtonIcon = () => (
110137
<Icon
111138
// TODO: Text size here should not be marked important after migration
@@ -141,3 +168,36 @@ const ChildContent = (
141168
</>
142169
)
143170
}
171+
172+
/**
173+
* Split props ripped from Ark UI and simplified:
174+
* https://github.com/chakra-ui/ark/blob/main/packages/react/src/utils/create-split-props.ts
175+
*/
176+
type EnsureKeys<ExpectedKeys extends (keyof ChildContentProps)[]> =
177+
keyof ChildContentProps extends ExpectedKeys[number]
178+
? unknown
179+
: `Missing required keys: ${Exclude<keyof ChildContentProps, ExpectedKeys[number]> & string}`
180+
181+
function createSplitProps<ParentProps>() {
182+
return <
183+
Keys extends (keyof ChildContentProps)[],
184+
Props = Required<ParentProps>,
185+
>(
186+
props: Props,
187+
keys: Keys & EnsureKeys<Keys>
188+
) =>
189+
(keys as string[]).reduce<
190+
[ChildContentProps, Omit<Props, Extract<(typeof keys)[number], string>>]
191+
>(
192+
(previousValue, currentValue) => {
193+
const [target, source] = previousValue
194+
const key = currentValue
195+
if (source[key] !== undefined) {
196+
target[key] = source[key]
197+
}
198+
delete source[key]
199+
return [target, source]
200+
},
201+
[{} as ChildContentProps, { ...props }]
202+
)
203+
}

0 commit comments

Comments
 (0)