1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-10-22 03:16:34 +00:00

A11y keyboard library list (#8288)

* [aria] make the 'view mode' tool accessible by keyboard for all the pages with it

* [aria] make the 'sort menu' tool accessible by keyboard for all the pages with it

* [aria] make the 'single dropdown menu' accessible by keyboard for all the pages with it

* [a11y] make the 'star/unstar' icon accessible by keyboard for repo list pages('my libs', 'shared with me', 'shared with all', group/department)

* [a11y] make the 'share, delete' icons accessible by keyboard for 'my libs'

* [a11y] improvements for 'my libs'

* [a11y] make the 'leave share' icon accessible by keyboard for 'shared with ocm'

* [a11y] improvements for 'shared with me'

* [a11y] improvements for 'shared with all', 'group', 'department'
This commit is contained in:
llj
2025-10-10 14:44:05 +08:00
committed by GitHub
parent dfaa8480f1
commit 36ef437c68
9 changed files with 146 additions and 108 deletions

View File

@@ -13,7 +13,7 @@ class OpIcon extends React.Component {
render() { render() {
const { className, op, title } = this.props; const { className, op, title } = this.props;
return ( return (
<span <i
tabIndex="0" tabIndex="0"
role="button" role="button"
className={className} className={className}
@@ -22,7 +22,7 @@ class OpIcon extends React.Component {
onClick={op} onClick={op}
onKeyDown={Utils.onKeyDown} onKeyDown={Utils.onKeyDown}
> >
</span> </i>
); );
} }
} }

View File

@@ -21,6 +21,7 @@ import RepoAPITokenDialog from '../dialog/repo-api-token-dialog';
import RepoShareAdminDialog from '../dialog/repo-share-admin-dialog'; import RepoShareAdminDialog from '../dialog/repo-share-admin-dialog';
import { LIST_MODE } from '../dir-view-mode/constants'; import { LIST_MODE } from '../dir-view-mode/constants';
import TransferDialog from '../dialog/transfer-dialog'; import TransferDialog from '../dialog/transfer-dialog';
import OpIcon from '../../components/op-icon';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -441,9 +442,28 @@ class SharedRepoListItem extends React.Component {
} else { } else {
operations = this.generatorOperations(); operations = this.generatorOperations();
} }
const shareOperation = <i className="op-icon sf3-font-share sf3-font" title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.onItemShare}></i>; const shareOperation = (
const unshareOperation = <i className="op-icon sf2-icon-x3" title={gettext('Unshare')} role="button" aria-label={gettext('Unshare')} onClick={this.onItemUnshare}></i>; <OpIcon
const deleteOperation = <i className="op-icon sf3-font-delete1 sf3-font" title={gettext('Delete')} role="button" aria-label={gettext('Delete')} onClick={this.onItemDeleteToggle}></i>; className="op-icon sf3-font-share sf3-font"
title={gettext('Share')}
op={this.onItemShare}
/>
);
const unshareOperation = (
<OpIcon
className="op-icon sf2-icon-x3"
title={gettext('Unshare')}
op={this.onItemUnshare}
/>
);
const deleteOperation = (
<OpIcon
className="op-icon sf3-font-delete1 sf3-font"
title={gettext('Delete')}
role="button" aria-label={gettext('Delete')}
op={this.onItemDeleteToggle}
/>
);
if (this.isDepartmentOwnerGroupMember) { if (this.isDepartmentOwnerGroupMember) {
const advancedOperations = this.getAdvancedOperations(); const advancedOperations = this.getAdvancedOperations();
@@ -482,8 +502,11 @@ class SharedRepoListItem extends React.Component {
> >
<DropdownToggle <DropdownToggle
tag="span" tag="span"
role="button"
tabIndex="0"
className="dropdown-item font-weight-normal rounded-0 d-flex justify-content-between align-items-center pr-2" className="dropdown-item font-weight-normal rounded-0 d-flex justify-content-between align-items-center pr-2"
onMouseEnter={this.toggleAdvancedMenuShown} onMouseEnter={this.toggleAdvancedMenuShown}
onKeyDown={this.toggleAdvancedMenuShown}
> >
{this.translateMenuItem(item)} {this.translateMenuItem(item)}
<i className="sf3-font-down sf3-font rotate-270"></i> <i className="sf3-font-down sf3-font rotate-270"></i>
@@ -521,8 +544,7 @@ class SharedRepoListItem extends React.Component {
} }
}; };
onToggleStarRepo = (e) => { onToggleStarRepo = () => {
e.preventDefault();
const { repo_name: repoName } = this.props.repo; const { repo_name: repoName } = this.props.repo;
if (this.state.isStarred) { if (this.state.isStarred) {
seafileAPI.unstarItem(this.props.repo.repo_id, '/').then(() => { seafileAPI.unstarItem(this.props.repo.repo_id, '/').then(() => {
@@ -565,22 +587,17 @@ class SharedRepoListItem extends React.Component {
onContextMenu={this.handleContextMenu} onContextMenu={this.handleContextMenu}
> >
<td className="text-center"> <td className="text-center">
<i <OpIcon
role="button"
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
onClick={this.onToggleStarRepo}
className={`${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`} className={`${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
> title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
</i> op={this.onToggleStarRepo}
/>
</td> </td>
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td> <td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
<td> <td>
{this.state.isRenaming ? {this.state.isRenaming ?
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> : <Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
<Fragment> <Link to={libPath}>{repo.repo_name}</Link>
<Link to={libPath}>{repo.repo_name}</Link>
</Fragment>
} }
</td> </td>
<td>{this.state.isOperationShow && this.generatorPCMenu()}</td> <td>{this.state.isOperationShow && this.generatorPCMenu()}</td>
@@ -603,14 +620,11 @@ class SharedRepoListItem extends React.Component {
<Fragment> <Fragment>
<Link to={libPath} className="library-name text-truncate" title={repo.repo_name}>{repo.repo_name}</Link> <Link to={libPath} className="library-name text-truncate" title={repo.repo_name}>{repo.repo_name}</Link>
{isStarred && {isStarred &&
<i <OpIcon
role="button" className='op-icon library-grid-item-icon sf3-font-star sf3-font'
title={gettext('Unstar')} title={gettext('Unstar')}
aria-label={gettext('Unstar')} op={this.onToggleStarRepo}
onClick={this.onToggleStarRepo} />
className='op-icon library-grid-item-icon sf3-font-star sf3-font'
>
</i>
} }
</Fragment> </Fragment>
} }

View File

@@ -51,16 +51,13 @@ class SortMenu extends React.Component {
toggle={this.toggleDropdownMenu} toggle={this.toggleDropdownMenu}
> >
<DropdownToggle <DropdownToggle
tag="span" className="border-0 font-weight-normal cur-view-path-btn px-1"
role="button"
data-toggle="dropdown"
title={gettext('Switch sort mode')} title={gettext('Switch sort mode')}
data-toggle="dropdown"
aria-label={gettext('Switch sort mode')} aria-label={gettext('Switch sort mode')}
aria-expanded={isDropdownMenuOpen} aria-expanded={isDropdownMenuOpen}
> >
<span className="cur-view-path-btn px-1"> <i className="sf3-font-sort2 sf3-font"></i>
<i className="sf3-font-sort2 sf3-font" aria-label={gettext('Switch sort mode')}></i>
</span>
</DropdownToggle> </DropdownToggle>
<DropdownMenu className="mt-1"> <DropdownMenu className="mt-1">
{sortOptions.map((item, index) => { {sortOptions.map((item, index) => {

View File

@@ -41,13 +41,11 @@ class SingleDropdownToolbar extends React.Component {
return ( return (
<Dropdown isOpen={this.state.isDropdownMenuOpen} toggle={this.toggleDropdownMenu} direction="down"> <Dropdown isOpen={this.state.isDropdownMenuOpen} toggle={this.toggleDropdownMenu} direction="down">
<DropdownToggle <DropdownToggle
tag="span" className={`border-0 font-weight-normal ${withPlusIcon ? 'ml-2 sf-dropdown-combined-toggle' : 'ml-1 sf-dropdown-toggle'}`}
role="button"
className={withPlusIcon ? 'ml-2 sf-dropdown-combined-toggle' : 'ml-1 sf-dropdown-toggle'}
onClick={this.toggleDropdownMenu} onClick={this.toggleDropdownMenu}
onKeyDown={this.onDropdownToggleKeyDown} onKeyDown={this.onDropdownToggleKeyDown}
data-toggle="dropdown" data-toggle="dropdown"
aria-label={gettext('More options')} aria-label={gettext('Operations')}
aria-expanded={this.state.isDropdownMenuOpen} aria-expanded={this.state.isDropdownMenuOpen}
> >
{withPlusIcon && <i className="sf3-font-new sf3-font main-icon"></i>} {withPlusIcon && <i className="sf3-font-new sf3-font main-icon"></i>}

View File

@@ -60,16 +60,13 @@ class ViewModes extends React.Component {
id="cur-view-change-mode-dropdown" id="cur-view-change-mode-dropdown"
> >
<DropdownToggle <DropdownToggle
tag="span" className="border-0 font-weight-normal cur-view-path-btn px-1"
role="button"
data-toggle="dropdown"
title={gettext('Switch view mode')} title={gettext('Switch view mode')}
data-toggle="dropdown"
aria-label={gettext('Switch view mode')} aria-label={gettext('Switch view mode')}
aria-expanded={isDropdownMenuOpen} aria-expanded={isDropdownMenuOpen}
> >
<span className='cur-view-path-btn px-1'> <i className={`sf3-font sf3-font-${currentViewMode}-view`}></i>
<span className={`sf3-font sf3-font-${currentViewMode}-view`} aria-label={gettext('Switch view mode')}></span>
</span>
</DropdownToggle> </DropdownToggle>
<DropdownMenu className="mt-1"> <DropdownMenu className="mt-1">
{options.map((item, index) => { {options.map((item, index) => {
@@ -77,13 +74,13 @@ class ViewModes extends React.Component {
<DropdownItem className='p-0' key={index} onClick={this.props.switchViewMode.bind(this, item.value)}> <DropdownItem className='p-0' key={index} onClick={this.props.switchViewMode.bind(this, item.value)}>
<div className="view-modes-dropdown-wrapper"> <div className="view-modes-dropdown-wrapper">
<span className='view-modes-dropdown-tick'> <span className='view-modes-dropdown-tick'>
{currentViewMode === item.value && <i className="sf2-icon-tick" aria-hidden="true"></i>} {currentViewMode === item.value && <i className="sf2-icon-tick"></i>}
</span> </span>
<span className="view-modes-dropdown-content d-flex align-items-center"> <span className="view-modes-dropdown-content d-flex align-items-center">
<span className={`sf3-font-${item.icon} sf3-font mr-2`} aria-hidden="true"></span> <span className={`sf3-font-${item.icon} sf3-font mr-2`} aria-hidden="true"></span>
<span>{item.text}</span> <span>{item.text}</span>
</span> </span>
<span className="view-modes-dropdown-shortcut ml-4 d-flex align-items-center" aria-hidden="true">{item.shortcut}</span> <span className="view-modes-dropdown-shortcut ml-4 d-flex align-items-center">{item.shortcut}</span>
</div> </div>
</DropdownItem> </DropdownItem>
); );

View File

@@ -2,13 +2,17 @@
color: var(--bs-icon-color); color: var(--bs-icon-color);
padding: 2px 4px; padding: 2px 4px;
border-radius: 3px; border-radius: 3px;
line-height: 1.2;
} }
.sf-dropdown-combined-toggle:active,
.sf-dropdown-combined-toggle:hover { .sf-dropdown-combined-toggle:hover {
background: var(--bs-hover-bg); color: var(--bs-icon-color)!important;
background: var(--bs-hover-bg)!important;
} }
.sf-dropdown-combined-toggle .main-icon { .sf-dropdown-combined-toggle .main-icon {
font-size: 1rem;
margin-right: 2px; margin-right: 2px;
} }

View File

@@ -23,6 +23,7 @@ import RepoShareAdminDialog from '../../components/dialog/repo-share-admin-dialo
import OfficeSuiteDialog from '../../components/dialog/repo-office-suite-dialog'; import OfficeSuiteDialog from '../../components/dialog/repo-office-suite-dialog';
import { LIST_MODE } from '../../components/dir-view-mode/constants'; import { LIST_MODE } from '../../components/dir-view-mode/constants';
import { userAPI } from '../../utils/user-api'; import { userAPI } from '../../utils/user-api';
import OpIcon from '../../components/op-icon';
const propTypes = { const propTypes = {
currentViewMode: PropTypes.string, currentViewMode: PropTypes.string,
@@ -61,13 +62,6 @@ class MylibRepoListItem extends React.Component {
}; };
} }
onFocus = () => {
if (!this.props.isItemFreezed) {
this.setState({
isOpIconShow: true
});
}
};
onMouseEnter = () => { onMouseEnter = () => {
if (!this.props.isItemFreezed) { if (!this.props.isItemFreezed) {
@@ -282,16 +276,19 @@ class MylibRepoListItem extends React.Component {
let iconTitle = Utils.getLibIconTitle(repo); let iconTitle = Utils.getLibIconTitle(repo);
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`; let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
return currentViewMode == LIST_MODE ? ( return currentViewMode == LIST_MODE ? (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onFocus} onContextMenu={this.handleContextMenu}> <tr
className={this.state.highlight ? 'tr-highlight' : ''}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
onFocus={this.onMouseEnter}
onContextMenu={this.handleContextMenu}
>
<td className="text-center"> <td className="text-center">
<i <OpIcon
role="button"
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
onClick={this.onToggleStarRepo}
className={`${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`} className={`${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
> title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
</i> op={this.onToggleStarRepo}
/>
</td> </td>
<td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td> <td><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
<td> <td>
@@ -303,9 +300,7 @@ class MylibRepoListItem extends React.Component {
/> />
)} )}
{!this.state.isRenaming && repo.repo_name && ( {!this.state.isRenaming && repo.repo_name && (
<Fragment> <Link to={repoURL}>{repo.repo_name}</Link>
<Link to={repoURL}>{repo.repo_name}</Link>
</Fragment>
)} )}
{!this.state.isRenaming && !repo.repo_name && {!this.state.isRenaming && !repo.repo_name &&
(gettext('Broken (please contact your administrator to fix this library)')) (gettext('Broken (please contact your administrator to fix this library)'))
@@ -314,8 +309,16 @@ class MylibRepoListItem extends React.Component {
<td> <td>
{(repo.repo_name && this.state.isOpIconShow) && ( {(repo.repo_name && this.state.isOpIconShow) && (
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
<i className="op-icon sf3-font-share sf3-font" title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.onShareToggle}></i> <OpIcon
<i className="op-icon sf3-font-delete1 sf3-font" title={gettext('Delete')} role="button" aria-label={gettext('Delete')} onClick={this.onDeleteToggle}></i> className="op-icon sf3-font-share sf3-font"
title={gettext('Share')}
op={this.onShareToggle}
/>
<OpIcon
className="op-icon sf3-font-delete1 sf3-font"
title={gettext('Delete')}
op={this.onDeleteToggle}
/>
<LibraryOpMenu <LibraryOpMenu
isPC={true} isPC={true}
repo={this.props.repo} repo={this.props.repo}
@@ -335,7 +338,7 @@ class MylibRepoListItem extends React.Component {
className="library-grid-item px-3 d-flex justify-content-between align-items-center" className="library-grid-item px-3 d-flex justify-content-between align-items-center"
onMouseEnter={this.onMouseEnter} onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave} onMouseLeave={this.onMouseLeave}
onFocus={this.onFocus} onFocus={this.onMouseEnter}
onContextMenu={this.handleContextMenu} onContextMenu={this.handleContextMenu}
> >
<div className="d-flex align-items-center text-truncate"> <div className="d-flex align-items-center text-truncate">
@@ -351,14 +354,11 @@ class MylibRepoListItem extends React.Component {
<Fragment> <Fragment>
<Link to={repoURL} className="library-name text-truncate" title={repo.repo_name}>{repo.repo_name}</Link> <Link to={repoURL} className="library-name text-truncate" title={repo.repo_name}>{repo.repo_name}</Link>
{isStarred && {isStarred &&
<i <OpIcon
role="button" className='op-icon library-grid-item-icon sf3-font-star sf3-font'
title={gettext('Unstar')} title={gettext('Unstar')}
aria-label={gettext('Unstar')} op={this.onToggleStarRepo}
className='op-icon library-grid-item-icon sf3-font-star sf3-font' />
onClick={this.onToggleStarRepo}
>
</i>
} }
</Fragment> </Fragment>
)} )}
@@ -368,8 +368,16 @@ class MylibRepoListItem extends React.Component {
</div> </div>
{(repo.repo_name && this.state.isOpIconShow) && ( {(repo.repo_name && this.state.isOpIconShow) && (
<div className="flex-shrink-0 d-flex align-items-center"> <div className="flex-shrink-0 d-flex align-items-center">
<i className="op-icon sf3-font-share sf3-font" title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.onShareToggle}></i> <OpIcon
<i className="op-icon sf3-font-delete1 sf3-font" title={gettext('Delete')} role="button" aria-label={gettext('Delete')} onClick={this.onDeleteToggle}></i> className="op-icon sf3-font-share sf3-font"
title={gettext('Share')}
op={this.onShareToggle}
/>
<OpIcon
className="op-icon sf3-font-delete1 sf3-font"
title={gettext('Delete')}
op={this.onDeleteToggle}
/>
<LibraryOpMenu <LibraryOpMenu
isPC={true} isPC={true}
repo={this.props.repo} repo={this.props.repo}

View File

@@ -18,6 +18,7 @@ import SortOptionsDialog from '../../components/dialog/sort-options';
import LibsMobileThead from '../../components/libs-mobile-thead'; import LibsMobileThead from '../../components/libs-mobile-thead';
import { LIST_MODE } from '../../components/dir-view-mode/constants'; import { LIST_MODE } from '../../components/dir-view-mode/constants';
import MobileItemMenu from '../../components/mobile-item-menu'; import MobileItemMenu from '../../components/mobile-item-menu';
import OpIcon from '../../components/op-icon';
const propTypes = { const propTypes = {
currentViewMode: PropTypes.string, currentViewMode: PropTypes.string,
@@ -202,7 +203,11 @@ class Item extends Component {
<td><img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" /></td> <td><img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" /></td>
<td><Link to={shareRepoUrl}>{item.repo_name}</Link></td> <td><Link to={shareRepoUrl}>{item.repo_name}</Link></td>
<td> <td>
<i role="button" className={`op-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Leave Share')} aria-label={gettext('Leave Share')} onClick={this.leaveShare}></i> <OpIcon
className={`op-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`}
title={gettext('Leave Share')}
op={this.leaveShare}
/>
</td> </td>
{inAllLibs {inAllLibs
? ( ? (
@@ -231,7 +236,11 @@ class Item extends Component {
<Link to={shareRepoUrl} className="library-name text-truncate" title={item.repo_name}>{item.repo_name}</Link> <Link to={shareRepoUrl} className="library-name text-truncate" title={item.repo_name}>{item.repo_name}</Link>
</div> </div>
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<i role="button" className={`op-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Leave Share')} aria-label={gettext('Leave Share')} onClick={this.leaveShare}></i> <OpIcon
className={`op-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`}
title={gettext('Leave Share')}
op={this.leaveShare}
/>
</div> </div>
</div> </div>
); );

View File

@@ -12,6 +12,7 @@ import ModalPortal from '../../components/modal-portal';
import ShareDialog from '../../components/dialog/share-dialog'; import ShareDialog from '../../components/dialog/share-dialog';
import MobileItemMenu from '../../components/mobile-item-menu'; import MobileItemMenu from '../../components/mobile-item-menu';
import { LIST_MODE } from '../../components/dir-view-mode/constants'; import { LIST_MODE } from '../../components/dir-view-mode/constants';
import OpIcon from '../../components/op-icon';
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
@@ -55,14 +56,11 @@ class Item extends Component {
} }
}; };
share = (e) => { share = () => {
e.preventDefault();
this.setState({ isShowSharedDialog: true }); this.setState({ isShowSharedDialog: true });
}; };
leaveShare = (e) => { leaveShare = () => {
e.preventDefault();
const data = this.props.data; const data = this.props.data;
let request; let request;
@@ -129,10 +127,10 @@ class Item extends Component {
onMenuItemClick = (operation, event) => { onMenuItemClick = (operation, event) => {
switch (operation) { switch (operation) {
case 'Share': case 'Share':
this.share(event); this.share();
break; break;
case 'Unshare': case 'Unshare':
this.leaveShare(event); this.leaveShare();
break; break;
default: default:
break; break;
@@ -158,29 +156,38 @@ class Item extends Component {
return ( return (
<Fragment> <Fragment>
{currentViewMode == LIST_MODE ? ( {currentViewMode == LIST_MODE ? (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver} onContextMenu={this.handleContextMenu}> <tr
className={this.state.highlight ? 'tr-highlight' : ''}
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}
onFocus={this.handleMouseOver}
onContextMenu={this.handleContextMenu}
>
<td className="text-center"> <td className="text-center">
<i <OpIcon
role="button"
title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
onClick={this.onToggleStarRepo}
className={`${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`} className={`${this.state.isStarred ? 'sf3-font-star' : 'sf3-font-star-empty'} sf3-font`}
> title={this.state.isStarred ? gettext('Unstar') : gettext('Star')}
</i> op={this.onToggleStarRepo}
/>
</td> </td>
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td> <td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
<td> <td>
<Fragment> <Link to={shareRepoUrl}>{data.repo_name}</Link>
<Link to={shareRepoUrl}>{data.repo_name}</Link>
</Fragment>
</td> </td>
<td> <td>
<div className="d-flex align-items-center"> <div className="d-flex align-items-center">
{(isPro && data.is_admin) && {(isPro && data.is_admin) &&
<a href="#" className={shareIconClassName} title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.share}></a> <OpIcon
className={shareIconClassName}
title={gettext('Share')}
op={this.share}
/>
} }
<a href="#" className={leaveShareIconClassName} title={gettext('Leave Share')} role="button" aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a> <OpIcon
className={leaveShareIconClassName}
title={gettext('Leave Share')}
op={this.leaveShare}
/>
</div> </div>
</td> </td>
<td>{data.size}</td> <td>{data.size}</td>
@@ -199,22 +206,26 @@ class Item extends Component {
<img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="36" className="mr-2" /> <img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="36" className="mr-2" />
<Link to={shareRepoUrl} className="text-truncate library-name" title={data.repo_name}>{data.repo_name}</Link> <Link to={shareRepoUrl} className="text-truncate library-name" title={data.repo_name}>{data.repo_name}</Link>
{isStarred && {isStarred &&
<i <OpIcon
role="button" className='op-icon library-grid-item-icon sf3-font-star sf3-font'
title={gettext('Unstar')} title={gettext('Unstar')}
aria-label={gettext('Unstar')} op={this.onToggleStarRepo}
onClick={this.onToggleStarRepo} />
className='op-icon library-grid-item-icon sf3-font-star sf3-font'
>
</i>
} }
</div> </div>
<div className="flex-shrink-0 d-flex align-items-center"> <div className="flex-shrink-0 d-flex align-items-center">
{(isPro && data.is_admin) && {(isPro && data.is_admin) &&
<a href="#" className={shareIconClassName} title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.share}></a> <OpIcon
className={shareIconClassName}
title={gettext('Share')}
op={this.share}
/>
} }
<a href="#" className={leaveShareIconClassName} title={gettext('Leave Share')} role="button" aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a> <OpIcon
className={leaveShareIconClassName}
title={gettext('Leave Share')}
op={this.leaveShare}
/>
</div> </div>
</div> </div>
)} )}