Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@
--color-filetype-xlsx: #28a745;
--color-filetype-json: #1f5f30;

/* `text-tiny`; the other sizes map onto tailwind defaults:
xs 12px, sm 14px, base 16px, xl 20px, 2xl 24px */
--text-tiny: 11px;
/* Font size only, line-height stays inherited (as the old theme did it).
Only tailwind's own sizes, anything else is a literal like `text-[11px]` */
--text-xs--line-height: initial;
--text-sm--line-height: initial;
--text-base--line-height: initial;
--text-lg--line-height: initial;
--text-xl--line-height: initial;
--text-2xl--line-height: initial;

/* bare `rounded` */
--radius: 3px;

--animate-spin-fast: spin 0.5s linear infinite;
}

@layer base {
Expand Down
79 changes: 35 additions & 44 deletions frontend/src/js/button/BadgeToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,37 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";
import type { ReactNode } from "react";
import { useHotkeys } from "react-hotkeys-hook";

import BasicButton from "./BasicButton";

const common = css`
font-weight: 700;
padding: 1px 4px;
white-space: nowrap;
`;

const ActiveButton = styled(BasicButton)`
border-radius: ${({ theme }) => theme.borderRadius};
border: 2px solid ${({ theme }) => theme.col.blueGrayDark};
background-color: white;
font-size: ${({ theme }) => theme.font.sm};
color: ${({ theme }) => theme.col.blueGrayDark};
${common};
import { tv } from "tailwind-variants";

&:hover {
background-color: ${({ theme }) => theme.col.grayVeryLight};
}
`;

const InactiveButton = styled(BasicButton)`
border-radius: ${({ theme }) => theme.borderRadius};
border: 2px dotted ${({ theme }) => theme.col.grayLight};
font-size: ${({ theme }) => theme.font.sm};
color: ${({ theme }) => theme.col.gray};
${common};
&:hover {
background-color: ${({ theme }) => theme.col.bg};
}
`;
import BasicButton from "./BasicButton";

const SuperScript = styled("span")`
padding-left: 3px;
font-size: ${({ theme }) => theme.font.tiny};
transform: translate(1px, -2px);
display: inline-block;
color: ${({ theme }) => theme.col.gray};
`;
const badgeToggleButton = tv({
base: ["rounded", "px-1 py-px", "text-sm", "font-bold", "whitespace-nowrap"],
variants: {
active: {
true: [
"border-2 border-primary-500",
"bg-white hover:bg-gray-50",
"text-primary-500",
],
false: [
"border-2 border-dotted border-gray-100",
"hover:bg-bg-50",
"text-gray-500",
],
},
},
});

const superScript = tv({
base: [
"inline-block",
"pl-[3px]",
"translate-x-px -translate-y-[2px]",
"text-[11px]",
"text-gray-500",
],
});

interface Props {
className?: string;
Expand All @@ -58,15 +48,16 @@ export const BadgeToggleButton = ({
children,
hotkey,
}: Props) => {
const Component = active ? ActiveButton : InactiveButton;

useHotkeys(hotkey || "", onClick, { enabled: !!hotkey }, [hotkey, onClick]);

return (
<Component className={className} onClick={onClick}>
<BasicButton
className={badgeToggleButton({ active: !!active, className })}
onClick={onClick}
>
{!active && "+ "}
{children}
{hotkey && <SuperScript>{hotkey}</SuperScript>}
</Component>
{hotkey && <span className={superScript()}>{hotkey}</span>}
</BasicButton>
);
};
73 changes: 43 additions & 30 deletions frontend/src/js/button/BasicButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from "@emotion/styled";
import type { ButtonHTMLAttributes, Ref } from "react";

import { tv } from "tailwind-variants";

export interface BasicButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement> {
bare?: boolean;
Expand All @@ -9,42 +10,54 @@ export interface BasicButtonProps
large?: boolean;
active?: boolean;
secondary?: boolean;
autoFocus?: boolean; // Should actually be within the extends, not sure why I had to declare this.
}

const Button = styled("button")<BasicButtonProps>`
cursor: pointer;
font-weight: ${({ active, secondary }) =>
active || secondary ? "700" : "400"};
padding: ${({ small, tiny, bare, large }) =>
bare
? "0"
: tiny
? "4px 6px"
: small
? "6px 8px"
: large
? "12px 18px"
: "8px 15px"};
font-size: ${({ theme, small, tiny, large }) =>
tiny || small ? theme.font.xs : large ? theme.font.lg : theme.font.sm};
transition: all 0.2s;
border-radius: ${({ theme }) => theme.borderRadius};

&:disabled {
cursor: not-allowed;
opacity: 0.4;
}
`;
const button = tv({
base: [
"cursor-pointer",
"rounded",
"px-[15px] py-2",
"text-sm",
"font-normal",
"transition-all duration-200",
"disabled:cursor-not-allowed disabled:opacity-40",
],
variants: {
active: { true: "font-bold" },
secondary: { true: "font-bold" },
// later wins when several are set
large: { true: "px-[18px] py-3 text-xl" },
small: { true: "px-2 py-[6px] text-xs" },
tiny: { true: "px-[6px] py-1 text-xs" },
bare: { true: "p-0" },
},
});

const BasicButton = ({
ref,
children,
className,
bare,
tiny,
small,
large,
active,
secondary,
...props
}: BasicButtonProps & { ref?: Ref<HTMLButtonElement> }) => (
<Button type="button" {...props} ref={ref}>
{children}
</Button>
<button
type="button"
className={button({
bare,
tiny,
small,
large,
active,
secondary,
className,
})}
{...props}
ref={ref}
/>
);

export default BasicButton;
28 changes: 16 additions & 12 deletions frontend/src/js/button/DestroyButton.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import styled from "@emotion/styled";
import type { ComponentProps } from "react";

import { tv } from "tailwind-variants";

import { TransparentButton } from "./TransparentButton";

export const DestroyButton = styled(TransparentButton)`
color: ${({ theme }) => theme.col.red};
border: 2px solid ${({ theme }) => theme.col.red};
const destroyButton = tv({
base: [
"text-red hover:text-white active:text-white focus:text-white",
"border-2 border-red",
"hover:bg-red active:bg-red focus:bg-red",
],
});

&:hover,
&:active,
&:focus {
color: white;
background-color: ${({ theme }) => theme.col.red};
border: 2px solid ${({ theme }) => theme.col.red};
}
`;
export const DestroyButton = ({
className,
...props
}: ComponentProps<typeof TransparentButton>) => (
<TransparentButton className={destroyButton({ className })} {...props} />
);
46 changes: 17 additions & 29 deletions frontend/src/js/button/DownloadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useTheme } from "@emotion/react";
import styled from "@emotion/styled";
import type { IconProp } from "@fortawesome/fontawesome-svg-core";
import {
faDownload,
Expand All @@ -10,41 +8,30 @@ import {
faFileExcel,
faFilePdf,
} from "@fortawesome/free-solid-svg-icons";
import { type ReactNode, type Ref, useContext, useMemo } from "react";

import { type ReactNode, type Ref, useContext } from "react";
import { tv } from "tailwind-variants";
import type { ResultUrlWithLabel } from "../api/types";
import { AuthTokenContext } from "../authorization/AuthTokenProvider";
import { getEnding } from "../query-runner/DownloadResultsDropdownButton";

import IconButton, { type IconButtonPropsT } from "./IconButton";

const SxIconButton = styled(IconButton)`
white-space: nowrap;
`;

const Link = styled("a")`
line-height: 1;
`;
const link = tv({ base: "leading-none" });

interface FileIcon {
icon: IconProp;
color?: string;
}

function useFileIcon(url: string): FileIcon {
const theme = useTheme();

const fileTypeToFileIcon: Record<string, FileIcon> = useMemo(
() => ({
ZIP: { icon: faFileArchive, color: theme.col.fileTypes.zip },
XLSX: { icon: faFileExcel, color: theme.col.fileTypes.xlsx },
PDF: { icon: faFilePdf, color: theme.col.fileTypes.pdf },
CSV: { icon: faFileCsv, color: theme.col.fileTypes.csv },
JSON: { icon: faFileCode, color: theme.col.fileTypes.json },
}),
[theme],
);
const fileTypeToFileIcon: Record<string, FileIcon> = {
ZIP: { icon: faFileArchive, color: "var(--color-filetype-zip)" },
XLSX: { icon: faFileExcel, color: "var(--color-filetype-xlsx)" },
PDF: { icon: faFilePdf, color: "var(--color-filetype-pdf)" },
CSV: { icon: faFileCsv, color: "var(--color-filetype-csv)" },
JSON: { icon: faFileCode, color: "var(--color-filetype-json)" },
};

function getFileIcon(url: string): FileIcon {
if (url.includes(".")) {
const ext = getEnding(url);

Expand Down Expand Up @@ -79,20 +66,21 @@ const DownloadButton = ({

const href = `${resultUrl.url}?access_token=${encodeURIComponent(authToken)}`;

const { icon, color } = useFileIcon(resultUrl.url);
const { icon, color } = getFileIcon(resultUrl.url);

return (
<Link href={href} className={className} ref={ref}>
<SxIconButton
<a href={href} className={link({ className })} ref={ref}>
<IconButton
{...restProps}
className="whitespace-nowrap"
large
icon={simpleIcon ? faDownload : icon}
onClick={onClick}
iconColor={showColoredIcon ? color : undefined}
>
{children}
</SxIconButton>
</Link>
</IconButton>
</a>
);
};

Expand Down
Loading
Loading