Skip to content

Commit

Permalink
feat: add LinkButton component, update FeaturedLink usage in PricingC…
Browse files Browse the repository at this point in the history
…ards
  • Loading branch information
jamiehenson committed Dec 12, 2024
1 parent 581566e commit 750b631
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 24 deletions.
46 changes: 34 additions & 12 deletions src/core/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type ButtonType = "priority" | "primary" | "secondary";

type ButtonSize = "lg" | "md" | "sm" | "xs";

type ButtonProps = {
export type ButtonPropsBase = {
/**
* The type of button: priority, primary, or secondary.
*/
Expand All @@ -28,7 +28,10 @@ type ButtonProps = {
* Optional classes to add to the button element.
*/
className?: string;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
};

type ButtonProps = ButtonPropsBase &
React.ButtonHTMLAttributes<HTMLButtonElement>;

// got to go the long way round because of ol' mate Taily Waily
const buttonClasses: Record<ButtonType, Record<ButtonSize, string>> = {
Expand Down Expand Up @@ -62,6 +65,32 @@ export const iconModifierClasses: Record<
xs: { left: "", right: "" },
};

export const commonButtonProps = (props: ButtonPropsBase) => {
const { variant = "primary", size, leftIcon, rightIcon, className } = props;

return {
className: cn(
buttonClasses[variant][size ?? "md"],
{ [iconModifierClasses[size ?? "md"].left]: leftIcon },
{ [iconModifierClasses[size ?? "md"].right]: rightIcon },
className,
),
};
};

export const commonButtonInterior = (
props: PropsWithChildren<ButtonPropsBase>,
) => {
const { leftIcon, rightIcon, children } = props;
return (
<>
{leftIcon ? <Icon name={leftIcon} /> : null}
{children}
{rightIcon ? <Icon name={rightIcon} /> : null}
</>
);
};

const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
variant = "primary",
size,
Expand All @@ -73,17 +102,10 @@ const Button: React.FC<PropsWithChildren<ButtonProps>> = ({
}) => {
return (
<button
className={cn(
buttonClasses[variant][size ?? "md"],
{ [iconModifierClasses[size ?? "md"].left]: leftIcon },
{ [iconModifierClasses[size ?? "md"].right]: rightIcon },
className,
)}
{...rest}
{...commonButtonProps({ variant, size, leftIcon, rightIcon, className })}
{...(rest as React.ButtonHTMLAttributes<HTMLButtonElement>)}
>
{leftIcon ? <Icon name={leftIcon} /> : null}
{children}
{rightIcon ? <Icon name={rightIcon} /> : null}
{commonButtonInterior({ leftIcon, rightIcon, children })}
</button>
);
};
Expand Down
54 changes: 54 additions & 0 deletions src/core/LinkButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React from "react";
import {
ButtonPropsBase,
commonButtonInterior,
commonButtonProps,
} from "./Button";
import cn from "./utils/cn";

export type LinkButtonProps = ButtonPropsBase & {
disabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
} & React.AnchorHTMLAttributes<HTMLAnchorElement>;

const LinkButton: React.FC<LinkButtonProps> = ({
variant = "primary",
size,
leftIcon,
rightIcon,
children,
className,
disabled,
onClick,
...rest
}) => {
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
if (disabled) {
e.preventDefault();
return;
}
onClick?.(e);
};

return (
<a
{...commonButtonProps({
variant,
size,
leftIcon,
rightIcon,
className: cn(className, {
"ui-button-disabled dark:ui-button-disabled-dark": disabled,
}),
})}
role="button"
aria-disabled={disabled}
onClick={handleClick}
{...(rest as React.AnchorHTMLAttributes<HTMLAnchorElement>)}
>
{commonButtonInterior({ leftIcon, rightIcon, children })}
</a>
);
};

export default LinkButton;
40 changes: 40 additions & 0 deletions src/core/LinkButton/LinkButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";
import { Meta, StoryFn } from "@storybook/react";
import LinkButton, { LinkButtonProps } from "../LinkButton";

export default {
title: "Components/Link Button",
component: LinkButton,
parameters: {
docs: {
description: {
component:
"A variant of the `Button` component that renders an `anchor` element instead of a `button` element, with all the typing constraints and props one would expect from an anchor.",
},
},
},
} as Meta;

const Template: StoryFn<LinkButtonProps> = (args) => <LinkButton {...args} />;

export const Default = Template.bind({});
Default.args = {
href: "#",
variant: "primary",
children: "Default Link Button",
};

export const External = Template.bind({});
External.args = {
href: "https://www.ably.com",
children: "External Link Button",
target: "_blank",
rel: "noopener noreferrer",
};

export const Disabled = Template.bind({});
Disabled.args = {
href: "#",
children: "Disabled Link Button",
disabled: true,
};
31 changes: 31 additions & 0 deletions src/core/LinkButton/__snapshots__/LinkButton.stories.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Components/Link Button Default smoke-test 1`] = `
<a class="ui-button-primary"
role="button"
href="#"
>
Default Link Button
</a>
`;

exports[`Components/Link Button Disabled smoke-test 1`] = `
<a class="ui-button-primary ui-button-disabled dark:ui-button-disabled-dark"
role="button"
aria-disabled="true"
href="#"
>
Disabled Link Button
</a>
`;

exports[`Components/Link Button External smoke-test 1`] = `
<a class="ui-button-primary"
role="button"
href="https://www.ably.com"
target="_blank"
rel="noopener noreferrer"
>
External Link Button
</a>
`;
16 changes: 8 additions & 8 deletions src/core/Pricing/PricingCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import throttle from "lodash.throttle";
import type { PricingDataFeature } from "./types";
import Icon from "../Icon";
import FeaturedLink from "../FeaturedLink";
import LinkButton from "../LinkButton";
import { IconName } from "../Icon/types";
import Tooltip from "../Tooltip";
import cn from "../utils/cn";
Expand Down Expand Up @@ -183,18 +184,17 @@ const PricingCards = ({ data, delimiter }: PricingCardsProps) => {
</div>
{cta ? (
<div className="group">
<FeaturedLink
additionalCSS={cn(
"text-center px-24 !py-12 hover:text-neutral-000 cursor-pointer",
cta.className ??
"ui-btn bg-neutral-1300 dark:bg-neutral-000 text-neutral-000 dark:text-neutral-1300",
)}
url={cta.url}
<LinkButton
className={cn("w-full", cta.className)}
variant={
cta.url === "/contact" ? "priority" : "primary"
}
href={cta.url}
onClick={cta.onClick}
disabled={cta.disabled}
>
{cta.text}
</FeaturedLink>
</LinkButton>
</div>
) : delimiter ? null : (
<div className="flex items-center justify-center h-48 w-full">
Expand Down
1 change: 0 additions & 1 deletion src/core/Pricing/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ export const planData: PricingDataFeature[] = [
cta: {
text: "Contact us",
url: "/contact",
className: "ui-btn-alt text-neutral-000",
},
sections: [
{
Expand Down
14 changes: 11 additions & 3 deletions src/core/styles/buttons.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@layer components {
.ui-button-base {
@apply inline-flex rounded justify-center items-center gap-12 text-neutral-000 transition-colors focus-base disabled:cursor-not-allowed;
@apply inline-flex rounded justify-center items-center gap-12 text-neutral-000 transition-colors focus-base;
}

.ui-button-lg-base {
Expand All @@ -20,11 +20,11 @@
}

.ui-button-disabled-base {
@apply disabled:bg-gui-unavailable disabled:text-gui-unavailable-dark disabled:hover:bg-gui-unavailable;
@apply disabled:cursor-not-allowed disabled:bg-gui-unavailable disabled:text-gui-unavailable-dark disabled:hover:bg-gui-unavailable;
}

.ui-button-disabled-base-dark {
@apply disabled:bg-gui-unavailable-dark disabled:text-gui-unavailable disabled:hover:bg-gui-unavailable-dark;
@apply disabled:cursor-not-allowed disabled:bg-gui-unavailable-dark disabled:text-gui-unavailable disabled:hover:bg-gui-unavailable-dark;
}

.ui-button-priority-base {
Expand Down Expand Up @@ -122,4 +122,12 @@
.ui-button-sm-right-icon {
@apply pr-[18px];
}

.ui-button-disabled {
@apply select-none pointer-events-none bg-gui-unavailable text-gui-unavailable-dark hover:bg-gui-unavailable;
}

.ui-theme-dark .ui-button-disabled {
@apply select-none pointer-events-none bg-gui-unavailable-dark text-gui-unavailable hover:bg-gui-unavailable-dark;
}
}

0 comments on commit 750b631

Please sign in to comment.