Browse Source

Merge pull request #1272 from transloadit/fix/dashboard-a11y

Dashboard a11y improvements: trap focus in the active panel only
Artur Paikin 6 years ago
parent
commit
11286fc1af

+ 1 - 3
packages/@uppy/dashboard/src/components/AddFiles.js

@@ -7,7 +7,6 @@ const poweredByUppy = (props) => {
     <path d="M7.365 10.5l-.01-4.045h2.612L5.5.806l-4.467 5.65h2.604l.01 4.044h3.718z" fill-rule="evenodd" />
   </svg><span class="uppy-Dashboard-poweredByUppy">Uppy</span></a>
 }
-
 class AddFiles extends Component {
   constructor (props) {
     super(props)
@@ -19,7 +18,6 @@ class AddFiles extends Component {
   }
 
   render () {
-    // const isHidden = Object.keys(this.props.files).length === 0
     const hasAcquirers = this.props.acquirers.length !== 0
 
     if (!hasAcquirers) {
@@ -86,7 +84,7 @@ class AddFiles extends Component {
                   role="tab"
                   tabindex={0}
                   aria-controls={`uppy-DashboardContent-panel--${target.id}`}
-                  aria-selected={this.props.activePanel.id === target.id}
+                  aria-selected={this.props.activePickerPanel.id === target.id}
                   onclick={() => this.props.showPanel(target.id)}>
                   {target.icon()}
                   <div class="uppy-DashboardTab-name">{target.name}</div>

+ 1 - 0
packages/@uppy/dashboard/src/components/AddFilesPanel.js

@@ -4,6 +4,7 @@ const AddFiles = require('./AddFiles')
 const AddFilesPanel = (props) => {
   return (
     <div class="uppy-Dashboard-AddFilesPanel"
+      data-uppy-panelType="AddFiles"
       aria-hidden={props.showAddFilesPanel}>
       <div class="uppy-DashboardContent-bar">
         <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">

+ 21 - 23
packages/@uppy/dashboard/src/components/Dashboard.js

@@ -1,8 +1,8 @@
 const FileList = require('./FileList')
 const AddFiles = require('./AddFiles')
 const AddFilesPanel = require('./AddFilesPanel')
-const PanelContent = require('./PanelContent')
-const PanelTopBar = require('./PanelTopBar')
+const PickerPanelContent = require('./PickerPanelContent')
+const PanelTopBar = require('./PickerPanelTopBar')
 const FileCard = require('./FileCard')
 const classNames = require('classnames')
 const isTouchDevice = require('@uppy/utils/lib/isTouchDevice')
@@ -12,11 +12,18 @@ const PreactCSSTransitionGroup = require('preact-css-transition-group')
 // http://dev.edenspiekermann.com/2016/02/11/introducing-accessible-modal-dialog
 // https://github.com/ghosh/micromodal
 
-module.exports = function Dashboard (props) {
-  // if (!props.inline && props.modal.isHidden) {
-  //   return <span />
-  // }
+function TransitionWrapper (props) {
+  return (
+    <PreactCSSTransitionGroup
+      transitionName="uppy-transition-slideDownUp"
+      transitionEnterTimeout={250}
+      transitionLeaveTimeout={250}>
+      {props.children}
+    </PreactCSSTransitionGroup>
+  )
+}
 
+module.exports = function Dashboard (props) {
   const noFiles = props.totalFileCount === 0
 
   const dashboardClassName = classNames(
@@ -33,7 +40,7 @@ module.exports = function Dashboard (props) {
 
   return (
     <div class={dashboardClassName}
-      aria-hidden={props.inline ? 'false' : props.modal.isHidden}
+      aria-hidden={props.inline ? 'false' : props.isHidden}
       aria-label={!props.inline ? props.i18n('dashboardWindowTitle') : props.i18n('dashboardTitle')}
       onpaste={props.handlePaste}>
 
@@ -63,26 +70,17 @@ module.exports = function Dashboard (props) {
             <AddFiles {...props} />
           )}
 
-          <PreactCSSTransitionGroup
-            transitionName="uppy-transition-slideDownUp"
-            transitionEnterTimeout={250}
-            transitionLeaveTimeout={250}>
+          <TransitionWrapper>
             { props.showAddFilesPanel ? <AddFilesPanel key="AddFilesPanel" {...props} /> : null }
-          </PreactCSSTransitionGroup>
+          </TransitionWrapper>
 
-          <PreactCSSTransitionGroup
-            transitionName="uppy-transition-slideDownUp"
-            transitionEnterTimeout={250}
-            transitionLeaveTimeout={250}>
+          <TransitionWrapper>
             { props.fileCardFor ? <FileCard key="FileCard" {...props} /> : null }
-          </PreactCSSTransitionGroup>
+          </TransitionWrapper>
 
-          <PreactCSSTransitionGroup
-            transitionName="uppy-transition-slideDownUp"
-            transitionEnterTimeout={250}
-            transitionLeaveTimeout={250}>
-            { props.activePanel ? <PanelContent key="PanelContent" {...props} /> : null }
-          </PreactCSSTransitionGroup>
+          <TransitionWrapper>
+            { props.activePickerPanel ? <PickerPanelContent key="PickerPanelContent" {...props} /> : null }
+          </TransitionWrapper>
 
           <div class="uppy-Dashboard-progressindicators">
             {props.progressindicators.map((target) => {

+ 1 - 0
packages/@uppy/dashboard/src/components/FileCard.js

@@ -71,6 +71,7 @@ class FileCard extends Component {
 
     return (
       <div class="uppy-DashboardFileCard"
+        data-uppy-panelType="FileCard"
         onDragOver={ignoreEvent}
         onDragLeave={ignoreEvent}
         onDrop={ignoreEvent}

+ 4 - 4
packages/@uppy/dashboard/src/components/FileItem.js

@@ -5,7 +5,7 @@ const prettyBytes = require('prettier-bytes')
 const FileItemProgress = require('./FileItemProgress')
 const getFileTypeIcon = require('../utils/getFileTypeIcon')
 const FilePreview = require('./FilePreview')
-const { iconCopy, iconRetry } = require('./icons')
+const { iconRetry } = require('./icons')
 const classNames = require('classnames')
 const { h } = require('preact')
 
@@ -147,7 +147,7 @@ module.exports = function fileItem (props) {
           </div>
         }
         {(!uploadInProgressOrComplete && props.metaFields && props.metaFields.length)
-          ? <button class="uppy-DashboardItem-edit"
+          ? <button class="uppy-u-reset uppy-DashboardItem-edit"
             type="button"
             aria-label={props.i18n('editFile')}
             title={props.i18n('editFile')}
@@ -157,7 +157,7 @@ module.exports = function fileItem (props) {
           : null
         }
         {props.showLinkToFileUploadResult && file.uploadURL
-          ? <button class="uppy-DashboardItem-copyLink"
+          ? <button class="uppy-u-reset uppy-DashboardItem-copyLink"
             type="button"
             aria-label={props.i18n('copyLink')}
             title={props.i18n('copyLink')}
@@ -168,7 +168,7 @@ module.exports = function fileItem (props) {
                   props.info(props.i18n('copyLinkToClipboardSuccess'), 'info', 3000)
                 })
                 .catch(props.log)
-            }}>{iconCopy()}</button>
+            }}>{props.i18n('link')}</button>
           : ''
         }
       </div>

+ 0 - 2
packages/@uppy/dashboard/src/components/FilePreview.js

@@ -17,5 +17,3 @@ module.exports = function FilePreview (props) {
     </div>
   )
 }
-
-// <span class="uppy-DashboardItem-previewType">{file.extension && file.extension.length < 5 ? file.extension : null}</span>

+ 4 - 3
packages/@uppy/dashboard/src/components/PanelContent.js → packages/@uppy/dashboard/src/components/PickerPanelContent.js

@@ -5,21 +5,22 @@ function PanelContent (props) {
   return (
     <div class="uppy-DashboardContent-panel"
       role="tabpanel"
-      id={props.activePanel && `uppy-DashboardContent-panel--${props.activePanel.id}`}
+      data-uppy-panelType="PickerPanel"
+      id={props.activePickerPanel && `uppy-DashboardContent-panel--${props.activePickerPanel.id}`}
       onDragOver={ignoreEvent}
       onDragLeave={ignoreEvent}
       onDrop={ignoreEvent}
       onPaste={ignoreEvent}>
       <div class="uppy-DashboardContent-bar">
         <div class="uppy-DashboardContent-title" role="heading" aria-level="h1">
-          {props.i18n('importFrom', { name: props.activePanel.name })}
+          {props.i18n('importFrom', { name: props.activePickerPanel.name })}
         </div>
         <button class="uppy-DashboardContent-back"
           type="button"
           onclick={props.hideAllPanels}>{props.i18n('done')}</button>
       </div>
       <div class="uppy-DashboardContent-panelBody">
-        {props.getPlugin(props.activePanel.id).render(props.state)}
+        {props.getPlugin(props.activePickerPanel.id).render(props.state)}
       </div>
     </div>
   )

+ 0 - 0
packages/@uppy/dashboard/src/components/PanelTopBar.js → packages/@uppy/dashboard/src/components/PickerPanelTopBar.js


+ 2 - 2
packages/@uppy/dashboard/src/components/icons.js

@@ -2,7 +2,7 @@ const { h } = require('preact')
 
 // https://css-tricks.com/creating-svg-icon-system-react/
 
-function defaultTabIcon () {
+function defaultPickerIcon () {
   return <svg aria-hidden="true" width="30" height="30" viewBox="0 0 30 30">
     <path d="M15 30c8.284 0 15-6.716 15-15 0-8.284-6.716-15-15-15C6.716 0 0 6.716 0 15c0 8.284 6.716 15 15 15zm4.258-12.676v6.846h-8.426v-6.846H5.204l9.82-12.364 9.82 12.364H19.26z" />
   </svg>
@@ -83,7 +83,7 @@ function iconText () {
 }
 
 module.exports = {
-  defaultTabIcon,
+  defaultPickerIcon,
   iconCopy,
   iconResume,
   iconPause,

+ 28 - 33
packages/@uppy/dashboard/src/index.js

@@ -8,9 +8,8 @@ const ThumbnailGenerator = require('@uppy/thumbnail-generator')
 const findAllDOMElements = require('@uppy/utils/lib/findAllDOMElements')
 const toArray = require('@uppy/utils/lib/toArray')
 const cuid = require('cuid')
-// const prettyBytes = require('prettier-bytes')
 const ResizeObserver = require('resize-observer-polyfill').default || require('resize-observer-polyfill')
-const { defaultTabIcon } = require('./components/icons')
+const { defaultPickerIcon } = require('./components/icons')
 
 // Some code for managing focus was adopted from https://github.com/ghosh/micromodal
 // MIT licence, https://github.com/ghosh/micromodal/blob/master/LICENSE.md
@@ -65,6 +64,7 @@ module.exports = class Dashboard extends Plugin {
         copyLinkToClipboardSuccess: 'Link copied to clipboard',
         copyLinkToClipboardFallback: 'Copy the URL below',
         copyLink: 'Copy link',
+        link: 'Link',
         fileSource: 'File source: %{name}',
         done: 'Done',
         back: 'Back',
@@ -127,7 +127,7 @@ module.exports = class Dashboard extends Plugin {
       width: 750,
       height: 550,
       thumbnailWidth: 280,
-      defaultTabIcon: defaultTabIcon,
+      defaultPickerIcon,
       showLinkToFileUploadResult: true,
       showProgressDetails: false,
       hideUploadButton: false,
@@ -145,7 +145,6 @@ module.exports = class Dashboard extends Plugin {
       proudlyDisplayPoweredByUppy: true,
       onRequestCloseModal: () => this.closeModal(),
       showSelectedFiles: true,
-      // locale: defaultLocale,
       browserBackButtonClose: false
     }
 
@@ -227,20 +226,22 @@ module.exports = class Dashboard extends Plugin {
 
   hideAllPanels () {
     this.setPluginState({
-      activePanel: false,
-      showAddFilesPanel: false
+      activePickerPanel: false,
+      showAddFilesPanel: false,
+      activeOverlayType: null
     })
   }
 
   showPanel (id) {
     const { targets } = this.getPluginState()
 
-    const activePanel = targets.filter((target) => {
+    const activePickerPanel = targets.filter((target) => {
       return target.type === 'acquirer' && target.id === id
     })[0]
 
     this.setPluginState({
-      activePanel: activePanel
+      activePickerPanel: activePickerPanel,
+      activeOverlayType: 'PickerPanel'
     })
   }
 
@@ -253,6 +254,14 @@ module.exports = class Dashboard extends Plugin {
   }
 
   getFocusableNodes () {
+    // if an overlay is open, we should trap focus inside the overlay
+    const activeOverlayType = this.getPluginState().activeOverlayType
+    if (activeOverlayType) {
+      const activeOverlay = this.el.querySelector(`[data-uppy-panelType="${activeOverlayType}"]`)
+      const nodes = activeOverlay.querySelectorAll(FOCUSABLE_ELEMENTS)
+      return Object.keys(nodes).map((key) => nodes[key])
+    }
+
     const nodes = this.el.querySelectorAll(FOCUSABLE_ELEMENTS)
     return Object.keys(nodes).map((key) => nodes[key])
   }
@@ -529,13 +538,15 @@ module.exports = class Dashboard extends Plugin {
 
   toggleFileCard (fileId) {
     this.setPluginState({
-      fileCardFor: fileId || false
+      fileCardFor: fileId || null,
+      activeOverlayType: fileId ? 'FileCard' : null
     })
   }
 
   toggleAddFilesPanel (show) {
     this.setPluginState({
-      showAddFilesPanel: show
+      showAddFilesPanel: show,
+      activeOverlayType: show ? 'AddFiles' : null
     })
   }
 
@@ -606,29 +617,11 @@ module.exports = class Dashboard extends Plugin {
 
     const isAllPaused = inProgressFiles.length !== 0 &&
       pausedFiles.length === inProgressFiles.length
-    // const isAllPaused = inProgressNotPausedFiles.length === 0 &&
-    //   !isAllComplete &&
-    //   !isAllErrored &&
-    //   uploadStartedFiles.length > 0
-
-    // let inProgressNotPausedFilesArray = []
-    // inProgressNotPausedFiles.forEach((file) => {
-    //   inProgressNotPausedFilesArray.push(files[file])
-    // })
-
-    // let totalSize = 0
-    // let totalUploadedSize = 0
-    // inProgressNotPausedFilesArray.forEach((file) => {
-    //   totalSize = totalSize + (file.progress.bytesTotal || 0)
-    //   totalUploadedSize = totalUploadedSize + (file.progress.bytesUploaded || 0)
-    // })
-    // totalSize = prettyBytes(totalSize)
-    // totalUploadedSize = prettyBytes(totalUploadedSize)
 
     const attachRenderFunctionToTarget = (target) => {
       const plugin = this.uppy.getPlugin(target.id)
       return Object.assign({}, target, {
-        icon: plugin.icon || this.opts.defaultTabIcon,
+        icon: plugin.icon || this.opts.defaultPickerIcon,
         render: plugin.render
       })
     }
@@ -668,7 +661,7 @@ module.exports = class Dashboard extends Plugin {
 
     return DashboardUI({
       state,
-      modal: pluginState,
+      isHidden: pluginState.isHidden,
       files,
       newFiles,
       uploadStartedFiles,
@@ -685,7 +678,7 @@ module.exports = class Dashboard extends Plugin {
       totalProgress: state.totalProgress,
       allowNewUpload,
       acquirers,
-      activePanel: pluginState.activePanel,
+      activePickerPanel: pluginState.activePickerPanel,
       animateOpenClose: this.opts.animateOpenClose,
       isClosing: pluginState.isClosing,
       getPlugin: this.uppy.getPlugin,
@@ -727,6 +720,7 @@ module.exports = class Dashboard extends Plugin {
       isWide: pluginState.containerWidth > 400,
       containerWidth: pluginState.containerWidth,
       isTargetDOMEl: this.isTargetDOMEl,
+      parentElement: this.el,
       allowedFileTypes: this.uppy.opts.restrictions.allowedFileTypes,
       maxNumberOfFiles: this.uppy.opts.restrictions.maxNumberOfFiles,
       showSelectedFiles: this.opts.showSelectedFiles
@@ -745,9 +739,10 @@ module.exports = class Dashboard extends Plugin {
     // Set default state for Dashboard
     this.setPluginState({
       isHidden: true,
-      showFileCard: false,
+      fileCardFor: null,
+      activeOverlayType: null,
       showAddFilesPanel: false,
-      activePanel: false,
+      activePickerPanel: false,
       metaFields: this.opts.metaFields,
       targets: []
     })

+ 5 - 7
packages/@uppy/dashboard/src/style.scss

@@ -116,7 +116,7 @@
   max-width: 100%; /* no !important */
   max-height: 100%; /* no !important */
   // min-width: 290px;
-  // min-height: 400px;
+  min-height: 450px;
   outline: none;
   border: 1px solid rgba($color-gray, 0.2);
   border-radius: 5px;
@@ -808,15 +808,13 @@
 
 .uppy-DashboardItem-edit,
 .uppy-DashboardItem-copyLink {
-  @include reset-button;
   display: inline-block;
   vertical-align: bottom;
   cursor: pointer;
-}
-
-.uppy-DashboardItem-copyLink {
-  width: 12px;
-  height: 11px;
+  font-family: inherit;
+  font-size: inherit;
+  line-height: 1;
+  color: inherit;
 }
 
 .uppy-DashboardItem-edit:not(:first-child),