Parcourir la source

dashboard: improve support for right-to-left scripts (#2705)

* dashboard: fix modal position on rtl pages, fixes #2495

* Align text to the right on right-to-left pages

* core: fix default text alignment on right-to-left pages

* dashboard: fix margin in provider browser on right-to-left pages

* dashboard: use direction-aware offset properties

* provider-views: use logical offset CSS properties

* status-bar: use logical offset CSS properties

* dashboard: fix addMore icon margin on RTL pages

* dashboard: some more logical properties

* build: compile logical properties to old CSS

* dashboard: set dir attribute if page did not provide one

* status-bar: set fallback text direction if not provided

* support direction option for inline and modal dashboard

* use companion.uppy.io on deploy previews

* provider-views: fix RTL alignment of file icons

* put the dir attribute on a wrapper div

* fix icon alignment in RTL provider views

* informer: fix ? alignment in RTL mode
Renée Kooi il y a 4 ans
Parent
commit
52760d2bb7

+ 8 - 1
bin/build-css.js

@@ -1,6 +1,8 @@
 const sass = require('sass')
 const postcss = require('postcss')
 const autoprefixer = require('autoprefixer')
+const postcssLogical = require('postcss-logical')
+const postcssDirPseudoClass = require('postcss-dir-pseudo-class')
 const cssnano = require('cssnano')
 // const safeImportant = require('postcss-safe-important')
 const chalk = require('chalk')
@@ -44,7 +46,12 @@ async function compileCSS () {
       }
     })
 
-    const postcssResult = await postcss([autoprefixer])
+    const plugins = [
+      autoprefixer,
+      postcssLogical(),
+      postcssDirPseudoClass()
+    ]
+    const postcssResult = await postcss(plugins)
       .process(scssResult.css, { from: file })
     postcssResult.warnings().forEach(function (warn) {
       console.warn(warn.toString())

+ 58 - 18
package-lock.json

@@ -8245,28 +8245,10 @@
         "marked": "^0.7.0"
       },
       "dependencies": {
-        "ecstatic": {
-          "version": "4.1.4",
-          "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-4.1.4.tgz",
-          "integrity": "sha512-8E4ZLK4uRuB9pwywGpy/B9vcz4gCp6IY7u4cMbeCINr/fjb1v+0wf0Ae2XlfSnG8xZYnE4uaJBjFkYI0bqcIdw==",
-          "requires": {
-            "charset": "^1.0.1",
-            "he": "^1.1.1",
-            "mime": "^2.4.1",
-            "minimist": "^1.1.0",
-            "on-finished": "^2.3.0",
-            "url-join": "^4.0.0"
-          }
-        },
         "marked": {
           "version": "0.7.0",
           "resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz",
           "integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg=="
-        },
-        "mime": {
-          "version": "2.4.7",
-          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz",
-          "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA=="
         }
       }
     },
@@ -16986,6 +16968,26 @@
         "safe-buffer": "^5.0.1"
       }
     },
+    "ecstatic": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-4.1.4.tgz",
+      "integrity": "sha512-8E4ZLK4uRuB9pwywGpy/B9vcz4gCp6IY7u4cMbeCINr/fjb1v+0wf0Ae2XlfSnG8xZYnE4uaJBjFkYI0bqcIdw==",
+      "requires": {
+        "charset": "^1.0.1",
+        "he": "^1.1.1",
+        "mime": "^2.4.1",
+        "minimist": "^1.1.0",
+        "on-finished": "^2.3.0",
+        "url-join": "^4.0.0"
+      },
+      "dependencies": {
+        "mime": {
+          "version": "2.4.7",
+          "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz",
+          "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA=="
+        }
+      }
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -33393,6 +33395,35 @@
         }
       }
     },
+    "postcss-dir-pseudo-class": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz",
+      "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==",
+      "dev": true,
+      "requires": {
+        "postcss": "^7.0.2",
+        "postcss-selector-parser": "^5.0.0-rc.3"
+      },
+      "dependencies": {
+        "cssesc": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz",
+          "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==",
+          "dev": true
+        },
+        "postcss-selector-parser": {
+          "version": "5.0.0",
+          "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz",
+          "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==",
+          "dev": true,
+          "requires": {
+            "cssesc": "^2.0.0",
+            "indexes-of": "^1.0.1",
+            "uniq": "^1.0.1"
+          }
+        }
+      }
+    },
     "postcss-discard-comments": {
       "version": "4.0.2",
       "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz",
@@ -33628,6 +33659,15 @@
         "import-cwd": "^2.0.0"
       }
     },
