diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx index 4bbad58b5d139c1..1fa42eacd2bee69 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx @@ -5,42 +5,32 @@ * 2.0. */ -import type { EuiPopoverProps } from '@elastic/eui'; import { EuiDescribedFormGroup, EuiFieldText, EuiFormRow, - EuiLoadingSpinner, - EuiPopover, - EuiSpacer, EuiTextArea, EuiTitle, } from '@elastic/eui'; import type { ChangeEvent } from 'react'; -import React, { Component, Fragment, lazy, Suspense } from 'react'; +import React, { Component } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import type { Space } from 'src/plugins/spaces_oss/common'; -import { isReservedSpace } from '../../../../common'; -import { getSpaceAvatarComponent } from '../../../space_avatar'; +import { getSpaceColor, getSpaceInitials } from '../../../space_avatar'; import type { SpaceValidator } from '../../lib'; import { toSpaceIdentifier } from '../../lib'; +import type { FormValues } from '../manage_space_page'; import { SectionPanel } from '../section_panel'; import { CustomizeSpaceAvatar } from './customize_space_avatar'; import { SpaceIdentifier } from './space_identifier'; -// No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana. -const LazySpaceAvatar = lazy(() => - getSpaceAvatarComponent().then((component) => ({ default: component })) -); - interface Props { validator: SpaceValidator; - space: Partial; + space: FormValues; editingExistingSpace: boolean; - onChange: (space: Partial) => void; + onChange: (space: FormValues) => void; } interface State { @@ -60,14 +50,10 @@ export class CustomizeSpace extends Component { const panelTitle = i18n.translate( 'xpack.spaces.management.manageSpacePage.customizeSpaceTitle', { - defaultMessage: 'Customize your space', + defaultMessage: 'General', } ); - const extraPopoverProps: Partial = { - initialFocus: 'input[name="spaceInitials"]', - }; - return ( {

} - description={this.getPanelDescription()} + description={i18n.translate('xpack.spaces.management.manageSpacePage.nameFormRowLabel', { + defaultMessage: 'Give your space a name and description for easy identification.', + })} fullWidth > { - - - {this.props.space && isReservedSpace(this.props.space) ? null : ( - - - - )} - { name="description" value={description} onChange={this.onDescriptionChange} + isInvalid={validator.validateSpaceDescription(this.props.space).isInvalid} fullWidth rows={2} /> - - - }> - - - - } - closePopover={this.closePopover} - {...extraPopoverProps} - ownFocus={true} - isOpen={this.state.customizingAvatar} - > -
- -
-
-
+ {editingExistingSpace ? null : ( + + )} +
+ + +

+ +

