Skip to content

Commit

Permalink
Merge pull request #64 from argos-ci/pricing-page
Browse files Browse the repository at this point in the history
feat: add pricing page
  • Loading branch information
jsfez committed May 20, 2023
2 parents 8678676 + 4407a33 commit ca176cb
Show file tree
Hide file tree
Showing 10 changed files with 765 additions and 7 deletions.
28 changes: 24 additions & 4 deletions components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@ import { forwardRef } from "react";

export type ButtonColor = "primary" | "neutral";
export type ButtonVariant = "contained" | "outline";
export type ButtonSize = "base" | "small" | "large";

export interface ButtonProps extends AriakitButtonProps<"button"> {
color?: ButtonColor;
variant?: ButtonVariant;
size?: ButtonSize;
}

const variantClassNames: Record<ButtonVariant, Record<ButtonColor, string>> = {
contained: {
primary:
"color-white border-transparent bg-primary-600 hover:bg-primary-700 active:bg-primary-800 aria-expanded:bg-primary-800",
"text-white border-transparent bg-primary-600 hover:bg-primary-700 active:bg-primary-800 aria-expanded:bg-primary-800",
neutral:
"color-white border-transparent bg-neutral-600 hover:bg-neutral-700 active:bg-neutral-800 aria-expanded:bg-neutral-800",
"text-white border-transparent bg-neutral-600 hover:bg-neutral-700 active:bg-neutral-800 aria-expanded:bg-neutral-800",
},
outline: {
primary:
Expand All @@ -26,9 +28,22 @@ const variantClassNames: Record<ButtonVariant, Record<ButtonColor, string>> = {
},
};

const sizeClassNames: Record<ButtonSize, string> = {
base: "rounded-lg py-2 px-3 text-sm leading-none",
small: "rounded py-1 px-2 text-xs leading-4",
large: "rounded py-2 px-3 text-base",
};

export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{ color = "primary", variant = "contained", children, className, ...props },
{
color = "primary",
variant = "contained",
size = "base",
children,
className,
...props
},
ref
) => {
const colorClassNames = variantClassNames[variant];
Expand All @@ -39,14 +54,19 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
if (!variantClassName) {
throw new Error(`Invalid color: ${color}`);
}
const sizeClassName = sizeClassNames[size];
if (!sizeClassName) {
throw new Error(`Invalid size: ${color}`);
}
return (
<AriakitButton
ref={ref}
as="button"
className={clsx(
className,
variantClassName,
"align-center inline-flex whitespace-nowrap rounded-lg border py-2 px-3 font-sans text-sm font-medium leading-none transition disabled:opacity-40 [&:is(button)]:cursor-default"
sizeClassName,
"align-center inline-flex whitespace-nowrap border font-sans font-medium transition disabled:opacity-40 [&:is(button)]:cursor-default"
)}
{...props}
>
Expand Down
63 changes: 63 additions & 0 deletions components/Card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { clsx } from "clsx";
import { HTMLProps } from "react";

export const Card = ({ className, ...props }: HTMLProps<HTMLDivElement>) => {
return (
<div
className={clsx(
className,
"w-full rounded-xl border border-border bg-slate-900/50"
)}
{...props}
/>
);
};

export const CardBody = ({
className,
...props
}: HTMLProps<HTMLDivElement>) => {
return <div className={clsx(className, "font-ms p-4")} {...props} />;
};

export const CardFooter = ({
className,
...props
}: HTMLProps<HTMLDivElement>) => {
return (
<div
className={clsx(className, "bg-slate-900/70 p-4 text-sm")}
{...props}
/>
);
};

export const CardTitle = ({
className,
...props
}: HTMLProps<HTMLDivElement>) => {
return (
<h2 className={clsx(className, "mb-2 text-xl font-semibold")} {...props} />
);
};

export const CardParagraph = ({
className,
...props
}: HTMLProps<HTMLDivElement>) => {
return <p className={clsx(className, "my-2 last-of-type:mb-0")} {...props} />;
};

export const CardSeparator = ({
className,
...props
}: HTMLProps<HTMLDivElement>) => {
return (
<div
role="separator"
aria-orientation="horizontal"
className={clsx(className, "border-t border-t-menu-border")}
{...props}
/>
);
};
162 changes: 162 additions & 0 deletions components/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {
Tooltip as AriakitTooltip,
TooltipAnchor as AriakitTooltipAnchor,
TooltipStateProps,
useTooltipState as useAriakitTooltipState,
} from "ariakit/tooltip";
import type {
TooltipAnchorProps as AriakitTooltipAnchorProps,
TooltipProps as AriakitTooltipProps,
} from "ariakit/tooltip";
import { clsx } from "clsx";
import {
Children,
cloneElement,
forwardRef,
useLayoutEffect,
useRef,
useState,
} from "react";

import { useEventCallback } from "./useEventCallback";

const useTooltipState = (props?: TooltipStateProps) =>
useAriakitTooltipState({ timeout: 800, ...props });

type TooltipAnchorProps = AriakitTooltipAnchorProps;
const TooltipAnchor = AriakitTooltipAnchor;

export type TooltipVariant = "default" | "info";

type TooltipProps = {
variant?: TooltipVariant | undefined;
} & AriakitTooltipProps<"div">;

const variantClassNames: Record<TooltipVariant, string> = {
default: "text-xs py-1 px-2 text-danger-500",
info: "text-sm p-2 [&_strong]:font-medium",
};

const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
({ variant = "default", ...props }, ref) => {
const variantClassName = variantClassNames[variant];
if (!variantClassName) {
throw new Error(`Invalid variant: ${variant}`);
}
return (
<AriakitTooltip
ref={ref}
className={clsx(
variantClassName,
"z-50 rounded border border-tooltip-border bg-tooltip-bg text-tooltip-on"
)}
{...props}
/>
);
}
);

export type MagicTooltipProps = {
tooltip: React.ReactNode;
variant?: TooltipVariant;
children: React.ReactElement;
timeout?: number;
} & Omit<TooltipAnchorProps, "children" | "state">;

type ActiveMagicTooltipProps = MagicTooltipProps & {
focusRef: React.MutableRefObject<boolean>;
hoverRef: React.MutableRefObject<boolean>;
timeout?: number;
};

const ActiveMagicTooltip = forwardRef<HTMLDivElement, ActiveMagicTooltipProps>(
(props, ref) => {
const {
tooltip,
children,
hoverRef,
focusRef,
variant,
timeout,
...restProps
} = props;
const state = useTooltipState({ timeout });
const { render, show } = state;
useLayoutEffect(() => {
if (hoverRef.current || focusRef.current) {
render();
show();
}
}, [render, show, hoverRef, focusRef]);

return (
<>
<TooltipAnchor ref={ref} state={state} {...restProps}>
{(referenceProps) => cloneElement(children, referenceProps)}
</TooltipAnchor>
<Tooltip state={state} variant={variant}>
{tooltip}
</Tooltip>
</>
);
}
);

export const MagicTooltip = forwardRef<HTMLDivElement, MagicTooltipProps>(
({ children, ...props }, ref) => {
const [active, setActive] = useState(false);
const hoverRef = useRef(false);
const handleMouseEnter = useEventCallback(
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
props.onMouseEnter?.(event);
hoverRef.current = true;
setActive(true);
}
);
const handleMouseLeave = useEventCallback(
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
props.onMouseLeave?.(event);
hoverRef.current = false;
}
);
const focusRef = useRef(false);
const handleFocus = useEventCallback(
(event: React.FocusEvent<HTMLDivElement>) => {
props.onFocus?.(event);
focusRef.current = true;
setActive(true);
}
);
const handleBlur = useEventCallback(
(event: React.FocusEvent<HTMLDivElement>) => {
props.onBlur?.(event);
focusRef.current = false;
}
);

const child = Children.only(children);
if (!props.tooltip) {
return cloneElement(child, props);
}
if (!active) {
const childProps = {
...props,
ref,
onMouseEnter: handleMouseEnter,
onMouseLeave: handleMouseLeave,
onFocus: handleFocus,
onBlur: handleBlur,
} as Record<string, any>;
if (typeof props.tooltip === "string" && !child.props["aria-label"]) {
childProps["aria-label"] = props["aria-label"] ?? props.tooltip;
}
return cloneElement(child, childProps);
}

return (
<ActiveMagicTooltip {...props} hoverRef={hoverRef} focusRef={focusRef}>
{child}
</ActiveMagicTooltip>
);
}
);
41 changes: 41 additions & 0 deletions components/useEventCallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useLayoutEffect, useMemo, useRef } from "react";

