Shortcut keys are working
This commit is contained in:
@@ -3,6 +3,7 @@ import "../app/tailwind.css";
|
||||
import { unstable_createRemixStub } from "@remix-run/testing";
|
||||
import React from "react";
|
||||
import { LocaleContextProvider } from "../app/components/primitives/LocaleProvider";
|
||||
import { OperatingSystemContextProvider } from "../app/components/primitives/OperatingSystemProvider";
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
@@ -33,9 +34,13 @@ const preview: Preview = {
|
||||
]);
|
||||
|
||||
return (
|
||||
<LocaleContextProvider locales={window.navigator.languages as string[]}>
|
||||
<RemixStub initialEntries={["/"]} />
|
||||
</LocaleContextProvider>
|
||||
<OperatingSystemContextProvider platform="mac">
|
||||
<LocaleContextProvider
|
||||
locales={window.navigator.languages as string[]}
|
||||
>
|
||||
<RemixStub initialEntries={["/"]} />
|
||||
</LocaleContextProvider>
|
||||
</OperatingSystemContextProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
|
||||
@@ -31,7 +31,8 @@ export function NavBar() {
|
||||
variant="secondary/small"
|
||||
data-attr="posthog-feedback-button"
|
||||
LeadingIcon={ChatBubbleLeftRightIcon}
|
||||
shortcut="F"
|
||||
shortcut={{ key: "f" }}
|
||||
onClick={() => console.log("feedback")}
|
||||
>
|
||||
Send us feedback
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { Link, LinkProps, NavLink, NavLinkProps } from "@remix-run/react";
|
||||
import React from "react";
|
||||
import { ShortcutDefinition } from "~/hooks/useShortcutKeys";
|
||||
import React, {
|
||||
ReactComponentElement,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { NamedIcon } from "./NamedIcon";
|
||||
import { ShortcutKey } from "./ShortcutKey";
|
||||
@@ -238,8 +243,23 @@ type ButtonPropsType = Pick<
|
||||
> &
|
||||
React.ComponentProps<typeof ButtonContent>;
|
||||
|
||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
|
||||
({ type, disabled, onClick, ...props }, ref) => {
|
||||
const innerRef = useRef<HTMLButtonElement>(null);
|
||||
useImperativeHandle(ref, () => innerRef.current as HTMLButtonElement);
|
||||
|
||||
if (props.shortcut) {
|
||||
useShortcutKeys({
|
||||
shortcut: props.shortcut,
|
||||
action: () => {
|
||||
if (innerRef.current) {
|
||||
innerRef.current.click();
|
||||
}
|
||||
},
|
||||
disabled,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
|
||||
@@ -248,7 +268,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
|
||||
onClick={onClick}
|
||||
name={props.name}
|
||||
value={props.value}
|
||||
ref={ref}
|
||||
ref={innerRef}
|
||||
form={props.form}
|
||||
>
|
||||
<ButtonContent {...props} />
|
||||
@@ -260,10 +280,23 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
|
||||
type LinkPropsType = Pick<LinkProps, "to" | "target"> &
|
||||
React.ComponentProps<typeof ButtonContent>;
|
||||
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
||||
const innerRef = useRef<HTMLAnchorElement>(null);
|
||||
if (props.shortcut) {
|
||||
useShortcutKeys({
|
||||
shortcut: props.shortcut,
|
||||
action: () => {
|
||||
if (innerRef.current) {
|
||||
innerRef.current.click();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (to.toString().startsWith("http")) {
|
||||
return (
|
||||
<ExtLink
|
||||
href={to.toString()}
|
||||
ref={innerRef}
|
||||
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
|
||||
>
|
||||
<ButtonContent {...props} />
|
||||
@@ -273,6 +306,7 @@ export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
||||
return (
|
||||
<Link
|
||||
to={to}
|
||||
ref={innerRef}
|
||||
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
|
||||
>
|
||||
<ButtonContent {...props} />
|
||||
@@ -314,16 +348,19 @@ type ExtLinkProps = JSX.IntrinsicElements["a"] & {
|
||||
href: string;
|
||||
};
|
||||
|
||||
function ExtLink({ className, href, children, ...props }: ExtLinkProps) {
|
||||
return (
|
||||
<a
|
||||
className={cn(className)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={href}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const ExtLink = forwardRef<HTMLAnchorElement, ExtLinkProps>(
|
||||
({ className, href, children, ...props }, ref) => {
|
||||
return (
|
||||
<a
|
||||
className={cn(className)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={href}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useContext } from "react";
|
||||
import { createContext } from "react";
|
||||
|
||||
export type OperatingSystemPlatform = "mac" | "windows";
|
||||
|
||||
type OperatingSystemContext = {
|
||||
platform: OperatingSystemPlatform;
|
||||
};
|
||||
|
||||
type OperatingSystemContextProviderProps = {
|
||||
platform: OperatingSystemPlatform;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const Context = createContext<OperatingSystemContext | null>(null);
|
||||
|
||||
export const OperatingSystemContextProvider = ({
|
||||
platform,
|
||||
children,
|
||||
}: OperatingSystemContextProviderProps) => {
|
||||
return <Context.Provider value={{ platform }}>{children}</Context.Provider>;
|
||||
};
|
||||
|
||||
const throwIfNoProvider = () => {
|
||||
throw new Error(
|
||||
"Please wrap your application in an OperatingSystemContextProvider."
|
||||
);
|
||||
};
|
||||
|
||||
export const useOperatingSystem = () => {
|
||||
const { platform } = useContext(Context) ?? throwIfNoProvider();
|
||||
return { platform };
|
||||
};
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } from "class-variance-authority";
|
||||
import { X } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { NamedIcon } from "./NamedIcon";
|
||||
import { ShortcutKey } from "./ShortcutKey";
|
||||
@@ -165,7 +164,7 @@ const SheetContent = React.forwardRef<
|
||||
<NamedIcon name="close" className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
<ShortcutKey shortcut={{ all: { key: "esc" } }} variant="medium" />
|
||||
<ShortcutKey shortcut={{ key: "esc" }} variant="small" />
|
||||
</div>
|
||||
<div className="flex max-h-full flex-col overflow-hidden">
|
||||
{children}
|
||||
@@ -212,4 +211,4 @@ export const SheetFooter = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
export { Sheet, SheetTrigger, SheetContent };
|
||||
export { Sheet, SheetContent, SheetTrigger };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Fragment } from "react";
|
||||
import { useIsMac } from "~/hooks/useIsMac";
|
||||
import { Modifier, ShortcutDefinition } from "~/hooks/useShortcutKeys";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { useOperatingSystem } from "./OperatingSystemProvider";
|
||||
|
||||
const variants = {
|
||||
small:
|
||||
@@ -23,9 +23,10 @@ export function ShortcutKey({
|
||||
variant,
|
||||
className,
|
||||
}: ShortcutKeyProps) {
|
||||
const isMac = useIsMac();
|
||||
const { platform } = useOperatingSystem();
|
||||
const isMac = platform === "mac";
|
||||
let relevantShortcut =
|
||||
"all" in shortcut ? shortcut.all : isMac ? shortcut.mac : shortcut.windows;
|
||||
"mac" in shortcut ? (isMac ? shortcut.mac : shortcut.windows) : shortcut;
|
||||
const modifiers = relevantShortcut.modifiers ?? [];
|
||||
const character = relevantShortcut.key;
|
||||
|
||||
|
||||
@@ -87,19 +87,19 @@ function ButtonList({ primary }: { primary: string }) {
|
||||
<Header3 className="mb-1 uppercase">Shortcut</Header3>
|
||||
<Button
|
||||
variant="primary/small"
|
||||
shortcut={{ all: { key: "s", modifiers: ["meta"] } }}
|
||||
shortcut={{ key: "s", modifiers: ["meta"] }}
|
||||
>
|
||||
Primary button
|
||||
</Button>
|
||||
<Button variant="secondary/small" shortcut={{ all: { key: "f" } }}>
|
||||
<Button variant="secondary/small" shortcut={{ key: "f" }}>
|
||||
Secondary button
|
||||
</Button>
|
||||
<Button variant="tertiary/small" shortcut={{ all: { key: "i" } }}>
|
||||
<Button variant="tertiary/small" shortcut={{ key: "i" }}>
|
||||
Tertiary button
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger/small"
|
||||
shortcut={{ all: { key: "s", modifiers: ["meta"] } }}
|
||||
shortcut={{ key: "s", modifiers: ["meta"] }}
|
||||
>
|
||||
Danger button
|
||||
</Button>
|
||||
@@ -203,25 +203,25 @@ function ButtonList({ primary }: { primary: string }) {
|
||||
<Header3 className="mb-1 uppercase">Shortcut</Header3>
|
||||
<Button
|
||||
variant="primary/medium"
|
||||
shortcut={{ all: { key: "s", modifiers: ["meta"] } }}
|
||||
shortcut={{ key: "s", modifiers: ["meta"] }}
|
||||
>
|
||||
Primary button
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary/medium"
|
||||
shortcut={{ all: { key: "s", modifiers: ["meta"] } }}
|
||||
shortcut={{ key: "s", modifiers: ["meta"] }}
|
||||
>
|
||||
Secondary button
|
||||
</Button>
|
||||
<Button
|
||||
variant="tertiary/medium"
|
||||
shortcut={{ all: { key: "s", modifiers: ["meta"] } }}
|
||||
shortcut={{ key: "s", modifiers: ["meta"] }}
|
||||
>
|
||||
Tertiary button
|
||||
</Button>
|
||||
<Button
|
||||
variant="danger/medium"
|
||||
shortcut={{ all: { key: "s", modifiers: ["meta"] } }}
|
||||
shortcut={{ key: "s", modifiers: ["meta"] }}
|
||||
>
|
||||
Danger button
|
||||
</Button>
|
||||
|
||||
@@ -26,11 +26,7 @@ function ClipboardFieldExample() {
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
variant="tertiary/small"
|
||||
icon={
|
||||
<EnvironmentLabel
|
||||
environment={{ type: "PRODUCTION", slug: "PROD" }}
|
||||
/>
|
||||
}
|
||||
icon={<EnvironmentLabel environment={{ type: "PRODUCTION" }} />}
|
||||
/>
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
@@ -43,11 +39,7 @@ function ClipboardFieldExample() {
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
variant="tertiary/medium"
|
||||
icon={
|
||||
<EnvironmentLabel
|
||||
environment={{ type: "DEVELOPMENT", slug: "DEV" }}
|
||||
/>
|
||||
}
|
||||
icon={<EnvironmentLabel environment={{ type: "DEVELOPMENT" }} />}
|
||||
/>
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
@@ -74,11 +66,7 @@ function ClipboardFieldExample() {
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
variant="tertiary/small"
|
||||
icon={
|
||||
<EnvironmentLabel
|
||||
environment={{ type: "STAGING", slug: "STAGING" }}
|
||||
/>
|
||||
}
|
||||
icon={<EnvironmentLabel environment={{ type: "STAGING" }} />}
|
||||
secure={true}
|
||||
/>
|
||||
<ClipboardField
|
||||
@@ -105,11 +93,7 @@ function ClipboardFieldExample() {
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
variant="tertiary/medium"
|
||||
icon={
|
||||
<EnvironmentLabel
|
||||
environment={{ type: "PRODUCTION", slug: "PROD" }}
|
||||
/>
|
||||
}
|
||||
icon={<EnvironmentLabel environment={{ type: "PRODUCTION" }} />}
|
||||
secure={true}
|
||||
/>
|
||||
<ClipboardField
|
||||
|
||||
@@ -42,7 +42,11 @@ function PageHeaders() {
|
||||
<PageTitleRow>
|
||||
<PageTitle title="Organizations" />
|
||||
<PageButtons>
|
||||
<LinkButton to={""} variant="primary/small" shortcut="N">
|
||||
<LinkButton
|
||||
to={""}
|
||||
variant="primary/small"
|
||||
shortcut={{ key: "n" }}
|
||||
>
|
||||
Create a new Organization
|
||||
</LinkButton>
|
||||
</PageButtons>
|
||||
@@ -61,7 +65,11 @@ function PageHeaders() {
|
||||
backButton={{ to: "#", text: "Orgs" }}
|
||||
/>
|
||||
<PageButtons>
|
||||
<LinkButton to={""} variant="primary/small" shortcut="N">
|
||||
<LinkButton
|
||||
to={""}
|
||||
variant="primary/small"
|
||||
shortcut={{ key: "n" }}
|
||||
>
|
||||
Create a new Organization
|
||||
</LinkButton>
|
||||
</PageButtons>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Switch } from "../primitives/Switch";
|
||||
import { ShortcutKey } from "../primitives/ShortcutKey";
|
||||
import { ShortcutDefinition } from "~/hooks/useShortcutKeys";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { OperatingSystemContextProvider } from "../primitives/OperatingSystemProvider";
|
||||
import { Header1 } from "../primitives/Headers";
|
||||
|
||||
const meta: Meta = {
|
||||
title: "Primitives/ShortcutKey",
|
||||
@@ -17,16 +19,26 @@ export const ShortcutKeys: Story = {
|
||||
};
|
||||
|
||||
const shortcuts: ShortcutDefinition[] = [
|
||||
{ all: { key: "esc" } },
|
||||
{ all: { key: "f" } },
|
||||
{ all: { key: "f", modifiers: ["meta"] } },
|
||||
{ all: { key: "k", modifiers: ["meta"] } },
|
||||
{ all: { key: "del", modifiers: ["alt", "ctrl"] } },
|
||||
{ key: "esc" },
|
||||
{ key: "f" },
|
||||
{ key: "f", modifiers: ["meta"] },
|
||||
{ key: "k", modifiers: ["meta"] },
|
||||
{ key: "del", modifiers: ["alt", "ctrl"] },
|
||||
];
|
||||
|
||||
function Collection() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-y-4">
|
||||
<Set platform="mac" />
|
||||
<Set platform="windows" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Set({ platform }: { platform: "mac" | "windows" }) {
|
||||
return (
|
||||
<OperatingSystemContextProvider platform={platform}>
|
||||
<Header1>{platform}</Header1>
|
||||
{shortcuts.map((shortcut, index) => (
|
||||
<div key={index} className="flex items-center gap-x-2">
|
||||
<ShortcutKey shortcut={shortcut} variant="small" />
|
||||
@@ -57,6 +69,6 @@ function Collection() {
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</OperatingSystemContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,12 +4,17 @@ import * as Sentry from "@sentry/remix";
|
||||
import { useEffect } from "react";
|
||||
import posthog from "posthog-js";
|
||||
import { LocaleContextProvider } from "./components/primitives/LocaleProvider";
|
||||
import { OperatingSystemContextProvider } from "./components/primitives/OperatingSystemProvider";
|
||||
|
||||
hydrateRoot(
|
||||
document,
|
||||
<LocaleContextProvider locales={window.navigator.languages as string[]}>
|
||||
<RemixBrowser />
|
||||
</LocaleContextProvider>
|
||||
<OperatingSystemContextProvider
|
||||
platform={window.navigator.userAgent.includes("Mac") ? "mac" : "windows"}
|
||||
>
|
||||
<LocaleContextProvider locales={window.navigator.languages as string[]}>
|
||||
<RemixBrowser />
|
||||
</LocaleContextProvider>
|
||||
</OperatingSystemContextProvider>
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
|
||||
@@ -8,6 +8,10 @@ import type { EntryContext, Headers } from "@remix-run/node"; // or cloudflare/d
|
||||
import { parseAcceptLanguage } from "intl-parse-accept-language";
|
||||
import isbot from "isbot";
|
||||
import { LocaleContextProvider } from "./components/primitives/LocaleProvider";
|
||||
import {
|
||||
OperatingSystemContextProvider,
|
||||
OperatingSystemPlatform,
|
||||
} from "./components/primitives/OperatingSystemProvider";
|
||||
|
||||
const ABORT_DELAY = 30000;
|
||||
|
||||
@@ -22,6 +26,16 @@ export default function handleRequest(
|
||||
validate: Intl.DateTimeFormat.supportedLocalesOf,
|
||||
});
|
||||
|
||||
//get whether it's a mac or pc from the headers
|
||||
const platform: OperatingSystemPlatform = request.headers
|
||||
.get("user-agent")
|
||||
?.includes("Mac")
|
||||
? "mac"
|
||||
: "windows";
|
||||
|
||||
console.log("User Agent", request.headers.get("user-agent"));
|
||||
console.log("Server Platform", platform);
|
||||
|
||||
// If the request is from a bot, we want to wait for the full
|
||||
// response to render before sending it to the client. This
|
||||
// ensures that bots can see the full page content.
|
||||
@@ -31,7 +45,8 @@ export default function handleRequest(
|
||||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext,
|
||||
locales
|
||||
locales,
|
||||
platform
|
||||
);
|
||||
}
|
||||
|
||||
@@ -40,7 +55,8 @@ export default function handleRequest(
|
||||
responseStatusCode,
|
||||
responseHeaders,
|
||||
remixContext,
|
||||
locales
|
||||
locales,
|
||||
platform
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,17 +65,20 @@ function serveTheBots(
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext,
|
||||
locales: string[]
|
||||
locales: string[],
|
||||
platform: OperatingSystemPlatform
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { pipe, abort } = renderToPipeableStream(
|
||||
<LocaleContextProvider locales={locales}>
|
||||
<RemixServer
|
||||
context={remixContext}
|
||||
url={request.url}
|
||||
abortDelay={ABORT_DELAY}
|
||||
/>
|
||||
</LocaleContextProvider>,
|
||||
<OperatingSystemContextProvider platform={platform}>
|
||||
<LocaleContextProvider locales={locales}>
|
||||
<RemixServer
|
||||
context={remixContext}
|
||||
url={request.url}
|
||||
abortDelay={ABORT_DELAY}
|
||||
/>
|
||||
</LocaleContextProvider>
|
||||
</OperatingSystemContextProvider>,
|
||||
{
|
||||
// Use onAllReady to wait for the entire document to be ready
|
||||
onAllReady() {
|
||||
@@ -87,18 +106,21 @@ function serveBrowsers(
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext,
|
||||
locales: string[]
|
||||
locales: string[],
|
||||
platform: OperatingSystemPlatform
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let didError = false;
|
||||
const { pipe, abort } = renderToPipeableStream(
|
||||
<LocaleContextProvider locales={locales}>
|
||||
<RemixServer
|
||||
context={remixContext}
|
||||
url={request.url}
|
||||
abortDelay={ABORT_DELAY}
|
||||
/>
|
||||
</LocaleContextProvider>,
|
||||
<OperatingSystemContextProvider platform={platform}>
|
||||
<LocaleContextProvider locales={locales}>
|
||||
<RemixServer
|
||||
context={remixContext}
|
||||
url={request.url}
|
||||
abortDelay={ABORT_DELAY}
|
||||
/>
|
||||
</LocaleContextProvider>
|
||||
</OperatingSystemContextProvider>,
|
||||
{
|
||||
// use onShellReady to wait until a suspense boundary is triggered
|
||||
onShellReady() {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useIsMac() {
|
||||
const [isMac, setIsMac] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMac(navigator.platform.includes("Mac"));
|
||||
}, []);
|
||||
|
||||
return isMac;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { useOperatingSystem } from "~/components/primitives/OperatingSystemProvider";
|
||||
|
||||
export type Modifier = "alt" | "ctrl" | "meta" | "shift";
|
||||
|
||||
@@ -12,6 +14,30 @@ export type ShortcutDefinition =
|
||||
windows: Shortcut;
|
||||
mac: Shortcut;
|
||||
}
|
||||
| {
|
||||
all: Shortcut;
|
||||
};
|
||||
| Shortcut;
|
||||
|
||||
type useShortcutKeysProps = {
|
||||
shortcut: ShortcutDefinition;
|
||||
action: (event: KeyboardEvent) => void;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export function useShortcutKeys({
|
||||
shortcut,
|
||||
action,
|
||||
disabled = false,
|
||||
}: useShortcutKeysProps) {
|
||||
const keys = createKeysFromShortcut(shortcut);
|
||||
useHotkeys(keys, action, { enabled: !disabled });
|
||||
}
|
||||
|
||||
function createKeysFromShortcut(shortcut: ShortcutDefinition) {
|
||||
const { platform } = useOperatingSystem();
|
||||
const isMac = platform === "mac";
|
||||
let relevantShortcut =
|
||||
"mac" in shortcut ? (isMac ? shortcut.mac : shortcut.windows) : shortcut;
|
||||
const modifiers = relevantShortcut.modifiers;
|
||||
const character = relevantShortcut.key;
|
||||
|
||||
return modifiers ? modifiers.map((k) => k).join("+") + "+" : "" + character;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export function OrgAdminHeader() {
|
||||
<LinkButton
|
||||
to={newProjectPath(organization)}
|
||||
variant="primary/small"
|
||||
shortcut="N"
|
||||
shortcut={{ key: "n" }}
|
||||
>
|
||||
Create a new project
|
||||
</LinkButton>
|
||||
|
||||
+3
-3
@@ -439,10 +439,10 @@ function RerunPopover({
|
||||
}) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<ButtonContent variant="primary/small" shortcut="R">
|
||||
<PopoverTrigger asChild={true}>
|
||||
<Button variant="primary/small" shortcut={{ key: "R" }}>
|
||||
Rerun Job
|
||||
</ButtonContent>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex w-80 flex-col gap-2 p-4" align="end">
|
||||
<Form method="post">
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ export default function Job() {
|
||||
<LinkButton
|
||||
to={jobTestPath(organization, project, job)}
|
||||
variant="primary/small"
|
||||
shortcut="T"
|
||||
shortcut={{ key: "t" }}
|
||||
>
|
||||
Test
|
||||
</LinkButton>
|
||||
|
||||
Reference in New Issue
Block a user