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