+    "postcss-logical": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-4.0.2.tgz",
+      "integrity": "sha512-tlX1n19np6/JznvyymZM6SIe0FymD5Ngwcg2j825vNKhADu0p1PTgEmsCjakCbvn78kaIFzYTI32NpgOEwgifQ==",
+      "dev": true,
+      "requires": {
+        "postcss": "^7.0.17"
+      }
+    },
     "postcss-merge-longhand": {
       "version": "4.0.11",
       "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz",

+ 2 - 0
package.json

@@ -182,6 +182,8 @@
     "onchange": "4.1.0",
     "pacote": "9.5.12",
     "postcss": "7.0.26",
+    "postcss-dir-pseudo-class": "^5.0.0",
+    "postcss-logical": "^4.0.2",
     "postcss-safe-important": "1.2.0",
     "pre-commit": "1.2.2",
     "react": "16.13.1",

+ 13 - 0
packages/@uppy/core/src/_common.scss

@@ -13,6 +13,12 @@
   color: $gray-800;
 }
 
+// One selector uses the dir attribute declared by the page. If that does not exist, Uppy adds a
+// fallback dir attribute on the root element itself, and we need a second selector to match that.
+[dir="rtl"] .uppy-Root, .uppy-Root[dir="rtl"] {
+  text-align: right;
+}
+
 .uppy-Root *, .uppy-Root *:before, .uppy-Root *:after {
   box-sizing: inherit;
 }
@@ -79,6 +85,10 @@
   white-space: normal;
   z-index: auto;
 }
+[dir="rtl"] .uppy-u-reset {
+  text-align: right;
+}
+
 
 // Inputs
 
@@ -137,6 +147,9 @@
   transition-property: background-color, color;
   transition-duration: 0.3s;
   user-select: none;
