feat: implement platform-specific keyboard shortcuts and refactor shortcut data management
This commit is contained in:
@@ -6,6 +6,7 @@ import { ChevronsLeft, ChevronsRight, PanelTopClose, PanelTopOpen, Search, X } f
|
|||||||
import type { MenuItemType } from '../types/menu.types';
|
import type { MenuItemType } from '../types/menu.types';
|
||||||
import type { SidebarVariant } from '@repo/ui/components';
|
import type { SidebarVariant } from '@repo/ui/components';
|
||||||
import { useTranslation } from '@repo/core-i18n';
|
import { useTranslation } from '@repo/core-i18n';
|
||||||
|
import { shortcutsData } from '@repo/ui/constants';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Props
|
// Props
|
||||||
@@ -333,13 +334,20 @@ export const SidebarMenu = memo(function SidebarMenu({
|
|||||||
}
|
}
|
||||||
}, [isMini, setSidebarVariant]);
|
}, [isMini, setSidebarVariant]);
|
||||||
|
|
||||||
const [isMac, setIsMac] = useState(true);
|
const [isMac, setIsMac] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsMac(typeof window !== 'undefined' && navigator.userAgent.includes('Mac'));
|
setIsMac(typeof window !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const filterMenuShortcutText = isMac ? '⇧⌘M' : 'Ctrl+Shift+M';
|
const filterMenuShortcutText = useMemo(() => {
|
||||||
const toggleShortcutText = isMac ? '⇧⌘B' : 'Ctrl+Shift+B';
|
const shortcutData = shortcutsData.find((s) => s.key === 'search_menu');
|
||||||
|
return isMac ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' ');
|
||||||
|
}, [isMac]);
|
||||||
|
|
||||||
|
const toggleShortcutText = useMemo(() => {
|
||||||
|
const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar');
|
||||||
|
return isMac ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' ');
|
||||||
|
}, [isMac]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
@@ -442,7 +450,7 @@ export const SidebarMenu = memo(function SidebarMenu({
|
|||||||
radius="md"
|
radius="md"
|
||||||
size="xs"
|
size="xs"
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
rightSectionWidth={searchQuery ? 30 : 48}
|
rightSectionWidth={searchQuery ? 30 : isMac ? 48 : 68}
|
||||||
rightSection={
|
rightSection={
|
||||||
searchQuery ? (
|
searchQuery ? (
|
||||||
<ActionIcon variant="subtle" color="gray" onClick={() => setSearchQuery('')} size="sm">
|
<ActionIcon variant="subtle" color="gray" onClick={() => setSearchQuery('')} size="sm">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useTranslation } from '@repo/core-i18n';
|
import { useTranslation } from '@repo/core-i18n';
|
||||||
import { Group, Kbd, Table, Text, TextInput, Box, Center, ScrollArea } from '@repo/ui/components';
|
import { Group, Kbd, Table, Text, TextInput, Box, Center, ScrollArea } from '@repo/ui/components';
|
||||||
|
import { shortcutsData } from '@repo/ui/constants';
|
||||||
import { Search, Keyboard } from 'lucide-react';
|
import { Search, Keyboard } from 'lucide-react';
|
||||||
import { useEffect, useState, useMemo } from 'react';
|
import { useEffect, useState, useMemo, Fragment } from 'react';
|
||||||
import { shortcuts } from './data';
|
|
||||||
|
|
||||||
export function Shortcut() {
|
export function Shortcut() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -11,15 +11,14 @@ export function Shortcut() {
|
|||||||
|
|
||||||
// OS Detection for rendering appropriate keyboard shortcuts
|
// OS Detection for rendering appropriate keyboard shortcuts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsMac(typeof window !== 'undefined' && navigator.userAgent.includes('Mac'));
|
setIsMac(typeof window !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Filter logika pencarian berdasarkan label ATAU deskripsi yang sudah diterjemahkan
|
|
||||||
const filteredShortcuts = useMemo(() => {
|
const filteredShortcuts = useMemo(() => {
|
||||||
const query = searchQuery.toLowerCase().trim();
|
const query = searchQuery.toLowerCase().trim();
|
||||||
if (!query) return shortcuts;
|
if (!query) return shortcutsData;
|
||||||
|
|
||||||
return shortcuts.filter((item) => {
|
return shortcutsData.filter((item) => {
|
||||||
const labelMatch = t(item.label).toLowerCase().includes(query);
|
const labelMatch = t(item.label).toLowerCase().includes(query);
|
||||||
const descMatch = t(item.desc).toLowerCase().includes(query);
|
const descMatch = t(item.desc).toLowerCase().includes(query);
|
||||||
return labelMatch || descMatch;
|
return labelMatch || descMatch;
|
||||||
@@ -34,23 +33,30 @@ export function Shortcut() {
|
|||||||
<Text size="sm" fw={600} style={{ color: 'var(--mantine-color-text)' }}>
|
<Text size="sm" fw={600} style={{ color: 'var(--mantine-color-text)' }}>
|
||||||
{t(element.label)}
|
{t(element.label)}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="xs" c="dimmed" mt={4} style={{ maxWidth: '600px', lineHeight: 1.4 }}>
|
<Text size="xs" c="dimmed" mt={4} style={{ lineHeight: 1.4 }}>
|
||||||
{t(element.desc)}
|
{t(element.desc)}
|
||||||
</Text>
|
</Text>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td w={250}>
|
<Table.Td w={250}>
|
||||||
<Group gap={6} wrap="nowrap">
|
<Group gap={6} wrap="nowrap" align="center">
|
||||||
{keys.map((key, i) => (
|
{keys.map((key, i) => (
|
||||||
<Kbd key={i} size="md" style={{ fontSize: '12px' }}>
|
<Fragment key={i}>
|
||||||
|
<Kbd size="md" style={{ fontSize: '12px' }}>
|
||||||
{key}
|
{key}
|
||||||
</Kbd>
|
</Kbd>
|
||||||
|
|
||||||
|
{i < keys.length - 1 && (
|
||||||
|
<Text size="sm" c="dimmed" fw={500}>
|
||||||
|
+
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Group>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Box p="md" style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}>
|
<Box p="md" style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}>
|
||||||
@@ -76,8 +82,8 @@ export function Shortcut() {
|
|||||||
>
|
>
|
||||||
<Table.Thead>
|
<Table.Thead>
|
||||||
<Table.Tr>
|
<Table.Tr>
|
||||||
<Table.Th>{t('information:info.table.action')}</Table.Th>
|
<Table.Th>{t('information:info.table.action').toUpperCase()}</Table.Th>
|
||||||
<Table.Th>{t('information:info.table.shortcut')}</Table.Th>
|
<Table.Th>{t('information:info.table.shortcut').toUpperCase()}</Table.Th>
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": "@repo/typescript-config/react-app.json",
|
"extends": "@repo/typescript-config/react-app.json",
|
||||||
"include": ["src"],
|
"include": ["src", "../../packages/ui/src/constant/shortcut.data.ts"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"./hooks": "./src/hooks/index.ts",
|
"./hooks": "./src/hooks/index.ts",
|
||||||
"./provider": "./src/provider/index.ts",
|
"./provider": "./src/provider/index.ts",
|
||||||
"./validators": "./src/validators/index.ts",
|
"./validators": "./src/validators/index.ts",
|
||||||
"./foundations": "./src/foundations/index.ts"
|
"./foundations": "./src/foundations/index.ts",
|
||||||
|
"./constants": "./src/constants/index.ts"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './shortcut.data';
|
||||||
+13
-4
@@ -1,20 +1,29 @@
|
|||||||
export const shortcuts = [
|
export const shortcutsData = [
|
||||||
{
|
{
|
||||||
|
key: 'create_data',
|
||||||
label: 'information:shortcuts.create.label',
|
label: 'information:shortcuts.create.label',
|
||||||
desc: 'information:shortcuts.create.desc',
|
desc: 'information:shortcuts.create.desc',
|
||||||
macKeys: ['⌘', '⇧', 'N'],
|
macKeys: ['Cmd', 'Shift', 'N'],
|
||||||
|
macKeyIcons: ['⌘', '⇧', 'N'],
|
||||||
winKeys: ['Ctrl', 'Shift', 'N'],
|
winKeys: ['Ctrl', 'Shift', 'N'],
|
||||||
|
winKeyIcons: ['Ctrl', '⇧', 'N'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'search_menu',
|
||||||
label: 'information:shortcuts.searchMenu.label',
|
label: 'information:shortcuts.searchMenu.label',
|
||||||
desc: 'information:shortcuts.searchMenu.desc',
|
desc: 'information:shortcuts.searchMenu.desc',
|
||||||
macKeys: ['⌘', '⇧', 'M'],
|
macKeys: ['Cmd', 'Shift', 'M'],
|
||||||
|
macKeyIcons: ['⌘', '⇧', 'M'],
|
||||||
winKeys: ['Ctrl', 'Shift', 'M'],
|
winKeys: ['Ctrl', 'Shift', 'M'],
|
||||||
|
winKeyIcons: ['Ctrl', '⇧', 'M'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'collapse_sidebar',
|
||||||
label: 'information:shortcuts.toggleSidebar.label',
|
label: 'information:shortcuts.toggleSidebar.label',
|
||||||
desc: 'information:shortcuts.toggleSidebar.desc',
|
desc: 'information:shortcuts.toggleSidebar.desc',
|
||||||
macKeys: ['⌘', '⇧', 'B'],
|
macKeys: ['Cmd', 'Shift', 'B'],
|
||||||
|
macKeyIcons: ['⌘', '⇧', 'B'],
|
||||||
winKeys: ['Ctrl', 'Shift', 'B'],
|
winKeys: ['Ctrl', 'Shift', 'B'],
|
||||||
|
winKeyIcons: ['Ctrl', '⇧', 'B'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
useEnterpriseModuleNavigationContext,
|
useEnterpriseModuleNavigationContext,
|
||||||
useEnterpriseModuleTranslationContext,
|
useEnterpriseModuleTranslationContext,
|
||||||
} from '../hooks/use-module.context';
|
} from '../hooks/use-module.context';
|
||||||
|
import { shortcutsData } from '../../../constants';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Helpers
|
// Helpers
|
||||||
@@ -18,9 +19,6 @@ import {
|
|||||||
/** Detect macOS / iOS for displaying platform-specific shortcut labels. */
|
/** Detect macOS / iOS for displaying platform-specific shortcut labels. */
|
||||||
const IS_MAC = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
|
const IS_MAC = typeof navigator !== 'undefined' && /Mac|iPod|iPhone|iPad/.test(navigator.userAgent);
|
||||||
|
|
||||||
/** Platform-aware shortcut label for the Create action. */
|
|
||||||
const CREATE_SHORTCUT_LABEL = IS_MAC ? '⇧⌘N' : 'Ctrl+Shift+N';
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Component
|
// Component
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -48,6 +46,13 @@ export function EnterpriseIndexPageProvider<E extends BaseEntity = BaseEntity>(p
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
// Global keyboard shortcut: Ctrl+Shift+N / ⌘+Shift+N → Create
|
// Global keyboard shortcut: Ctrl+Shift+N / ⌘+Shift+N → Create
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Platform-aware shortcut label for the Create action. */
|
||||||
|
const CREATE_SHORTCUT_LABEL = useMemo(() => {
|
||||||
|
const shortcutData = shortcutsData.find((s) => s.key === 'collapse_sidebar');
|
||||||
|
return IS_MAC ? shortcutData?.macKeyIcons.join(' ') : shortcutData?.winKeyIcons.join(' ');
|
||||||
|
}, [IS_MAC]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
const isShortcut = (e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'n';
|
const isShortcut = (e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === 'n';
|
||||||
|
|||||||
Reference in New Issue
Block a user