feat: kbd and shoutcuts modal
Signed-off-by: Innei <i@innei.in>
This commit is contained in:
parent
ba1f1f63b6
commit
4fcacbfd66
|
|
@ -1,12 +1,15 @@
|
|||
import { cn, getOS } from "@renderer/lib/utils"
|
||||
import type { FC } from "react"
|
||||
import { Fragment, memo } from "react"
|
||||
import * as React from "react"
|
||||
import { Fragment, memo } from "react"
|
||||
import { isHotkeyPressed } from "react-hotkeys-hook"
|
||||
|
||||
const SharedKeys = {
|
||||
backspace: "⌫",
|
||||
space: "␣",
|
||||
pageup: "PageUp",
|
||||
pagedown: "PageDown",
|
||||
tab: "Tab",
|
||||
}
|
||||
const SpecialKeys = {
|
||||
Windows: {
|
||||
|
|
@ -89,38 +92,149 @@ export const Kbd: FC<{ children: string; className?: string }> = memo(({ childre
|
|||
}, [children])
|
||||
|
||||
return (
|
||||
<kbd
|
||||
className={cn(
|
||||
"kbd h-4 space-x-1 font-mono text-[0.7rem]",
|
||||
|
||||
isKeyPressed ? "" : "border-b-2",
|
||||
className,
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsKeyPressed(true)
|
||||
setTimeout(() => {
|
||||
setIsKeyPressed(false)
|
||||
}, 100)
|
||||
}}
|
||||
>
|
||||
{children.split("+").map((key_) => {
|
||||
let key: string = key_.toLowerCase()
|
||||
for (const [k, v] of Object.entries(specialKeys)) {
|
||||
key = key.replace(k, v)
|
||||
}
|
||||
<kbd
|
||||
className={cn(
|
||||
"kbd h-5 space-x-1 text-[0.7rem]",
|
||||
|
||||
switch (key) {
|
||||
case SharedKeys.space: {
|
||||
return <i className="i-mingcute-space-line" key={key} />
|
||||
isKeyPressed ? "" : "border-b-2",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children.split("+").map((key_) => {
|
||||
let key: string = key_.toLowerCase()
|
||||
for (const [k, v] of Object.entries(specialKeys)) {
|
||||
key = key.replace(k, v)
|
||||
}
|
||||
|
||||
case SharedKeys.backspace: {
|
||||
return <i className="i-mingcute-delete-back-line" key={key} />
|
||||
}
|
||||
switch (key) {
|
||||
case SharedKeys.space: {
|
||||
return <MaterialSymbolsSpaceBarRounded key={key} />
|
||||
}
|
||||
|
||||
default: {
|
||||
return (
|
||||
<span className="capitalize" key={key}>
|
||||
{key}
|
||||
</span>
|
||||
)
|
||||
case SharedKeys.backspace: {
|
||||
return <MaterialSymbolsBackspace key={key} />
|
||||
}
|
||||
case SpecialKeys.macOS.meta: {
|
||||
return <MaterialSymbolsKeyboardCommandKey key={key} />
|
||||
}
|
||||
case SpecialKeys.macOS.alt: {
|
||||
return <MaterialSymbolsKeyboardOptionKey key={key} />
|
||||
}
|
||||
|
||||
case SpecialKeys.macOS.ctrl: {
|
||||
return <MaterialSymbolsKeyboardControlKey key={key} />
|
||||
}
|
||||
|
||||
case SpecialKeys.macOS.shift: {
|
||||
return <MaterialSymbolsShiftOutlineRounded key={key} />
|
||||
}
|
||||
|
||||
case SharedKeys.tab: {
|
||||
return <MaterialSymbolsKeyboardTabRounded key={key} />
|
||||
}
|
||||
case SpecialKeys.Windows.meta: {
|
||||
return <MaterialSymbolsWindowOutlineSharp key={key} />
|
||||
}
|
||||
default: {
|
||||
return (
|
||||
<span className="capitalize" key={key}>
|
||||
{key}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})}
|
||||
</kbd>
|
||||
})}
|
||||
</kbd>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
function MaterialSymbolsKeyboardCommandKey(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M6.5 21q-1.45 0-2.475-1.025T3 17.5t1.025-2.475T6.5 14H8v-4H6.5q-1.45 0-2.475-1.025T3 6.5t1.025-2.475T6.5 3t2.475 1.025T10 6.5V8h4V6.5q0-1.45 1.025-2.475T17.5 3t2.475 1.025T21 6.5t-1.025 2.475T17.5 10H16v4h1.5q1.45 0 2.475 1.025T21 17.5t-1.025 2.475T17.5 21t-2.475-1.025T14 17.5V16h-4v1.5q0 1.45-1.025 2.475T6.5 21m0-2q.625 0 1.063-.437T8 17.5V16H6.5q-.625 0-1.062.438T5 17.5t.438 1.063T6.5 19m11 0q.625 0 1.063-.437T19 17.5t-.437-1.062T17.5 16H16v1.5q0 .625.438 1.063T17.5 19M10 14h4v-4h-4zM6.5 8H8V6.5q0-.625-.437-1.062T6.5 5t-1.062.438T5 6.5t.438 1.063T6.5 8M16 8h1.5q.625 0 1.063-.437T19 6.5t-.437-1.062T17.5 5t-1.062.438T16 6.5z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MaterialSymbolsKeyboardOptionKey(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path fill="currentColor" d="M14.775 19L7.85 7H3V5h6l6.925 12H21v2zM15 7V5h6v2z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MaterialSymbolsKeyboardControlKey(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path fill="currentColor" d="M6.4 13.4L5 12l7-7l7 7l-1.4 1.4L12 7.825z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MaterialSymbolsShiftOutlineRounded(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M8 20v-7H5.1q-.65 0-.912-.562t.137-1.063l6.9-8.425q.3-.375.775-.375t.775.375l6.9 8.425q.4.5.138 1.063T18.9 13H16v7q0 .425-.288.713T15 21H9q-.425 0-.712-.288T8 20m2-1h4v-8h2.775L12 5.15L7.225 11H10zm2-8"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MaterialSymbolsKeyboardTabRounded(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M21 18q-.425 0-.712-.288T20 17V7q0-.425.288-.712T21 6t.713.288T22 7v10q0 .425-.288.713T21 18m-6.825-5H3q-.425 0-.712-.288T2 12t.288-.712T3 11h11.175L11.3 8.1q-.275-.275-.288-.687T11.3 6.7q.275-.275.7-.275t.7.275l4.6 4.6q.15.15.213.325t.062.375t-.062.375t-.213.325l-4.6 4.6q-.275.275-.687.275T11.3 17.3q-.3-.3-.3-.712t.3-.713z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MaterialSymbolsSpaceBarRounded(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M6 15q-.825 0-1.412-.587T4 13v-3q0-.425.288-.712T5 9t.713.288T6 10v3h12v-3q0-.425.288-.712T19 9t.713.288T20 10v3q0 .825-.587 1.413T18 15z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
function MaterialSymbolsBackspace(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M7.95 19L3 12l4.95-7H21v14zm3.45-3l2.6-2.6l2.6 2.6l1.4-1.4l-2.6-2.6L18 9.4L16.6 8L14 10.6L11.4 8L10 9.4l2.6 2.6l-2.6 2.6z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MaterialSymbolsWindowOutlineSharp(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M21 21H3V3h18zm-8-8v6h6v-6zm0-2h6V5h-6zm-2 0V5H5v6zm0 2H5v6h6z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
import { MotionButtonBase } from "@renderer/components/ui/button"
|
||||
import { KbdCombined } from "@renderer/components/ui/kbd/Kbd"
|
||||
import { useCurrentModal, useModalStack } from "@renderer/components/ui/modal"
|
||||
import { NoopChildren } from "@renderer/components/ui/modal/stacked/utils"
|
||||
import { ScrollArea } from "@renderer/components/ui/scroll-area"
|
||||
import { shortcuts } from "@renderer/constants/shortcuts"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { m } from "framer-motion"
|
||||
import { useCallback } from "react"
|
||||
|
||||
const ShortcutModalContent = () => {
|
||||
const { dismiss } = useCurrentModal()
|
||||
return (
|
||||
<m.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="center absolute inset-0 m-auto flex max-h-[80vh] w-[60ch] max-w-[90vw] flex-col rounded-md border bg-theme-modal-background-opaque"
|
||||
>
|
||||
<h2 className="mb-2 mt-6 font-medium">Shortcuts Guideline</h2>
|
||||
<MotionButtonBase onClick={dismiss} className="absolute right-3 top-5 p-2">
|
||||
<i className="i-mgc-close-cute-re" />
|
||||
</MotionButtonBase>
|
||||
<ScrollArea.ScrollArea scrollbarClassName="w-2" rootClassName="w-full h-full">
|
||||
<div className="w-full space-y-6 px-4 pb-5">
|
||||
{Object.keys(shortcuts).map((type) => (
|
||||
<section key={type}>
|
||||
<div className="mb-2 text-base font-medium capitalize">{type}</div>
|
||||
<div className="rounded-md border text-[13px] text-zinc-600 dark:text-zinc-300">
|
||||
{Object.keys(shortcuts[type]).map((action, index) => (
|
||||
<div
|
||||
key={`${type}-${action}`}
|
||||
className={cn(
|
||||
"flex items-center justify-between px-3 py-1.5",
|
||||
index % 2 && "bg-native/40",
|
||||
)}
|
||||
>
|
||||
<div>{shortcuts[type][action].name}</div>
|
||||
<div>
|
||||
<KbdCombined joint>
|
||||
{`${shortcuts[type][action].key}${shortcuts[type][action].extra ? `, ${shortcuts[type][action].extra}` : ""}`}
|
||||
</KbdCombined>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea.ScrollArea>
|
||||
</m.div>
|
||||
)
|
||||
}
|
||||
|
||||
export const useShortcutsModal = () => {
|
||||
const { present } = useModalStack()
|
||||
|
||||
return useCallback(() => {
|
||||
present({
|
||||
title: "Shortcuts",
|
||||
content: () => <ShortcutModalContent />,
|
||||
CustomModalComponent: NoopChildren,
|
||||
clickOutsideToDismiss: true,
|
||||
})
|
||||
}, [present])
|
||||
}
|
||||
|
|
@ -21,10 +21,10 @@ import { LoginModalContent } from "@renderer/modules/auth/LoginModalContent"
|
|||
import { FeedColumn } from "@renderer/modules/feed-column"
|
||||
import { AutoUpdater } from "@renderer/modules/feed-column/auto-updater"
|
||||
import { CornerPlayer } from "@renderer/modules/feed-column/corner-player"
|
||||
import { useShortcutsModal } from "@renderer/modules/modal/shortcuts"
|
||||
import { CmdF } from "@renderer/modules/panel/cmdf"
|
||||
import { SearchCmdK } from "@renderer/modules/panel/cmdk"
|
||||
import { CmdNTrigger } from "@renderer/modules/panel/cmdn"
|
||||
import { useSettingModal } from "@renderer/modules/settings/modal/hooks"
|
||||
import { throttle } from "lodash-es"
|
||||
import type { PropsWithChildren } from "react"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
|
|
@ -169,16 +169,10 @@ const FeedResponsiveResizerContainer = ({
|
|||
scopes: HotKeyScopeMap.Home,
|
||||
},
|
||||
)
|
||||
const settingModalPresent = useSettingModal()
|
||||
useHotkeys(
|
||||
shortcuts.layout.showShortcuts.key,
|
||||
() => {
|
||||
settingModalPresent("shortcuts")
|
||||
},
|
||||
{
|
||||
scopes: HotKeyScopeMap.Home,
|
||||
},
|
||||
)
|
||||
const showShortcuts = useShortcutsModal()
|
||||
useHotkeys(shortcuts.layout.showShortcuts.key, showShortcuts, {
|
||||
scopes: HotKeyScopeMap.Home,
|
||||
})
|
||||
|
||||
const [delayShowSplitter, setDelayShowSplitter] = useState(feedColumnShow)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Kbd, KbdCombined } from "@renderer/components/ui/kbd/Kbd"
|
||||
import { KbdCombined } from "@renderer/components/ui/kbd/Kbd"
|
||||
import { shortcuts } from "@renderer/constants/shortcuts"
|
||||
import { cn } from "@renderer/lib/utils"
|
||||
import { SettingsTitle } from "@renderer/modules/settings/title"
|
||||
|
|
@ -17,9 +17,7 @@ export function Component() {
|
|||
return (
|
||||
<>
|
||||
<SettingsTitle />
|
||||
<p className="text-sm">
|
||||
Press <Kbd>H</Kbd> to show this Shortcuts modal.
|
||||
</p>
|
||||
|
||||
<div className="mt-4 space-y-6">
|
||||
{Object.keys(shortcuts).map((type) => (
|
||||
<section key={type}>
|
||||
|
|
|
|||
Loading…
Reference in New Issue