type Fn<ARGS extends any[], R> = (...args: ARGS) => R;

/**
* # React hook `useEventCallback`
* Aimed to be easier to use than `useCallback` and solve problems raised in [this ticket](https://github.com/facebook/react/issues/14099).
*
* `useEventCallback` doesn't need any dependencies list.
* The returned function should not be used during rendering.
*
* ### Example
*
* ```jsx
* import useEventCallback from 'use-event-callback';
* const Input = () => {
* const [value, setValue] = useState('');
* const onChange = useEventCallback((event) => {
* setValue(event.target.value);
* });
* return <input value={value} onChange={onChange} />;
* }
*/
export const useEventCallback = <A extends any[], R>(
fn: Fn<A, R>
): Fn<A, R> => {
const ref = useRef<Fn<A, R>>(fn);
useLayoutEffect(() => {
ref.current = fn;
});
return useMemo(
() =>
(...args: A): R => {
const { current } = ref;
return current(...args);
},
[]
);
};

export default useEventCallback;
4 changes: 1 addition & 3 deletions containers/AppNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ export const AppNavbar: React.FC = () => {
secondary={
<>
<NavbarLink href="https://argos-ci.com/docs/">Docs</NavbarLink>
<NavbarLink href="https://github.com/marketplace/argos-ci#pricing-and-setup">
Pricing
</NavbarLink>
<NavbarLink href="/pricing">Pricing</NavbarLink>
<NavbarLink href="/blog">Blog</NavbarLink>
<NavbarLink href="https://github.com/login/oauth/authorize?scope=user:email&client_id=Iv1.d1a5403395ac817e">
Login
Expand Down
1 change: 1 addition & 0 deletions cypress/e2e/pages.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const pages = {
privacy: "/privacy",
security: "/security",
blog: "/blog",
pricing: "/pricing",
"blog-post-1": "/blog/visual-testing",
"blog-post-2": "/blog/improve-dx",
"blog-post-3": "/blog/playwright",
Expand Down
Loading

1 comment on commit ca176cb

@vercel
Copy link

@vercel vercel bot commented on ca176cb May 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checks for Deployment have failed

Please sign in to comment.