Skip to content

Commit

Permalink
Add Button component and url util to index page
Browse files Browse the repository at this point in the history
  • Loading branch information
manuelpuchta committed Nov 8, 2024
1 parent 09e4719 commit d504e02
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 0 deletions.
99 changes: 99 additions & 0 deletions app/components/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import classNames from "classnames";
import { cloneElement, type ReactElement } from "react";
import { z } from "zod";
import { isExternalUrl, isFileDownloadUrl } from "~/util/url";

export const iconSchema = z
.custom<ReactElement<{ className: string }>>()
.optional();

export const ButtonPropsSchema = z.object({
text: z.string().optional(),
look: z.enum(["primary", "secondary", "tertiary", "ghost"]).optional(),
size: z.enum(["large", "medium", "small"]).optional(),
href: z.string().optional(),
iconLeft: iconSchema,
iconRight: iconSchema,
fullWidth: z.boolean().optional(),
});

export type ButtonProps = z.infer<typeof ButtonPropsSchema>;

type LinkProps = React.ComponentPropsWithoutRef<"a">;

function formatIcon(icon: z.infer<typeof iconSchema>) {
if (!icon) return undefined;
const className = `ds-button-icon ${icon.props.className ?? ""}`;
return cloneElement(icon, { className });
}

function Button({
children,
text,
iconLeft,
iconRight,
fullWidth,
look,
size,
href,
...props
}: ButtonProps & LinkProps & React.ComponentPropsWithoutRef<"button">) {
const buttonClasses = classNames(
"ds-button",
{
"ds-button-secondary": look == "secondary",
"ds-button-tertiary": look == "tertiary",
"ds-button-ghost": look == "ghost",
"ds-button-large": size == "large",
"ds-button-small": size == "small",
"ds-button-with-icon": iconLeft ?? iconRight,
"ds-button-with-icon-only": (iconLeft ?? iconRight) && !children,
"ds-button-full-width": fullWidth,
},
"contrast-more:border-4 forced-colors:border-4 border-solid contrast-more:border-black", // TODO: move into angie?
props.className,
);

const textSpan = text ? <span className="ds-button-label">{text}</span> : "";
const childrenSpan = <span className="ds-button-label">{children}</span>;
iconLeft = formatIcon(iconLeft);
iconRight = formatIcon(iconRight);

// for links that look like buttons, we want to add an event handler so that it can
// be activated with the space bar
// see: https://github.com/digitalservicebund/a2j-rechtsantragstelle/commit/43710c9e7d59e06f304830cc7e6b92893e7c7aa1#commitcomment-144257987
const onKeyDown = (event: React.KeyboardEvent<HTMLAnchorElement>) => {
if (event.code === "Space") {
event.currentTarget.click();
event.preventDefault();
}
};

if (href) {
const isExternal = isExternalUrl(href);
const isFile = isFileDownloadUrl(href);
const opts =
isFile || isExternal ? { target: "_blank", rel: "noopener" } : {};
if (isExternal) opts.rel = "noopener noreferrer";

return (
<a
{...props}
href={href}
className={buttonClasses}
onKeyDown={onKeyDown}
{...opts}
>
{iconLeft} {children ? childrenSpan : textSpan} {iconRight}
</a>
);
}

return (
<button {...(props as ButtonProps)} className={buttonClasses}>
{iconLeft} {children ? childrenSpan : textSpan} {iconRight}
</button>
);
}

export default Button;
12 changes: 12 additions & 0 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { MetaFunction } from "@remix-run/node";
import Button from "~/components/Button";
import AccountBalanceIcon from "@digitalservicebund/icons/AccountBalance";

export const meta: MetaFunction = () => {
return [
Expand All @@ -13,6 +15,16 @@ export default function Index() {
<h1 className={"ds-heading-01-reg mb-40"}>
Hello Kommunikationsplattform!
</h1>
<div>
<Button
look="primary"
text="Cool"
iconRight={<AccountBalanceIcon />}
onClick={() => {
alert("Hi");
}}
/>
</div>
</main>
);
}
16 changes: 16 additions & 0 deletions app/util/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function isExternalUrl(url: string) {
return url.startsWith("https://");
}

export function isFileDownloadUrl(url: string) {
return url.endsWith("/download/pdf");
}

export function getYoutubeVideoId(url: string): string | undefined {
const regex = /(?:youtu\.be\/|youtube\.com\/watch\?v=)(\w+)/;
const match = url.match(regex);
if (match) {
return match[1];
}
return undefined;
}

0 comments on commit d504e02

Please sign in to comment.