feat(QuickLinks): add card style option for quick links with hover effects

This commit is contained in:
alexsparkes
2026-02-01 20:13:30 +00:00
parent a361e6f3d5
commit 8356ea1af5
3 changed files with 176 additions and 71 deletions

View File

@@ -19,11 +19,13 @@ const QuickLinks = memo(() => {
if (!element) return;
const zoom = localStorage.getItem('zoomQuicklinks') || 100;
const style = localStorage.getItem('quickLinksStyle') || 'card';
for (const link of element.getElementsByTagName('span')) {
link.style.fontSize = `${14 * Number(zoom / 100)}px`;
}
if (localStorage.getItem('quickLinksStyle') !== 'text_only') {
if (style !== 'text_only') {
for (const img of element.getElementsByTagName('img')) {
img.style.height = `${30 * Number(zoom / 100)}px`;
}
@@ -81,7 +83,11 @@ const QuickLinks = memo(() => {
const tooltipEnabled = localStorage.getItem('quicklinkstooltip');
const quickLink = (item, index) => {
if (localStorage.getItem('quickLinksStyle') === 'text_only') {
const zoom = localStorage.getItem('zoomQuicklinks') || 100;
const iconSize = 30 * Number(zoom / 100);
const style = localStorage.getItem('quickLinksStyle') || 'card';
if (style === 'text_only') {
return (
<a
className="quicklinkstext"
@@ -96,10 +102,7 @@ const QuickLinks = memo(() => {
);
}
const zoom = localStorage.getItem('zoomQuicklinks') || 100;
const iconSize = 30 * Number(zoom / 100);
if (localStorage.getItem('quickLinksStyle') === 'metro') {
if (style === 'metro') {
return (
<a
className="quickLinksMetro"
@@ -115,6 +118,25 @@ const QuickLinks = memo(() => {
);
}
if (style === 'card') {
return (
<a
className="quickLinksCard"
key={`quicklink-${item.key}-${index}`}
href={item.url}
target={target}
rel={rel}
draggable={false}
>
<div className="card-icon-container">
<SmartIcon item={item} size={iconSize} />
</div>
<span className="card-label">{item.name}</span>
</a>
);
}
// Default icon style
const link = (
<a
key={`quicklink-${item.key}-${index}`}

View File

@@ -7,10 +7,9 @@ import {
MdOutlineStyle,
MdOutlineVisibility,
MdOutlineFolderOpen,
MdOutlineKeyboardArrowRight,
} from 'react-icons/md';
import { arrayMove } from '@dnd-kit/sortable';
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
import { Header, Row, Content, Action, PreferencesWrapper, Section } from 'components/Layout/Settings';
import { Checkbox, Dropdown } from 'components/Form/Settings';
import { Button } from 'components/Elements';
import Modal from 'react-modal';
@@ -23,23 +22,6 @@ import { BookmarkService } from 'utils/quicklinks';
import EventBus from 'utils/eventbus';
import { getTitleFromUrl, isValidUrl } from 'utils/links';
const NavigationCard = ({ icon: Icon, title, subtitle, onClick }) => {
return (
<div className="moreSettings" onClick={onClick}>
<div className="left">
<Icon />
<div className="content">
<span className="title">{title}</span>
<span className="subtitle">{subtitle}</span>
</div>
</div>
<div className="action">
<MdOutlineKeyboardArrowRight />
</div>
</div>
);
};
const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName }) => {
const [items, setItems] = useState(readQuicklinks());
const [showAddModal, setShowAddModal] = useState(false);
@@ -239,6 +221,7 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName
name="quickLinksStyle"
category="quicklinks"
items={[
{ value: 'card', text: 'Card' },
{ value: 'icon', text: variables.getMessage(`${QUICKLINKS_SECTION}.options.icon`) },
{
value: 'text_only',
@@ -351,10 +334,23 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName
const [syncStatus, setSyncStatus] = useState('');
const [syncing, setSyncing] = useState(false);
const handleSyncToggle = (value) => {
const config = JSON.parse(localStorage.getItem('quicklinks_config') || '{}');
config.bookmarkSyncEnabled = value;
localStorage.setItem('quicklinks_config', JSON.stringify(config));
};
const handleSync = async () => {
setSyncing(true);
setSyncStatus('Syncing...');
try {
// Enable sync first if not already enabled
const config = JSON.parse(localStorage.getItem('quicklinks_config') || '{}');
if (!config.bookmarkSyncEnabled) {
config.bookmarkSyncEnabled = true;
localStorage.setItem('quicklinks_config', JSON.stringify(config));
}
const hasPermission = await BookmarkService.checkPermissions();
if (!hasPermission) {
const granted = await BookmarkService.requestPermissions();
@@ -377,22 +373,38 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName
};
return (
<Row>
<Content
title="Bookmark Sync"
subtitle="Import and sync browser bookmarks to quick links"
/>
<Action>
<Button
type="settings"
onClick={handleSync}
icon={<MdSync />}
label="Sync Bookmarks"
disabled={syncing}
<>
<Row>
<Content
title="Bookmark Sync"
subtitle="Automatically sync browser bookmarks to quick links"
/>
{syncStatus && <p style={{ marginTop: '8px', fontSize: '14px' }}>{syncStatus}</p>}
</Action>
</Row>
<Action>
<Checkbox
name="quicklinks_bookmarkSyncEnabled"
text="Enable Bookmark Sync"
category="quicklinks"
onChange={handleSyncToggle}
/>
</Action>
</Row>
<Row>
<Content
title="Manual Sync"
subtitle="Manually sync bookmarks now"
/>
<Action>
<Button
type="settings"
onClick={handleSync}
icon={<MdSync />}
label="Sync Bookmarks"
disabled={syncing}
/>
{syncStatus && <p style={{ marginTop: '8px', fontSize: '14px' }}>{syncStatus}</p>}
</Action>
</Row>
</>
);
};
@@ -455,33 +467,39 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName
{!currentSubSection && (
<>
<NavigationCard
icon={MdOutlineStyle}
title="Appearance"
subtitle="Customize style and layout of quick links"
onClick={() => onSubSectionChange('appearance', sectionName)}
/>
<PreferencesWrapper
setting="quicklinksenabled"
category="quicklinks"
visibilityToggle={true}
>
<Section
title="Appearance"
subtitle="Customize style and layout of quick links"
icon={<MdOutlineStyle />}
onClick={() => onSubSectionChange('appearance', sectionName)}
/>
<NavigationCard
icon={MdOutlineVisibility}
title="Display"
subtitle="Configure tooltips and tab behavior"
onClick={() => onSubSectionChange('display', sectionName)}
/>
<Section
title="Display"
subtitle="Configure tooltips and tab behavior"
icon={<MdOutlineVisibility />}
onClick={() => onSubSectionChange('display', sectionName)}
/>
<NavigationCard
icon={MdOutlineFolderOpen}
title="Organization"
subtitle="Organize links into groups"
onClick={() => onSubSectionChange('organization', sectionName)}
/>
<Section
title="Organization"
subtitle="Organize links into groups"
icon={<MdOutlineFolderOpen />}
onClick={() => onSubSectionChange('organization', sectionName)}
/>
<NavigationCard
icon={MdSync}
title="Bookmark Sync"
subtitle="Import and sync browser bookmarks"
onClick={() => onSubSectionChange('sync', sectionName)}
/>
<Section
title="Bookmark Sync"
subtitle="Import and sync browser bookmarks"
icon={<MdSync />}
onClick={() => onSubSectionChange('sync', sectionName)}
/>
</PreferencesWrapper>
</>
)}
@@ -531,7 +549,7 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName
<PreferencesWrapper
setting="quicklinksenabled"
category="quicklinks"
visibilityToggle={true}
visibilityToggle={false}
>
<Row final={true}>
<Content title={variables.getMessage(`${QUICKLINKS_SECTION}.title`)} />
@@ -567,12 +585,12 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName
)}
</PreferencesWrapper>
<div
className={`quicklinks-container ${!enabled ? 'disabled' : ''}`}
ref={quicklinksContainer}
aria-hidden={!enabled}
<PreferencesWrapper
setting="quicklinksenabled"
category="quicklinks"
visibilityToggle={false}
>
<div className={`messagesContainer ${!enabled ? 'disabled' : ''}`}>
<div className={`messagesContainer`}>
<SortableList
items={items}
enabled={enabled}
@@ -581,7 +599,7 @@ const QuickLinksOptions = ({ currentSubSection, onSubSectionChange, sectionName
deleteLink={(key, e) => deleteLink(key, e)}
/>
</div>
</div>
</PreferencesWrapper>
</>
)}

View File

@@ -431,6 +431,71 @@ button.quicklinks {
}
}
.quickLinksCard {
text-decoration: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 6px;
transition: all 0.2s ease;
padding: 5px;
border-radius: 12px;
&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
&:hover .card-icon-container {
transform: scale(1.1);
@include themed {
background: t($modal-sidebarActive);
}
}
.card-icon-container {
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
border-radius: 14px;
transition: all 0.2s ease;
@include themed {
background: t($modal-sidebar);
}
img {
width: 32px;
height: 32px;
object-fit: contain;
}
}
.card-label {
font-size: 11px;
text-align: center;
word-break: break-word;
line-height: 1.2;
max-width: 76px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-shadow: 0 0 10px rgb(0 0 0 / 30%);
@include themed {
color: t($color);
}
}
}
.quicklinknostyle {
text-decoration: none;
font-size: 14px;