Focus visible style refinements (#1577)

* reduces the thickness of the focus-visible

* More subtle Input style

* Updates focus-visible styles for the SimpleSelect

* Tables tab through rows and action cells only

* Refined focus visible style

* Adds the custom focus visible to the TextLink

* Moves the onClick handling to the table row rather than the cell

* Makes table row heights consistent

* Adds gap between task search bar and button

* Prevents long tag values from wrapping

* Removes unnecessary rows from table header

* Added gap between search and filters

* Fixes the schedules table pagination staying fixed to the bottom of the page

* Removed more unnecessary header table rows to prevent them being selectable

* Removed isSelected styles (not working)

* Added <tr> back to the main Table compontent

* Table row handles modifier keys

* Adds to={path} to the TableRow only

* Revert "Adds to={path} to the TableRow only"

This reverts commit 8a814d434ff85b8ab9c2305d336074f659d86094.

* Revert "Table row handles modifier keys"

This reverts commit b2394741fa2ed6b908e00673b1a395ae89ca7146.

* Table reverted to use linked cells rather than rows

* Set the tab index of a cell and style the table row when tabbed

* Tabbed row style applied to the sticky cells

* Adds isTabbableCell to each table

* Improves the spcificity of the row highlighting

* Reduces the height of the task rows to match the other tables

* Adds tab styles to fill in row dividers top and bottom

* Removed old row onClick and to props

* Removed duplicate table header row

* Creates table style variants so tables look good in the inspector panels

* Full width tables on the schedules inspector panel

* Reduce padding on Alerts page table rows

* Updates the Deploy page inspector table to the new style

* Removes the duplicate Table Row from the table headers in v2

* Adds TableRow to the EventsTable

* Adds TableRow to more tables

* Adds TableRow to more tables

* Adds TableRow to more tables

* Adds TableRow to more tables

* Fix for showing correct cursor for linked table rows

* Removed variants from some of the table components
This commit is contained in:
James Ritchie
2025-01-21 21:11:59 +00:00
committed by GitHub
parent f96bf7210b
commit 16cf357380
16 changed files with 298 additions and 229 deletions
@@ -4,7 +4,7 @@ import { cn } from "~/utils/cn";
import { Icon, RenderIcon } from "./Icon";
const containerBase =
"has-[:focus-visible]:outline-none has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-text-link has-[:focus-visible]:ring-offset-0 has-[:focus]:border-ring has-[:focus]:outline-none has-[:focus]:ring-2 has-[:focus]:ring-ring has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 ring-offset-background transition cursor-text";
"has-[:focus-visible]:outline-none has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-charcoal-650 has-[:focus-visible]:ring-offset-0 has-[:focus]:border-ring has-[:focus]:outline-none has-[:focus]:ring-1 has-[:focus]:ring-ring has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 ring-offset-background transition cursor-text";
const inputBase =
"h-full w-full text-text-bright bg-transparent file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 disabled:cursor-not-allowed outline-none ring-0 border-none";
@@ -17,27 +17,27 @@ const variants = {
container:
"px-1 w-full h-10 rounded-[3px] border border-charcoal-800 bg-charcoal-750 hover:border-charcoal-600 hover:bg-charcoal-650",
input: "px-2 text-sm",
iconSize: "h-4 w-4 ml-1",
iconSize: "size-4 ml-1",
shortcut: "mr-1 min-w-[22px] rounded-sm py-[3px] px-[5px] text-[0.6rem] select-none",
},
medium: {
container:
"px-1 h-8 w-full rounded border border-charcoal-800 bg-charcoal-750 hover:border-charcoal-600 hover:bg-charcoal-650",
input: "px-1.5 rounded text-sm",
iconSize: "h-4 w-4 ml-0.5",
iconSize: "size-4 ml-0.5",
shortcut: "min-w-[22px] rounded-sm py-[3px] px-[5px] text-[0.6rem]",
},
small: {
container:
"px-1 h-6 w-full rounded border border-charcoal-800 bg-charcoal-750 hover:border-charcoal-600 hover:bg-charcoal-650",
input: "px-1 rounded text-xs",
iconSize: "h-3 w-3 ml-0.5",
iconSize: "size-3 ml-0.5",
shortcut: "min-w-[22px] rounded-[2px] py-px px-[3px] text-[0.5rem]",
},
tertiary: {
container: "px-1 h-6 w-full rounded hover:bg-charcoal-750",
input: "px-1 rounded text-xs",
iconSize: "h-3 w-3 ml-0.5",
iconSize: "size-3 ml-0.5",
shortcut: "min-w-[22px] rounded-[2px] py-px px-[3px] text-[0.5rem]",
},
};
@@ -29,7 +29,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"ring-offset-background focus-visible:ring-ring group flex items-center justify-between gap-x-1 rounded text-text-dimmed transition placeholder:text-text-dimmed hover:text-text-bright focus-visible:bg-tertiary focus-visible:text-text-bright focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
"ring-offset-background group flex items-center justify-between gap-x-1 rounded text-text-dimmed transition placeholder:text-text-dimmed hover:text-text-bright focus-visible:focus-custom disabled:cursor-not-allowed disabled:opacity-50",
width === "full" ? "w-full" : "w-min",
sizeClassName,
className
+85 -48
View File
@@ -1,10 +1,33 @@
import { ChevronRightIcon } from "@heroicons/react/24/solid";
import { Link } from "@remix-run/react";
import { ReactNode, forwardRef, useState } from "react";
import React, { ReactNode, forwardRef, useState, useContext, createContext } from "react";
import { cn } from "~/utils/cn";
import { Popover, PopoverContent, PopoverVerticalEllipseTrigger } from "./Popover";
import { InfoIconTooltip } from "./Tooltip";
const variants = {
bright: {
header: "bg-background-bright",
cell: "group-hover/table-row:bg-charcoal-750 group-has-[[tabindex='0']:focus]/table-row:bg-charcoal-750",
stickyCell: "bg-background-bright group-hover/table-row:bg-charcoal-750",
menuButton:
"bg-background-bright group-hover/table-row:bg-charcoal-750 group-hover/table-row:ring-charcoal-600/70 group-has-[[tabindex='0']:focus]/table-row:bg-charcoal-750",
menuButtonDivider: "group-hover/table-row:border-charcoal-600/70",
rowSelected: "bg-charcoal-750 group-hover/table-row:bg-charcoal-750",
},
dimmed: {
header: "bg-background-dimmed",
cell: "group-hover/table-row:bg-charcoal-800 group-has-[[tabindex='0']:focus]/table-row:bg-background-bright",
stickyCell: "group-hover/table-row:bg-charcoal-800",
menuButton:
"bg-background-dimmed group-hover/table-row:bg-charcoal-800 group-hover/table-row:ring-grid-bright group-has-[[tabindex='0']:focus]/table-row:bg-background-bright",
menuButtonDivider: "group-hover/table-row:border-grid-dimmed",
rowSelected: "bg-charcoal-750 group-hover/table-row:bg-charcoal-750",
},
} as const;
export type TableVariant = keyof typeof variants;
type TableProps = {
containerClassName?: string;
className?: string;
@@ -12,20 +35,25 @@ type TableProps = {
fullWidth?: boolean;
};
export const Table = forwardRef<HTMLTableElement, TableProps>(
({ className, containerClassName, children, fullWidth }, ref) => {
// Add TableContext
const TableContext = createContext<{ variant: TableVariant }>({ variant: "dimmed" });
export const Table = forwardRef<HTMLTableElement, TableProps & { variant?: TableVariant }>(
({ className, containerClassName, children, fullWidth, variant = "dimmed" }, ref) => {
return (
<div
className={cn(
"overflow-x-auto whitespace-nowrap border-t border-grid-bright scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
containerClassName,
fullWidth && "w-full"
)}
>
<table ref={ref} className={cn("w-full", className)}>
{children}
</table>
</div>
<TableContext.Provider value={{ variant }}>
<div
className={cn(
"overflow-x-auto whitespace-nowrap border-t scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
containerClassName,
fullWidth && "w-full"
)}
>
<table ref={ref} className={cn("w-full", className)}>
{children}
</table>
</div>
</TableContext.Provider>
);
}
);
@@ -37,11 +65,13 @@ type TableHeaderProps = {
export const TableHeader = forwardRef<HTMLTableSectionElement, TableHeaderProps>(
({ className, children }, ref) => {
const { variant } = useContext(TableContext);
return (
<thead
ref={ref}
className={cn(
"sticky top-0 z-10 bg-background-dimmed after:absolute after:bottom-0 after:left-0 after:right-0 after:h-px after:bg-grid-bright",
"sticky top-0 z-10 after:absolute after:bottom-0 after:left-0 after:right-0 after:h-px after:bg-grid-bright",
variants[variant].header,
className
)}
>
@@ -75,13 +105,14 @@ type TableRowProps = {
export const TableRow = forwardRef<HTMLTableRowElement, TableRowProps>(
({ className, disabled, isSelected, children }, ref) => {
const { variant } = useContext(TableContext);
return (
<tr
ref={ref}
className={cn(
"group/table-row relative w-full after:absolute after:bottom-0 after:left-3 after:right-0 after:h-px after:bg-grid-dimmed",
"group/table-row relative w-full outline-none after:absolute after:bottom-0 after:left-3 after:right-0 after:h-px after:bg-grid-dimmed",
isSelected && variants[variant].rowSelected,
disabled && "opacity-50",
isSelected && isSelectedStyle,
className
)}
>
@@ -94,7 +125,7 @@ export const TableRow = forwardRef<HTMLTableRowElement, TableRowProps>(
type TableCellBasicProps = {
className?: string;
alignment?: "left" | "center" | "right";
children: ReactNode;
children?: ReactNode;
colSpan?: number;
};
@@ -125,6 +156,7 @@ export const TableHeaderCell = forwardRef<HTMLTableCellElement, TableHeaderCellP
className
)}
colSpan={colSpan}
tabIndex={-1}
>
{hiddenLabel ? (
<span className="sr-only">{children}</span>
@@ -147,24 +179,12 @@ type TableCellProps = TableCellBasicProps & {
hasAction?: boolean;
isSticky?: boolean;
actionClassName?: string;
rowHoverStyle?: keyof typeof rowHoverStyles;
rowHoverStyle?: string;
isSelected?: boolean;
isTabbableCell?: boolean;
children?: ReactNode;
};
const rowHoverStyles = {
default:
"group-hover/table-row:bg-charcoal-800 group-hover/table-row:before:absolute group-hover/table-row:before:bg-charcoal-750 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:left-0 group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:after:absolute group-hover/table-row:after:bg-charcoal-750 group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3",
dimmed:
"group-hover/table-row:bg-charcoal-850 group-hover/table-row:before:absolute group-hover/table-row:before:bg-charcoal-800 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:left-0 group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:after:absolute group-hover/table-row:after:bg-charcoal-800 group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3",
bright:
"group-hover/table-row:bg-charcoal-750 group-hover/table-row:before:absolute group-hover/table-row:before:bg-charcoal-700 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:left-0 group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:after:absolute group-hover/table-row:after:bg-charcoal-700 group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3",
};
const stickyStyles =
"sticky right-0 bg-background-dimmed group-hover/table-row:bg-charcoal-750 w-[--sticky-width] [&:has(.group-hover\\/table-row\\:block)]:w-auto";
const isSelectedStyle = "bg-charcoal-750 group-hover:bg-charcoal-750";
export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
(
{
@@ -177,8 +197,8 @@ export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
onClick,
hasAction = false,
isSticky = false,
rowHoverStyle = "default",
isSelected,
isTabbableCell = false,
},
ref
) => {
@@ -193,34 +213,47 @@ export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
}
const flexClasses = cn(
"flex w-full whitespace-nowrap px-3 py-3 text-xs text-text-dimmed",
"flex w-full whitespace-nowrap px-3 py-3 items-center text-xs text-text-dimmed",
alignment === "left"
? "justify-start text-left"
: alignment === "center"
? "justify-center text-center"
: "justify-end text-right"
);
const { variant } = useContext(TableContext);
return (
<td
ref={ref}
className={cn(
"text-xs text-charcoal-400",
to || onClick || hasAction ? "cursor-pointer" : "px-3 py-3 align-middle",
"text-xs text-charcoal-400 has-[[tabindex='0']:focus]:before:absolute has-[[tabindex='0']:focus]:before:-top-px has-[[tabindex='0']:focus]:before:left-0 has-[[tabindex='0']:focus]:before:h-px has-[[tabindex='0']:focus]:before:w-3 has-[[tabindex='0']:focus]:before:bg-grid-dimmed has-[[tabindex='0']:focus]:after:absolute has-[[tabindex='0']:focus]:after:bottom-0 has-[[tabindex='0']:focus]:after:left-0 has-[[tabindex='0']:focus]:after:right-0 has-[[tabindex='0']:focus]:after:h-px has-[[tabindex='0']:focus]:after:bg-grid-dimmed",
variants[variant].cell,
to || onClick || hasAction ? "cursor-pointer" : "cursor-default px-3 py-3 align-middle",
!to && !onClick && alignmentClassName,
isSticky && stickyStyles,
isSelected && isSelectedStyle,
!isSelected && rowHoverStyles[rowHoverStyle],
isSticky &&
"[&:has(.group-hover/table-row:block)]:w-auto sticky right-0 bg-background-dimmed",
isSticky && variants[variant].stickyCell,
isSelected && variants[variant].rowSelected,
!isSelected &&
"group-hover/table-row:before:absolute group-hover/table-row:before:left-0 group-hover/table-row:before:top-[-1px] group-hover/table-row:before:h-px group-hover/table-row:before:w-3 group-hover/table-row:before:bg-charcoal-750 group-hover/table-row:after:absolute group-hover/table-row:after:bottom-0 group-hover/table-row:after:left-0 group-hover/table-row:after:h-px group-hover/table-row:after:w-3 group-hover/table-row:after:bg-charcoal-750 group-focus-visible/table-row:bg-background-bright",
className
)}
colSpan={colSpan}
>
{to ? (
<Link to={to} className={cn("focus-custom", flexClasses, actionClassName)}>
<Link
to={to}
className={cn("cursor-pointer focus:outline-none", flexClasses, actionClassName)}
tabIndex={isTabbableCell ? 0 : -1}
>
{children}
</Link>
) : onClick ? (
<button onClick={onClick} className={cn("focus-custom", flexClasses, actionClassName)}>
<button
onClick={onClick}
className={cn("cursor-pointer focus:outline-none", flexClasses, actionClassName)}
tabIndex={isTabbableCell ? 0 : -1}
>
{children}
</button>
) : (
@@ -258,7 +291,7 @@ export const TableCellChevron = forwardRef<
export const TableCellMenu = forwardRef<
HTMLTableCellElement,
{
TableCellProps & {
className?: string;
isSticky?: boolean;
onClick?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
@@ -283,6 +316,7 @@ export const TableCellMenu = forwardRef<
ref
) => {
const [isOpen, setIsOpen] = useState(false);
const { variant } = useContext(TableContext);
return (
<TableCell
@@ -297,15 +331,18 @@ export const TableCellMenu = forwardRef<
<div className="relative h-full p-1">
<div
className={cn(
"absolute right-0 top-1/2 mr-1 flex -translate-y-1/2 items-center justify-end gap-0.5 rounded-[0.25rem] bg-background-dimmed p-0.5 group-hover/table-row:bg-background-bright group-hover/table-row:ring-1 group-hover/table-row:ring-grid-bright",
isSelected && isSelectedStyle,
isSelected &&
"group-hover/table-row:bg-charcoal-750 group-hover/table-row:ring-charcoal-600/50"
"absolute right-0 top-1/2 mr-1 flex -translate-y-1/2 items-center justify-end gap-0.5 rounded-[0.25rem] p-0.5 group-hover/table-row:ring-1",
variants[variant].menuButton
)}
>
{/* Hidden buttons that show on hover */}
{hiddenButtons && (
<div className="hidden pr-0.5 group-hover/table-row:block group-hover/table-row:border-r group-hover/table-row:border-grid-dimmed">
<div
className={cn(
"hidden pr-0.5 group-hover/table-row:block group-hover/table-row:border-r",
variants[variant].menuButtonDivider
)}
>
{hiddenButtons}
</div>
)}
@@ -4,9 +4,9 @@ import { cn } from "~/utils/cn";
const variations = {
primary:
"text-indigo-500 transition hover:text-indigo-400 inline-flex gap-0.5 items-center group",
"text-indigo-500 transition hover:text-indigo-400 inline-flex gap-0.5 items-center group focus-visible:focus-custom",
secondary:
"text-text-dimmed transition underline underline-offset-2 decoration-dimmed/50 hover:decoration-dimmed inline-flex gap-0.5 items-center group",
"text-text-dimmed transition underline underline-offset-2 decoration-dimmed/50 hover:decoration-dimmed inline-flex gap-0.5 items-center group focus-visible:focus-custom",
} as const;
type TextLinkProps = {
@@ -34,14 +34,14 @@ export function TextLink({
<Link to={to} className={cn(classes, className)} {...props}>
{children}{" "}
{trailingIcon && (
<NamedIcon name={trailingIcon} className={cn("h-4 w-4", trailingIconClassName)} />
<NamedIcon name={trailingIcon} className={cn("size-4", trailingIconClassName)} />
)}
</Link>
) : href ? (
<a href={href} className={cn(classes, className)} {...props}>
{children}{" "}
{trailingIcon && (
<NamedIcon name={trailingIcon} className={cn("h-4 w-4", trailingIconClassName)} />
<NamedIcon name={trailingIcon} className={cn("size-4", trailingIconClassName)} />
)}
</a>
) : (
@@ -22,7 +22,7 @@ export function RunTag({ tag }: { tag: string }) {
<span className="flex items-center border-y border-r border-charcoal-700 bg-charcoal-800 pr-1.5 text-text-dimmed">
{tagResult.key}
</span>
<span className="flex items-center rounded-r-sm border-y border-r border-charcoal-700 bg-charcoal-750 px-1.5 text-text-dimmed">
<span className="flex items-center whitespace-nowrap rounded-r-sm border-y border-r border-charcoal-700 bg-charcoal-750 px-1.5 text-text-dimmed">
{tagResult.value}
</span>
</span>
@@ -93,7 +93,7 @@ export function ScheduleFilters({ possibleEnvironments, possibleTasks }: Schedul
}, []);
return (
<div className="flex w-full flex-row">
<div className="flex w-full">
<Input
name="search"
placeholder="Search schedule id, external id, deduplication id or CRON pattern"
@@ -103,7 +103,7 @@ export function ScheduleFilters({ possibleEnvironments, possibleTasks }: Schedul
defaultValue={search}
onChange={(e) => handleSearchChange(e.target.value)}
/>
<SelectGroup>
<SelectGroup className="ml-2">
<Select name="type" value={type ?? "ALL"} onValueChange={handleTypeChange}>
<SelectTrigger size="minimal" width="full">
<SelectValue placeholder={"Select type"} className="ml-2 whitespace-nowrap p-0" />
@@ -39,6 +39,7 @@ import {
TableHeader,
TableHeaderCell,
TableRow,
TableVariant,
} from "../../primitives/Table";
import { CancelRunDialog } from "./CancelRunDialog";
import { LiveTimer } from "./LiveTimer";
@@ -58,6 +59,7 @@ type RunsTableProps = {
runs: RunListItem[];
isLoading?: boolean;
allowSelection?: boolean;
variant?: TableVariant;
};
export function TaskRunsTable({
@@ -67,6 +69,7 @@ export function TaskRunsTable({
runs,
isLoading = false,
allowSelection = false,
variant = "dimmed",
}: RunsTableProps) {
const user = useUser();
const organization = useOrganization();
@@ -104,7 +107,7 @@ export function TaskRunsTable({
);
return (
<Table className="max-h-full overflow-y-auto">
<Table variant={variant} className="max-h-full overflow-y-auto">
<TableHeader>
<TableRow>
{allowSelection && (
@@ -300,7 +303,7 @@ export function TaskRunsTable({
/>
</TableCell>
)}
<TableCell to={path} alignment="right">
<TableCell to={path} alignment="right" isTabbableCell>
{formatNumber(run.number)}
</TableCell>
<TableCell to={path}>
@@ -237,7 +237,7 @@ export default function Page() {
<div className="flex min-w-0 max-w-full flex-col">
{!userHasTasks && <UserHasNoTasks />}
<div className="max-h-full overflow-hidden">
<div className="flex items-center p-2">
<div className="flex items-center gap-1 p-2">
<Input
placeholder="Search tasks"
variant="tertiary"
@@ -295,7 +295,7 @@ export default function Page() {
return (
<TableRow key={task.slug} className="group">
<TableCell to={path}>
<TableCell to={path} isTabbableCell>
<div className="flex items-center gap-2">
<SimpleTooltip
button={<TaskTriggerSourceIcon source={task.triggerSource} />}
@@ -304,13 +304,13 @@ export default function Page() {
<span>{task.slug}</span>
</div>
</TableCell>
<TableCell to={path} className="py-0" actionClassName="py-0">
<TableCell to={path}>
<TaskFunctionName
functionName={task.exportName}
variant="extra-extra-small"
/>
</TableCell>
<TableCell to={path} className="p-0">
<TableCell to={path}>
<Suspense
fallback={
<>
@@ -326,7 +326,7 @@ export default function Page() {
</TypedAwait>
</Suspense>
</TableCell>
<TableCell to={path} className="p-0">
<TableCell to={path}>
<Suspense fallback={<></>}>
<TypedAwait resolve={runningStats}>
{(data) => {
@@ -336,7 +336,7 @@ export default function Page() {
</TypedAwait>
</Suspense>
</TableCell>
<TableCell to={path} className="p-0" actionClassName="py-0">
<TableCell to={path} actionClassName="py-1.5">
<Suspense fallback={<TaskActivityBlankState />}>
<TypedAwait resolve={activity}>
{(data) => {
@@ -356,7 +356,7 @@ export default function Page() {
</TypedAwait>
</Suspense>
</TableCell>
<TableCell to={path} className="p-0">
<TableCell to={path}>
<Suspense fallback={<></>}>
<TypedAwait resolve={durations}>
{(data) => {
@@ -222,7 +222,7 @@ export default function Page() {
/>
))}
</TableCell>
<TableCell className={alertChannel.enabled ? "" : "opacity-50"}>
<TableCell className={cn("py-1", alertChannel.enabled ? "" : "opacity-50")}>
<AlertChannelDetails alertChannel={alertChannel} />
</TableCell>
<TableCell className={alertChannel.enabled ? "" : "opacity-50"}>
@@ -466,6 +466,7 @@ function AlertChannelDetails({ alertChannel }: { alertChannel: AlertChannelListP
label={"Email"}
description={alertChannel.properties.email}
boxClassName="group-hover/table-row:bg-charcoal-800"
className="h-12"
/>
);
}
@@ -195,7 +195,9 @@ function BatchesTable({ batches, hasFilters, filters }: BatchList) {
const path = v3BatchRunsPath(organization, project, batch);
return (
<TableRow key={batch.id}>
<TableCell to={path}>{batch.friendlyId}</TableCell>
<TableCell to={path} isTabbableCell>
{batch.friendlyId}
</TableCell>
<TableCell to={path}>
<EnvironmentLabel
environment={batch.environment}
@@ -209,7 +211,7 @@ function BatchesTable({ batches, hasFilters, filters }: BatchList) {
disableHoverableContent
button={
<span className="flex items-center gap-1">
<ExclamationCircleIcon className="size-4 text-slate-500" />
<ExclamationCircleIcon className="size-4 text-text-dimmed" />
<span>Legacy batch</span>
</span>
}
@@ -4,7 +4,6 @@ import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { UserAvatar } from "~/components/UserProfilePhoto";
import { AdminDebugTooltip } from "~/components/admin/debugTooltip";
import { CodeBlock } from "~/components/code/CodeBlock";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { Badge } from "~/components/primitives/Badge";
import { LinkButton } from "~/components/primitives/Buttons";
@@ -108,107 +107,118 @@ export default function Page() {
<LinkButton
to={`${v3DeploymentsPath(organization, project)}${page ? `?page=${page}` : ""}`}
variant="minimal/medium"
LeadingIcon={ExitIcon}
variant="minimal/small"
TrailingIcon={ExitIcon}
shortcut={{ key: "esc" }}
shortcutPosition="before-trailing-icon"
className="pl-1"
/>
</div>
<div className="overflow-y-auto px-3 pt-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
<div className="flex flex-col gap-4">
<Property.Table>
<Property.Item>
<Property.Label>Deploy</Property.Label>
<Property.Value className="flex items-center gap-2">
<span>{deployment.shortCode}</span>
{deployment.label && <Badge variant="outline-rounded">{deployment.label}</Badge>}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Environment</Property.Label>
<Property.Value>
<EnvironmentLabel environment={deployment.environment} userName={usernameForEnv} />
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Version</Property.Label>
<Property.Value>{deployment.version}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Status</Property.Label>
<Property.Value>
<DeploymentStatus
status={deployment.status}
isBuilt={deployment.isBuilt}
className="text-sm"
/>
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Tasks</Property.Label>
<Property.Value>{deployment.tasks ? deployment.tasks.length : ""}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>SDK Version</Property.Label>
<Property.Value>{deployment.sdkVersion ? deployment.sdkVersion : ""}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>CLI Version</Property.Label>
<Property.Value>{deployment.cliVersion ? deployment.cliVersion : ""}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Started at</Property.Label>
<Property.Value>
<DateTimeAccurate date={deployment.createdAt} /> UTC
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Built at</Property.Label>
<Property.Value>
{deployment.builtAt ? (
<>
<DateTimeAccurate date={deployment.builtAt} /> UTC
</>
) : (
""
)}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Deployed at</Property.Label>
<Property.Value>
{deployment.deployedAt ? (
<>
<DateTimeAccurate date={deployment.deployedAt} /> UTC
</>
) : (
""
)}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Deployed by</Property.Label>
<Property.Value>
{deployment.deployedBy ? (
<div className="flex items-center gap-1">
<UserAvatar
avatarUrl={deployment.deployedBy.avatarUrl}
name={deployment.deployedBy.name ?? deployment.deployedBy.displayName}
className="h-4 w-4"
/>
<Paragraph variant="small">
{deployment.deployedBy.name ?? deployment.deployedBy.displayName}
</Paragraph>
</div>
) : (
""
)}
</Property.Value>
</Property.Item>
</Property.Table>
<div className="overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
<div className="flex flex-col">
<div className="p-3">
<Property.Table>
<Property.Item>
<Property.Label>Deploy</Property.Label>
<Property.Value className="flex items-center gap-2">
<span>{deployment.shortCode}</span>
{deployment.label && <Badge variant="outline-rounded">{deployment.label}</Badge>}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Environment</Property.Label>
<Property.Value>
<EnvironmentLabel
environment={deployment.environment}
userName={usernameForEnv}
/>
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Version</Property.Label>
<Property.Value>{deployment.version}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Status</Property.Label>
<Property.Value>
<DeploymentStatus
status={deployment.status}
isBuilt={deployment.isBuilt}
className="text-sm"
/>
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Tasks</Property.Label>
<Property.Value>{deployment.tasks ? deployment.tasks.length : ""}</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>SDK Version</Property.Label>
<Property.Value>
{deployment.sdkVersion ? deployment.sdkVersion : ""}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>CLI Version</Property.Label>
<Property.Value>
{deployment.cliVersion ? deployment.cliVersion : ""}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Started at</Property.Label>
<Property.Value>
<DateTimeAccurate date={deployment.createdAt} /> UTC
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Built at</Property.Label>
<Property.Value>
{deployment.builtAt ? (
<>
<DateTimeAccurate date={deployment.builtAt} /> UTC
</>
) : (
""
)}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Deployed at</Property.Label>
<Property.Value>
{deployment.deployedAt ? (
<>
<DateTimeAccurate date={deployment.deployedAt} /> UTC
</>
) : (
""
)}
</Property.Value>
</Property.Item>
<Property.Item>
<Property.Label>Deployed by</Property.Label>
<Property.Value>
{deployment.deployedBy ? (
<div className="flex items-center gap-1">
<UserAvatar
avatarUrl={deployment.deployedBy.avatarUrl}
name={deployment.deployedBy.name ?? deployment.deployedBy.displayName}
className="h-4 w-4"
/>
<Paragraph variant="small">
{deployment.deployedBy.name ?? deployment.deployedBy.displayName}
</Paragraph>
</div>
) : (
""
)}
</Property.Value>
</Property.Item>
</Property.Table>
</div>
{deployment.tasks ? (
<div className="divide-y divide-charcoal-800 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
<Table>
<Table variant="bright">
<TableHeader>
<TableRow>
<TableHeaderCell className="px-2">Task</TableHeaderCell>
@@ -177,7 +177,7 @@ export default function Page() {
const isSelected = deploymentParam === deployment.shortCode;
return (
<TableRow key={deployment.id} className="group" isSelected={isSelected}>
<TableCell to={path} isSelected={isSelected}>
<TableCell to={path} isTabbableCell isSelected={isSelected}>
<div className="flex items-center gap-2">
<Paragraph variant="extra-small">{deployment.shortCode}</Paragraph>
{deployment.label && (
@@ -220,11 +220,12 @@ export default function Page() {
TrailingIcon={ExitIcon}
shortcut={{ key: "esc" }}
shortcutPosition="before-trailing-icon"
className="pl-1"
/>
</div>
<div className="overflow-y-scroll scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
<div className="p-3">
<div className="space-y-3">
<div className="space-y-3">
<div className="p-3">
<Property.Table>
<Property.Item>
<Property.Label>Schedule ID</Property.Label>
@@ -282,61 +283,64 @@ export default function Page() {
</>
)}
</Property.Table>
<div className="flex flex-col gap-1">
<Header3>Last 5 runs</Header3>
<TaskRunsTable
total={schedule.runs.length}
hasFilters={false}
filters={{
tasks: [],
versions: [],
statuses: [],
environments: [],
from: undefined,
to: undefined,
}}
runs={schedule.runs}
isLoading={false}
/>
</div>
<div className="flex flex-col gap-1">
<Header3>Next 5 runs</Header3>
<Table>
<TableHeader>
<TableRow>
{!isUtc && <TableHeaderCell>{schedule.timezone}</TableHeaderCell>}
<TableHeaderCell>UTC</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{schedule.active ? (
schedule.nextRuns.length ? (
schedule.nextRuns.map((run, index) => (
<TableRow key={index}>
{!isUtc && (
<TableCell>
<DateTime date={run} timeZone={schedule.timezone} />
</TableCell>
)}
</div>
<div className="flex flex-col gap-1">
<Header3 className="pb-1 pl-3">Last 5 runs</Header3>
<TaskRunsTable
total={schedule.runs.length}
hasFilters={false}
filters={{
tasks: [],
versions: [],
statuses: [],
environments: [],
from: undefined,
to: undefined,
}}
runs={schedule.runs}
isLoading={false}
variant="bright"
/>
</div>
<div className="flex flex-col gap-1 pt-2">
<Header3 className="pb-1 pl-3">Next 5 runs</Header3>
<Table variant="bright">
<TableHeader>
<TableRow>
{!isUtc && <TableHeaderCell>{schedule.timezone}</TableHeaderCell>}
<TableHeaderCell>UTC</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{schedule.active ? (
schedule.nextRuns.length ? (
schedule.nextRuns.map((run, index) => (
<TableRow key={index}>
{!isUtc && (
<TableCell>
<DateTime date={run} timeZone="UTC" />
<DateTime date={run} timeZone={schedule.timezone} />
</TableCell>
</TableRow>
))
) : (
<TableBlankRow colSpan={isUtc ? 1 : 2}>
<PlaceholderText title="You found a bug" />
</TableBlankRow>
)
)}
<TableCell>
<DateTime date={run} timeZone="UTC" />
</TableCell>
</TableRow>
))
) : (
<TableBlankRow colSpan={isUtc ? 1 : 2}>
<PlaceholderText title="Schedule disabled" />
<PlaceholderText title="You found a bug" />
</TableBlankRow>
)}
</TableBody>
</Table>
</div>
{!isImperative && (
)
) : (
<TableBlankRow colSpan={isUtc ? 1 : 2}>
<PlaceholderText title="Schedule disabled" />
</TableBlankRow>
)}
</TableBody>
</Table>
</div>
{!isImperative && (
<div className="p-3">
<InfoPanel
title="Editing declarative schedules"
icon={BookOpenIcon}
@@ -349,8 +353,8 @@ export default function Page() {
You can only edit a declarative schedule by updating your schedules.task and then
running the CLI dev and deploy commands.
</InfoPanel>
)}
</div>
</div>
)}
</div>
</div>
{isImperative && (
@@ -65,6 +65,7 @@ import {
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
import { ArrowUpCircleIcon } from "@heroicons/react/24/outline";
import { SimpleTooltip } from "~/components/primitives/Tooltip";
import { cn } from "~/utils/cn";
export const meta: MetaFunction = () => {
return [
@@ -218,9 +219,19 @@ export default function Page() {
</div>
</div>
<div className="h-fit max-h-full overflow-x-auto">
<div
className={cn(
"grid h-fit max-h-full min-h-full overflow-x-auto",
totalPages > 1 ? "grid-rows-[1fr_auto]" : "grid-rows-[1fr]"
)}
>
<SchedulesTable schedules={schedules} hasFilters={hasFilters} />
<div className="flex justify-end py-3">
<div
className={cn(
"flex min-h-full",
totalPages > 1 && "justify-end border-t border-grid-dimmed px-2 py-3"
)}
>
<PaginationControls currentPage={currentPage} totalPages={totalPages} />
</div>
</div>
@@ -451,7 +462,7 @@ function SchedulesTable({
const cellClass = schedule.active ? "" : "opacity-50";
return (
<TableRow key={schedule.id} className={isSelected ? "bg-grid-dimmed" : undefined}>
<TableCell to={path} className={cellClass}>
<TableCell to={path} isTabbableCell className={cellClass}>
{schedule.friendlyId}
</TableCell>
<TableCell to={path} className={cellClass}>
@@ -281,6 +281,7 @@ function TaskRow({ task, environmentSlug }: { task: TaskListItem; environmentSlu
</TableCell>
<TableCell
to={path}
isTabbableCell
actionClassName="pl-1 pr-2 py-1.5"
className={cn((isActive || isPending) && "group-hover/table-row:bg-indigo-500/5")}
>
+2 -2
View File
@@ -212,10 +212,10 @@ module.exports = {
prod: prodEnv,
},
focusStyles: {
outline: "2px solid",
outline: "1px solid",
outlineOffset: "0px",
outlineColor: textLink,
borderRadius: "2px",
borderRadius: "3px",
},
borderRadius: {
lg: radius,