New Help & Feedback menu and tab-selected styles (#1374)

* Added a new dropdown help and feedback menu to the side menu

* Added a shortcut to the popover menu

* Removed dev cli connected button for now

* Contact us form uses original Feedback component to prevent broken links

* Improved the messaging when selecting different options in the email form

* buttons style tweak

* SideMenuItem supports the trailingIconClassName

* Adding a consistent focus-visible states

* Removing tooltips for now

* Squashed commit of the following:

commit 7d11123c0a
Author: Eric Goldman <eric@sequin.io>
Date:   Mon Sep 30 17:54:06 2024 -0700

    Add sequin guide (#1368)

    Co-authored-by: James Ritchie <james@trigger.dev>

commit 8da495ac00
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Mon Sep 30 13:42:22 2024 +0100

    Improve checkpoint reliability and cleanup of temp files (#1367)

    * improve cleanup reliability

    * improve logging

    * bye-bye execa

    * fix for trailing newlines

    * prettier errors

    * trim args and log output by default

    * fix archive cleanup

    * prevent potential memleak

    * more cleanup debug logs

    * ignore abort during cleanup

    * rename checkpoint dir env var and move to helper

    * add global never throw override

    * add tmp cleaner

    * also clean up checkpoint dir by default

    * split by any whitespace, not just tabs

    * only create tmp cleaner if paths to clean

commit 69ec68ee31
Author: Eric Allam <eallam@icloud.com>
Date:   Sun Sep 29 19:18:39 2024 -0700

    Release 3.0.9

commit a6ea8444c9
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Sun Sep 29 19:17:26 2024 -0700

    chore: Update version for release (#1366)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 4c1ee3d6ea
Author: Eric Allam <eallam@icloud.com>
Date:   Sun Sep 29 19:09:38 2024 -0700

    fix: run metadata not working when using npx/pnpm dlx

* More support for custom-focus

* More custom focus styles added

* Support for focus-visible style for the Segmented control

* Fixed table triple dot menu z-index issue

* Improved help menu wording

* When you submit the help form, close the modal

* focus-visible style for radio buttons

* button prop is now optional in the SideMenu component

* focus styling for a text link

* Deleted unused sequin files
This commit is contained in:
James Ritchie
2024-10-10 14:28:18 +01:00
committed by GitHub
parent b1319fb88c
commit 244988e212
30 changed files with 452 additions and 384 deletions
+100 -158
View File
@@ -1,31 +1,23 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import { BookOpenIcon } from "@heroicons/react/20/solid";
import {
CalendarDaysIcon,
ChevronRightIcon,
EnvelopeIcon,
LifebuoyIcon,
LightBulbIcon,
} from "@heroicons/react/24/solid";
import { EnvelopeIcon, LightBulbIcon } from "@heroicons/react/24/solid";
import { Form, useActionData, useLocation, useNavigation } from "@remix-run/react";
import { DiscordIcon } from "@trigger.dev/companyicons";
import { ActivityIcon } from "lucide-react";
import { type ReactNode, useState } from "react";
import { type ReactNode, useState, useEffect } from "react";
import { type FeedbackType, feedbackTypeLabel, schema } from "~/routes/resources.feedback";
import { cn } from "~/utils/cn";
import { docsTroubleshootingPath } from "~/utils/pathBuilder";
import { Button, LinkButton } from "./primitives/Buttons";
import { Button } from "./primitives/Buttons";
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog";
import { Fieldset } from "./primitives/Fieldset";
import { FormButtons } from "./primitives/FormButtons";
import { FormError } from "./primitives/FormError";
import { Header1 } from "./primitives/Headers";
import { Icon } from "./primitives/Icon";
import { InfoPanel } from "./primitives/InfoPanel";
import { InputGroup } from "./primitives/InputGroup";
import { Label } from "./primitives/Label";
import { Paragraph } from "./primitives/Paragraph";
import { Select, SelectItem } from "./primitives/Select";
import { Sheet, SheetBody, SheetContent, SheetTrigger } from "./primitives/Sheet";
import { TextArea } from "./primitives/TextArea";
import { InformationCircleIcon } from "@heroicons/react/20/solid";
import { TextLink } from "./primitives/TextLink";
type FeedbackProps = {
button: ReactNode;
@@ -37,161 +29,111 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
const location = useLocation();
const lastSubmission = useActionData();
const navigation = useNavigation();
const [type, setType] = useState<FeedbackType>(defaultValue);
const [form, { path, feedbackType, message }] = useForm({
id: "accept-invite",
// TODO: type this
lastSubmission: lastSubmission as any,
onValidate({ formData }) {
return parse(formData, { schema });
},
shouldRevalidate: "onInput",
});
if (
open &&
navigation.formAction === "/resources/feedback" &&
form.error === undefined &&
form.errors.length === 0
) {
setOpen(false);
}
useEffect(() => {
if (
navigation.formAction === "/resources/feedback" &&
navigation.state === "loading" &&
form.error === undefined &&
form.errors.length === 0
) {
setOpen(false);
}
}, [navigation, form]);
return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild={true}>{button}</SheetTrigger>
<SheetContent className="@container">
<SheetBody className="flex h-full flex-col justify-between">
<LinkBanner
title="Join our Discord community"
icon={<DiscordIcon className="size-9" />}
to="https://trigger.dev/discord"
className="hover:border-text-link"
>
<Paragraph>The quickest way to get answers from the Trigger.dev community.</Paragraph>
</LinkBanner>
<LinkBanner
title="Book a 15 min chat with the founders"
icon={<CalendarDaysIcon className="size-9 text-green-500" />}
to="https://cal.com/team/triggerdotdev/founders-call"
className="hover:border-green-500"
>
<Paragraph>Have a question or want to chat? Book a time to talk with us.</Paragraph>
</LinkBanner>
<LinkBanner
title="Suggest a feature"
icon={<LightBulbIcon className="size-9 text-sun-500" />}
to="https://feedback.trigger.dev/"
className="hover:border-sun-400"
>
<Paragraph>Have an idea for a new feature or improvement? Let us know!</Paragraph>
</LinkBanner>
<LinkBanner
title="Troubleshooting"
icon={<LifebuoyIcon className="size-9 text-rose-500" />}
>
<Paragraph>
If you're having trouble, check out our troubleshooting guide or the Trigger.dev
Status page.
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>{button}</DialogTrigger>
<DialogContent>
<DialogHeader>Contact us</DialogHeader>
<div className="mt-2 flex flex-col gap-4">
<div className="flex items-center gap-4">
<Icon icon={EnvelopeIcon} className="size-10 min-w-[2.5rem] text-blue-500" />
<Paragraph variant="base/bright">
How can we help? We read every message and will respond as quickly as we can.
</Paragraph>
<div className="flex flex-wrap gap-2">
<LinkButton
to={docsTroubleshootingPath("")}
variant="tertiary/medium"
LeadingIcon={BookOpenIcon}
>
Troubleshooting Docs
</LinkButton>
<LinkButton
to={"https://status.trigger.dev/"}
variant="tertiary/medium"
LeadingIcon={ActivityIcon}
>
Trigger.dev Status
</LinkButton>
</div>
</LinkBanner>
<LinkBanner
title="Send us an email"
icon={<EnvelopeIcon className="size-9 text-blue-500" />}
>
<Paragraph>We read every message and respond quickly.</Paragraph>
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
<Fieldset className="max-w-full gap-y-3">
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
<InputGroup className="max-w-full">
<Select
{...conform.select(feedbackType)}
variant="tertiary/medium"
defaultValue={defaultValue}
placeholder="Select type"
text={(value) => feedbackTypeLabel[value]}
dropdownIcon
</div>
<hr className="border-charcoal-800" />
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
<Fieldset className="max-w-full gap-y-3">
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
<InputGroup className="max-w-full">
{type === "feature" && (
<InfoPanel
icon={InformationCircleIcon}
iconClassName="text-blue-500"
panelClassName="w-full mb-2"
>
{Object.entries(feedbackTypeLabel).map(([name, title]) => (
<SelectItem key={name} value={name}>
{title}
</SelectItem>
))}
</Select>
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
</InputGroup>
<InputGroup className="max-w-full">
<Label>Message</Label>
<TextArea {...conform.textarea(message)} />
<FormError id={message.errorId}>{message.error}</FormError>
</InputGroup>
<FormError>{form.error}</FormError>
<div className="flex w-full justify-end">
<FormButtons
className="m-0 w-max"
confirmButton={
<Button type="submit" variant="tertiary/medium">
Send message
</Button>
}
/>
</div>
</Fieldset>
</Form>
</LinkBanner>
</SheetBody>
</SheetContent>
</Sheet>
);
}
function LinkBanner({
className,
icon,
title,
children,
to,
}: {
className?: string;
icon?: ReactNode;
title?: string;
children?: ReactNode;
to?: string;
}) {
return (
<a
href={to}
target="_blank"
className={cn(
"group/banner mb-4 flex w-full items-center justify-between rounded-md border border-grid-bright bg-charcoal-750 p-4 transition",
className
)}
>
<div className="flex w-full items-start gap-4">
<span>{icon}</span>
<div className="flex w-full flex-col gap-2">
<Header1 className="text-2xl font-semibold text-text-bright">{title}</Header1>
{children}
<Paragraph variant="small">
All our feature requests are public and voted on by the community. The best
way to submit your feature request is to{" "}
<TextLink to="https://feedback.trigger.dev">
post it to our feedback forum
</TextLink>
.
</Paragraph>
</InfoPanel>
)}
{type === "help" && (
<InfoPanel
icon={InformationCircleIcon}
iconClassName="text-blue-500"
panelClassName="w-full mb-2"
>
<Paragraph variant="small">
The quickest way to get answers from the Trigger.dev team and community is to{" "}
<TextLink to="https://trigger.dev/discord">ask in our Discord</TextLink>.
</Paragraph>
</InfoPanel>
)}
<Select
{...conform.select(feedbackType)}
variant="tertiary/medium"
value={type}
defaultValue={type}
setValue={(v) => setType(v as FeedbackType)}
placeholder="Select type"
text={(value) => feedbackTypeLabel[value as FeedbackType]}
dropdownIcon
>
{Object.entries(feedbackTypeLabel).map(([name, title]) => (
<SelectItem key={name} value={name}>
{title}
</SelectItem>
))}
</Select>
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
</InputGroup>
<InputGroup className="max-w-full">
<Label>Message</Label>
<TextArea {...conform.textarea(message)} />
<FormError id={message.errorId}>{message.error}</FormError>
</InputGroup>
<FormError>{form.error}</FormError>
<div className="flex w-full justify-end">
<FormButtons
className="m-0 w-max"
confirmButton={
<Button type="submit" variant="tertiary/medium">
Send message
</Button>
}
/>
</div>
</Fieldset>
</Form>
</div>
</div>
{to && (
<ChevronRightIcon className="size-5 text-charcoal-500 transition group-hover:translate-x-1 group-hover/banner:text-text-bright" />
)}
</a>
</DialogContent>
</Dialog>
);
}
@@ -27,7 +27,7 @@ export function FreePlanUsage({ to, percentage }: { to: string; percentage: numb
<ArrowUpCircleIcon className="h-5 w-5 text-text-dimmed" />
<Paragraph className="text-2sm text-text-bright">Free Plan</Paragraph>
</div>
<Link to={to} className="text-2sm text-text-link">
<Link to={to} className="text-2sm text-text-link focus-custom">
Upgrade
</Link>
</div>
@@ -241,7 +241,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
onMouseEnter={() => setMouseOver(true)}
onMouseLeave={() => setMouseOver(false)}
className={cn(
"absolute right-3 z-50 transition-colors duration-100 hover:cursor-pointer",
"absolute right-3 z-50 transition-colors duration-100 focus-custom hover:cursor-pointer",
showChrome ? "top-10" : "top-2.5",
copied ? "text-emerald-500" : "text-charcoal-500 hover:text-charcoal-300"
)}
@@ -38,7 +38,7 @@ export function AccountSideMenu({ user }: { user: User }) {
<SideMenuItem
name="Your profile"
icon="account"
iconColor="text-indigo-500"
activeIconColor="text-indigo-500"
to={accountPath()}
data-action="account"
/>
@@ -49,7 +49,7 @@ export function AccountSideMenu({ user }: { user: User }) {
<SideMenuItem
name="Personal Access Tokens"
icon={ShieldCheckIcon}
iconColor="text-emerald-500"
activeIconColor="text-emerald-500"
to={personalAccessTokensPath()}
data-action="tokens"
/>
+195 -114
View File
@@ -1,14 +1,19 @@
import {
AcademicCapIcon,
ArrowRightOnRectangleIcon,
ArrowUpRightIcon,
BeakerIcon,
BellAlertIcon,
CalendarDaysIcon,
ChartBarIcon,
ChatBubbleLeftEllipsisIcon,
ClockIcon,
CreditCardIcon,
CursorArrowRaysIcon,
EnvelopeIcon,
IdentificationIcon,
KeyIcon,
LightBulbIcon,
RectangleStackIcon,
ServerStackIcon,
ShieldCheckIcon,
@@ -17,13 +22,14 @@ import {
import { UserGroupIcon, UserPlusIcon } from "@heroicons/react/24/solid";
import { useNavigation } from "@remix-run/react";
import { DiscordIcon, SlackIcon } from "@trigger.dev/companyicons";
import { Fragment, useEffect, useRef, useState } from "react";
import { Fragment, ReactNode, useEffect, useRef, useState } from "react";
import { TaskIcon } from "~/assets/icons/TaskIcon";
import { useFeatures } from "~/hooks/useFeatures";
import { type MatchedOrganization } from "~/hooks/useOrganizations";
import { type MatchedProject } from "~/hooks/useProject";
import { type User } from "~/models/user.server";
import { useCurrentPlan } from "~/routes/_app.orgs.$organizationSlug/route";
import { FeedbackType } from "~/routes/resources.feedback";
import { cn } from "~/utils/cn";
import {
accountPath,
@@ -77,11 +83,12 @@ import {
PopoverCustomTrigger,
PopoverMenuItem,
PopoverSectionHeader,
PopoverSideMenuTrigger,
} from "../primitives/Popover";
import { StepNumber } from "../primitives/StepNumber";
import { TextLink } from "../primitives/TextLink";
import { SideMenuHeader } from "./SideMenuHeader";
import { SideMenuItem } from "./SideMenuItem";
import { MenuCount, SideMenuItem } from "./SideMenuItem";
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
type SideMenuProject = Pick<MatchedProject, "id" | "name" | "slug" | "version">;
@@ -91,6 +98,8 @@ type SideMenuProps = {
project: SideMenuProject;
organization: MatchedOrganization;
organizations: MatchedOrganization[];
button?: ReactNode;
defaultValue?: FeedbackType;
};
export function SideMenu({ user, project, organization, organizations }: SideMenuProps) {
@@ -171,7 +180,7 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
name="Team"
icon={UserGroupIcon}
to={organizationTeamPath(organization)}
iconColor="text-amber-500"
activeIconColor="text-amber-500"
data-action="team"
/>
{organization.projects.some((proj) => proj.version === "V3") && isManagedCloud && (
@@ -180,14 +189,14 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
name="Usage"
icon={ChartBarIcon}
to={v3UsagePath(organization)}
iconColor="text-green-600"
activeIconColor="text-green-600"
data-action="usage"
/>
<SideMenuItem
name="Billing"
icon={CreditCardIcon}
to={v3BillingPath(organization)}
iconColor="text-blue-600"
activeIconColor="text-blue-600"
data-action="billing"
badge={
currentPlan?.v3Subscription?.isPaying
@@ -202,14 +211,14 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
name="Usage (v2)"
icon={ChartBarIcon}
to={organizationBillingPath(organization)}
iconColor="text-green-600"
activeIconColor="text-green-600"
data-action="usage & billing"
/>
)}
<SideMenuItem
name="Organization settings"
icon="settings"
iconColor="text-teal-500"
activeIconColor="text-teal-500"
to={organizationSettingsPath(organization)}
data-action="organization-settings"
/>
@@ -239,96 +248,7 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
)}
</div>
<div className="flex flex-col gap-1 border-t border-grid-bright p-1">
{currentPlan?.v3Subscription?.plan?.limits.support === "slack" && (
<Dialog>
<DialogTrigger asChild>
<Button
variant="small-menu-item"
LeadingIcon={SlackIcon}
data-action="join our slack"
fullWidth
textAlignLeft
>
Join our Slack
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>Join our Slack</DialogHeader>
<div className="mt-2 flex flex-col gap-4">
<div className="flex items-center gap-4">
<Icon icon={SlackIcon} className="h-10 w-10 min-w-[2.5rem]" />
<Paragraph variant="base/bright">
As a subscriber, you have access to a dedicated Slack channel for 1-to-1
support with the Trigger.dev team.
</Paragraph>
</div>
<hr className="border-charcoal-800" />
<div>
<StepNumber stepNumber="1" title="Email us" />
<StepContentContainer>
<Paragraph>
Send us an email to this address from your Trigger.dev account email
address:
<ClipboardField
variant="primary/medium"
value="priority-support@trigger.dev"
className="my-2"
/>
</Paragraph>
</StepContentContainer>
<StepNumber stepNumber="2" title="Look out for an invite from Slack" />
<StepContentContainer>
<Paragraph>
As soon as we can, we'll setup a Slack Connect channel and say hello!
</Paragraph>
</StepContentContainer>
</div>
</div>
</DialogContent>
</Dialog>
)}
<SideMenuItem
name="Join our Discord"
icon={DiscordIcon}
to="https://trigger.dev/discord"
data-action="join our discord"
target="_blank"
/>
<SideMenuItem
name="Documentation"
icon="docs"
to="https://trigger.dev/docs"
data-action="documentation"
target="_blank"
/>
<SideMenuItem
name="Changelog"
icon="star"
to="https://trigger.dev/changelog"
data-action="changelog"
target="_blank"
/>
<SideMenuItem
name="Status"
icon={SignalIcon}
to="https://status.trigger.dev/"
data-action="status"
target="_blank"
/>
<Feedback
button={
<Button
variant="small-menu-item"
LeadingIcon="log"
leadingIconClassName="text-primary"
data-action="help & feedback"
fullWidth
textAlignLeft
>
<span className="text-primary">Help & Feedback</span>
</Button>
}
/>
<HelpAndFeedback />
{isV3Project && isFreeV3User && (
<FreePlanUsage
to={v3BillingPath(organization)}
@@ -341,6 +261,167 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
);
}
function HelpAndFeedback() {
const [isHelpMenuOpen, setHelpMenuOpen] = useState(false);
const currentPlan = useCurrentPlan();
return (
<Popover onOpenChange={(open) => setHelpMenuOpen(open)}>
<PopoverSideMenuTrigger isOpen={isHelpMenuOpen} shortcut={{ key: "h" }}>
<div className="flex items-center gap-1.5">
<ChatBubbleLeftEllipsisIcon className="size-4 text-success" />
Help & Feedback
</div>
</PopoverSideMenuTrigger>
<PopoverContent
className="min-w-[14rem] divide-y divide-grid-bright overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
align="start"
>
<Fragment>
<div className="flex flex-col gap-1 p-1">
<SideMenuItem
name="Documentation"
icon="docs"
trailingIcon={ArrowUpRightIcon}
trailingIconClassName="text-text-dimmed"
inactiveIconColor="text-green-500"
activeIconColor="text-green-500"
to="https://trigger.dev/docs"
data-action="documentation"
target="_blank"
/>
</div>
<div className="flex flex-col gap-1 p-1">
<SideMenuItem
name="Status"
icon={SignalIcon}
trailingIcon={ArrowUpRightIcon}
trailingIconClassName="text-text-dimmed"
inactiveIconColor="text-green-500"
activeIconColor="text-green-500"
to="https://status.trigger.dev/"
data-action="status"
target="_blank"
/>
<SideMenuItem
name="Suggest a feature"
icon={LightBulbIcon}
trailingIcon={ArrowUpRightIcon}
trailingIconClassName="text-text-dimmed"
inactiveIconColor="text-sun-500"
activeIconColor="text-sun-500"
to="https://feedback.trigger.dev/"
data-action="suggest-a-feature"
target="_blank"
/>
<SideMenuItem
name="Changelog"
icon="star"
trailingIcon={ArrowUpRightIcon}
trailingIconClassName="text-text-dimmed"
inactiveIconColor="text-sun-500"
activeIconColor="text-sun-500"
to="https://trigger.dev/changelog"
data-action="changelog"
target="_blank"
/>
</div>
<div className="flex flex-col gap-1 p-1">
<Paragraph className="pb-1 pl-1.5 pt-1.5 text-xs">Need help?</Paragraph>
{currentPlan?.v3Subscription?.plan?.limits.support === "slack" && (
<div>
<Dialog>
<DialogTrigger asChild>
<Button
variant="small-menu-item"
LeadingIcon={SlackIcon}
data-action="join-our-slack"
fullWidth
textAlignLeft
>
<div className="flex w-full items-center justify-between">
<span className="text-text-bright">Join our Slack</span>
<MenuCount count="PRO" />
</div>
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>Join our Slack</DialogHeader>
<div className="mt-2 flex flex-col gap-4">
<div className="flex items-center gap-4">
<Icon icon={SlackIcon} className="h-10 w-10 min-w-[2.5rem]" />
<Paragraph variant="base/bright">
As a subscriber, you have access to a dedicated Slack channel for 1-to-1
support with the Trigger.dev team.
</Paragraph>
</div>
<hr className="border-charcoal-800" />
<div>
<StepNumber stepNumber="1" title="Email us" />
<StepContentContainer>
<Paragraph>
Send us an email to this address from your Trigger.dev account email
address:
<ClipboardField
variant="secondary/medium"
value="priority-support@trigger.dev"
className="my-2"
/>
</Paragraph>
</StepContentContainer>
<StepNumber stepNumber="2" title="Look out for an invite from Slack" />
<StepContentContainer>
<Paragraph>
As soon as we can, we'll setup a Slack Connect channel and say hello!
</Paragraph>
</StepContentContainer>
</div>
</div>
</DialogContent>
</Dialog>
</div>
)}
<SideMenuItem
name="Ask in our Discord"
icon={DiscordIcon}
trailingIcon={ArrowUpRightIcon}
trailingIconClassName="text-text-dimmed"
to="https://trigger.dev/discord"
data-action="join our discord"
target="_blank"
/>
<SideMenuItem
name="Book a 15 min call"
icon={CalendarDaysIcon}
trailingIcon={ArrowUpRightIcon}
trailingIconClassName="text-text-dimmed"
inactiveIconColor="text-rose-500"
activeIconColor="text-rose-500"
to="https://cal.com/team/triggerdotdev/founders-call"
data-action="book-a-call"
target="_blank"
/>
<Feedback
button={
<Button
variant="small-menu-item"
LeadingIcon={EnvelopeIcon}
leadingIconClassName="text-blue-500"
data-action="contact-us"
fullWidth
textAlignLeft
>
Contact us
</Button>
}
/>
</div>
</Fragment>
</PopoverContent>
</Popover>
);
}
function ProjectSelector({
project,
organizations,
@@ -495,47 +576,47 @@ function V2ProjectSideMenu({
<SideMenuItem
name="Jobs"
icon="job"
iconColor="text-indigo-500"
activeIconColor="text-indigo-500"
to={projectPath(organization, project)}
data-action="jobs"
/>
<SideMenuItem
name="Runs"
icon="runs"
iconColor="text-teal-500"
activeIconColor="text-teal-500"
to={projectRunsPath(organization, project)}
/>
<SideMenuItem
name="Triggers"
icon="trigger"
iconColor="text-amber-500"
activeIconColor="text-amber-500"
to={projectTriggersPath(organization, project)}
data-action="triggers"
/>
<SideMenuItem
name="Events"
icon={CursorArrowRaysIcon}
iconColor="text-sky-500"
activeIconColor="text-sky-500"
to={projectEventsPath(organization, project)}
/>
<SideMenuItem
name="HTTP endpoints"
icon="http-endpoint"
iconColor="text-pink-500"
activeIconColor="text-pink-500"
to={projectHttpEndpointsPath(organization, project)}
data-action="httpendpoints"
/>
<SideMenuItem
name="Environments & API Keys"
icon="environment"
iconColor="text-rose-500"
activeIconColor="text-rose-500"
to={projectEnvironmentsPath(organization, project)}
data-action="environments & api keys"
/>
<SideMenuItem
name="Project settings"
icon="settings"
iconColor="text-teal-500"
activeIconColor="text-teal-500"
to={projectSettingsPath(organization, project)}
data-action="project-settings"
/>
@@ -558,41 +639,41 @@ function V3ProjectSideMenu({
<SideMenuItem
name="Tasks"
icon={TaskIcon}
iconColor="text-blue-500"
activeIconColor="text-blue-500"
to={v3ProjectPath(organization, project)}
data-action="tasks"
/>
<SideMenuItem
name="Runs"
icon="runs"
iconColor="text-teal-500"
activeIconColor="text-teal-500"
to={v3RunsPath(organization, project)}
/>
<SideMenuItem
name="Test"
icon={BeakerIcon}
iconColor="text-lime-500"
activeIconColor="text-lime-500"
to={v3TestPath(organization, project)}
data-action="test"
/>
<SideMenuItem
name="Schedules"
icon={ClockIcon}
iconColor="text-sun-500"
activeIconColor="text-sun-500"
to={v3SchedulesPath(organization, project)}
data-action="schedules"
/>
<SideMenuItem
name="API keys"
icon={KeyIcon}
iconColor="text-amber-500"
activeIconColor="text-amber-500"
to={v3ApiKeysPath(organization, project)}
data-action="api keys"
/>
<SideMenuItem
name="Environment variables"
icon={IdentificationIcon}
iconColor="text-pink-500"
activeIconColor="text-pink-500"
to={v3EnvironmentVariablesPath(organization, project)}
data-action="environment variables"
/>
@@ -600,7 +681,7 @@ function V3ProjectSideMenu({
<SideMenuItem
name="Deployments"
icon={ServerStackIcon}
iconColor="text-blue-500"
activeIconColor="text-blue-500"
to={v3DeploymentsPath(organization, project)}
data-action="deployments"
/>
@@ -608,7 +689,7 @@ function V3ProjectSideMenu({
<SideMenuItem
name="Alerts"
icon={BellAlertIcon}
iconColor="text-red-500"
activeIconColor="text-red-500"
to={v3ProjectAlertsPath(organization, project)}
data-action="alerts"
/>
@@ -616,14 +697,14 @@ function V3ProjectSideMenu({
<SideMenuItem
name="Concurrency limits"
icon={RectangleStackIcon}
iconColor="text-indigo-500"
activeIconColor="text-indigo-500"
to={v3ConcurrencyPath(organization, project)}
data-action="concurrency"
/>
<SideMenuItem
name="Project settings"
icon="settings"
iconColor="text-teal-500"
activeIconColor="text-teal-500"
to={v3ProjectSettingsPath(organization, project)}
data-action="project-settings"
/>
@@ -3,25 +3,26 @@ import { usePathName } from "~/hooks/usePathName";
import { cn } from "~/utils/cn";
import { LinkButton } from "../primitives/Buttons";
import { type IconNames } from "../primitives/NamedIcon";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
import { Icon } from "../primitives/Icon";
import { IconExclamationCircle } from "@tabler/icons-react";
export function SideMenuItem({
icon,
iconColor,
activeIconColor,
inactiveIconColor,
trailingIcon,
trailingIconClassName,
name,
to,
hasWarning,
badge,
target,
subItem = false,
}: {
icon?: IconNames | React.ComponentType<any>;
iconColor?: string;
activeIconColor?: string;
inactiveIconColor?: string;
trailingIcon?: IconNames | React.ComponentType<any>;
trailingIconClassName?: string;
name: string;
to: string;
hasWarning?: string | boolean;
badge?: string;
target?: AnchorHTMLAttributes<HTMLAnchorElement>["target"];
subItem?: boolean;
@@ -35,11 +36,13 @@ export function SideMenuItem({
fullWidth
textAlignLeft
LeadingIcon={icon}
leadingIconClassName={isActive ? iconColor : "text-text-dimmed"}
leadingIconClassName={isActive ? activeIconColor : inactiveIconColor ?? "text-text-dimmed"}
TrailingIcon={trailingIcon}
trailingIconClassName={trailingIconClassName}
to={to}
target={target}
className={cn(
"text-text-bright group-hover:bg-charcoal-750",
"text-text-bright group-hover:bg-charcoal-750 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0",
subItem ? "text-text-dimmed" : "",
isActive ? "bg-tertiary text-text-bright" : "group-hover:text-text-bright"
)}
@@ -48,27 +51,13 @@ export function SideMenuItem({
{name}
<div className="flex items-center gap-1">
{badge !== undefined && <MenuCount count={badge} />}
{typeof hasWarning === "string" ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Icon icon={IconExclamationCircle} className="h-5 w-5 text-error" />
</TooltipTrigger>
<TooltipContent className="flex items-center gap-1 border border-error bg-error/20 backdrop-blur-xl">
{hasWarning}
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
hasWarning && <Icon icon={IconExclamationCircle} className="h-5 w-5 text-error" />
)}
</div>
</div>
</LinkButton>
);
}
function MenuCount({ count }: { count: number | string }) {
export function MenuCount({ count }: { count: number | string }) {
return (
<div className="rounded-full bg-charcoal-900 px-2 py-1 text-xxs uppercase tracking-wider text-text-dimmed">
{count}
@@ -45,7 +45,10 @@ export function AppliedFilter({
</div>
{removable && (
<button
className={cn("group flex size-6 items-center justify-center", variantClassName.clear)}
className={cn(
"group flex size-6 items-center justify-center focus-custom",
variantClassName.clear
)}
onClick={onRemove}
>
<XMarkIcon className="size-3.5" />
@@ -48,11 +48,11 @@ const theme = {
"border-black/40 text-charcoal-900 group-hover:border-black/60 group-hover:text-charcoal-900",
},
secondary: {
textColor: "text-secondary group-hover:text-secondary transition group-disabled:text-secondary",
textColor: "text-text-bright transition group-disabled:text-text-dimmed/80",
button:
"bg-transparent border border-secondary group-hover:border-secondary group-hover:bg-secondary/10 group-disabled:opacity-30 group-disabled:border-secondary group-disabled:bg-transparent group-disabled:pointer-events-none",
"bg-secondary group-hover:bg-charcoal-600 group-hover:border-charcoal-650 border border-charcoal-600 group-disabled:bg-secondary group-disabled:opacity-60 group-disabled:pointer-events-none",
shortcut:
"border-secondary/30 text-secondary group-hover:text-text-bright/80 group-hover:border-dimmed/60",
"border-text-dimmed/40 text-text-dimmed group-hover:text-text-bright group-hover:border-text-dimmed",
},
tertiary: {
textColor: "text-text-bright transition group-disabled:text-text-dimmed/80",
@@ -114,8 +114,7 @@ const variant = {
"danger/extra-large": createVariant("extra-large", "danger"),
"menu-item": {
textColor: "text-text-bright px-1",
button:
"h-9 px-[0.475rem] text-sm rounded-sm bg-transparent group-hover:bg-charcoal-800 transition",
button: "h-9 px-[0.475rem] text-sm rounded-sm bg-transparent group-hover:bg-charcoal-750",
icon: "h-5",
iconSpacing: "gap-x-0.5",
shortcutVariant: undefined,
@@ -124,7 +123,7 @@ const variant = {
"small-menu-item": {
textColor: "text-text-bright",
button:
"h-[1.8rem] px-[0.4rem] text-2sm rounded-sm text-text-dimmed bg-transparent group-hover:bg-charcoal-850 transition",
"h-[1.8rem] px-[0.4rem] text-2sm rounded-sm text-text-dimmed bg-transparent group-hover:bg-charcoal-750",
icon: "h-4",
iconSpacing: "gap-x-1.5",
shortcutVariant: undefined,
@@ -133,7 +132,7 @@ const variant = {
"small-menu-sub-item": {
textColor: "text-text-dimmed",
button:
"h-[1.8rem] px-[0.5rem] ml-5 text-2sm rounded-sm text-text-dimmed bg-transparent group-hover:bg-charcoal-850 transition",
"h-[1.8rem] px-[0.5rem] ml-5 text-2sm rounded-sm text-text-dimmed bg-transparent group-hover:bg-charcoal-750 focus-custom",
icon: undefined,
iconSpacing: undefined,
shortcutVariant: undefined,
@@ -142,7 +141,7 @@ const variant = {
};
const allVariants = {
$all: "font-normal text-center font-sans justify-center items-center shrink-0 transition duration-150 rounded-[3px] select-none group-focus:outline-none group-disabled:opacity-75 group-disabled:pointer-events-none",
$all: "font-normal text-center font-sans justify-center items-center shrink-0 transition duration-150 rounded-[3px] select-none group-focus:outline-none group-disabled:opacity-75 group-disabled:pointer-events-none focus-custom",
variant: variant,
};
@@ -268,7 +267,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
return (
<button
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
className={cn("group outline-none focus-custom", props.fullWidth ? "w-full" : "")}
type={type}
disabled={disabled}
onClick={onClick}
@@ -328,7 +327,7 @@ export const LinkButton = ({
<ExtLink
href={to.toString()}
ref={innerRef}
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
className={cn("group focus-custom", props.fullWidth ? "w-full" : "")}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseEnter={onMouseEnter}
@@ -343,7 +342,7 @@ export const LinkButton = ({
<Link
to={to}
ref={innerRef}
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
className={cn("group focus-custom", props.fullWidth ? "w-full" : "")}
onClick={onClick}
onMouseDown={onMouseDown}
onMouseEnter={onMouseEnter}
@@ -50,7 +50,7 @@ const DialogContent = React.forwardRef<
>
<hr className="absolute left-0 top-11 w-full" />
{children}
<DialogPrimitive.Close className="ring-offset-background data-[state=open]:bg-accent data-[state=open]:text-muted-foreground focus-visible:ring-ring absolute right-3 top-3 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none">
<DialogPrimitive.Close className="data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute right-3 top-2 rounded-sm py-1 pr-1 opacity-70 transition-opacity focus-custom hover:opacity-100 disabled:pointer-events-none">
<div className="flex gap-x-2">
<ShortcutKey
shortcut={{
@@ -1,7 +1,7 @@
import { LinkButton } from "./Buttons";
import { Header3 } from "./Headers";
import { Paragraph } from "./Paragraph";
import { cn } from "~/utils/cn";
import { LinkButton } from "./Buttons";
import { Header2 } from "./Headers";
import { Paragraph } from "./Paragraph";
const variants = {
info: {
@@ -57,7 +57,7 @@ export function InfoPanel({
)}
</div>
<div className="flex flex-col gap-1">
{title && <Header3 className="text-text-bright">{title}</Header3>}
{title && <Header2 className="text-text-bright">{title}</Header2>}
{typeof children === "string" ? (
<Paragraph variant={"small"} className="text-text-dimmed">
{children}
@@ -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-ring 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-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";
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";
@@ -49,7 +49,7 @@ export function PageTitle({ title, backButton }: PageTitleProps) {
<div className="group -ml-1.5 flex items-center gap-0">
<Link
to={backButton.to}
className="rounded px-1.5 py-1 text-xs text-text-dimmed transition group-hover:bg-charcoal-700 group-hover:text-text-bright"
className="rounded px-1.5 py-1 text-xs text-text-dimmed transition focus-custom group-hover:bg-charcoal-700 group-hover:text-text-bright"
>
{backButton.text}
</Link>
@@ -6,6 +6,8 @@ import * as React from "react";
import { cn } from "~/utils/cn";
import { type ButtonContentPropsType, LinkButton } from "./Buttons";
import { Paragraph, type ParagraphVariant } from "./Paragraph";
import { ShortcutKey } from "./ShortcutKey";
import { ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
@@ -91,7 +93,7 @@ function PopoverCustomTrigger({
<PopoverTrigger
{...props}
className={cn(
"group flex items-center justify-end gap-1 rounded text-text-dimmed transition hover:bg-charcoal-850 hover:text-text-bright",
"group flex items-center justify-end gap-1 rounded text-text-dimmed transition focus-custom hover:bg-charcoal-850 hover:text-text-bright",
className
)}
>
@@ -100,6 +102,45 @@ function PopoverCustomTrigger({
);
}
function PopoverSideMenuTrigger({
isOpen,
children,
className,
shortcut,
...props
}: { isOpen?: boolean; shortcut?: ShortcutDefinition } & React.ComponentPropsWithoutRef<
typeof PopoverTrigger
>) {
const ref = React.useRef<HTMLButtonElement>(null);
useShortcutKeys({
shortcut: shortcut,
action: (e) => {
e.preventDefault();
e.stopPropagation();
if (ref.current) {
ref.current.click();
}
},
});
return (
<PopoverTrigger
{...props}
ref={ref}
className={cn(
"flex h-[1.8rem] shrink-0 select-none items-center gap-x-1.5 rounded-sm bg-transparent px-[0.4rem] text-center font-sans text-2sm font-normal text-text-bright transition duration-150 focus-custom hover:bg-charcoal-750",
shortcut ? "justify-between" : "",
className
)}
>
{children}
{shortcut && (
<ShortcutKey className={cn("size-4 flex-none")} shortcut={shortcut} variant={"small"} />
)}
</PopoverTrigger>
);
}
function PopoverArrowTrigger({
isOpen,
children,
@@ -116,7 +157,7 @@ function PopoverArrowTrigger({
<PopoverTrigger
{...props}
className={cn(
"group flex h-6 items-center gap-1 rounded px-2 text-text-dimmed transition hover:bg-charcoal-700 hover:text-text-bright",
"group flex h-6 items-center gap-1 rounded px-2 text-text-dimmed transition focus-custom hover:bg-charcoal-700 hover:text-text-bright",
fullWidth && "w-full justify-between",
className
)}
@@ -149,7 +190,7 @@ function PopoverVerticalEllipseTrigger({
<PopoverTrigger
{...props}
className={cn(
"group flex items-center justify-end gap-1 rounded px-1.5 py-1.5 text-text-dimmed transition hover:bg-charcoal-750 hover:text-text-bright",
"group flex items-center justify-end gap-1 rounded px-1.5 py-1.5 text-text-dimmed transition focus-custom hover:bg-charcoal-750 hover:text-text-bright",
className
)}
>
@@ -165,6 +206,7 @@ export {
PopoverCustomTrigger,
PopoverMenuItem,
PopoverSectionHeader,
PopoverSideMenuTrigger,
PopoverTrigger,
PopoverVerticalEllipseTrigger,
};
@@ -70,7 +70,7 @@ export function RadioButtonCircle({
return (
<div
className={cn(
"ring-offset-background focus-visible:ring-ring aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-full border border-charcoal-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"ring-offset-background aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-full border border-charcoal-600 focus-custom disabled:cursor-not-allowed disabled:opacity-50",
boxClassName
)}
>
@@ -81,7 +81,9 @@ export function RadioButtonCircle({
outerCircleClassName
)}
>
<Circle className={cn("h-1.5 w-1.5 fill-white text-white", innerCircleClassName)} />
<Circle
className={cn("size-1.5 fill-text-bright text-text-bright", innerCircleClassName)}
/>
</div>
)}
</div>
@@ -121,7 +123,7 @@ export const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"group flex cursor-pointer items-start gap-x-2 transition",
"group flex cursor-pointer items-start gap-x-2 transition focus-custom",
variation.button,
className
)}
@@ -129,7 +131,7 @@ export const RadioGroupItem = React.forwardRef<
>
<div
className={cn(
"ring-offset-background focus-visible:ring-ring aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-full border border-charcoal-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"ring-offset-background focus-visible:ring-ring aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-full border border-charcoal-600 focus-custom disabled:cursor-not-allowed disabled:opacity-50",
variation.inputPosition
)}
>
@@ -26,7 +26,7 @@ const ResizableHandle = ({
}) => (
<PanelResizer
className={cn(
"focus-visible:ring-ring group relative flex w-0.75 items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 hover:w-0.75 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-secondary/50 focus-visible:ring-offset-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
"group relative flex w-0.75 items-center justify-center focus-custom after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 hover:w-0.75 [&[data-panel-group-direction=vertical]>div]:rotate-90",
className
)}
size="3px"
@@ -65,10 +65,7 @@ export default function SegmentedControl({
value={option.value}
className={({ active, checked }) =>
cn(
"relative flex h-full grow cursor-pointer text-center font-normal focus:outline-none",
active
? "ring-offset-2 focus-visible:ring focus-visible:ring-secondary focus-visible:ring-opacity-60"
: "",
"relative flex h-full grow cursor-pointer text-center font-normal focus-custom",
checked
? variants[variant].active
: "text-text-dimmed transition hover:text-text-bright"
@@ -15,18 +15,18 @@ const sizes = {
button: "h-6 rounded text-xs px-2 ",
},
medium: {
button: "h-8 rounded text-xs px-3 text-sm",
button: "h-8 rounded px-3 text-sm",
},
};
const style = {
tertiary: {
button:
"bg-tertiary focus-within:ring-charcoal-500 border border-tertiary hover:text-text-bright hover:border-charcoal-600",
"bg-tertiary focus-custom border border-tertiary hover:text-text-bright hover:border-charcoal-600",
},
minimal: {
button:
"bg-transparent focus-within:ring-charcoal-500 hover:bg-tertiary disabled:bg-transparent disabled:pointer-events-none",
"bg-transparent focus-custom hover:bg-tertiary disabled:bg-transparent disabled:pointer-events-none",
},
};
@@ -322,7 +322,7 @@ export function SelectTrigger({
render={
<Ariakit.Select
className={cn(
"group flex items-center gap-1 outline-offset-0 focus-within:outline-none focus-within:ring-1 disabled:cursor-not-allowed disabled:opacity-50",
"group flex items-center gap-1 focus-custom disabled:cursor-not-allowed disabled:opacity-50",
variantClasses.button,
className
)}
@@ -426,7 +426,7 @@ export function SelectList(props: SelectListProps) {
<Component
{...props}
className={cn(
"overflow-y-auto overscroll-contain outline-none scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600",
"overflow-y-auto overscroll-contain scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 focus-custom",
props.className
)}
/>
@@ -440,11 +440,11 @@ export interface SelectItemProps extends Ariakit.SelectItemProps {
}
const selectItemClasses =
"group cursor-pointer px-1 pt-1 text-xs text-text-dimmed outline-none last:pb-1";
"group cursor-pointer px-1 pt-1 text-sm text-text-dimmed focus-custom last:pb-1";
export function SelectItem({
icon,
checkIcon = <Ariakit.SelectItemCheck className="size-8 flex-none text-white" />,
checkIcon = <Ariakit.SelectItemCheck className="size-8 flex-none text-text-bright" />,
shortcut,
...props
}: SelectItemProps) {
@@ -477,7 +477,7 @@ export function SelectItem({
)}
ref={ref}
>
<div className="flex h-7 w-full items-center gap-1 rounded-sm px-2 group-data-[active-item=true]:bg-tertiary">
<div className="flex h-8 w-full items-center gap-1 rounded-sm px-2 group-data-[active-item=true]:bg-tertiary">
{icon}
<div className="grow truncate">{props.children || props.value}</div>
{checkIcon}
@@ -6,14 +6,14 @@ import { cn } from "~/utils/cn";
const variations = {
large: {
container: "flex items-center gap-x-2 rounded-md hover:bg-tertiary p-2 transition",
container: "flex items-center gap-x-2 rounded-md hover:bg-tertiary p-2 transition focus-custom",
root: "h-6 w-11",
thumb: "h-5 w-5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
text: "text-sm text-charcoal-400 group-hover:text-charcoal-200 transition",
},
small: {
container:
"flex items-center gap-x-1.5 rounded hover:bg-tertiary pr-1 py-[0.1rem] pl-1.5 transition",
"flex items-center h-[1.5rem] gap-x-1.5 rounded hover:bg-tertiary pr-1 py-[0.1rem] pl-1.5 transition focus-custom",
root: "h-3 w-6",
thumb: "h-2.5 w-2.5 data-[state=checked]:translate-x-2.5 data-[state=unchecked]:translate-x-0",
text: "text-xs text-charcoal-400 group-hover:text-charcoal-200 hover:cursor-pointer transition",
@@ -38,14 +38,14 @@ export const Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitives.
) : null}
<div
className={cn(
"group-focus-visible:ring-ring group-focus-visible:ring-offset-background peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors group-focus-visible:ring-2 group-focus-visible:ring-offset-2 group-disabled:cursor-not-allowed group-disabled:opacity-50 group-data-[state=checked]:bg-secondary group-data-[state=unchecked]:bg-charcoal-700 focus-visible:outline-none",
"inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors group-disabled:cursor-not-allowed group-disabled:opacity-50 group-data-[state=checked]:bg-text-link group-data-[state=unchecked]:bg-charcoal-700",
root
)}
>
<SwitchPrimitives.Thumb
className={cn(
thumb,
"pointer-events-none block rounded-full bg-charcoal-200 shadow-lg ring-0 transition group-data-[state=checked]:bg-charcoal-700"
"pointer-events-none block rounded-full bg-charcoal-200 transition group-data-[state=checked]:bg-text-bright"
)}
/>
</div>
@@ -151,7 +151,7 @@ const rowHoverStyles = {
};
const stickyStyles =
"sticky right-0 z-10 w-[2.8rem] min-w-[2.8rem] bg-background-dimmed before:absolute before:pointer-events-none before:-left-8 before:top-0 before:h-full before:min-w-[2rem]";
"sticky right-0 w-[2.8rem] min-w-[2.8rem] bg-background-dimmed before:absolute before:pointer-events-none before:-left-8 before:top-0 before:h-full before:min-w-[2rem]";
export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
(
@@ -202,11 +202,11 @@ export const TableCell = forwardRef<HTMLTableCellElement, TableCellProps>(
colSpan={colSpan}
>
{to ? (
<Link to={to} className={cn(flexClasses, actionClassName)}>
<Link to={to} className={cn("focus-custom", flexClasses, actionClassName)}>
{children}
</Link>
) : onClick ? (
<button onClick={onClick} className={cn(flexClasses, actionClassName)}>
<button onClick={onClick} className={cn("focus-custom", flexClasses, actionClassName)}>
{children}
</button>
) : (
@@ -46,7 +46,7 @@ export function TabLink({
layoutId: string;
}) {
return (
<NavLink to={to} className="group flex flex-col items-center pt-1" end>
<NavLink to={to} className="group flex flex-col items-center pt-1 focus-custom" end>
{({ isActive, isPending }) => {
return (
<>
@@ -96,7 +96,7 @@ export function TabButton({
return (
<button
className={cn("group flex flex-col items-center pt-1", props.className)}
className={cn("group flex flex-col items-center pt-1 focus-custom", props.className)}
type="button"
ref={ref}
{...props}
@@ -8,7 +8,7 @@ export function TextArea({ className, rows, ...props }: TextAreaProps) {
{...props}
rows={rows ?? 6}
className={cn(
"ring-offset-background placeholder:text-muted-foreground focus:border-ring focus:ring-ring focus-visible:ring-ring w-full rounded-md border border-tertiary bg-tertiary px-3 text-sm text-text-bright transition file:border-0 file:bg-transparent file:text-base file:font-medium hover:border-charcoal-600 focus:outline-none focus:ring-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
"placeholder:text-muted-foreground w-full rounded-md border border-tertiary bg-tertiary px-3 text-sm text-text-bright transition focus-custom file:border-0 file:bg-transparent file:text-base file:font-medium hover:border-charcoal-600 focus:border-transparent focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
/>
@@ -41,7 +41,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
"z-50 overflow-hidden animate-in data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1 focus-visible:outline-none",
variantClasses[variant],
className
)}
@@ -72,7 +72,9 @@ function SimpleTooltip({
return (
<TooltipProvider disableHoverableContent={disableHoverableContent}>
<Tooltip>
<TooltipTrigger className={cn("h-fit", buttonClassName)}>{button}</TooltipTrigger>
<TooltipTrigger tabIndex={-1} className={cn("h-fit", buttonClassName)}>
{button}
</TooltipTrigger>
<TooltipContent
side={side}
hidden={hidden}
@@ -368,7 +368,7 @@ function AppliedStatusFilter() {
{(search, setSearch) => (
<StatusDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer" />}>
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Status"
value={appliedSummary(statuses.map((v) => runStatusTitle(v as TaskRunStatus)))}
@@ -456,7 +456,7 @@ function AppliedEnvironmentFilter({
{(search, setSearch) => (
<EnvironmentsDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer" />}>
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Environment"
value={appliedSummary(
@@ -547,7 +547,7 @@ function AppliedTaskFilter({ possibleTasks }: Pick<RunFiltersProps, "possibleTas
{(search, setSearch) => (
<TasksDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer" />}>
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Task"
value={appliedSummary(
@@ -645,7 +645,7 @@ function AppliedBulkActionsFilter({ bulkActions }: Pick<RunFiltersProps, "bulkAc
{(search, setSearch) => (
<BulkActionsDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer" />}>
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Bulk action"
value={bulkId}
@@ -764,7 +764,7 @@ function AppliedTagsFilter() {
{(search, setSearch) => (
<TagsDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer" />}>
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Tags"
value={appliedSummary(values("tags"))}
@@ -902,7 +902,7 @@ function AppliedPeriodFilter() {
{(search, setSearch) => (
<CreatedDropdown
trigger={
<Ariakit.Select render={<div className="group cursor-pointer" />}>
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
<AppliedFilter
label="Created"
value={
@@ -75,7 +75,7 @@ export function RunInspector({
if (!run) {
return (
<div className="grid h-full max-h-full grid-rows-[2.5rem_1fr] overflow-hidden bg-background-bright">
<div className="mx-3 flex items-center justify-between gap-2 overflow-x-hidden">
<div className="flex items-center justify-between gap-2 overflow-x-hidden px-3">
<div className="flex items-center gap-1 overflow-x-hidden">
<RunIcon name={"task"} spanName="" className="h-4 min-h-4 w-4 min-w-4" />
<Header2 className={cn("overflow-x-hidden text-blue-500")}>
@@ -101,7 +101,7 @@ export function RunInspector({
return (
<div className="grid h-full max-h-full grid-rows-[2.5rem_2rem_1fr_3.25rem] overflow-hidden bg-background-bright">
<div className="mx-3 flex items-center justify-between gap-2 overflow-x-hidden">
<div className="flex items-center justify-between gap-2 overflow-x-hidden px-3">
<div className="flex items-center gap-1 overflow-x-hidden">
<RunIcon
name={"task"}
@@ -47,7 +47,7 @@ export function SpanInspector({
return (
<div className="grid h-full max-h-full grid-rows-[2.5rem_2rem_1fr] overflow-hidden bg-background-bright">
<div className="mx-3 flex items-center justify-between gap-2 overflow-x-hidden">
<div className="flex items-center justify-between gap-2 overflow-x-hidden px-3">
<div className="flex items-center gap-1 overflow-x-hidden">
<RunIcon
name={span.style?.icon}
@@ -501,16 +501,14 @@ function TasksTreeView({
return (
<div className="grid h-full grid-rows-[2.5rem_1fr_3.25rem] overflow-hidden">
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
<div className="flex items-center justify-between gap-2 border-b border-grid-dimmed px-2">
<SearchField onChange={setFilterText} />
<div className="flex items-center gap-2">
<Switch
variant="small"
label="Errors only"
checked={errorsOnly}
onCheckedChange={(e) => setErrorsOnly(e.valueOf())}
/>
</div>
<Switch
variant="small"
label="Errors only"
checked={errorsOnly}
onCheckedChange={(e) => setErrorsOnly(e.valueOf())}
/>
</div>
<ResizablePanelGroup autosaveId={resizableSettings.tree.autosaveId}>
{/* Tree list */}
@@ -1008,7 +1006,7 @@ function ShowParentLink({
{mouseOver ? (
<ShowParentIconSelected className="h-4 w-4 text-indigo-500" />
) : (
<ShowParentIcon className="text-charcoal-650 h-4 w-4" />
<ShowParentIcon className="h-4 w-4 text-charcoal-650" />
)}
<Paragraph
variant="small"
@@ -528,16 +528,14 @@ function TasksTreeView({
return (
<div className="grid h-full grid-rows-[2.5rem_1fr_3.25rem] overflow-hidden">
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
<div className="flex items-center justify-between gap-2 border-b border-grid-dimmed px-3">
<SearchField onChange={setFilterText} />
<div className="flex items-center gap-2">
<Switch
variant="small"
label="Errors only"
checked={errorsOnly}
onCheckedChange={(e) => setErrorsOnly(e.valueOf())}
/>
</div>
<Switch
variant="small"
label="Errors only"
checked={errorsOnly}
onCheckedChange={(e) => setErrorsOnly(e.valueOf())}
/>
</div>
<ResizablePanelGroup autosaveId={resizableSettings.tree.autosaveId}>
{/* Tree list */}
@@ -1015,7 +1013,7 @@ function ShowParentLink({ runFriendlyId }: { runFriendlyId: string }) {
{mouseOver ? (
<ShowParentIconSelected className="h-4 w-4 text-indigo-500" />
) : (
<ShowParentIcon className="text-charcoal-650 h-4 w-4" />
<ShowParentIcon className="h-4 w-4 text-charcoal-650" />
)}
<Paragraph
variant="small"
+1 -1
View File
@@ -14,7 +14,7 @@ export const feedbackTypeLabel = {
feature: "Feature request",
help: "Help me out",
enterprise: "Enterprise enquiry",
"developer preview": "Developer preview feedback",
feedback: "General feedback",
};
export type FeedbackType = keyof typeof feedbackTypeLabel;
@@ -172,7 +172,7 @@ function SpanBody({
return (
<div className="grid h-full max-h-full grid-rows-[2.5rem_2rem_1fr] overflow-hidden bg-background-bright">
<div className="mx-3 flex items-center justify-between gap-2 overflow-x-hidden">
<div className="flex items-center justify-between gap-2 overflow-x-hidden px-3">
<div className="flex items-center gap-1 overflow-x-hidden">
<RunIcon
name={span.style?.icon}
@@ -413,7 +413,7 @@ function RunBody({
return (
<div className="grid h-full max-h-full grid-rows-[2.5rem_2rem_1fr_3.25rem] overflow-hidden bg-background-bright">
<div className="mx-3 flex items-center justify-between gap-2 overflow-x-hidden">
<div className="flex items-center justify-between gap-2 overflow-x-hidden px-3">
<div className="flex items-center gap-1 overflow-x-hidden">
<RunIcon
name={"task"}
+16 -1
View File
@@ -62,6 +62,7 @@ const charcoal = {
400: "#878C99",
500: "#5F6570",
600: "#3B3E45",
650: "#2C3034",
700: "#272A2E",
750: "#212327",
800: "#1A1B1F",
@@ -132,7 +133,7 @@ const lavender = {
/** Text colors */
const primary = apple[500];
const secondary = lavender[400];
const secondary = charcoal[650];
const tertiary = charcoal[700];
const textLink = lavender[400];
const textDimmed = charcoal[400];
@@ -210,6 +211,12 @@ module.exports = {
staging: stagingEnv,
prod: prodEnv,
},
focusStyles: {
outline: "2px solid",
outlineOffset: "0px",
outlineColor: textLink,
borderRadius: "2px",
},
borderRadius: {
lg: radius,
md: `calc(${radius} - 2px)`,
@@ -280,5 +287,13 @@ module.exports = {
require("tailwind-scrollbar"),
require("tailwind-scrollbar-hide"),
require("tailwindcss-textshadow"),
function ({ addUtilities, theme }) {
const focusStyles = theme("focusStyles", {});
addUtilities({
".focus-custom": {
"&:focus-visible": focusStyles,
},
});
},
],
};