diff --git a/src/components/BugBountyCards.tsx b/src/components/BugBountyCards.tsx index fb017ea3611..56786711e3e 100644 --- a/src/components/BugBountyCards.tsx +++ b/src/components/BugBountyCards.tsx @@ -14,7 +14,10 @@ const CardRow = ({ children }: ChildOnlyProp) => ( {children} ) -const SubmitBugBountyButton = ({ children, ...props }: ButtonLinkProps) => ( +const SubmitBugBountyButton = ({ + children, + ...props +}: Omit) => ( & { - href?: never - } - -type ButtonLinkTypeProps = CommonProps & - Omit & { - toId?: never - } - -type ButtonTwoLinesProps = ButtonTypeProps | ButtonLinkTypeProps - -const hasHref = (props: ButtonTwoLinesProps): props is ButtonLinkTypeProps => { - return "href" in props -} - -const ButtonTwoLines = (props: ButtonTwoLinesProps) => { - const { - icon: Icon, - iconAlignment = "start", - mainText, - helperText, - reverseTextOrder = false, - size = "md", - ...rest - } = props - - const isIconLeft = ["left", "start"].includes(iconAlignment) - - const vertPadding: ButtonTwoLinesProps["py"] = size === "md" ? "4" : "2" - - const buttonStyles: ButtonProps = { - [isIconLeft ? "leftIcon" : "rightIcon"]: , - textAlign: isIconLeft ? "start" : "end", - justifyContent: isIconLeft ? "flex-start" : "flex-end", - } - - const Component = hasHref(props) ? ButtonLink : Button - - return ( - // TODO: fix type error - // @ts-expect-error incompatible prop type shapes - - - - {mainText} - - - {helperText} - - - - ) -} - -export default ButtonTwoLines diff --git a/src/components/Buttons/index.ts b/src/components/Buttons/index.ts index 73e0e2bcbb1..6f05838f36d 100644 --- a/src/components/Buttons/index.ts +++ b/src/components/Buttons/index.ts @@ -1,4 +1,3 @@ export { default as Button, type ButtonProps, checkIsSecondary } from "./Button" export { default as ButtonLink, type ButtonLinkProps } from "./ButtonLink" -export { default as ButtonTwoLines } from "./ButtonTwoLines" export { default as IconButton } from "./IconButton" diff --git a/src/components/ui/__stories__/ButtonLinkTwoLines.stories.tsx b/src/components/ui/__stories__/ButtonLinkTwoLines.stories.tsx new file mode 100644 index 00000000000..0c0ffaaf90e --- /dev/null +++ b/src/components/ui/__stories__/ButtonLinkTwoLines.stories.tsx @@ -0,0 +1,35 @@ +import { BiCircle } from "react-icons/bi" +import type { Meta, StoryObj } from "@storybook/react" + +import { ButtonLinkTwoLines as ButtonLinkTwoLinesComponent } from "../buttons/ButtonTwoLines" +import { Stack } from "../flex" + +const meta = { + title: "Atoms / Form / Buttons / ButtonTwoLines", + component: ButtonLinkTwoLinesComponent, +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const ButtonLinkTwoLines: Story = { + args: { + icon: BiCircle, + mainText: "Main Text", + helperText: "Helper Text", + className: "w-[300px]", + href: "#", + }, + render: (args) => ( + + + + + ), +} diff --git a/src/components/ui/__stories__/ButtonTwoLines.stories.tsx b/src/components/ui/__stories__/ButtonTwoLines.stories.tsx index 07606c0f1bd..dc3850cc3c9 100644 --- a/src/components/ui/__stories__/ButtonTwoLines.stories.tsx +++ b/src/components/ui/__stories__/ButtonTwoLines.stories.tsx @@ -1,8 +1,8 @@ import { BiCircle } from "react-icons/bi" -import { Stack } from "@chakra-ui/react" import { Meta, StoryObj } from "@storybook/react" -import ButtonTwoLinesComponent from "../buttons/ButtonTwoLines" +import { ButtonTwoLines as ButtonTwoLinesComponent } from "../buttons/ButtonTwoLines" +import { Stack } from "../flex" const meta = { title: "Atoms / Form / Buttons / ButtonTwoLines", @@ -15,14 +15,13 @@ type Story = StoryObj export const ButtonTwoLines: Story = { args: { - componentType: "button", icon: BiCircle, mainText: "Main Text", helperText: "Helper Text", className: "w-[300px]", }, render: (args) => ( - + ( ) Button.displayName = "Button" -type ButtonLinkProps = LinkProps & +type ButtonLinkProps = Omit & Pick & { + href: string buttonProps?: Omit customEventOptions?: MatomoEventOptions } diff --git a/src/components/ui/buttons/ButtonTwoLines.tsx b/src/components/ui/buttons/ButtonTwoLines.tsx index 332290ba5bc..b2b5dcd84bf 100644 --- a/src/components/ui/buttons/ButtonTwoLines.tsx +++ b/src/components/ui/buttons/ButtonTwoLines.tsx @@ -36,76 +36,103 @@ type CommonProps = { type OmittedTypes = "variant" | "size" | "children" -type ButtonTypeProps = CommonProps & - Omit & { - componentType: "button" - } +type ButtonTwoLinesProps = Omit & CommonProps -type ButtonLinkTypeProps = CommonProps & - Omit & { - componentType: "link" - } - -type ButtonTwoLinesProps = ButtonTypeProps | ButtonLinkTypeProps - -const ButtonTwoLines = ({ - iconAlignment = "start", +/** + * Button that renders two styled lines of text + */ +export const ButtonTwoLines = ({ className, + iconAlignment = "start", size = "md", ...props }: ButtonTwoLinesProps) => { const isIconLeft = ["left", "start"].includes(iconAlignment) - const commonClassStyles = cn( - isIconLeft ? "text-start justify-start" : "text-end justify-end", - size === "md" ? "py-4" : "py-2", - className + const [childProps, ownProps] = createSplitProps()( + { ...props, isIconLeft, size }, + [ + "reverseTextOrder", + "mainText", + "helperText", + "variant", + "icon", + "isIconLeft", + "isSecondary", + "size", + ] ) - if (props.componentType === "link") { - const { buttonProps, ...rest } = props - return ( - - - - ) - } return ( - ) } -export default ButtonTwoLines - -const ChildContent = ( - props: Omit & { - isIconLeft: boolean - isSecondary?: boolean - } -) => { - const { - reverseTextOrder = false, - size, - mainText, - helperText, - icon: Icon, - isIconLeft, - isSecondary, - variant, - } = props +type ButtonLinkTwoLinesProps = Omit & CommonProps + +/** + * ButtonLink that renders two styled lines of text + */ +export const ButtonLinkTwoLines = ({ + className, + iconAlignment = "start", + size = "md", + ...props +}: ButtonLinkTwoLinesProps) => { + const isIconLeft = ["left", "start"].includes(iconAlignment) + + const [childProps, ownProps] = createSplitProps()( + { ...props, isIconLeft, size }, + [ + "reverseTextOrder", + "mainText", + "helperText", + "variant", + "icon", + "isIconLeft", + "isSecondary", + "size", + ] + ) + + return ( + + + + ) +} + +type ChildContentProps = Omit & { + isIconLeft: boolean + isSecondary?: boolean +} +const ChildContent = ({ + helperText, + icon: Icon, + mainText, + reverseTextOrder = false, + size, + variant, + isIconLeft, + isSecondary, +}: ChildContentProps) => { const ButtonIcon = () => ( ) } + +/** + * Split props ripped from Ark UI and simplified: + * https://github.com/chakra-ui/ark/blob/main/packages/react/src/utils/create-split-props.ts + */ +type EnsureKeys = + keyof ChildContentProps extends ExpectedKeys[number] + ? unknown + : `Missing required keys: ${Exclude & string}` + +function createSplitProps() { + return < + Keys extends (keyof ChildContentProps)[], + Props = Required, + >( + props: Props, + keys: Keys & EnsureKeys + ) => + (keys as string[]).reduce< + [ChildContentProps, Omit>] + >( + (previousValue, currentValue) => { + const [target, source] = previousValue + const key = currentValue + if (source[key] !== undefined) { + target[key] = source[key] + } + delete source[key] + return [target, source] + }, + [{} as ChildContentProps, { ...props }] + ) +}