-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: keyboard shortcut system and ui enhancements (#38)
- Loading branch information
Showing
59 changed files
with
1,442 additions
and
462 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"pinorama-studio": minor | ||
--- | ||
|
||
keyboard shortcut system and ui enhancements |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
packages/pinorama-studio/src/components/clipboard-button/clipboard-button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { CheckIcon, CopyIcon } from "lucide-react" | ||
import { forwardRef, useCallback, useImperativeHandle, useState } from "react" | ||
import { useIntl } from "react-intl" | ||
import { IconButton } from "../icon-button/icon-button" | ||
|
||
type ClipboardButtonProps = { | ||
textToCopy: string | ||
keystroke?: string | ||
} | ||
|
||
export type ImperativeClipboardButtonHandle = { | ||
copyToClipboard: () => void | ||
} | ||
|
||
export const ClipboardButton = forwardRef< | ||
ImperativeClipboardButtonHandle, | ||
ClipboardButtonProps | ||
>(function ClipboardButton(props, ref) { | ||
const intl = useIntl() | ||
const [isCopied, setIsCopied] = useState(false) | ||
|
||
const handleClick = useCallback(async () => { | ||
if (isCopied || !props.textToCopy) { | ||
return | ||
} | ||
|
||
try { | ||
await navigator.clipboard.writeText(props.textToCopy) | ||
setIsCopied(true) | ||
|
||
setTimeout(() => { | ||
setIsCopied(false) | ||
}, 1500) | ||
} catch (err) { | ||
console.error("Failed to copy to clipboard", err) | ||
} | ||
}, [props.textToCopy, isCopied]) | ||
|
||
useImperativeHandle( | ||
ref, | ||
() => ({ | ||
copyToClipboard: handleClick | ||
}), | ||
[handleClick] | ||
) | ||
|
||
return ( | ||
<IconButton | ||
variant="ghost" | ||
keystroke={props.keystroke} | ||
disabled={!props.textToCopy} | ||
icon={isCopied ? CheckIcon : CopyIcon} | ||
aria-label={intl.formatMessage({ id: "labels.copyToClipboard" })} | ||
tooltip={intl.formatMessage({ id: "labels.copyToClipboard" })} | ||
onClick={handleClick} | ||
/> | ||
) | ||
}) |
1 change: 1 addition & 0 deletions
1
packages/pinorama-studio/src/components/clipboard-button/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./clipboard-button" |
57 changes: 57 additions & 0 deletions
57
packages/pinorama-studio/src/components/icon-button/icon-button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { Button } from "@/components/ui/button" | ||
import { | ||
Tooltip, | ||
TooltipContent, | ||
TooltipPortal, | ||
TooltipTrigger | ||
} from "@/components/ui/tooltip" | ||
import { cn } from "@/lib/utils" | ||
import { LoaderIcon, type LucideIcon } from "lucide-react" | ||
import type { ComponentProps } from "react" | ||
import { Kbd } from "../kbd/kbd" | ||
|
||
type IconButtonProps = ComponentProps<typeof Button> & { | ||
icon: LucideIcon | ||
tooltip?: string | ||
loading?: boolean | ||
keystroke?: string | ||
} | ||
|
||
export function IconButton({ | ||
variant = "outline2", | ||
icon, | ||
tooltip, | ||
keystroke, | ||
loading, | ||
...props | ||
}: IconButtonProps) { | ||
const Icon = loading ? LoaderIcon : icon | ||
|
||
const Component = ( | ||
<Button className="px-2.5" variant={variant} disabled={loading} {...props}> | ||
<Icon className={cn("h-[18px] w-[18px]", loading && "animate-spin")} /> | ||
</Button> | ||
) | ||
|
||
return tooltip || keystroke | ||
? withTooltip(Component, tooltip, keystroke) | ||
: Component | ||
} | ||
|
||
function withTooltip( | ||
WrappedComponent: React.ReactNode, | ||
tooltip?: string, | ||
keystroke?: string | ||
) { | ||
return ( | ||
<Tooltip> | ||
<TooltipTrigger asChild>{WrappedComponent}</TooltipTrigger> | ||
<TooltipPortal> | ||
<TooltipContent className="flex space-x-1.5"> | ||
<div>{tooltip}</div> | ||
{keystroke ? <Kbd>{keystroke}</Kbd> : null} | ||
</TooltipContent> | ||
</TooltipPortal> | ||
</Tooltip> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./icon-button" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./kbd" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { cn } from "@/lib/utils" | ||
|
||
export function Kbd({ | ||
children, | ||
className | ||
}: { children: React.ReactNode; className?: string }) { | ||
return ( | ||
<kbd | ||
className={cn( | ||
"h-5 items-center rounded border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground opacity-100", | ||
className | ||
)} | ||
> | ||
{children} | ||
</kbd> | ||
) | ||
} |
18 changes: 15 additions & 3 deletions
18
packages/pinorama-studio/src/components/search-input/search-input.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,40 +1,52 @@ | ||
import { SearchIcon, XIcon } from "lucide-react" | ||
import { forwardRef } from "react" | ||
import { Kbd } from "../kbd" | ||
import { Button } from "../ui/button" | ||
import { Input } from "../ui/input" | ||
|
||
type SearchInputProps = { | ||
value: string | ||
onChange: (text: string) => void | ||
placeholder: string | ||
keystroke?: string | ||
} | ||
|
||
export const SearchInput = forwardRef(function SearchInput( | ||
props: SearchInputProps, | ||
ref: React.Ref<HTMLInputElement> | ||
) { | ||
const hasValue = props.value.length > 0 | ||
|
||
return ( | ||
<div className="relative flex items-center w-full"> | ||
<SearchIcon className="h-4 w-4 absolute left-3 text-muted-foreground" /> | ||
<Input | ||
ref={ref} | ||
type="text" | ||
placeholder={props.placeholder} | ||
className="pl-9" | ||
className="pl-9 pr-16" | ||
value={props.value} | ||
onChange={(e) => props.onChange(e.target.value)} | ||
onKeyDown={(e) => { | ||
if (e.key === "Escape" && e.target instanceof HTMLInputElement) { | ||
e.target.blur() | ||
} | ||
}} | ||
/> | ||
{props.value.length > 0 ? ( | ||
{hasValue ? ( | ||
<Button | ||
variant={"ghost"} | ||
size={"xs"} | ||
aria-label="Clear" | ||
className="absolute right-2" | ||
className="absolute right-8" | ||
onClick={() => props.onChange("")} | ||
> | ||
<XIcon className="h-4 w-4" /> | ||
</Button> | ||
) : null} | ||
{props.keystroke ? ( | ||
<Kbd className="absolute right-2">{props.keystroke}</Kbd> | ||
) : null} | ||
</div> | ||
) | ||
}) |
77 changes: 77 additions & 0 deletions
77
packages/pinorama-studio/src/components/title-bar/components/hotkeys-button.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { Kbd } from "@/components/kbd/kbd" | ||
import { Button } from "@/components/ui/button" | ||
import { | ||
Dialog, | ||
DialogContent, | ||
DialogDescription, | ||
DialogHeader, | ||
DialogTitle, | ||
DialogTrigger | ||
} from "@/components/ui/dialog" | ||
import { | ||
Tooltip, | ||
TooltipContent, | ||
TooltipPortal, | ||
TooltipTrigger | ||
} from "@/components/ui/tooltip" | ||
import { useAllModuleHotkeys } from "@/hooks/use-module-hotkeys" | ||
import { KeyboardIcon } from "lucide-react" | ||
import { FormattedMessage } from "react-intl" | ||
|
||
export function HotkeysButton() { | ||
const hotkeys = useAllModuleHotkeys() | ||
|
||
const handleClick = () => {} | ||
|
||
return ( | ||
<Dialog> | ||
<Tooltip> | ||
<TooltipTrigger> | ||
<DialogTrigger asChild> | ||
<Button | ||
aria-label={"Settings"} | ||
variant={"secondary"} | ||
size={"sm"} | ||
onClick={handleClick} | ||
> | ||
<KeyboardIcon className="h-4 w-4" /> | ||
</Button> | ||
</DialogTrigger> | ||
</TooltipTrigger> | ||
<TooltipPortal> | ||
<TooltipContent> | ||
<FormattedMessage id="labels.keyboardShortcuts" /> | ||
</TooltipContent> | ||
</TooltipPortal> | ||
</Tooltip> | ||
<DialogContent className="w-80"> | ||
<DialogHeader> | ||
<DialogTitle> | ||
<FormattedMessage id="labels.keyboardShortcuts" /> | ||
</DialogTitle> | ||
<DialogDescription> | ||
<FormattedMessage id="labels.allShortcutsSeparatedByModule" /> | ||
</DialogDescription> | ||
</DialogHeader> | ||
{Object.entries(hotkeys).map(([module, hotkeys]) => ( | ||
<div key={module}> | ||
<div className="text-sm font-semibold py-2">{module}</div> | ||
<ul className="space-y-2"> | ||
{hotkeys.map((hotkey) => ( | ||
<li | ||
key={hotkey.keystroke} | ||
className="flex items-center justify-between" | ||
> | ||
<span className="text-muted-foreground text-sm"> | ||
{hotkey.description} | ||
</span> | ||
<Kbd>{hotkey.keystroke}</Kbd> | ||
</li> | ||
))} | ||
</ul> | ||
</div> | ||
))} | ||
</DialogContent> | ||
</Dialog> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.