diff --git a/Tests/IntegrationTests/Fixtures/1Dimension/switchingDimensions.e2e.js b/Tests/IntegrationTests/Fixtures/1Dimension/switchingDimensions.e2e.js index d50b5feb76..4b3864af6f 100644 --- a/Tests/IntegrationTests/Fixtures/1Dimension/switchingDimensions.e2e.js +++ b/Tests/IntegrationTests/Fixtures/1Dimension/switchingDimensions.e2e.js @@ -16,7 +16,7 @@ test('Switching dimensions', async t => { const otherPageName = 'Untranslated page'; await Page.goToPage(translatedPageName); - await DimensionSwitcher.switchLanguageDimension('Latvian'); + await DimensionSwitcher.switchSingleDimension('Latvian'); await t.click('#neos-NodeVariantCreationDialog-CreateEmpty'); await Page.waitForIframeLoading(); await t @@ -24,7 +24,7 @@ test('Switching dimensions', async t => { .expect(Page.treeNode.withText(otherPageName).exists).notOk('Untranslated node gone from the tree'); subSection('Switch back to original dimension'); - await DimensionSwitcher.switchLanguageDimension('English (US)'); + await DimensionSwitcher.switchSingleDimension('English (US)'); await Page.waitForIframeLoading(); await t .expect(await Page.getReduxState(state => state.cr.contentDimensions.active.language[0])).eql('en_US', 'Dimension back to English') diff --git a/Tests/IntegrationTests/pageModel.js b/Tests/IntegrationTests/pageModel.js index 4bc3c927d8..4e5fad99ac 100644 --- a/Tests/IntegrationTests/pageModel.js +++ b/Tests/IntegrationTests/pageModel.js @@ -40,6 +40,12 @@ export class DimensionSwitcher { .click(this.dimensionSwitcherFirstDimensionSelector) .click(this.dimensionSwitcherFirstDimensionSelectorWithShallowDropDownContents.find('li').withText(name)); } + + static async switchSingleDimension(name) { + await t + .click(this.dimensionSwitcher) + .click(ReactSelector('DimensionSelectorOption').withProps('option', {label: name})); + } } export class PublishDropDown { diff --git a/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/DimensionSelector.js b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/DimensionSelector.js new file mode 100644 index 0000000000..9e6d1ad23f --- /dev/null +++ b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/DimensionSelector.js @@ -0,0 +1,89 @@ +import React, {PureComponent} from 'react'; +import PropTypes from 'prop-types'; +import SelectBox from '@neos-project/react-ui-components/src/SelectBox/'; +import style from './style.module.css'; +import {$get, $transform} from 'plow-js'; +import mapValues from 'lodash.mapvalues'; +import sortBy from 'lodash.sortby'; +import {neos} from '@neos-project/neos-ui-decorators'; +import DimensionSelectorOption from './DimensionSelectorOption'; + +const searchOptions = (searchTerm, processedSelectBoxOptions) => + processedSelectBoxOptions.filter(option => option.label && option.label.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1); + +@neos(globalRegistry => ({ + i18nRegistry: globalRegistry.get('i18n') +})) +export default class DimensionSelector extends PureComponent { + static propTypes = { + icon: PropTypes.string.isRequired, + dimensionLabel: PropTypes.string.isRequired, + presets: PropTypes.object.isRequired, + activePreset: PropTypes.string.isRequired, + dimensionName: PropTypes.string.isRequired, + isLoading: PropTypes.bool, + onSelect: PropTypes.func.isRequired, + showDropDownHeaderIcon: PropTypes.bool, + + i18nRegistry: PropTypes.object.isRequired + }; + + state = { + searchTerm: '' + }; + + render() { + const { + activePreset, + isLoading, + i18nRegistry, + dimensionName, + onSelect, + presets, + showDropDownHeaderIcon + } = this.props; + + const presetOptions = mapValues( + presets, + (presetConfiguration, presetName) => { + return $transform( + { + label: $get('label'), + value: presetName, + disallowed: $get('disallowed') + }, + presetConfiguration + ); + } + ); + + const sortedPresetOptions = sortBy(presetOptions, ['label']); + + const onPresetSelect = presetName => { + onSelect(dimensionName, presetName); + }; + + return ( + = 10} + searchOptions={searchOptions(this.state.searchTerm, sortedPresetOptions)} + onSearchTermChange={this.handleSearchTermChange} + noMatchesFoundLabel={i18nRegistry.translate('Neos.Neos:Main:noMatchesFound')} + searchBoxLeftToTypeLabel={i18nRegistry.translate('Neos.Neos:Main:searchBoxLeftToType')} + threshold={0} + ListPreviewElement={DimensionSelectorOption} + className={style.dimensionSwitcherDropDown} + /> + ) + } + + handleSearchTermChange = searchTerm => { + this.setState({searchTerm}); + } +} diff --git a/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/index.js b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/index.js index c3ff5f1624..1c71e90942 100644 --- a/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/index.js +++ b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/index.js @@ -4,16 +4,14 @@ import {connect} from 'react-redux'; import Button from '@neos-project/react-ui-components/src/Button/'; import Icon from '@neos-project/react-ui-components/src/Icon/'; import DropDown from '@neos-project/react-ui-components/src/DropDown/'; -import SelectBox from '@neos-project/react-ui-components/src/SelectBox/'; import style from './style.module.css'; import backend from '@neos-project/neos-ui-backend-connector'; import {$get, $transform} from 'plow-js'; import mapValues from 'lodash.mapvalues'; import {selectors, actions} from '@neos-project/neos-ui-redux-store'; import I18n from '@neos-project/neos-ui-i18n'; -import sortBy from 'lodash.sortby'; import {neos} from '@neos-project/neos-ui-decorators'; -import DimensionSelectorOption from './DimensionSelectorOption'; +import DimensionSelector from './DimensionSelector'; // TODO Add title prop to Icon component const SelectedPreset = props => { @@ -32,82 +30,6 @@ SelectedPreset.propTypes = { dimensionName: PropTypes.string.isRequired }; -const searchOptions = (searchTerm, processedSelectBoxOptions) => - processedSelectBoxOptions.filter(option => option.label && option.label.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1); - -@neos(globalRegistry => ({ - i18nRegistry: globalRegistry.get('i18n') -})) -class DimensionSelector extends PureComponent { - static propTypes = { - icon: PropTypes.string.isRequired, - dimensionLabel: PropTypes.string.isRequired, - presets: PropTypes.object.isRequired, - activePreset: PropTypes.string.isRequired, - dimensionName: PropTypes.string.isRequired, - isLoading: PropTypes.bool, - onSelect: PropTypes.func.isRequired, - - i18nRegistry: PropTypes.object.isRequired - }; - - state = { - searchTerm: '' - }; - - render() { - const {icon, dimensionLabel, presets, dimensionName, activePreset, onSelect, isLoading, i18nRegistry} = this.props; - - const presetOptions = mapValues( - presets, - (presetConfiguration, presetName) => { - return $transform( - { - label: $get('label'), - value: presetName, - disallowed: $get('disallowed') - }, - presetConfiguration - ); - } - ); - - const sortedPresetOptions = sortBy(presetOptions, ['label']); - - const onPresetSelect = presetName => { - onSelect(dimensionName, presetName); - }; - - return ( -
  • -
    - - -
    - = 10} - searchOptions={searchOptions(this.state.searchTerm, sortedPresetOptions)} - onSearchTermChange={this.handleSearchTermChange} - noMatchesFoundLabel={i18nRegistry.translate('Neos.Neos:Main:noMatchesFound')} - searchBoxLeftToTypeLabel={i18nRegistry.translate('Neos.Neos:Main:searchBoxLeftToType')} - threshold={0} - ListPreviewElement={DimensionSelectorOption} - className={style.dimensionSwitcherDropDown} - /> -
  • - ); - } - - handleSearchTermChange = searchTerm => { - this.setState({searchTerm}); - } -} - @connect($transform({ contentDimensions: selectors.CR.ContentDimensions.byName, allowedPresets: selectors.CR.ContentDimensions.allowedPresets, @@ -142,6 +64,11 @@ export default class DimensionSwitcher extends PureComponent { loadingPresets: {} }; + getDimensionIcon = (dimensionName, contentDimensionsObject) => { + const dimensionConfiguration = contentDimensionsObject[dimensionName]; + return dimensionConfiguration?.icon || null; + } + // // Merge active presets comming from redux with local transientPresets state (i.e. presents selected, but not yet applied) // @@ -228,74 +155,104 @@ export default class DimensionSwitcher extends PureComponent { this.setState({isOpen: false}); } + renderSingleDimensionSelector = (dimensionName, contentDimensionsObject) => { + const dimensionConfiguration = contentDimensionsObject[dimensionName]; + const icon = this.getDimensionIcon(dimensionName, contentDimensionsObject); + // First look for active preset in transient state, else take it from activePresets prop + const activePreset = this.getEffectivePresets(this.state.transientPresets)[dimensionName]; + return ( + + ); + } + render() { const {contentDimensions, activePresets, i18nRegistry} = this.props; const contentDimensionsObject = contentDimensions; const contentDimensionsObjectKeys = Object.keys(contentDimensionsObject); - return contentDimensionsObjectKeys.length ? ( - - + {this.renderSingleDimensionSelector(dimensionName, contentDimensionsObject)} + + ) + } + + if (contentDimensionsObjectKeys.length > 1) { + return ( + - {contentDimensionsObjectKeys.map(dimensionName => { - const dimensionConfiguration = contentDimensionsObject[dimensionName]; - const icon = $get('icon', dimensionConfiguration) && $get('icon', dimensionConfiguration); - return ( - ); - })} - - - {contentDimensionsObjectKeys.map(dimensionName => { - const dimensionConfiguration = contentDimensionsObject[dimensionName]; - const icon = $get('icon', dimensionConfiguration) && $get('icon', dimensionConfiguration); - // First look for active preset in transient state, else take it from activePresets prop - const activePreset = this.getEffectivePresets(this.state.transientPresets)[dimensionName]; - return ( - ); - })} - {Object.keys(contentDimensions).length > 1 &&
    - - + -
    } -
    -
    - ) : null; + + + } + + + ) + } + + return null; } presetsForDimension(dimensionName) { diff --git a/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/style.module.css b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/style.module.css index 42f7e4ca5e..d3a87a2db3 100644 --- a/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/style.module.css +++ b/packages/neos-ui/src/Containers/PrimaryToolbar/DimensionSwitcher/style.module.css @@ -2,6 +2,7 @@ max-width: 320px; width: auto; } + .dropDown__header { background-color: transparent; @@ -9,6 +10,7 @@ color: var(--colors-PrimaryBlue); } } + .dropDown__contents { width: fit-content; min-width: 100%; @@ -16,16 +18,18 @@ background-color: var(--colors-ContrastDarker) !important; box-shadow: 0 5px 5px rgba(0, 0, 0, .2); } + .dropDown__btnIcon { margin-right: .5em; } + .dimensionCategory { background-color: var(--colors-ContrastDarkest); padding: var(--spacing-Full); } .dimensionSwitcherDropDown { - min-width: 240px; + min-width: fit-content; } .dimensionCategory + .dimensionCategory { @@ -49,6 +53,7 @@ .buttonGroup button { flex: 1 0 auto; } + @media only screen and (max-width: 1024px) { .selectPresetLabel { display: none; @@ -62,3 +67,17 @@ .selectPreset + .selectPreset { margin-left: var(--spacing-Half); } + +.singleDimensionDropdown { + display: flex; + align-items: center; + + .dimensionSwitcherDropDown { + background: unset; + min-width: fit-content; + } + + .dimensionSwitcherDropDown div[role='button'] { + padding: 5px 0 5px var(--spacing-Full); + } +} diff --git a/packages/react-ui-components/src/SelectBox/selectBox.js b/packages/react-ui-components/src/SelectBox/selectBox.js index da5221b724..22dd6b6390 100644 --- a/packages/react-ui-components/src/SelectBox/selectBox.js +++ b/packages/react-ui-components/src/SelectBox/selectBox.js @@ -77,6 +77,11 @@ export default class SelectBox extends PureComponent { */ placeholderIcon: PropTypes.string, + /** + * This prop is an icon for the header. + */ + headerIcon: PropTypes.string, + /** * Text for the group label of options without a group */ diff --git a/packages/react-ui-components/src/SelectBox_Header/selectBox_Header.js b/packages/react-ui-components/src/SelectBox_Header/selectBox_Header.js index 36697a3c55..3236155778 100644 --- a/packages/react-ui-components/src/SelectBox_Header/selectBox_Header.js +++ b/packages/react-ui-components/src/SelectBox_Header/selectBox_Header.js @@ -17,6 +17,7 @@ class SelectBox_Header extends PureComponent { }), placeholder: PropTypes.string, placeholderIcon: PropTypes.string, + headerIcon: PropTypes.string, showResetButton: PropTypes.bool.isRequired, onReset: PropTypes.func, onClick: PropTypes.func, @@ -68,6 +69,7 @@ class SelectBox_Header extends PureComponent { theme, placeholder, placeholderIcon, + headerIcon, displayLoadingIndicator, Icon, ListPreviewElement, @@ -75,7 +77,7 @@ class SelectBox_Header extends PureComponent { } = this.props; const label = option ? option.label : placeholder; - const icon = option && option.icon ? option.icon : placeholderIcon; + const icon = option && option.icon ? option.icon : (headerIcon ? headerIcon : placeholderIcon); const restProps = omit(this.props, ['showResetButton, IconButton']); return ( @@ -92,6 +94,7 @@ class SelectBox_Header extends PureComponent { icon={icon} disabled={disabled} onClick={this.handleListPreviewElementClick} + showIcon={true} /> : (
    {icon && diff --git a/packages/react-ui-components/src/SelectBox_ListPreview/selectBox_ListPreview.js b/packages/react-ui-components/src/SelectBox_ListPreview/selectBox_ListPreview.js index 004a3af387..cf50eca472 100644 --- a/packages/react-ui-components/src/SelectBox_ListPreview/selectBox_ListPreview.js +++ b/packages/react-ui-components/src/SelectBox_ListPreview/selectBox_ListPreview.js @@ -38,10 +38,13 @@ class SelectBox_ListPreview extends PureComponent { SelectBox_ListPreviewFlat, SelectBox_ListPreviewGrouped, searchBoxLeftToTypeLabel, + onCreateNew, + searchTerm, theme } = this.props; const ListPreviewComponent = options.some(option => option.group) ? SelectBox_ListPreviewGrouped : SelectBox_ListPreviewFlat; + const isCreateNewEnabled = onCreateNew && searchTerm; // TODO: replace horible self-made I18n replace return ( @@ -64,9 +67,11 @@ class SelectBox_ListPreview extends PureComponent { />
    )} -
    - -
    + {isCreateNewEnabled && ( +
    + +
    + )} ); } diff --git a/packages/react-ui-components/src/SelectBox_Option_SingleLine/__snapshots__/selectBox_Option_SingleLine.spec.js.snap b/packages/react-ui-components/src/SelectBox_Option_SingleLine/__snapshots__/selectBox_Option_SingleLine.spec.js.snap index 19a93184bf..1a76b85a52 100644 --- a/packages/react-ui-components/src/SelectBox_Option_SingleLine/__snapshots__/selectBox_Option_SingleLine.spec.js.snap +++ b/packages/react-ui-components/src/SelectBox_Option_SingleLine/__snapshots__/selectBox_Option_SingleLine.spec.js.snap @@ -4,6 +4,7 @@ exports[` should render correctly. 1`] = ` + {option.label} );