mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
feat: redesign legacy fullscreen tool catalog
This commit is contained in:
parent
7a13d42116
commit
0740ad946c
@ -1963,10 +1963,14 @@ viewer.totalPages=Total Pages
|
||||
toolPanel.modeToggle.sidebar=Switch to advanced sidebar
|
||||
toolPanel.modeToggle.fullscreen=Switch to legacy fullscreen
|
||||
toolPanel.overlay.title=All tools
|
||||
toolPanel.overlay.subtitle=Browse and launch tools in the legacy fullscreen catalog.
|
||||
toolPanel.overlay.subtitle=Browse every tool in the legacy fullscreen catalog.
|
||||
toolPanel.overlay.close=Close
|
||||
toolPanel.overlay.totalLabel_one={{count}} tool available
|
||||
toolPanel.overlay.totalLabel_other={{count}} tools available
|
||||
toolPanel.overlay.layoutLabel=Layout
|
||||
toolPanel.overlay.layoutCompact=Compact grid
|
||||
toolPanel.overlay.layoutDetailed=Detailed cards
|
||||
toolPanel.overlay.matchedSynonym=Matches "{{text}}"
|
||||
toolPanel.overlayHint=Select a tool to open it in the workspace.
|
||||
toolPanel.modePrompt.title=Choose your tools view
|
||||
toolPanel.modePrompt.description=Preview both layouts and choose how you want to explore Stirling PDF tools.
|
||||
|
||||
@ -17,38 +17,66 @@
|
||||
|
||||
.tool-panel-overlay--closing {
|
||||
opacity: 0;
|
||||
transform: translateX(-10%);
|
||||
transform: translateX(-6%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__paper {
|
||||
flex: 1;
|
||||
max-width: 72rem;
|
||||
max-width: 96rem;
|
||||
margin: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-background);
|
||||
border-radius: 1.25rem;
|
||||
border-radius: 1.5rem;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-overlay, 0 24px 64px rgba(15, 23, 42, 0.32));
|
||||
}
|
||||
|
||||
.tool-panel-overlay__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
padding: 1.75rem;
|
||||
gap: 1rem;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
background: var(--bg-elevated);
|
||||
}
|
||||
|
||||
.tool-panel-overlay__search {
|
||||
padding: 1rem 1.5rem;
|
||||
display: flex;
|
||||
gap: 1.25rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.25rem 1.75rem;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
background: var(--bg-toolbar);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__search-input {
|
||||
flex: 1 1 22rem;
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__search-input .search-input-container {
|
||||
width: min(32rem, 100%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__search-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__layout-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__layout-toggle .mantine-SegmentedControl-control {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__body {
|
||||
@ -61,21 +89,160 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__results,
|
||||
.tool-panel-overlay__picker {
|
||||
padding: 1.5rem;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__picker {
|
||||
.tool-panel-overlay__content {
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--bg-toolbar);
|
||||
border-radius: 1rem;
|
||||
gap: 2.5rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__picker .tool-picker-scrollable {
|
||||
padding-bottom: 2rem;
|
||||
.tool-panel-overlay__section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__section-header {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.5rem;
|
||||
color: var(--text-strong);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__grid {
|
||||
display: grid;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__grid--compact {
|
||||
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
|
||||
}
|
||||
|
||||
.tool-panel-overlay__grid--detailed {
|
||||
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-link,
|
||||
.tool-panel-overlay__tile-button {
|
||||
all: unset;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-button[aria-disabled="true"],
|
||||
.tool-panel-overlay__tile-link[aria-disabled="true"] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 1.25rem;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
color: var(--text-primary);
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile[data-variant="compact"] {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
min-height: 10.5rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile[data-variant="detailed"] {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
min-height: 8.75rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile[data-selected="true"] {
|
||||
border-color: var(--accent-primary, var(--mantine-color-pink-6));
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 25%, transparent);
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile[data-disabled="true"] {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile:hover:not([data-disabled="true"]) {
|
||||
transform: translateY(-4px);
|
||||
border-color: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 35%, var(--border-subtle));
|
||||
box-shadow: 0 16px 32px rgba(15, 23, 42, 0.18);
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 0.9rem;
|
||||
background: color-mix(in srgb, var(--accent-primary, var(--mantine-color-pink-6)) 8%, transparent);
|
||||
color: var(--tools-text-and-icon-color);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-icon svg,
|
||||
.tool-panel-overlay__tile-icon span {
|
||||
font-size: 1.9rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-name {
|
||||
font-size: 1rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-description {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-match {
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__tile-hotkey {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--mantine-color-dimmed);
|
||||
}
|
||||
|
||||
.tool-panel-overlay__empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 12rem;
|
||||
border: 1px dashed var(--border-subtle);
|
||||
border-radius: 1.25rem;
|
||||
background: rgba(148, 163, 184, 0.04);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.tool-panel-overlay__paper {
|
||||
margin: 1.5rem;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
@ -83,4 +250,19 @@
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.tool-panel-overlay__header,
|
||||
.tool-panel-overlay__search {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.tool-panel-overlay__grid--compact {
|
||||
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||
}
|
||||
|
||||
.tool-panel-overlay__grid--detailed {
|
||||
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,34 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ActionIcon, Badge, Group, Paper, ScrollArea, Text, Tooltip } from '@mantine/core';
|
||||
import { ActionIcon, Badge, Group, Paper, ScrollArea, SegmentedControl, Text, Tooltip } from '@mantine/core';
|
||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||
import ViewSidebarRoundedIcon from '@mui/icons-material/ViewSidebarRounded';
|
||||
import DashboardCustomizeRoundedIcon from '@mui/icons-material/DashboardCustomizeRounded';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolWorkflow } from '../../contexts/ToolWorkflowContext';
|
||||
import ToolSearch from './toolPicker/ToolSearch';
|
||||
import ToolPicker from './ToolPicker';
|
||||
import SearchResults from './SearchResults';
|
||||
import { ToolId } from '../../types/toolId';
|
||||
import { useToolSections } from '../../hooks/useToolSections';
|
||||
import NoToolsFound from './shared/NoToolsFound';
|
||||
import ToolPanelOverlayTile from './ToolPanelOverlayTile';
|
||||
import { getSubcategoryLabel } from '../../data/toolsTaxonomy';
|
||||
import './ToolPanelOverlay.css';
|
||||
|
||||
type LayoutVariant = 'compact' | 'detailed';
|
||||
|
||||
interface ToolPanelOverlayProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
const EXIT_ANIMATION_MS = 320;
|
||||
const LAYOUT_STORAGE_KEY = 'toolPanelOverlayLayout';
|
||||
|
||||
const getInitialLayout = (): LayoutVariant => {
|
||||
if (typeof window === 'undefined') {
|
||||
return 'compact';
|
||||
}
|
||||
const stored = window.localStorage.getItem(LAYOUT_STORAGE_KEY);
|
||||
return stored === 'detailed' ? 'detailed' : 'compact';
|
||||
};
|
||||
|
||||
export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
|
||||
const { t } = useTranslation();
|
||||
@ -33,6 +46,14 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
|
||||
|
||||
const [shouldRender, setShouldRender] = useState(isOpen);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [layout, setLayout] = useState<LayoutVariant>(getInitialLayout);
|
||||
|
||||
const { sections, searchGroups } = useToolSections(filteredTools, searchQuery);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.localStorage.setItem(LAYOUT_STORAGE_KEY, layout);
|
||||
}, [layout]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@ -69,6 +90,24 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
|
||||
const showSearchResults = useMemo(() => searchQuery.trim().length > 0, [searchQuery]);
|
||||
const totalToolCount = showSearchResults ? filteredTools.length : Object.keys(toolRegistry).length;
|
||||
|
||||
const matchedTextMap = useMemo(() => {
|
||||
const map = new Map<string, string>();
|
||||
filteredTools.forEach(({ item: [id], matchedText }) => {
|
||||
if (matchedText) {
|
||||
map.set(id, matchedText);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}, [filteredTools]);
|
||||
|
||||
const subcategoryGroups = useMemo(() => {
|
||||
if (showSearchResults) {
|
||||
return searchGroups;
|
||||
}
|
||||
const allSection = sections.find(section => section.key === 'all');
|
||||
return allSection ? allSection.subcategories : [];
|
||||
}, [searchGroups, sections, showSearchResults]);
|
||||
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
}
|
||||
@ -82,6 +121,8 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
|
||||
? t('toolPanel.modeToggle.sidebar', 'Switch to advanced sidebar')
|
||||
: t('toolPanel.modeToggle.fullscreen', 'Switch to legacy fullscreen');
|
||||
|
||||
const layoutLabel = t('toolPanel.overlay.layoutLabel', 'Layout');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`tool-panel-overlay ${isClosing || !isOpen ? 'tool-panel-overlay--closing' : 'tool-panel-overlay--open'}`}
|
||||
@ -96,7 +137,7 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
|
||||
{t('toolPanel.overlay.title', 'All tools')}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
{t('toolPanel.overlay.subtitle', 'Browse and launch tools in the legacy fullscreen catalog.')}
|
||||
{t('toolPanel.overlay.subtitle', 'Browse every tool in the legacy fullscreen catalog.')}
|
||||
</Text>
|
||||
</div>
|
||||
<Group gap="xs">
|
||||
@ -130,44 +171,69 @@ export default function ToolPanelOverlay({ isOpen }: ToolPanelOverlayProps) {
|
||||
</header>
|
||||
|
||||
<div className="tool-panel-overlay__search">
|
||||
<Group justify="space-between" align="center">
|
||||
<div className="tool-panel-overlay__search-input">
|
||||
<ToolSearch
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
toolRegistry={toolRegistry}
|
||||
mode="filter"
|
||||
autoFocus
|
||||
<div className="tool-panel-overlay__search-input">
|
||||
<ToolSearch
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
toolRegistry={toolRegistry}
|
||||
mode="filter"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="tool-panel-overlay__search-controls">
|
||||
<div className="tool-panel-overlay__layout-toggle">
|
||||
<SegmentedControl
|
||||
value={layout}
|
||||
onChange={value => setLayout(value as LayoutVariant)}
|
||||
size="sm"
|
||||
aria-label={layoutLabel}
|
||||
data={[
|
||||
{ label: t('toolPanel.overlay.layoutCompact', 'Compact grid'), value: 'compact' },
|
||||
{ label: t('toolPanel.overlay.layoutDetailed', 'Detailed cards'), value: 'detailed' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<Badge variant="light" size="lg" radius="sm">
|
||||
{t('toolPanel.overlay.totalLabel', '{{count}} tools available', {
|
||||
{t('toolPanel.overlay.totalLabel', {
|
||||
count: totalToolCount,
|
||||
defaultValue: '{{count}} tools available',
|
||||
})}
|
||||
</Badge>
|
||||
</Group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="tool-panel-overlay__body">
|
||||
<ScrollArea className="tool-panel-overlay__scroll" type="always">
|
||||
{showSearchResults ? (
|
||||
<div className="tool-panel-overlay__results">
|
||||
<SearchResults
|
||||
filteredTools={filteredTools}
|
||||
onSelect={(id) => handleToolSelect(id as ToolId)}
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="tool-panel-overlay__picker">
|
||||
<ToolPicker
|
||||
selectedToolKey={selectedToolKey}
|
||||
onSelect={(id) => handleToolSelect(id as ToolId)}
|
||||
filteredTools={filteredTools}
|
||||
isSearching={showSearchResults}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="tool-panel-overlay__content">
|
||||
{subcategoryGroups.length === 0 ? (
|
||||
<div className="tool-panel-overlay__empty">
|
||||
<NoToolsFound />
|
||||
</div>
|
||||
) : (
|
||||
subcategoryGroups.map(group => (
|
||||
<section key={group.subcategoryId} className="tool-panel-overlay__section">
|
||||
<header className="tool-panel-overlay__section-header">
|
||||
<Text fw={600} size="sm">
|
||||
{getSubcategoryLabel(t, group.subcategoryId)}
|
||||
</Text>
|
||||
</header>
|
||||
<div className={`tool-panel-overlay__grid tool-panel-overlay__grid--${layout}`}>
|
||||
{group.tools.map(({ id, tool }) => (
|
||||
<ToolPanelOverlayTile
|
||||
key={id}
|
||||
id={id}
|
||||
tool={tool}
|
||||
layout={layout}
|
||||
onSelect={toolId => handleToolSelect(toolId as ToolId)}
|
||||
isSelected={selectedToolKey === id}
|
||||
matchedSynonym={matchedTextMap.get(id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
176
frontend/src/components/tools/ToolPanelOverlayTile.tsx
Normal file
176
frontend/src/components/tools/ToolPanelOverlayTile.tsx
Normal file
@ -0,0 +1,176 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolRegistryEntry } from '../../data/toolsTaxonomy';
|
||||
import { Tooltip } from '../shared/Tooltip';
|
||||
import { useToolNavigation } from '../../hooks/useToolNavigation';
|
||||
import { handleUnlessSpecialClick } from '../../utils/clickHandlers';
|
||||
import { useHotkeys } from '../../contexts/HotkeyContext';
|
||||
import HotkeyDisplay from '../hotkeys/HotkeyDisplay';
|
||||
|
||||
interface ToolPanelOverlayTileProps {
|
||||
id: string;
|
||||
tool: ToolRegistryEntry;
|
||||
layout: 'compact' | 'detailed';
|
||||
onSelect: (id: string) => void;
|
||||
isSelected: boolean;
|
||||
matchedSynonym?: string;
|
||||
}
|
||||
|
||||
const ToolPanelOverlayTile: React.FC<ToolPanelOverlayTileProps> = ({
|
||||
id,
|
||||
tool,
|
||||
layout,
|
||||
onSelect,
|
||||
isSelected,
|
||||
matchedSynonym,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { getToolNavigation } = useToolNavigation();
|
||||
const { hotkeys } = useHotkeys();
|
||||
|
||||
const isUnavailable = !tool.component && !tool.link && id !== 'read' && id !== 'multiTool';
|
||||
const binding = hotkeys[id];
|
||||
|
||||
const navProps = !isUnavailable && !tool.link ? getToolNavigation(id, tool) : null;
|
||||
|
||||
const tooltipContent = useMemo(() => {
|
||||
if (layout !== 'compact') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.35rem' }}>
|
||||
<span>{tool.description}</span>
|
||||
{binding && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', fontSize: '0.75rem' }}>
|
||||
<span style={{ color: 'var(--mantine-color-dimmed)', fontWeight: 500 }}>
|
||||
{t('settings.hotkeys.shortcut', 'Shortcut')}
|
||||
</span>
|
||||
<HotkeyDisplay binding={binding} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [binding, layout, t, tool.description]);
|
||||
|
||||
const iconNode = useMemo(() => {
|
||||
if (!tool.icon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (React.isValidElement(tool.icon)) {
|
||||
const existingStyle = (tool.icon.props as { style?: React.CSSProperties }).style || {};
|
||||
return React.cloneElement(tool.icon, {
|
||||
style: {
|
||||
...existingStyle,
|
||||
fontSize: layout === 'compact' ? '1.75rem' : '2rem',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return tool.icon;
|
||||
}, [layout, tool.icon]);
|
||||
|
||||
const handleSelect = () => {
|
||||
if (isUnavailable) return;
|
||||
if (tool.link) {
|
||||
window.open(tool.link, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
onSelect(id);
|
||||
};
|
||||
|
||||
const handleButtonClick = (event: React.MouseEvent) => {
|
||||
handleUnlessSpecialClick(event, handleSelect);
|
||||
};
|
||||
|
||||
const matchedLine = matchedSynonym
|
||||
? t('toolPanel.overlay.matchedSynonym', 'Matches "{{text}}"', { text: matchedSynonym })
|
||||
: null;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
className="tool-panel-overlay__tile"
|
||||
data-variant={layout}
|
||||
data-selected={isSelected}
|
||||
data-disabled={isUnavailable || undefined}
|
||||
>
|
||||
<div className="tool-panel-overlay__tile-icon" aria-hidden>
|
||||
{iconNode}
|
||||
</div>
|
||||
<div className="tool-panel-overlay__tile-body">
|
||||
<Text fw={600} size="sm" className="tool-panel-overlay__tile-name">
|
||||
{tool.name}
|
||||
</Text>
|
||||
{layout === 'detailed' && (
|
||||
<Text size="sm" c="dimmed" className="tool-panel-overlay__tile-description">
|
||||
{tool.description}
|
||||
</Text>
|
||||
)}
|
||||
{matchedLine && (
|
||||
<Text size="xs" c="dimmed" className="tool-panel-overlay__tile-match">
|
||||
{matchedLine}
|
||||
</Text>
|
||||
)}
|
||||
{layout === 'detailed' && binding && (
|
||||
<div className="tool-panel-overlay__tile-hotkey">
|
||||
<span>{t('settings.hotkeys.shortcut', 'Shortcut')}</span>
|
||||
<HotkeyDisplay binding={binding} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const wrappedContent = layout === 'compact' && tooltipContent ? (
|
||||
<Tooltip content={tooltipContent} position="top" arrow>
|
||||
{content}
|
||||
</Tooltip>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
|
||||
if (navProps) {
|
||||
return (
|
||||
<a
|
||||
href={navProps.href}
|
||||
onClick={navProps.onClick}
|
||||
className="tool-panel-overlay__tile-link"
|
||||
aria-disabled={isUnavailable}
|
||||
data-variant={layout}
|
||||
>
|
||||
{wrappedContent}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (tool.link && !isUnavailable) {
|
||||
return (
|
||||
<a
|
||||
href={tool.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={handleButtonClick}
|
||||
className="tool-panel-overlay__tile-link"
|
||||
data-variant={layout}
|
||||
>
|
||||
{wrappedContent}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleButtonClick}
|
||||
className="tool-panel-overlay__tile-button"
|
||||
data-variant={layout}
|
||||
aria-disabled={isUnavailable}
|
||||
>
|
||||
{wrappedContent}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToolPanelOverlayTile;
|
||||
Loading…
Reference in New Issue
Block a user