Tabs redesign

This commit is contained in:
Tom Najdek 2023-12-14 09:43:11 +01:00 committed by Dan Stillman
parent a2e034c921
commit bd067df4f6
13 changed files with 213 additions and 172 deletions

View file

@ -1293,6 +1293,45 @@ var CollectionTree = class CollectionTree extends LibraryTree {
return false;
}
getIconName(index) {
const treeRow = this.getRow(index);
let collectionType = treeRow.type;
let icon = collectionType;
switch (collectionType) {
case 'group':
icon = 'library-group';
break;
case 'feed':
// Better alternative needed: https://github.com/zotero/zotero/pull/902#issuecomment-183185973
/*
if (treeRow.ref.updating) {
collectionType += 'Updating';
} else */if (treeRow.ref.lastCheckError) {
icon += '-error';
}
break;
case 'trash':
if (this._trashNotEmpty[treeRow.ref.libraryID]) {
icon += '-full';
}
break;
case 'feeds':
icon = 'feed-library';
break;
case 'header':
if (treeRow.ref.id == 'group-libraries-header') {
icon = 'groups';
}
break;
case 'separator':
return null;
}
return icon;
}
////////////////////////////////////////////////////////////////////////////////
///
@ -2234,47 +2273,11 @@ var CollectionTree = class CollectionTree extends LibraryTree {
}
_getIcon(index) {
const treeRow = this.getRow(index);
var collectionType = treeRow.type;
if (collectionType == 'separator') {
return document.createElement('span');
}
let iconName = this.getIconName(index);
let icon = collectionType;
switch (collectionType) {
case 'group':
icon = 'library-group';
break;
case 'feed':
// Better alternative needed: https://github.com/zotero/zotero/pull/902#issuecomment-183185973
/*
if (treeRow.ref.updating) {
collectionType += 'Updating';
} else */if (treeRow.ref.lastCheckError) {
icon += '-error';
}
break;
case 'trash':
if (this._trashNotEmpty[treeRow.ref.libraryID]) {
icon += '-full';
}
break;
case 'feeds':
icon = 'feed-library';
break;
case 'header':
if (treeRow.ref.id == 'group-libraries-header') {
icon = 'groups';
}
break;
}
return getCSSIcon(icon);
return iconName
? getCSSIcon(iconName)
: document.createElement('span');
}
/**

View file

@ -109,22 +109,6 @@ i('Twisty', (
<path d="M8 13.4c-.5 0-.9-.2-1.2-.6L.4 5.2C0 4.7-.1 4.3.2 3.7S1 3 1.6 3h12.8c.6 0 1.2.1 1.4.7.3.6.2 1.1-.2 1.6l-6.4 7.6c-.3.4-.7.5-1.2.5z"/>
</svg>
));
i('ArrowLeft', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="m5.001 8.352 5.465 5.466a.626.626 0 0 0 .884-.886L6.416 7.999l4.933-4.932a.626.626 0 0 0-.885-.885L5 7.647l.001.705z"/>
</svg>
));
i('ArrowRight', (
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="currentColor">
<path d="m10.999 8.352-5.465 5.466a.626.626 0 0 1-.884-.886l4.935-4.934-4.934-4.931a.626.626 0 0 1 .885-.885L11 7.647l-.001.705z"/>
</svg>
));
i('Cross', "chrome://zotero/skin/cross.png");
i('Tick', "chrome://zotero/skin/tick.png");
i('ArrowRefresh', "chrome://zotero/skin/arrow_refresh.png");
@ -253,14 +237,14 @@ module.exports.getCSSIcon = function (key) {
let iconEl = document.createElement('span');
iconEl.classList.add('icon');
iconEl.classList.add('icon-css');
iconEl.classList.add(key);
iconEl.classList.add(`icon-${key}`);
cssIconsCache.set(key, iconEl);
}
return cssIconsCache.get(key).cloneNode(true);
};
module.exports.getCSSItemTypeIcon = function (itemType, key = 'icon-item-type') {
module.exports.getCSSItemTypeIcon = function (itemType, key = 'item-type') {
let icon = module.exports.getCSSIcon(key);
icon.dataset.itemType = itemType;
return icon;

View file

@ -27,7 +27,7 @@
import React, { forwardRef, useState, useRef, useImperativeHandle, useEffect, useLayoutEffect } from 'react';
import cx from 'classnames';
const { IconXmark, IconArrowLeft, IconArrowRight } = require('./icons');
const { CSSIcon } = require('./icons');
const SCROLL_ARROW_SCROLL_BY = 222;
@ -312,7 +312,7 @@ const TabBar = forwardRef(function (props, ref) {
}
}
function renderTab({ id, title, selected, iconBackgroundImage }, index) {
function renderTab({ id, title, selected, icon }, index) {
return (
<div
key={id}
@ -328,18 +328,18 @@ const TabBar = forwardRef(function (props, ref) {
onKeyDown={handleKeyDown}
tabIndex="-1"
>
<div className="tab-name" dir="auto">{iconBackgroundImage &&
<span className="icon-bg" style={{ backgroundImage: iconBackgroundImage }}/>}{title}</div>
{icon}
<div className="tab-name" dir="auto">{title}</div>
<div
className="tab-close"
onClick={(event) => handleTabClose(event, id)}
>
<IconXmark/>
<CSSIcon name="x-8" className="icon-16" />
</div>
</div>
);
}
return (
<div>
<div
@ -359,9 +359,14 @@ const TabBar = forwardRef(function (props, ref) {
ref={startArrowRef}
className="scroll-start-arrow"
style={{ transform: Zotero.rtl ? 'scaleX(-1)' : undefined }}
onClick={handleClickScrollStart}
onDoubleClick={handleScrollArrowDoubleClick}
><IconArrowLeft/></div>
>
<button
onClick={handleClickScrollStart}
onDoubleClick={handleScrollArrowDoubleClick}
>
<CSSIcon name="chevron-tabs" className="icon-20" />
</button>
</div>
<div className="tabs-wrapper">
<div
ref={tabsRef}
@ -378,12 +383,19 @@ const TabBar = forwardRef(function (props, ref) {
ref={endArrowRef}
className="scroll-end-arrow"
style={{ transform: Zotero.rtl ? 'scaleX(-1)' : undefined }}
onClick={handleClickScrollEnd}
onDoubleClick={handleScrollArrowDoubleClick}
><IconArrowRight/></div>
>
<button
onClick={handleClickScrollEnd}
onDoubleClick={handleScrollArrowDoubleClick}
>
<CSSIcon name="chevron-tabs" className="icon-20" />
</button>
</div>
</div>
</div>
);
});
TabBar.displayName = 'TabBar';
export default TabBar;

View file

@ -2744,19 +2744,19 @@ var ItemTree = class ItemTree extends LibraryTree {
// If the item has a child attachment
if (type !== null && type != 'none') {
if (type == 'pdf') {
icon = getCSSItemTypeIcon('attachmentPDF', 'icon-attachment-type');
icon = getCSSItemTypeIcon('attachmentPDF', 'attachment-type');
ariaLabel = Zotero.getString('pane.item.attachments.hasPDF');
}
else if (type == 'snapshot') {
icon = getCSSItemTypeIcon('attachmentSnapshot', 'icon-attachment-type');
icon = getCSSItemTypeIcon('attachmentSnapshot', 'attachment-type');
ariaLabel = Zotero.getString('pane.item.attachments.hasSnapshot');
}
else if (type == 'epub') {
icon = getCSSItemTypeIcon('attachmentEPUB', 'icon-attachment-type');
icon = getCSSItemTypeIcon('attachmentEPUB', 'attachment-type');
ariaLabel = Zotero.getString('pane.item.attachments.hasEPUB');
}
else {
icon = getCSSItemTypeIcon('attachmentFile', 'icon-attachment-type');
icon = getCSSItemTypeIcon('attachmentFile', 'attachment-type');
ariaLabel = Zotero.getString('pane.item.attachments.has');
}

View file

@ -29,6 +29,7 @@
var React = require('react');
var ReactDOM = require('react-dom');
import TabBar from 'components/tabBar';
import { CSSIcon, CSSItemTypeIcon } from 'components/icons';
// Reduce loaded tabs limit if the system has 8 GB or less memory.
// TODO: Revise this after upgrading to Zotero 7
@ -81,13 +82,33 @@ var Zotero_Tabs = new function () {
};
this._update = function () {
this._tabBarRef.current.setTabs(this._tabs.map(tab => ({
id: tab.id,
type: tab.type,
title: tab.title,
selected: tab.id == this._selectedID,
iconBackgroundImage: tab.iconBackgroundImage
})));
this._tabBarRef.current.setTabs(this._tabs.map((tab) => {
let icon = null;
if (tab.id === 'zotero-pane') {
let index = ZoteroPane.collectionsView?.selection?.focused;
if (typeof index !== 'undefined' && ZoteroPane.collectionsView.getRow(index)) {
let iconName = ZoteroPane.collectionsView.getIconName(index);
icon = <CSSIcon name={iconName} className="tab-icon" />;
}
}
else if (tab.data?.itemID) {
try {
let item = Zotero.Items.get(tab.data.itemID);
icon = <CSSItemTypeIcon itemType={item.getItemTypeIconName()} className="tab-icon" />;
}
catch (e) {
// item might not yet be loaded, we will get the icon on the next update
}
}
return {
id: tab.id,
type: tab.type,
title: tab.title,
selected: tab.id == this._selectedID,
icon,
};
}));
// Disable File > Close menuitem if multiple tabs are open
const multipleTabsOpen = this._tabs.length > 1;
document.getElementById('cmd_close').setAttribute('disabled', multipleTabsOpen);
@ -243,23 +264,9 @@ var Zotero_Tabs = new function () {
return;
}
tab.title = title;
Zotero_Tabs.updateLibraryTabIcon();
this._update();
};
this.updateLibraryTabIcon = () => {
let index = ZoteroPane.collectionsView.selection.focused;
if (!ZoteroPane.collectionsView.getRow(index)) {
return;
}
let icon = ZoteroPane.collectionsView._getIcon(index);
var { tab } = this._getTab('zotero-pane');
if (!tab || !icon.style.backgroundImage) {
return;
}
tab.iconBackgroundImage = icon.style.backgroundImage;
};
/**
* Close tabs
*

View file

@ -754,7 +754,7 @@
</toolbar>
</toolbox>
<!-- Keep in sync with Zotero.test conditional block in overlay.js -->
<hbox>
<hbox id="zotero-title-bar">
<div xmlns="http://www.w3.org/1999/xhtml" id="tab-bar-container" style="-moz-box-flex: 1;"/>
<hbox id="zotero-tabs-toolbar" align="center">
<toolbarbutton id="zotero-tb-opened-tabs" class="zotero-tb-button" tabindex="-1" data-l10n-id="zotero-toolbar-opened-tabs-menu" type="panel" />

View file

@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon">
<path id="Vector" d="M13.8839 2.88388L13 2L5 10L13 18L13.8839 17.1161L6.76777 10L13.8839 2.88388Z" fill="context-fill"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 243 B

View file

@ -19,3 +19,7 @@
width: 290px;
background: var(--material-background);
}
#zotero-title-bar {
border-bottom: var(--material-panedivider);
}

View file

@ -3,6 +3,14 @@ $icons: (
library-group, publications, trash-full, trash, unfiled, retracted, search,
);
@each $icon in $icons {
.icon-css.icon-#{$icon} {
@include focus-states using ($color) {
@include svgicon($icon, $color, "16", "collection-tree");
}
}
}
#zotero-collections-tree-container {
height: 5.2em;
}
@ -22,7 +30,7 @@ $icons: (
}
@each $icon in $icons {
.icon-css.#{$icon} {
.icon-css.icon-#{$icon} {
@include focus-states using ($color) {
@include svgicon($icon, $color, "16", "collection-tree");
}

View file

@ -25,6 +25,11 @@
-moz-appearance: none !important;
}
.icon-20 {
width: 20px;
height: 20px;
}
.icon-16 {
width: 16px;
height: 16px;
@ -40,6 +45,8 @@ $-icons: (
chevron-6: 8,
filter: 16,
note: 16,
x-8: 16,
chevron-tabs: 20
);
@each $icon, $size in $-icons {

View file

@ -1,12 +1,7 @@
// Styling for displaying tabs in the title bar
:root:not([legacytoolbar="true"]) {
& {
@media (-moz-platform: windows) {
--tab-min-height: 36px;
}
@media not (-moz-platform: windows) {
--tab-min-height: 30px;
}
--tab-min-height: 36px;
--tabs-border-color: rgba(0,0,0,.3);
--tabline-color: #0a84ff;
@ -138,14 +133,6 @@
background: var(--material-tabbar);
-moz-window-dragging: drag;
}
#tab-bar-container .tab {
background: transparent;
}
#tab-bar-container .tab.selected {
background: var(--material-tabbar)
}
.zotero-toolbar {
-moz-appearance: none;

View file

@ -4,7 +4,7 @@
--safe-area-end: 0;
--tab-border: .5px solid var(--tab-border);
min-height: 30px;
min-height: var(--tab-min-height);
&:not([hidden]) {
// display: flex overrides the hidden attribute
@ -39,51 +39,77 @@
}
.scroll-start-arrow, .scroll-end-arrow {
height: 30px;
padding: 0 3px;
padding: 0 4px;
align-items: center;
z-index: 1;
color: var(--fill-tertiary);
color: var(--fill-secondary);
display: none;
box-shadow: none;
// Add bottom border to scroll arrows,
// and negate the extra vertical pixel added by the border with a negative margin
border-bottom: var(--tab-border);
margin-bottom: -1px;
box-sizing: border-box;
z-index: 1;
> button {
width: auto;
padding: 0;
border: 0;
height: 28px;
border-radius: 5px;
background: transparent;
}
.icon {
display: flex;
fill: var(--fill-tertiary);
}
&.active {
color: var(--fill-secondary);
&:hover {
background-color: var(--fill-quinary);
button {
&:hover {
background-color: var(--fill-quinary);
}
&:active {
background-color: var(--fill-quarternary);
}
}
.icon {
fill: var(--fill-secondary);
}
}
}
.scroll-start-arrow {
border-right: var(--material-border-transparent);
border-left: var(--material-border-quarternary);
&.active {
border-right: var(--material-panedivider);
box-shadow: 3px 0 3px -3px rgba(0, 0, 0, .3);
box-shadow: 0.5px 0.5px 0px -0.5px rgba(0, 0, 0, 0.05), 3px 0px 3px -3px rgba(0, 0, 0, .3)
}
}
.scroll-end-arrow {
border-left: var(--material-border-transparent);
&.active {
border-left: var(--material-panedivider);
box-shadow: -3px 0 3px -3px rgba(0, 0, 0, .3)
box-shadow: -0.5px 0.5px 0px -0.5px rgba(0, 0, 0, 0.05), -3px 0 3px -3px rgba(0, 0, 0, .3)
}
.icon {
transform: rotate(180deg);
}
}
.pinned-tabs {
display: none;
padding-right: 8px;
.tabs {
overflow: unset;
}
.tab {
max-width: 110px;
max-width: 100px;
}
}
@ -108,74 +134,76 @@
overflow: hidden;
left: 0;
right: 0;
padding: 4px 1px;
column-gap: 4px;
&:before {
content: "";
width: 0;
min-width: 0;
border-inline-end: var(--tab-border);
}
&:after {
content: "";
flex: 1 0 0;
width: 100%;
min-width: 0;
// Add bottom border to the space between the last tab and the spacer in #tab-bar-container::after
border-bottom: var(--tab-border);
border-inline-start: var(--tab-border);
.pinned-tabs & {
padding: 4px 0;
}
}
.tab {
box-sizing: border-box;
-moz-appearance: none;
max-width: 200px;
flex: 1 1 200px;
height: 30px;
line-height: 30px;
position: relative;
color: var(--fill-primary);
text-align: center;
padding: 0 22px;
min-width: 110px;
&:not(:last-child) {
border-inline-end: var(--tab-border);
}
min-width: 100px;
height: 28px;
line-height: 16px;
padding: 6px 22px 6px 6px;
display: flex;
border-radius: 5px;
transition: background-color 0.1s ease-out;
&.selected {
background: var(--material-button);
box-shadow: 0px 0px 0px 0.5px rgba(0, 0, 0, 0.05), 0px 0.5px 2.5px 0px rgba(0, 0, 0, 0.30);
}
&.dragging {
border-inline-start: var(--tab-border);
z-index: 1;
}
&.dragging + & {
border-inline-start: var(--tab-border);
&:not(:last-child) {
border-inline-end: var(--tab-border);
}
&:not(.selected):hover {
background-color: var(--fill-quinary);
}
&:first-child {
padding: 6px; // no 22px padding for X button because pinned tab cannot be closed
.tab-close {
display: none;
}
}
.tab-name {
line-height: 30px;
overflow-x: hidden;
text-overflow: ellipsis;
display: -moz-box;
-moz-box-pack: center;
position: relative;
top: -2px;
overflow-y: hidden;
background: linear-gradient(to left, transparent 0px, var(--fill-primary) 20px) text;
flex: 1 1 100%;
line-height: 16px;
margin-inline-start: 4px;
overflow: hidden;
text-align: left;
-webkit-text-fill-color: transparent;
white-space: nowrap;
.icon-bg {
margin-inline-end: 7px;
margin-top: -2px;
@include state('.tab:first-child') {
background: linear-gradient(to left, transparent 0px, var(--fill-primary) 10px) text;
}
}
.tab-icon {
flex: 0 0 16px;
}
.tab-close {
position: absolute;
transition: background-color 0.1s ease-out;
&:dir(ltr) {
right: 6px;
@ -205,10 +233,6 @@
}
}
&:first-child .tab-close {
display: none;
}
&:not(.selected) {
border-bottom: var(--tab-border);
}

View file

@ -1,5 +1,5 @@
#tab-bar-container {
--safe-area-start: 78px;
--safe-area-start: 73px;
--safe-area-end: 20px;
}