mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-26 19:31:12 +01:00
Add configurable hotkeys support (#343)
This commit is contained in:
@@ -34,7 +34,7 @@ import { jotaiStore } from '../../lib/jotai';
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
import { Overlay } from '../Overlay';
|
||||
import { Button } from './Button';
|
||||
import { HotKey } from './HotKey';
|
||||
import { Hotkey } from './Hotkey';
|
||||
import { Icon } from './Icon';
|
||||
import { LoadingIcon } from './LoadingIcon';
|
||||
import { Separator } from './Separator';
|
||||
@@ -630,7 +630,7 @@ function MenuItem({ className, focused, onFocus, item, onSelect, ...props }: Men
|
||||
[focused],
|
||||
);
|
||||
|
||||
const rightSlot = item.rightSlot ?? <HotKey action={item.hotKeyAction ?? null} />;
|
||||
const rightSlot = item.rightSlot ?? <Hotkey action={item.hotKeyAction ?? null} />;
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
||||
@@ -9,23 +9,34 @@ interface Props {
|
||||
variant?: 'text' | 'with-bg';
|
||||
}
|
||||
|
||||
export function HotKey({ action, className, variant }: Props) {
|
||||
export function Hotkey({ action, className, variant }: Props) {
|
||||
const labelParts = useFormattedHotkey(action);
|
||||
if (labelParts === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <HotkeyRaw labelParts={labelParts} className={className} variant={variant} />;
|
||||
}
|
||||
|
||||
interface HotkeyRawProps {
|
||||
labelParts: string[];
|
||||
className?: string;
|
||||
variant?: 'text' | 'with-bg';
|
||||
}
|
||||
|
||||
export function HotkeyRaw({ labelParts, className, variant }: HotkeyRawProps) {
|
||||
return (
|
||||
<HStack
|
||||
className={classNames(
|
||||
className,
|
||||
variant === 'with-bg' && 'rounded border',
|
||||
'text-text-subtlest',
|
||||
variant === 'with-bg' &&
|
||||
'rounded bg-surface-highlight px-1 border border-border text-text-subtle',
|
||||
variant === 'text' && 'text-text-subtlest',
|
||||
)}
|
||||
>
|
||||
{labelParts.map((char, index) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: none
|
||||
<div key={index} className="min-w-[1.1em] text-center">
|
||||
<div key={index} className="min-w-[1em] text-center">
|
||||
{char}
|
||||
</div>
|
||||
))}
|
||||
@@ -1,14 +1,14 @@
|
||||
import classNames from 'classnames';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { useHotKeyLabel } from '../../hooks/useHotKey';
|
||||
import { useHotkeyLabel } from '../../hooks/useHotKey';
|
||||
|
||||
interface Props {
|
||||
action: HotkeyAction;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function HotKeyLabel({ action, className }: Props) {
|
||||
const label = useHotKeyLabel(action);
|
||||
export function HotkeyLabel({ action, className }: Props) {
|
||||
const label = useHotkeyLabel(action);
|
||||
return (
|
||||
<span className={classNames(className, 'text-text-subtle whitespace-nowrap')}>{label}</span>
|
||||
);
|
||||
@@ -2,8 +2,8 @@ import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Fragment } from 'react';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { HotKey } from './HotKey';
|
||||
import { HotKeyLabel } from './HotKeyLabel';
|
||||
import { Hotkey } from './Hotkey';
|
||||
import { HotkeyLabel } from './HotkeyLabel';
|
||||
|
||||
interface Props {
|
||||
hotkeys: HotkeyAction[];
|
||||
@@ -11,14 +11,14 @@ interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const HotKeyList = ({ hotkeys, bottomSlot, className }: Props) => {
|
||||
export const HotkeyList = ({ hotkeys, bottomSlot, className }: Props) => {
|
||||
return (
|
||||
<div className={classNames(className, 'h-full flex items-center justify-center')}>
|
||||
<div className="grid gap-2 grid-cols-[auto_auto]">
|
||||
{hotkeys.map((hotkey) => (
|
||||
<Fragment key={hotkey}>
|
||||
<HotKeyLabel className="truncate" action={hotkey} />
|
||||
<HotKey className="ml-4" action={hotkey} />
|
||||
<HotkeyLabel className="truncate" action={hotkey} />
|
||||
<Hotkey className="ml-4" action={hotkey} />
|
||||
</Fragment>
|
||||
))}
|
||||
{bottomSlot}
|
||||
@@ -127,6 +127,7 @@ import {
|
||||
UploadIcon,
|
||||
VariableIcon,
|
||||
Wand2Icon,
|
||||
WifiIcon,
|
||||
WrenchIcon,
|
||||
XIcon,
|
||||
} from 'lucide-react';
|
||||
@@ -260,6 +261,7 @@ const icons = {
|
||||
update: RefreshCcwIcon,
|
||||
upload: UploadIcon,
|
||||
variable: VariableIcon,
|
||||
wifi: WifiIcon,
|
||||
wrench: WrenchIcon,
|
||||
x: XIcon,
|
||||
_unknown: ShieldAlertIcon,
|
||||
|
||||
@@ -51,12 +51,21 @@ export function TableRow({ children }: { children: ReactNode }) {
|
||||
return <tr>{children}</tr>;
|
||||
}
|
||||
|
||||
export function TableCell({ children, className }: { children: ReactNode; className?: string }) {
|
||||
export function TableCell({
|
||||
children,
|
||||
className,
|
||||
align = 'left',
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
}) {
|
||||
return (
|
||||
<td
|
||||
className={classNames(
|
||||
className,
|
||||
'py-2 [&:not(:first-child)]:pl-4 text-left whitespace-nowrap',
|
||||
'py-2 [&:not(:first-child)]:pl-4 whitespace-nowrap',
|
||||
align === 'left' ? 'text-left' : align === 'center' ? 'text-center' : 'text-right',
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -13,11 +13,13 @@ export type TabItem =
|
||||
value: string;
|
||||
label: string;
|
||||
hidden?: boolean;
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
}
|
||||
| {
|
||||
value: string;
|
||||
options: Omit<RadioDropdownProps, 'children'>;
|
||||
leftSlot?: ReactNode;
|
||||
rightSlot?: ReactNode;
|
||||
};
|
||||
|
||||
@@ -95,7 +97,7 @@ export function Tabs({
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
layout === 'horizontal' && 'flex flex-col gap-1 w-full pb-3 mb-auto',
|
||||
layout === 'horizontal' && 'flex flex-col w-full pb-3 mb-auto',
|
||||
layout === 'vertical' && 'flex flex-row flex-shrink-0 gap-2 w-full',
|
||||
)}
|
||||
>
|
||||
@@ -107,7 +109,6 @@ export function Tabs({
|
||||
const isActive = t.value === value;
|
||||
|
||||
const btnProps: Partial<ButtonProps> = {
|
||||
size: 'sm',
|
||||
color: 'custom',
|
||||
justify: layout === 'horizontal' ? 'start' : 'center',
|
||||
onClick: isActive ? undefined : () => onChangeValue(t.value),
|
||||
@@ -142,6 +143,7 @@ export function Tabs({
|
||||
onChange={t.options.onChange}
|
||||
>
|
||||
<Button
|
||||
leftSlot={t.leftSlot}
|
||||
rightSlot={
|
||||
<div className="flex items-center">
|
||||
{t.rightSlot}
|
||||
@@ -165,7 +167,7 @@ export function Tabs({
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button key={t.value} rightSlot={t.rightSlot} {...btnProps}>
|
||||
<Button key={t.value} leftSlot={t.leftSlot} rightSlot={t.rightSlot} {...btnProps}>
|
||||
{t.label}
|
||||
</Button>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user