+ + } + description={i18n.translate('xpack.spaces.management.manageSpacePage.avatarDescription', { + defaultMessage: 'Choose how you wish your space’s avatar to appear across Kibana.', + })} + fullWidth + > +
); } - public togglePopover = () => { - this.setState({ - customizingAvatar: !this.state.customizingAvatar, - }); - }; - - public closePopover = () => { - this.setState({ - customizingAvatar: false, - }); - }; - - public getPanelDescription = () => { - return this.props.editingExistingSpace ? ( -

- -

- ) : ( -

- -

- ); - }; - public onNameChange = (e: ChangeEvent) => { if (!this.props.space) { return; @@ -230,6 +170,12 @@ export class CustomizeSpace extends Component { ...this.props.space, name: e.target.value, id, + initials: this.props.space.customAvatarInitials + ? this.props.space.initials + : getSpaceInitials({ name: e.target.value }), + color: this.props.space.customAvatarColor + ? this.props.space.color + : getSpaceColor({ name: e.target.value }), }); }; @@ -252,7 +198,7 @@ export class CustomizeSpace extends Component { }); }; - public onAvatarChange = (space: Partial) => { + public onAvatarChange = (space: FormValues) => { this.props.onChange(space); }; } diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx index 96cd094c146458c..49a85d150f6cf31 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx @@ -6,28 +6,35 @@ */ import { - EuiButton, + EuiAvatar, EuiColorPicker, EuiFieldText, EuiFilePicker, + EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiIcon, + EuiKeyPadMenu, + EuiKeyPadMenuItem, + EuiLoadingSpinner, EuiSpacer, - isValidHex, } from '@elastic/eui'; -import type { ChangeEvent } from 'react'; -import React, { Component } from 'react'; +import type { ChangeEvent, FunctionComponent } from 'react'; +import React, { Component, lazy, Suspense } from 'react'; import { i18n } from '@kbn/i18n'; -import type { Space } from 'src/plugins/spaces_oss/common'; +import { euiThemeVars } from '@kbn/ui-shared-deps/theme'; import { MAX_SPACE_INITIALS } from '../../../../common'; import { encode, imageTypes } from '../../../../common/lib/dataurl'; -import { getSpaceColor, getSpaceInitials } from '../../../space_avatar'; +import { getSpaceAvatarComponent, getSpaceInitials } from '../../../space_avatar'; +import type { SpaceValidator } from '../../lib'; +import type { FormValues } from '../manage_space_page'; interface Props { - space: Partial; - onChange: (space: Partial) => void; + space: FormValues; + onChange: (space: FormValues) => void; + validator: SpaceValidator; } interface State { @@ -35,6 +42,11 @@ interface State { pendingInitials?: string | null; } +// No need to wrap LazySpaceAvatar in an error boundary, because it is one of the first chunks loaded when opening Kibana. +const LazySpaceAvatar = lazy(() => + getSpaceAvatarComponent().then((component) => ({ default: component })) +); + export class CustomizeSpaceAvatar extends Component { private initialsRef: HTMLInputElement | null = null; @@ -106,93 +118,186 @@ export class CustomizeSpaceAvatar extends Component { public render() { const { space } = this.props; - const { initialsHasFocus, pendingInitials } = this.state; - - const spaceColor = getSpaceColor(space); - const isInvalidSpaceColor = !isValidHex(spaceColor) && spaceColor !== ''; - return (
false}> - + + + this.props.onChange({ + ...space, + avatarType: 'initials', + }) + } + style={{ + border: + space.avatarType !== 'image' + ? `1px solid ${euiThemeVars.euiLinkColor}` + : `1px solid ${euiThemeVars.euiBorderColor}`, + color: space.avatarType !== 'image' ? euiThemeVars.euiLinkColor : undefined, + }} + > + + + + this.props.onChange({ + ...space, + avatarType: 'image', + }) + } + style={{ + marginLeft: euiThemeVars.spacerSizes.s, + border: + space.avatarType === 'image' + ? `1px solid ${euiThemeVars.euiLinkColor}` + : `1px solid ${euiThemeVars.euiBorderColor}`, + color: space.avatarType === 'image' ? euiThemeVars.euiLinkColor : undefined, + }} + > + + + - - - + + + {space.avatarType !== 'image' ? ( + <> + + + + + + + + + + + + ) : ( + + + + + + )} + + + + {space.avatarType === 'image' && space.imageUrl ? ( + }> + + + ) : space.avatarType !== 'image' && (space.name || space.initials) ? ( + }> + + + ) : ( + + )} + + - - {this.filePickerOrImage()} ); } - private removeImageUrl() { - this.props.onChange({ - ...this.props.space, - imageUrl: '', - }); - } - - public filePickerOrImage() { - if (!this.props.space.imageUrl) { - return ( - - - - ); - } else { - return ( - - this.removeImageUrl()} color="danger" iconType="trash"> - {i18n.translate('xpack.spaces.management.customizeSpaceAvatar.removeImage', { - defaultMessage: 'Remove custom image', - })} - - - ); - } - } - public initialsInputRef = (ref: HTMLInputElement) => { if (ref) { this.initialsRef = ref; @@ -230,6 +335,7 @@ export class CustomizeSpaceAvatar extends Component { this.props.onChange({ ...this.props.space, + customAvatarInitials: true, initials, }); }; @@ -237,7 +343,24 @@ export class CustomizeSpaceAvatar extends Component { public onColorChange = (color: string) => { this.props.onChange({ ...this.props.space, + customAvatarColor: true, color, }); }; } + +const InitialsIcon: FunctionComponent = () => ( + +); diff --git a/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx b/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx index bc863e4add2cc31..b69222d3aa17ab9 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx @@ -5,66 +5,52 @@ * 2.0. */ -import { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; +import { EuiFieldText, EuiFormRow } from '@elastic/eui'; import type { ChangeEvent } from 'react'; import React, { Component, Fragment } from 'react'; -import type { InjectedIntl } from '@kbn/i18n/react'; -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; import type { Space } from 'src/plugins/spaces_oss/common'; import type { SpaceValidator } from '../../lib'; -import { toSpaceIdentifier } from '../../lib'; interface Props { space: Partial; editable: boolean; validator: SpaceValidator; - intl: InjectedIntl; onChange: (updatedIdentifier: string) => void; } -interface State { - editing: boolean; -} - -class SpaceIdentifierUI extends Component { - private textFieldRef: HTMLInputElement | null = null; - - constructor(props: Props) { - super(props); - this.state = { - editing: false, - }; - } - +export class SpaceIdentifier extends Component { public render() { - const { intl } = this.props; const { id = '' } = this.props.space; return ( + } + helpText={ + /s/{id}, + }} + /> + } {...this.props.validator.validateURLIdentifier(this.props.space)} fullWidth > (this.textFieldRef = ref)} + isInvalid={this.props.validator.validateURLIdentifier(this.props.space).isInvalid} fullWidth /> @@ -72,106 +58,7 @@ class SpaceIdentifierUI extends Component { ); } - public getLabel = () => { - if (!this.props.editable) { - return ( -

- -

- ); - } - - const editLinkText = this.state.editing ? ( - - ) : ( - - ); - - const editLinkLabel = this.state.editing - ? this.props.intl.formatMessage({ - id: 'xpack.spaces.management.spaceIdentifier.resetSpaceNameLinkLabel', - defaultMessage: 'Reset the URL identifier', - }) - : this.props.intl.formatMessage({ - id: 'xpack.spaces.management.spaceIdentifier.customizeSpaceNameLinkLabel', - defaultMessage: 'Customize the URL identifier', - }); - - return ( -

- - - {editLinkText} - -

- ); - }; - - public getHelpText = ( - identifier: string = this.props.intl.formatMessage({ - id: 'xpack.spaces.management.spaceIdentifier.emptySpaceIdentifierText', - defaultMessage: 'awesome-space', - }) - ) => { - return ( -

- /s/{identifier}, - }} - /> -

- ); - }; - - public onEditClick = () => { - const currentlyEditing = this.state.editing; - if (currentlyEditing) { - // "Reset" clicked. Create space identifier based on the space name. - const resetIdentifier = toSpaceIdentifier(this.props.space.name); - - this.setState({ - editing: false, - }); - this.props.onChange(resetIdentifier); - } else { - this.setState( - { - editing: true, - }, - () => { - if (this.textFieldRef) { - this.textFieldRef.focus(); - } - } - ); - } - }; - public onChange = (e: ChangeEvent) => { - if (!this.state.editing) { - return; - } this.props.onChange(e.target.value); }; } - -export const SpaceIdentifier = injectI18n(SpaceIdentifierUI); diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx index 667878df3254a56..0b2d5dd37bd4bf1 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx @@ -6,8 +6,8 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; -import type { ReactNode } from 'react'; -import React, { Component, Fragment } from 'react'; +import type { FunctionComponent } from 'react'; +import React from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -15,7 +15,6 @@ import type { ApplicationStart } from 'src/core/public'; import type { Space } from 'src/plugins/spaces_oss/common'; import type { KibanaFeatureConfig } from '../../../../../features/public'; -import { getEnabledFeatures } from '../../lib/feature_utils'; import { SectionPanel } from '../section_panel'; import { FeatureTable } from './feature_table'; @@ -26,114 +25,42 @@ interface Props { getUrlForApp: ApplicationStart['getUrlForApp']; } -export class EnabledFeatures extends Component { - public render() { - const description = i18n.translate( +export const EnabledFeatures: FunctionComponent = (props) => ( + - - - -

- -

-
- - {this.getDescription()} -
- - - -
-
- ); - } - - private getPanelTitle = () => { - const featureCount = this.props.features.length; - const enabledCount = getEnabledFeatures(this.props.features, this.props.space).length; - - let details: null | ReactNode = null; - - if (enabledCount === featureCount) { - details = ( - - - - - - ); - } else if (enabledCount === 0) { - details = ( - - + )} + data-test-subj="enabled-features-panel" + > + + + +

- - - ); - } else { - details = ( - - - - - - ); - } - - return ( - - {' '} - {details} - - ); - }; - - private getDescription = () => { - return ( - +

+ +

-
- ); - }; -} + + + + + + +); diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss index 35b9dc1d45661b3..20828c4b832d44e 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.scss @@ -1,4 +1,4 @@ .spcFeatureTableAccordionContent { // Align accordion content with the feature category logo in the accordion's buttonContent - padding-left: $euiSizeXL; + padding-left: $euiSizeL; } diff --git a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx index 2c9eaf4563d05bf..5fb00700683f340 100644 --- a/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx +++ b/x-pack/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx @@ -10,13 +10,13 @@ import './feature_table.scss'; import type { EuiCheckboxProps } from '@elastic/eui'; import { EuiAccordion, + EuiButtonEmpty, EuiCallOut, EuiCheckbox, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiIcon, - EuiLink, EuiSpacer, EuiText, EuiTitle, @@ -88,9 +88,9 @@ export class FeatureTable extends Component { const buttonContent = ( { if (!canExpandCategory) { const isChecked = enabledCount > 0; @@ -110,22 +110,22 @@ export class FeatureTable extends Component { ) : null} - -

{category.label}

+ +

{category.label}

); const label: string = i18n.translate('xpack.spaces.management.featureAccordionSwitchLabel', { - defaultMessage: '{enabledCount} / {featureCount} features visible', + defaultMessage: '{enabledCount}/{featureCount} features visible', values: { enabledCount, featureCount, }, }); const extraAction = ( -