Skip to content

Commit

Permalink
feat: kbd and shoutcuts modal
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei committed Sep 12, 2024
1 parent ba1f1f6 commit 4fcacbf
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 44 deletions.
172 changes: 143 additions & 29 deletions src/renderer/src/components/ui/kbd/Kbd.tsx
Original file line number Diff line number Diff line change
@@ -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: {
Expand Down Expand Up @@ -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)
}

switch (key) {
case SharedKeys.space: {
return <i className="i-mingcute-space-line" key={key} />
}
<kbd
className={cn(
"kbd h-5 space-x-1 text-[0.7rem]",

case SharedKeys.backspace: {
return <i className="i-mingcute-delete-back-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)
}

default: {
return (
<span className="capitalize" key={key}>
{key}
</span>
)
switch (key) {
case SharedKeys.space: {
return <MaterialSymbolsSpaceBarRounded key={key} />
}

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>
)
}
66 changes: 66 additions & 0 deletions src/renderer/src/modules/modal/shortcuts.tsx
Original file line number Diff line number Diff line change
@@ -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])
}
16 changes: 5 additions & 11 deletions src/renderer/src/pages/(main)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 2 additions & 4 deletions src/renderer/src/pages/settings/(settings)/shortcuts.tsx
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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}>
Expand Down

0 comments on commit 4fcacbf

Please sign in to comment.