+
+  // Override right-to-left variant of the uppy-u-reset class
+  [dir="rtl"] & { text-align: center; }
 }
 
   .uppy-c-btn:not(:disabled):not(.disabled) {

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

@@ -22,8 +22,11 @@ module.exports = function Dashboard (props) {
   const noFiles = props.totalFileCount === 0
   const isSizeMD = props.containerWidth > WIDTH_MD
 
+  const wrapperClassName = classNames({
+    'uppy-Root': props.isTargetDOMEl
+  })
+
   const dashboardClassName = classNames({
-    'uppy-Root': props.isTargetDOMEl,
     'uppy-Dashboard': true,
     'uppy-Dashboard--animateOpenClose': props.animateOpenClose,
     'uppy-Dashboard--isClosing': props.isClosing,
@@ -49,7 +52,7 @@ module.exports = function Dashboard (props) {
 
   const showFileList = props.showSelectedFiles && !noFiles
 
-  return (
+  const dashboard = (
     <div
       class={dashboardClassName}
       data-uppy-theme={props.theme}
@@ -131,4 +134,11 @@ module.exports = function Dashboard (props) {
       </div>
     </div>
   )
+
+  return (
+    // Wrap it for RTL language support
+    <div class={wrapperClassName} dir={props.direction}>
+      {dashboard}
+    </div>
+  )
 }

+ 2 - 2
packages/@uppy/dashboard/src/components/FileCard/index.scss

@@ -73,7 +73,7 @@
       @include blue-border-focus;
       position: absolute;
       top: 10px;
-      right: 10px;
+      inset-inline-end: 10px;
       font-size: 13px;
       background-color: rgba($black, 0.5);
       color: $white;
@@ -153,7 +153,7 @@
     }
 
       .uppy-Dashboard-FileCard-actionsBtn {
-        margin-right: 10px;
+        margin-inline-end: 10px;
       }
     // ...uppy-Dashboard-FileCard-actions|
 

+ 3 - 3
packages/@uppy/dashboard/src/components/FileItem/Buttons/index.scss

@@ -6,7 +6,7 @@
 
   &:hover {
     opacity: 1;
-    color: $gray-900;    
+    color: $gray-900;
   }
 
   [data-uppy-theme="dark"] & {
@@ -16,7 +16,7 @@
 
   [data-uppy-theme="dark"] &:hover {
     color: $gray-200;
-  }  
+  }
 }
 
 .uppy-Dashboard-Item-action--remove {
@@ -73,7 +73,7 @@
     z-index: $zIndex-3;
     position: absolute;
     top: -8px;
-    right: -8px;
+    inset-inline-end: -8px;
     width: 18px;
     height: 18px;
     padding: 0;

+ 2 - 2
packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.scss

@@ -1,5 +1,5 @@
 .uppy-Dashboard-Item-fileInfo {
-  padding-right: 5px;
+  padding-inline-end: 5px;
 }
   .uppy-Dashboard-Item-name {
     font-size: 12px;
@@ -39,7 +39,7 @@
       color: $gray-400;
       &:not(:first-child) {
         position: relative;
-        margin-left: 14px;
+        margin-inline-start: 14px;
       }
       svg,
       svg * {

+ 2 - 2
packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.scss

@@ -81,8 +81,8 @@
 .uppy-Dashboard-Item.is-complete .uppy-Dashboard-Item-progress {
   transform: initial;
   top: -9px;
-  right: -8px;
-  left: initial;
+  inset-inline-end: -8px;
+  inset-inline-start: initial;
   width: auto;
 }
 

+ 5 - 5
packages/@uppy/dashboard/src/components/FileItem/index.scss

@@ -8,7 +8,7 @@
   align-items: center;
   border-bottom: 1px solid $gray-200;
   padding: 10px;
-  padding-right: 0;
+  padding-inline-end: 0;
 
   [data-uppy-theme="dark"] & {
     border-bottom: 1px solid $gray-800;
@@ -21,7 +21,7 @@
     // For the Remove button
     position: relative;
     display: block;
-    float: left;
+    float: inline-start;
     margin: 5px $rl-margin;
     padding: 0;
     /* When changing width: also update `itemsPerRow` values in `src/components/Dashboard.js`. */
@@ -70,8 +70,8 @@
 
   .uppy-Dashboard-Item-fileInfoAndButtons {
     flex-grow: 1;
-    padding-right: 8px;
-    padding-left: 12px;
+    padding-inline-end: 8px;
+    padding-inline-start: 12px;
 
     display: flex;
     align-items: center;
@@ -123,7 +123,7 @@
   border-radius: 50%;
   position: relative;
   top: -1px;
-  left: 6px;
+  inset-inline-start: 6px;
   font-size: 8px;
   font-weight: 600;
   text-align: center;

+ 11 - 0
packages/@uppy/dashboard/src/index.js

@@ -8,6 +8,7 @@ const ThumbnailGenerator = require('@uppy/thumbnail-generator')
 const findAllDOMElements = require('@uppy/utils/lib/findAllDOMElements')
 const toArray = require('@uppy/utils/lib/toArray')
 const getDroppedFiles = require('@uppy/utils/lib/getDroppedFiles')
+const getTextDirection = require('@uppy/utils/lib/getTextDirection')
 const trapFocus = require('./utils/trapFocus')
 const cuid = require('cuid')
 const ResizeObserver = require('resize-observer-polyfill').default || require('resize-observer-polyfill')
@@ -892,6 +893,7 @@ module.exports = class Dashboard extends Plugin {
       allowNewUpload,
       acquirers,
       theme,
+      direction: this.opts.direction,
       activePickerPanel: pluginState.activePickerPanel,
       showFileEditor: pluginState.showFileEditor,
       animateOpenClose: this.opts.animateOpenClose,
@@ -965,6 +967,15 @@ module.exports = class Dashboard extends Plugin {
     })
   }
 
+  onMount () {
+    // Set the text direction if the page has not defined one.
+    const element = this.el
+    const direction = getTextDirection(element)
+    if (!direction) {
+      element.dir = 'ltr'
+    }
+  }
+
   install = () => {
     // Set default state for Dashboard
     this.setPluginState({

+ 13 - 12
packages/@uppy/dashboard/src/style.scss

@@ -166,6 +166,7 @@
   @media #{$screen-medium} {
     top: 50%;
     left: 50%;
+    right: auto; // else the 15px from above may override the positioning
     transform: translate(-50%, -50%);
     box-shadow: 0 5px 15px 4px rgba($black, 0.15);
   }
@@ -176,7 +177,7 @@
   display: block;
   position: absolute;
   top: -33px;
-  right: -2px;
+  inset-inline-end: -2px;
   cursor: pointer;
   color: rgba($white, 0.9);
   font-size: 27px;
@@ -189,7 +190,7 @@
   @media #{$screen-medium} {
     font-size: 35px;
     top: -10px;
-    right: -35px;
+    inset-inline-end: -35px;
   }
 }
 
@@ -227,7 +228,7 @@
   padding-top: 15px;
   padding-bottom: 15px;
   margin-top: auto;
-  
+
   // hide on short note and “powered by” on short screens
   // such as CodePen, or inline dashboard with height < 400px
   display: none;
@@ -345,7 +346,7 @@
 
   .uppy-size--md & {
     width: 86px;
-    margin-right: 1px;
+    margin-inline-end: 1px;
     flex-direction: column;
     padding: 10px 3px;
     border-radius: 5px;
@@ -385,10 +386,10 @@
     vertical-align: text-top;
     overflow: hidden;
     transition: transform ease-in-out .15s;
-    margin-right: 10px;
+    margin-inline-end: 10px;
 
     .uppy-size--md & {
-      margin-right: 0;
+      margin-inline-end: 0;
     }
   }
 
@@ -490,7 +491,7 @@
     cursor: pointer;
     color: $blue;
     padding: 7px 6px;
-    margin-left: -6px;
+    margin-inline-start: -6px;
 
     .uppy-size--md & {
       font-size: 14px;
@@ -511,13 +512,13 @@
     width: 29px;
     height: 29px;
     padding: 7px 8px;
-    margin-right: -5px;
+    margin-inline-end: -5px;
 
     .uppy-size--md & {
       font-size: 14px;
       width: auto;
       height: auto;
-      margin-right: -8px;
+      margin-inline-end: -8px;
     }
 
     [data-uppy-theme="dark"] & {
@@ -527,7 +528,7 @@
 
     .uppy-DashboardContent-addMore svg {
       vertical-align: baseline;
-      margin-right: 4px;
+      margin-inline-end: 4px;
 
       .uppy-size--md & {
         width: 11px;
@@ -682,7 +683,7 @@
   color: $black;
   margin-top: 15px;
   margin-bottom: 5px;
-  text-align: left;
+  text-align: inline-start;
   padding: 0 15px;
   width: 100%;
 
@@ -803,7 +804,7 @@ a.uppy-Dashboard-poweredBy {
 .uppy-Dashboard-uploadCount {
   position: absolute;
   top: -12px;
-  right: -12px;
+  inset-inline-end: -12px;
   background-color: $green;
   color: $white;
   border-radius: 50%;

+ 2 - 2
packages/@uppy/informer/src/style.scss

@@ -56,9 +56,9 @@
   border-radius: 50%;
   position: relative;
   top: -1px;
-  left: 3px;
+  inset-inline-start: 3px;
   font-size: 10px;
-  margin-left: -1px;
+  margin-inline-start: -1px;
 }
 
   .uppy-Informer span:hover {

+ 7 - 7
packages/@uppy/provider-views/src/style.scss

@@ -63,7 +63,7 @@
   color: $gray-700;
   font-size: 12px;
   margin-bottom: 10px;
-  text-align: left;
+  text-align: start;
 
   .uppy-size--md & {
     margin-bottom: 0;
@@ -77,7 +77,7 @@
     display: inline-block;
     color: $gray-700;
     vertical-align: middle;
-    margin-right: 4px;
+    margin-inline-end: 4px;
     line-height: 1;
   }
     .uppy-Provider-breadcrumbsIcon svg {
@@ -127,7 +127,7 @@
   .uppy-ProviderBrowser-user:after {
     content: '\00B7';
     position: relative;
-    left: 4px;
+    inset-inline-start: 4px;
     color: $gray-500;
     font-weight: normal;
   }
@@ -191,7 +191,7 @@
   position: absolute;
   width: 12px;
   height: 12px;
-  left: 16px;
+  inset-inline-start: 16px;
   z-index: $zIndex-3;
   color: $gray-400;
 }
@@ -206,7 +206,7 @@
   line-height: 1.4;
   border: 0;
   margin: 0 8px;
-  padding-left: 27px;
+  padding-inline-start: 27px;
   z-index: $zIndex-2;
   border-radius: 4px;
 
@@ -230,7 +230,7 @@
   width: 22px;
   height: 22px;
   padding: 6px;
-  right: 12px;
+  inset-inline-end: 12px;
   top: 4px;
   z-index: $zIndex-3;
   color: $gray-500;
@@ -317,7 +317,7 @@
   padding: 0 15px;
 
   & button {
-    margin-right: 8px;
+    margin-inline-end: 8px;
   }
 
   [data-uppy-theme="dark"] & {

+ 3 - 3
packages/@uppy/provider-views/src/style/uppy-ProviderBrowser-viewType--grid.scss

@@ -23,11 +23,11 @@
     .uppy-size--md & {
       width: 33.3333%;
     }
-    
+
     .uppy-size--lg & {
       width: 25%;
     }
-    
+
     &::before {
       content: '';
       padding-top: 100%;
@@ -107,7 +107,7 @@
     // Checkmark icon
     &:after {
       width: 12px; height: 7px;
-      left: 7px; top: 8px;
+      inset-inline-start: 7px; top: 8px;
     }
   }
   // Checked: show the checkmark

+ 5 - 5
packages/@uppy/provider-views/src/style/uppy-ProviderBrowser-viewType--list.scss

@@ -26,7 +26,7 @@
 
   // Checkbox
   .uppy-ProviderBrowserItem-fakeCheckbox {
-    margin-right: 15px;
+    margin-inline-end: 15px;
     height: 17px; width: 17px;
     border-radius: 3px;
     background-color: $white;
@@ -43,13 +43,13 @@
     &::after {
       opacity: 0;
       height: 5px; width: 9px;
-      left: 3px; top: 4px;
+      inset-inline-start: 3px; top: 4px;
     }
 
     [data-uppy-theme="dark"] &:focus {
       border-color: rgba($highlight--dark, 0.7);
       box-shadow: 0 0 0 3px rgba($highlight--dark, 0.2);
-    }   
+    }
   }
   // Checked: color the background, show the checkmark
   .uppy-ProviderBrowserItem-fakeCheckbox--is-checked {
@@ -76,7 +76,7 @@
       text-decoration: underline;
     }
     img, svg {
-      margin-right: 8px;
+      margin-inline-end: 8px;
       max-width: 20px;
       max-height: 20px;
     }
@@ -93,6 +93,6 @@
 
   .uppy-ProviderBrowserItem-iconWrap {
     width: 20px;
-    margin-right: 7px;
+    margin-inline-end: 7px;
   }
 }

+ 2 - 0
packages/@uppy/provider-views/src/style/uppy-ProviderBrowserItem-fakeCheckbox.scss

@@ -12,6 +12,8 @@
     content: '';
     position: absolute;
     cursor: pointer;
+    // Not using border-inline-start here: this is part of a CSS trick to get
+    // a check mark icon that only works in one direction
     border-left: 2px solid $gray-200;
     border-bottom: 2px solid $gray-200;
     transform: rotate(-45deg);

+ 10 - 0
packages/@uppy/status-bar/src/index.js

@@ -4,6 +4,7 @@ const StatusBarUI = require('./StatusBar')
 const statusBarStates = require('./StatusBarStates')
 const getSpeed = require('@uppy/utils/lib/getSpeed')
 const getBytesRemaining = require('@uppy/utils/lib/getBytesRemaining')
+const getTextDirection = require('@uppy/utils/lib/getTextDirection')
 
 /**
  * StatusBar: renders a status bar with upload/pause/resume/cancel/retry buttons,
@@ -241,6 +242,15 @@ module.exports = class StatusBar extends Plugin {
     })
   }
 
+  onMount () {
+    // Set the text direction if the page has not defined one.
+    const element = this.el
+    const direction = getTextDirection(element)
+    if (!direction) {
+      element.dir = 'ltr'
+    }
+  }
+
   install () {
     const target = this.opts.target
     if (target) {

+ 14 - 14
packages/@uppy/status-bar/src/style.scss

@@ -105,14 +105,14 @@
   align-items: center;
   position: relative;
   z-index: $zIndex-3;
-  padding-left: 10px;
+  padding-inline-start: 10px;
   white-space: nowrap;
   text-overflow: ellipsis;
   color: $gray-800;
   height: 100%;
 
   .uppy-size--md & {
-    padding-left: 15px;
+    padding-inline-start: 15px;
   }
 
   [data-uppy-theme="dark"] & {
@@ -126,7 +126,7 @@
   display: flex;
   flex-direction: column;
   justify-content: center;
-  padding-right: 0.3em;
+  padding-inline-end: 0.3em;
 }
 
 // Don't display elements with class .-additionalInfo if we're not at least on .md
@@ -159,11 +159,11 @@
   .uppy-StatusBar-statusSecondaryHint {
     display: inline-block;
     vertical-align: middle;
-    margin-right: 5px;
+    margin-inline-end: 5px;
     line-height: 1;
 
     .uppy-size--md & {
-      margin-right: 8px;
+      margin-inline-end: 8px;
     }
   }
 
@@ -175,7 +175,7 @@
   position: relative;
   top: 1px;
   color: $gray-700;
-  margin-right: 7px;
+  margin-inline-end: 7px;
 
   svg {
     vertical-align: text-bottom;
@@ -188,7 +188,7 @@
   position: absolute;
   top: 0;
   bottom: 0;
-  right: 10px;
+  inset-inline-end: 10px;
   z-index: $zIndex-4;
 }
 
@@ -246,7 +246,7 @@
     @include blue-border-focus();
     height: 16px;
     border-radius: 8px;
-    margin-right: 6px;
+    margin-inline-end: 6px;
     background-color: $pomegranate;
     line-height: 1;
     color: #fff;
@@ -264,7 +264,7 @@
     svg {
       position: absolute;
       top: 3px;
-      left: 6px;
+      inset-inline-start: 6px;
     }
   }
 
@@ -275,7 +275,7 @@
     color: $white;
     background-color: $green;
     line-height: 1;
-    
+
     &:hover {
       background-color: darken($green, 5%);
     }
@@ -302,8 +302,8 @@
   .uppy-StatusBar-actionBtn--uploadNewlyAdded {
     // for focus
     @include blue-border-focus;
-    padding-right: 3px;
-    padding-left: 3px;
+    padding-inline-end: 3px;
+    padding-inline-start: 3px;
     padding-bottom: 1px;
     border-radius: 3px;
 
@@ -338,7 +338,7 @@
   border-radius: 50%;
   position: relative;
   top: 0;
-  left: 2px;
+  inset-inline-start: 2px;
   font-size: 10px;
   font-weight: 600;
   text-align: center;
@@ -355,7 +355,7 @@
   animation-duration: 1s;
   animation-iteration-count: infinite;
   animation-timing-function: linear;
-  margin-right: 10px;
+  margin-inline-end: 10px;
   fill: $blue;
 }
 

+ 21 - 0
packages/@uppy/utils/src/getTextDirection.js

@@ -0,0 +1,21 @@
+/**
+ * Get the declared text direction for an element.
+ *
+ * @param {Node} element
+ * @returns {string|undefined}
+ */
+
+function getTextDirection (element) {
+  // There is another way to determine text direction using getComputedStyle(), as done here:
+  // https://github.com/pencil-js/text-direction/blob/2a235ce95089b3185acec3b51313cbba921b3811/text-direction.js
+  //
+  // We do not use that approach because we are interested specifically in the _declared_ text direction.
+  // If no text direction is declared, we have to provide our own explicit text direction so our
+  // bidirectional CSS style sheets work.
+  while (element && !element.dir) {
+    element = element.parentNode
+  }
+  return element ? element.dir : undefined
+}
+
+module.exports = getTextDirection

+ 10 - 0
website/src/examples/dashboard/app.es6

@@ -17,6 +17,8 @@ const localeList = require('../locale_list.json')
 
 const COMPANION = require('../env')
 
+const RTL_LOCALES = ['ar_SA', 'fa_IR', 'he_IL']
+
 if (typeof window !== 'undefined' && typeof window.Uppy === 'undefined') {
   window.Uppy = {
     locales: {}
@@ -195,9 +197,17 @@ function setLocale (localeName) {
     loadLocaleFromCDN(localeName)
   }
   whenLocaleAvailable(localeName, (localeObj) => {
+    const direction = RTL_LOCALES.indexOf(localeName) !== -1
+      ? 'rtl'
+      : 'ltr'
+
     window.uppy.setOptions({
       locale: localeObj
     })
+
+    window.uppy.getPlugin('Dashboard').setOptions({
+      direction
+    })
   })
 }
 

+ 1 - 1
website/src/examples/env.js

@@ -1,6 +1,6 @@
 let companionEndpoint = 'http://localhost:3020'
 
-if (location.hostname === 'uppy.io') {
+if (location.hostname === 'uppy.io' || /--uppy\.netlify\.app$/.test(location.hostname)) {
   companionEndpoint = '//companion.uppy.io'
 }