Better responsive sidebar

Also fix a i18n missing string and add some `role` attributes for
accessibility.
This commit is contained in:
Lucas Verney 2016-08-01 12:01:11 +02:00
parent 40f6223bd0
commit 2b3207ec44
14 changed files with 95 additions and 40 deletions

View File

@ -130,7 +130,7 @@ export function loginUser(username, passwordOrToken, endpoint, rememberMe, redir
// Remember me connection // Remember me connection
if (passwordOrToken.expires < new Date()) { if (passwordOrToken.expires < new Date()) {
// Token has expired // Token has expired
return loginUserFailure("Your session expired… =("); return loginUserFailure("app.login.expired");
} }
time = Math.floor(Date.now() / 1000); time = Math.floor(Date.now() / 1000);
passphrase = passwordOrToken.token; passphrase = passwordOrToken.token;

View File

@ -77,7 +77,7 @@ class LoginFormCSSIntl extends Component {
{ {
this.props.error ? this.props.error ?
<div className="row"> <div className="row">
<div className="alert alert-danger" id="loginFormError"> <div className="alert alert-danger" id="loginFormError" role="alert">
<p> <p>
<span className="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> { errorMessage } <span className="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> { errorMessage }
</p> </p>
@ -88,7 +88,7 @@ class LoginFormCSSIntl extends Component {
{ {
this.props.info ? this.props.info ?
<div className="row"> <div className="row">
<div className="alert alert-info" id="loginFormInfo"> <div className="alert alert-info" id="loginFormInfo" role="alert">
<p>{ infoMessage }</p> <p>{ infoMessage }</p>
</div> </div>
</div> </div>

View File

@ -29,7 +29,7 @@ class FilterBarCSSIntl extends Component {
<FormattedMessage {...filterMessages["app.filter.whatAreWeListeningToToday"]} /> <FormattedMessage {...filterMessages["app.filter.whatAreWeListeningToToday"]} />
</p> </p>
<div className="col-xs-12 col-sm-6 col-md-4 input-group"> <div className="col-xs-12 col-sm-6 col-md-4 input-group">
<form className="form-inline" onSubmit={this.handleChange} aria-describedby="filterInputDescription"> <form className="form-inline" onSubmit={this.handleChange} aria-describedby="filterInputDescription" role="search">
<div className="form-group" styleName="form-group"> <div className="form-group" styleName="form-group">
<input type="text" className="form-control" placeholder={formatMessage(filterMessages["app.filter.filter"])} aria-label={formatMessage(filterMessages["app.filter.filter"])} value={this.props.filterText} onChange={this.handleChange} ref="filterTextInput" /> <input type="text" className="form-control" placeholder={formatMessage(filterMessages["app.filter.filter"])} aria-label={formatMessage(filterMessages["app.filter.filter"])} value={this.props.filterText} onChange={this.handleChange} ref="filterTextInput" />
</div> </div>

View File

@ -68,7 +68,7 @@ class PaginationCSSIntl extends Component {
// Push first page // Push first page
pagesButton.push( pagesButton.push(
<li className="page-item" key={key}> <li className="page-item" key={key}>
<Link className="page-link" title={formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: 1})} to={this.props.buildLinkToPage(1)}> <Link role="button" className="page-link" title={formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: 1})} to={this.props.buildLinkToPage(1)}>
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: 1 }} /> <FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: 1 }} />
</Link> </Link>
</li> </li>
@ -95,7 +95,7 @@ class PaginationCSSIntl extends Component {
const title = formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: i }); const title = formatMessage(paginationMessages["app.pagination.goToPageWithoutMarkup"], { pageNumber: i });
pagesButton.push( pagesButton.push(
<li className={className} key={key}> <li className={className} key={key}>
<Link className="page-link" title={title} to={this.props.buildLinkToPage(i)}> <Link role="button" className="page-link" title={title} to={this.props.buildLinkToPage(i)}>
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: i }} /> <FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: i }} />
{currentSpan} {currentSpan}
</Link> </Link>
@ -117,7 +117,7 @@ class PaginationCSSIntl extends Component {
// Push last page // Push last page
pagesButton.push( pagesButton.push(
<li className="page-item" key={key}> <li className="page-item" key={key}>
<Link className="page-link" title={title} to={this.props.buildLinkToPage(this.props.nPages)}> <Link role="button" className="page-link" title={title} to={this.props.buildLinkToPage(this.props.nPages)}>
<FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: this.props.nPages }} /> <FormattedHTMLMessage {...paginationMessages["app.pagination.goToPage"]} values={{ pageNumber: this.props.nPages }} />
</Link> </Link>
</li> </li>
@ -126,8 +126,8 @@ class PaginationCSSIntl extends Component {
if (pagesButton.length > 1) { if (pagesButton.length > 1) {
return ( return (
<div> <div>
<nav className="pagination-nav" styleName="nav" aria-label={formatMessage(paginationMessages["app.pagination.pageNavigation"])}> <nav className="pagination-nav" styleName="nav" aria-label={formatMessage(paginationMessages["app.pagination.pageNavigation"])} role="navigation" >
<ul className="pagination" styleName="pointer"> <ul className="pagination" styleName="pointer" role="group">
{ pagesButton } { pagesButton }
</ul> </ul>
</nav> </nav>

View File

@ -25,13 +25,21 @@ class SidebarLayoutIntl extends Component {
return ( return (
<div> <div>
<div className="col-xs-12 col-md-1 col-lg-2" styleName="sidebar"> <div className="col-xs-12 col-md-1 col-lg-2" styleName="sidebar">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#main-navbar" aria-expanded="false" styleName="toggle">
<span className="sr-only">
<FormattedMessage {...sidebarLayoutMessages["app.sidebarLayout.toggleNavigation"]} />
</span>
<span className="icon-bar" styleName="icon-bar"></span>
<span className="icon-bar" styleName="icon-bar"></span>
<span className="icon-bar" styleName="icon-bar"></span>
</button>
<h1 className="text-center" styleName="title"> <h1 className="text-center" styleName="title">
<IndexLink to="/" styleName="link"> <IndexLink to="/" styleName="link">
<img alt="A" src="./app/assets/img/ampache-blue.png" styleName="imgTitle" /> <img alt="A" src="./app/assets/img/ampache-blue.png" styleName="imgTitle" />
<span className="hidden-md">mpache</span> <span className="hidden-md">mpache</span>
</IndexLink> </IndexLink>
</h1> </h1>
<nav aria-label={formatMessage(sidebarLayoutMessages["app.sidebarLayout.mainNavigationMenu"])}> <nav className="collapse" styleName="collapse" aria-label={formatMessage(sidebarLayoutMessages["app.sidebarLayout.mainNavigationMenu"])} id="main-navbar" role="navigation">
<div className="navbar text-center" styleName="icon-navbar"> <div className="navbar text-center" styleName="icon-navbar">
<div className="container-fluid" styleName="container-fluid"> <div className="container-fluid" styleName="container-fluid">
<ul className="nav navbar-nav" styleName="nav"> <ul className="nav navbar-nav" styleName="nav">

File diff suppressed because one or more lines are too long

48
app/dist/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
app/dist/style.css vendored

File diff suppressed because one or more lines are too long

View File

@ -38,6 +38,7 @@ module.exports = {
"app.sidebarLayout.mainNavigationMenu": "Main navigation menu", // ARIA label for the main navigation menu "app.sidebarLayout.mainNavigationMenu": "Main navigation menu", // ARIA label for the main navigation menu
"app.sidebarLayout.search": "Search", // Search "app.sidebarLayout.search": "Search", // Search
"app.sidebarLayout.settings": "Settings", // Settings "app.sidebarLayout.settings": "Settings", // Settings
"app.sidebarLayout.toggleNavigation": "Toggle navigation", // Screen reader description of toggle navigation button
"app.songs.genre": "Genre", // Genre (song) "app.songs.genre": "Genre", // Genre (song)
"app.songs.length": "Length", // Length (song) "app.songs.length": "Length", // Length (song)
"app.songs.title": "Title", // Title (song) "app.songs.title": "Title", // Title (song)

View File

@ -38,6 +38,7 @@ module.exports = {
"app.sidebarLayout.mainNavigationMenu": "Menu principal", // ARIA label for the main navigation menu "app.sidebarLayout.mainNavigationMenu": "Menu principal", // ARIA label for the main navigation menu
"app.sidebarLayout.search": "Rechercher", // Search "app.sidebarLayout.search": "Rechercher", // Search
"app.sidebarLayout.settings": "Préférences", // Settings "app.sidebarLayout.settings": "Préférences", // Settings
"app.sidebarLayout.toggleNavigation": "Afficher le menu", // Screen reader description of toggle navigation button
"app.songs.genre": "Genre", // Genre (song) "app.songs.genre": "Genre", // Genre (song)
"app.songs.length": "Durée", // Length (song) "app.songs.length": "Durée", // Length (song)
"app.songs.title": "Titre", // Title (song) "app.songs.title": "Titre", // Title (song)

View File

@ -48,6 +48,11 @@ const messages = [
id: "app.sidebarLayout.search", id: "app.sidebarLayout.search",
description: "Search", description: "Search",
defaultMessage: "Search" defaultMessage: "Search"
},
{
id: "app.sidebarLayout.toggleNavigation",
description: "Screen reader description of toggle navigation button",
defaultMessage: "Toggle navigation"
} }
]; ];

View File

@ -35,7 +35,7 @@ function _parseToJSON (responseText) {
function _checkAPIErrors (jsonData) { function _checkAPIErrors (jsonData) {
if (jsonData.error) { if (jsonData.error) {
return Promise.reject(jsonData.error.cdata + " (" + jsonData.error.code + ")"); return Promise.reject(jsonData.error.__cdata + " (" + jsonData.error.code + ")");
} else if (!jsonData) { } else if (!jsonData) {
// No data returned // No data returned
return Promise.reject(new i18nRecord({ return Promise.reject(new i18nRecord({

View File

@ -15,12 +15,15 @@ $condensedNavPadding: 5px;
left: 0; left: 0;
z-index: 1000; z-index: 1000;
display: block; display: block;
padding: 20px; padding: $mainPadding;
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
background-color: $background; background-color: $background;
color: white; color: white;
border-right: 1px solid $lightgrey; }
.collapse {
display: block;
} }
/* Sidebar elements */ /* Sidebar elements */
@ -59,6 +62,14 @@ $condensedNavPadding: 5px;
} }
/* Sidebar navigation */ /* Sidebar navigation */
button.toggle {
background-color: $foreground;
}
.icon-bar {
background-color: $background;
}
.icon-navbar { .icon-navbar {
background-color: #555; background-color: #555;
font-size: 1.2em; font-size: 1.2em;
@ -86,6 +97,7 @@ $condensedNavPadding: 5px;
*/ */
.main-panel { .main-panel {
padding: $mainPadding; padding: $mainPadding;
z-index: -10;
} }
/* /*
@ -118,4 +130,32 @@ $condensedNavPadding: 5px;
.sidebar { .sidebar {
position: static; position: static;
} }
.title {
float: left;
margin-bottom: 0;
}
button.toggle {
display: block;
margin-top: 0;
margin-bottom: 0;
}
.collapse {
clear: both;
}
}
:global {
@media (max-width: 991px) {
.collapse {
display: none;
padding-top: $titleMarginBottom;
}
.collapsing {
padding-top: $titleMarginBottom;
}
}
} }