diff --git a/Button/Button.js b/Button/Button.js
index fa5f775b53..e496c85a64 100644
--- a/Button/Button.js
+++ b/Button/Button.js
@@ -20,16 +20,13 @@ import PropTypes from 'prop-types';
import compose from 'ramda/src/compose';
import React from 'react';
-import {IconBase} from '../Icon';
+import Icon from '../Icon';
import {MarqueeDecorator} from '../Marquee';
import Skinnable from '../Skinnable';
import TooltipDecorator from '../TooltipDecorator';
import componentCss from './Button.module.less';
-// Make a basic Icon in case we need it later. This cuts `Pure` out of icon for a small gain.
-const Icon = Skinnable(IconBase);
-
/**
* A button component.
*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66d04c27c6..04e9ed2bd0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ The following is a curated list of changes in the Enact sandstone module, newest
### Added
- `sandstone/Dropdown` prop `title` to optionally display a heading above the component
+- `sandstone/Icon` prop `flip` value `"auto"` to automatically flip the icon horizontally for RTL locales.
- `sandstone/TooltipDecorator` prop `tooltipType` to support new transparent label-style tooltips
- `sandstone/FixedPopupPanels` and `sandstone/FlexiblePopupPanels` prop `fullHeight` to force these components to always stretch to the screen edges
@@ -22,6 +23,7 @@ The following is a curated list of changes in the Enact sandstone module, newest
- `sandstone/VirtualList` jumping focus when keys pressed in a row
- `sandstone/Dropdown` to not read x of y feature when focusing on an item
- `sandstone/ContextualPopupDecorator` to position itself correctly when `direction` is changed
+- `sandstone/FixedPopupPanels` layout in RTL locales
- `sandstone/FixedPopupPanels` to support accessibility properly
- `sandstone/FixedPopupPanels` to flex to the content size and invoke scrolling (when using `sandstone/Scroller`) when the content is too big
- `sandstone/TabLayout` performance when focusing items in the layout
diff --git a/FlexiblePopupPanels/FlexiblePopupPanels.module.less b/FlexiblePopupPanels/FlexiblePopupPanels.module.less
index ddf7f1d0b3..de6e17ba62 100644
--- a/FlexiblePopupPanels/FlexiblePopupPanels.module.less
+++ b/FlexiblePopupPanels/FlexiblePopupPanels.module.less
@@ -23,21 +23,19 @@
}
}
- .navButton {
- position: absolute;
- }
+ // this block is "tricks" the layout so the following .navButton rules position the before
+ // button correctly in LTR and RTL locales
+ .navCellBefore {
+ direction: rtl;
- .navCellBefore .navButton {
- left: 0;
- transform: translate(-100%, -50%);
- }
-
- .navCellAfter .navButton {
- right: 0;
- transform: translate(100%, -50%);
+ :global(.enact-locale-right-to-left) & {
+ direction: ltr;
+ }
}
.navButton {
+ position: absolute;
+ transform: translate(0, -50%);
margin: @sand-flexiblepopuppanels-navbutton-margin;
}
@@ -92,6 +90,18 @@
padding: @sand-flexiblepopuppanels-panel-padding;
}
+ :global(.enact-locale-right-to-left) & {
+ // force the body to LTR maintain left alignment of panel contents
+ > .body {
+ direction: ltr;
+
+ // but reset direction for content
+ .bodyLayout {
+ direction: rtl;
+ }
+ }
+ }
+
.applySkins({
.content {
background-color: @sand-flexiblepopuppanels-panel-bg-color;
diff --git a/FlexiblePopupPanels/Panel.js b/FlexiblePopupPanels/Panel.js
index aa403fe0c0..780909448a 100644
--- a/FlexiblePopupPanels/Panel.js
+++ b/FlexiblePopupPanels/Panel.js
@@ -175,6 +175,7 @@ const PanelBase = kind({
component={prevButton}
className={css.navButton}
icon="arrowlargeleft"
+ iconFlip="auto"
onClick={onPrevClick}
size="small"
visible={isPrevButtonVisible}
@@ -188,6 +189,7 @@ const PanelBase = kind({
component={nextButton}
className={css.navButton}
icon="arrowlargeright"
+ iconFlip="auto"
onClick={onNextClick}
size="small"
visible={isNextButtonVisible}
diff --git a/Icon/Icon.js b/Icon/Icon.js
index 15ae5cd6b4..3240b52f77 100644
--- a/Icon/Icon.js
+++ b/Icon/Icon.js
@@ -12,6 +12,7 @@
*/
import kind from '@enact/core/kind';
+import {I18nContextDecorator} from '@enact/i18n/I18nDecorator';
import UiIcon from '@enact/ui/Icon';
import Pure from '@enact/ui/internal/Pure';
import {scaleToRem} from '@enact/ui/resolution';
@@ -54,6 +55,26 @@ const IconBase = kind({
*/
css: PropTypes.object,
+ /**
+ * Flips the icon
+ *
+ * When `'auto'` and `rtl`, the icon is flipped horizontally.
+ *
+ * @type {('auto'|'both'|'horizontal'|'vertical')}
+ * @public
+ */
+ flip: PropTypes.oneOf(['auto', 'both', 'horizontal', 'vertical']),
+
+ /**
+ * Indicates the content's text direction is right-to-left.
+ *
+ * This is set automatically when using {@link ui/Icon.Icon}.
+ *
+ * @type {Boolean}
+ * @public
+ */
+ rtl: PropTypes.bool,
+
/**
* The size of the icon.
*
@@ -84,18 +105,29 @@ const IconBase = kind({
className: ({size, styler}) => styler.append(
(typeof size === 'string' ? size : null)
),
+ flip: ({flip, rtl}) => {
+ if (flip === 'auto') {
+ return rtl ? 'horizontal' : null;
+ }
+
+ return flip;
+ },
style: ({size, style}) => ({
...style,
'--icon-size': (typeof size === 'number') ? scaleToRem(size) : null
})
},
- render: ({css, size, ...rest}) => UiIcon.inline({
- ...rest,
- size: (typeof size === 'string' ? size : void 0),
- css,
- iconList
- })
+ render: ({css, size, ...rest}) => {
+ delete rest.rtl;
+
+ return UiIcon.inline({
+ ...rest,
+ size: (typeof size === 'string' ? size : void 0),
+ css,
+ iconList
+ });
+ }
});
// Let's find a way to import this list directly, and bonus feature, render our icons in the docs
@@ -235,7 +267,8 @@ const IconBase = kind({
*/
const IconDecorator = compose(
Pure,
- Skinnable
+ Skinnable,
+ I18nContextDecorator({rtlProp: 'rtl'})
);
/**
diff --git a/Panels/Header.js b/Panels/Header.js
index 142fe83c5d..0ee71f1bec 100644
--- a/Panels/Header.js
+++ b/Panels/Header.js
@@ -525,6 +525,7 @@ const HeaderBase = kind({
backgroundOpacity={backButtonBackgroundOpacity}
className={css.back}
icon="arrowhookleft"
+ iconFlip="auto"
onClick={onBack}
size="small"
spotlightDisabled={!(backButtonAvailable && !noBackButton && hover)}
diff --git a/WizardPanels/tests/WizardPanels-specs.js b/WizardPanels/tests/WizardPanels-specs.js
index f09bb9a81c..b3603decbf 100644
--- a/WizardPanels/tests/WizardPanels-specs.js
+++ b/WizardPanels/tests/WizardPanels-specs.js
@@ -5,8 +5,8 @@ import {Panel, WizardPanels, WizardPanelsBase} from '../';
describe('WizardPanel Specs', () => {
- const findNextButton = subject => subject.find('.slotAfter').find('Pure');
- const findPrevButton = subject => subject.find('.slotBefore').find('Pure');
+ const findNextButton = subject => subject.find('.slotAfter').find('Pure').first();
+ const findPrevButton = subject => subject.find('.slotBefore').find('Pure').first();
test(
'should have title in `Header`',
diff --git a/samples/sampler/stories/default/Button.js b/samples/sampler/stories/default/Button.js
index 245bc04b85..1fe3c4c0a2 100644
--- a/samples/sampler/stories/default/Button.js
+++ b/samples/sampler/stories/default/Button.js
@@ -17,6 +17,7 @@ const Config = mergeComponentMetadata('Button', UIButtonBase, UIButton, ButtonBa
const prop = {
backgroundOpacity: {'undefined/null (automatic)': '', 'opaque (Default for text buttons)': 'opaque', 'transparent (Default for icon-only buttons)': 'transparent'},
color: ['', 'red', 'green', 'yellow', 'blue'],
+ iconFlip: ['', 'auto', 'both', 'horizontal', 'vertical'],
iconPosition: ['', 'before', 'after'],
icons: ['', ...iconNames],
minWidth: {'undefined/null (automatic)': '', 'true (enforce)': true, 'false (ignore)': 'false'},
@@ -43,6 +44,7 @@ storiesOf('Sandstone', module)
color={select('color', prop.color, Config)}
disabled={boolean('disabled', Config)}
icon={select('icon', prop.icons, Config)}
+ iconFlip={select('iconFlip', prop.iconFlip, Config)}
iconPosition={select('iconPosition', prop.iconPosition, Config)}
minWidth={threeWayBoolean(select('minWidth', prop.minWidth, Config))}
selected={boolean('selected', Config)}
diff --git a/samples/sampler/stories/default/FlexiblePopupPanels.js b/samples/sampler/stories/default/FlexiblePopupPanels.js
index abbabd4eb4..48b4aaa05a 100644
--- a/samples/sampler/stories/default/FlexiblePopupPanels.js
+++ b/samples/sampler/stories/default/FlexiblePopupPanels.js
@@ -71,6 +71,12 @@ storiesOf('Sandstone', module)
+
+
+
+ - Item 1
+
+
);
diff --git a/samples/sampler/stories/default/Icon.js b/samples/sampler/stories/default/Icon.js
index 61d1277d9c..952b74e1d5 100644
--- a/samples/sampler/stories/default/Icon.js
+++ b/samples/sampler/stories/default/Icon.js
@@ -22,7 +22,7 @@ storiesOf('Sandstone', module)
.add(
'Icon',
() => {
- const flip = select('flip', ['', 'both', 'horizontal', 'vertical'], Config, '');
+ const flip = select('flip', ['', 'auto', 'both', 'horizontal', 'vertical'], Config, '');
let size = select('size', ['tiny', 'small', 'medium', 'large', 'custom number'], Config);
if (size === 'custom number') {
diff --git a/tests/screenshot/apps/components/Button.js b/tests/screenshot/apps/components/Button.js
index f332c3d606..aeb9ff0932 100644
--- a/tests/screenshot/apps/components/Button.js
+++ b/tests/screenshot/apps/components/Button.js
@@ -80,6 +80,28 @@ const ButtonTests = [
,
,
+ // iconFlip
+ ,
+ ,
+ ,
+ ,
+ {
+ locale: 'ar-SA',
+ component:
+ },
+ {
+ locale: 'ar-SA',
+ component:
+ },
+ {
+ locale: 'ar-SA',
+ component:
+ },
+ {
+ locale: 'ar-SA',
+ component:
+ },
+
// [GT-28183]
,
// *************************************************************
diff --git a/tests/screenshot/apps/components/Icon.js b/tests/screenshot/apps/components/Icon.js
index 898e86d878..fd5abcd803 100644
--- a/tests/screenshot/apps/components/Icon.js
+++ b/tests/screenshot/apps/components/Icon.js
@@ -30,6 +30,11 @@ const IconTests = [
rotate,
rotate,
rotate,
+ rotate,
+ {
+ locale: 'ar-SA',
+ component: rotate
+ },
{
textSize: 'large',
component: plus