[Platform security] Replace SCSS with CSS in JS (Part 1 - Spaces plugin) (#214798)

## Summary

Part of https://github.com/elastic/kibana/issues/211652

Removed most SASS files from the Spaces plugin. (Full checklist on
parent issue)

Remaining file: 

`x-pack/platform/plugins/shared/spaces/public/space_selector/space_selector.scss`
This file requires a custom mixin that we'll need to migrate once this
PR: https://github.com/elastic/kibana/pull/214729/files lands. It
introduces a `cssUtils` file to ensure consistency in Kibana specific
mixins.

### How to test
Testing visual regression isn't super straightforward here. For my local
testing, i started two instances of Kibana (main and this branch)

On main:

- Start es:
```
yarn es snapshot --license=trial -E http.port=9400
```

- Start kibana with the following config (CLI or kibana.dev.yml)
``` 
server.port: 5602
elasticsearch.hosts: ["http://localhost:9400"]
```
Once started, in a private browsing window, you should have access to
Kibana on main on `localhost:5602`

On this PR:
Start ES and Kibana normally (Kibana should be available on
localhost:5601)

This PR contains changes to the following parts of the Spaces plugin:
- Copy Saved Objects to Space flyout
- Share Saved Objects to Space flyout
- Space selector screen
- Space editing screen
- Space selector drop down menu in Nav Bar

Ideally, you should see no visual regression between the two versions. 


## Screenshots

| Component | Main | PR |
|--------|--------|--------|
| Space Edit | <img width="300" alt="space_edit_main"
src="https://github.com/user-attachments/assets/786feeb7-5047-443c-bb63-41e90e31a82b"
/> | <img width="300" alt="space_edit_pr"
src="https://github.com/user-attachments/assets/975cc096-25d7-4bd5-804d-f82f65a908bf"
/> |
| Space selector nav bar | <img width="300"
alt="space_selector_nav_bar_main"
src="https://github.com/user-attachments/assets/c6c05d28-3dfa-43c2-9586-b66a24f990d6"
/> | <img width="317" alt="Screenshot 2025-03-20 at 09 11 50"
src="https://github.com/user-attachments/assets/277d3094-640b-4604-adc7-5c8465aeb21c"
/> |
| Share to space | <img width="300" alt="share_to_space_main"
src="https://github.com/user-attachments/assets/5782a314-66f7-4780-bcfb-b0a85cece035"
/> | <img width="300" alt="share_to_space_pr"
src="https://github.com/user-attachments/assets/73a48305-7fa7-4637-9856-60461cbad770"
/> |
| Copy to Space flyout | <img width="300" alt="copy_to_space_pr"
src="https://github.com/user-attachments/assets/54342ca2-b2e1-4844-a66f-fae512ff8910"
/> | <img width="300" alt="copy_to_space_main"
src="https://github.com/user-attachments/assets/a629f12a-75c4-4ba6-a7cf-cdeca1310ef3"
/> |
| Copy to Space confirmation | <img width="300"
alt="copy_to_space_confirmed_main"
src="https://github.com/user-attachments/assets/78f93d73-e789-487f-94c1-eebcef7ce183"
/> | <img width="300" alt="copy_to_space_confirmed_pr"
src="https://github.com/user-attachments/assets/2020e71a-88b4-4107-9b05-ae90bf7d39f1"
/> |
| Space selector | <img width="300" alt="Space_selector_before"
src="https://github.com/user-attachments/assets/b8ed7269-e6f6-4bc0-bb24-1c53ac451083"
/> | <img width="300" alt="Space_selector_after"
src="https://github.com/user-attachments/assets/770d2141-8642-483f-b72c-bce6d5ebd282"
/> |

### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [x] The risk of inexact conversion: verifying this PR requires manual
checks to ensure that the conversion has not created any regressions in
the style.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Sid 2025-03-22 01:38:47 +01:00 committed by GitHub
parent bce77d761a
commit 960caf9e2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 357 additions and 316 deletions

View file

@ -1,7 +0,0 @@
.spcCopyToSpace__summaryCountBadge {
margin-left: $euiSizeXS;
}
.spcCopyToSpace__missingReferencesIcon {
margin-left: $euiSizeXS;
}

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import './copy_status_summary_indicator.scss';
import { EuiBadge, EuiIconTip, EuiLoadingSpinner } from '@elastic/eui';
import { EuiBadge, EuiIconTip, EuiLoadingSpinner, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import React, { Fragment } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
@ -26,7 +25,8 @@ interface Props {
onDestinationMapChange: (value?: Map<string, string>) => void;
}
const renderIcon = (props: Props) => {
const CopyStatusIcon = (props: Props) => {
const { euiTheme } = useEuiTheme();
const {
space,
summarizedCopyResult,
@ -83,7 +83,11 @@ const renderIcon = (props: Props) => {
}
const missingReferences = hasMissingReferences ? (
<span className="spcCopyToSpace__missingReferencesIcon">
<span
css={css`
margin-left: ${euiTheme.size.xs};
`}
>
<EuiIconTip
type={'link'}
color={'warning'}
@ -134,11 +138,15 @@ const renderIcon = (props: Props) => {
export const CopyStatusSummaryIndicator = (props: Props) => {
const { summarizedCopyResult } = props;
const { euiTheme } = useEuiTheme();
return (
<Fragment>
{renderIcon(props)}
<EuiBadge className="spcCopyToSpace__summaryCountBadge">
<CopyStatusIcon {...props} />
<EuiBadge
css={css`
margin-left: ${euiTheme.size.xs};
`}
>
{summarizedCopyResult.objects.length}
</EuiBadge>
</Fragment>

View file

@ -1,4 +0,0 @@
.spcCopyToSpace__resolveAllConflictsLink {
font-size: $euiFontSizeS;
margin-right: $euiSizeS;
}

View file

@ -89,15 +89,9 @@ describe('ResolveAllConflicts', () => {
<EuiPopover
anchorPosition="downLeft"
button={
<EuiLink
className="spcCopyToSpace__resolveAllConflictsLink"
onClick={[Function]}
>
<Memo(MemoizedFormattedMessage)
defaultMessage="(resolve all)"
id="xpack.spaces.management.copyToSpace.resolveAllConflictsLink"
/>
</EuiLink>
<ResolveAllButton
onButtonClick={[Function]}
/>
}
closePopover={[Function]}
display="inline-block"

View file

@ -5,9 +5,14 @@
* 2.0.
*/
import './resolve_all_conflicts.scss';
import { EuiContextMenuItem, EuiContextMenuPanel, EuiLink, EuiPopover } from '@elastic/eui';
import {
EuiContextMenuItem,
EuiContextMenuPanel,
EuiLink,
EuiPopover,
useEuiFontSize,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
@ -47,20 +52,34 @@ const options: ResolveOption[] = [
},
];
interface ResolveAllButtonProps {
onButtonClick: () => void;
}
const ResolveAllButton = ({ onButtonClick }: ResolveAllButtonProps) => {
const { fontSize } = useEuiFontSize('s');
return (
<EuiLink
onClick={onButtonClick}
css={css`
font-size: ${fontSize};
`}
>
<FormattedMessage
id="xpack.spaces.management.copyToSpace.resolveAllConflictsLink"
defaultMessage="(resolve all)"
/>
</EuiLink>
);
};
export class ResolveAllConflicts extends Component<ResolveAllConflictsProps, State> {
public state = {
isPopoverOpen: false,
};
public render() {
const button = (
<EuiLink onClick={this.onButtonClick} className={'spcCopyToSpace__resolveAllConflictsLink'}>
<FormattedMessage
id="xpack.spaces.management.copyToSpace.resolveAllConflictsLink"
defaultMessage="(resolve all)"
/>
</EuiLink>
);
const button = <ResolveAllButton onButtonClick={this.onButtonClick} />;
const items = options.map((item) => {
return (

View file

@ -1,3 +0,0 @@
.spcCopyToSpace__spacesList {
margin-top: $euiSizeXS;
}

View file

@ -5,10 +5,9 @@
* 2.0.
*/
import './selectable_spaces_control.scss';
import type { EuiSelectableOption } from '@elastic/eui';
import { EuiIconTip, EuiLoadingSpinner, EuiSelectable } from '@elastic/eui';
import { EuiIconTip, EuiLoadingSpinner, EuiSelectable, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import React, { lazy, Suspense } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
@ -33,6 +32,7 @@ interface Props {
type SpaceOption = EuiSelectableOption & { ['data-space-id']: string };
export const SelectableSpacesControl = (props: Props) => {
const { euiTheme } = useEuiTheme();
if (props.spaces.length === 0) {
return <EuiLoadingSpinner />;
}
@ -81,7 +81,9 @@ export const SelectableSpacesControl = (props: Props) => {
listProps={{
bordered: true,
rowHeight: 40,
className: 'spcCopyToSpace__spacesList',
css: css`
margin-top: ${euiTheme.size.xs};
`,
'data-test-subj': 'cts-form-space-selector',
}}
searchable={options.length > SPACE_SEARCH_COUNT_THRESHOLD}

View file

@ -1,4 +0,0 @@
.spcCopyToSpaceResult {
padding-bottom: $euiSizeS;
border-bottom: $euiBorderThin;
}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import './space_result.scss';
import {
EuiAccordion,
EuiFlexGroup,
@ -14,7 +12,9 @@ import {
EuiLoadingSpinner,
EuiSpacer,
EuiText,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { lazy, Suspense, useState } from 'react';
import { CopyStatusSummaryIndicator } from './copy_status_summary_indicator';
@ -47,12 +47,15 @@ const getInitialDestinationMap = (objects: SummarizedCopyToSpaceResult['objects'
export const SpaceResultProcessing = (props: Pick<Props, 'space'>) => {
const { space } = props;
const { euiTheme } = useEuiTheme();
return (
<EuiAccordion
id={`copyToSpace-${space.id}`}
data-test-subj={`cts-space-result-${space.id}`}
className="spcCopyToSpaceResult"
css={css`
padding-bottom: ${euiTheme.size.s};
border-bottom: ${euiTheme.border.thin};
`}
buttonContent={
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>
@ -82,12 +85,16 @@ export const SpaceResult = (props: Props) => {
const onDestinationMapChange = (value?: Map<string, string>) => {
setDestinationMap(value || getInitialDestinationMap(objects));
};
const { euiTheme } = useEuiTheme();
return (
<EuiAccordion
id={`copyToSpace-${space.id}`}
data-test-subj={`cts-space-result-${space.id}`}
className="spcCopyToSpaceResult"
css={css`
padding-bottom: ${euiTheme.size.s};
border-bottom: ${euiTheme.border.thin};
`}
buttonContent={
<EuiFlexGroup responsive={false}>
<EuiFlexItem grow={false}>

View file

@ -1,38 +0,0 @@
.spcCopyToSpaceResultDetails {
margin-top: $euiSizeS;
padding-left: $euiSizeL;
}
.spcCopyToSpaceResultDetails__row {
margin-bottom: $euiSizeXS;
}
.spcCopyToSpaceResultDetails__savedObjectName {
// Constrains name to the flex item, and allows for truncation when necessary
min-width: 0;
}
.spcCopyToSpaceResultDetails__selectControl {
margin-left: $euiSizeL;
}
.spcCopyToSpaceResultDetails__selectControl__childWrapper {
// Derived from euiAccordion
visibility: hidden;
opacity: 0;
height: 0;
overflow: hidden;
transform: translatez(0);
// sass-lint:disable-block indentation
transition:
height $euiAnimSpeedNormal $euiAnimSlightResistance,
opacity $euiAnimSpeedNormal $euiAnimSlightResistance;
}
.spcCopyToSpaceResultDetails__selectControl.spcCopyToSpaceResultDetails__selectControl-isOpen {
.spcCopyToSpaceResultDetails__selectControl__childWrapper {
visibility: visible;
opacity: 1;
height: auto;
}
}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import './space_result_details.scss';
import type { EuiSwitchEvent } from '@elastic/eui';
import {
EuiFlexGroup,
@ -16,7 +14,9 @@ import {
EuiSwitch,
EuiText,
EuiToolTip,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import moment from 'moment';
import React, { Fragment } from 'react';
@ -59,9 +59,15 @@ const isAmbiguousConflictError = (
export const SpaceCopyResultDetails = (props: Props) => {
const { destinationMap, onDestinationMapChange, summarizedCopyResult } = props;
const { objects } = summarizedCopyResult;
const { euiTheme } = useEuiTheme();
return (
<div className="spcCopyToSpaceResultDetails">
<div
css={css`
margin-top: ${euiTheme.size.s};
padding-left: ${euiTheme.size.l};
`}
>
{objects.map((object, index) => {
const { type, id, name, icon, conflict } = object;
const pendingObjectRetry = props.retries.find((r) => r.type === type && r.id === id);
@ -124,10 +130,17 @@ export const SpaceCopyResultDetails = (props: Props) => {
props.onRetriesChange([...filtered, retry]);
},
};
const selectContainerClass =
selectProps.options.length > 0 && isOverwritePending
? ' spcCopyToSpaceResultDetails__selectControl-isOpen'
: '';
const childWrapperStyles = (isOpen: boolean) => css`
overflow: hidden;
transform: translateZ(0);
transition: height ${euiTheme.animation.normal} ${euiTheme.animation.resistance},
${euiTheme.animation.normal} ${euiTheme.animation.resistance};
visibility: ${isOpen ? 'visible' : 'hidden'};
opacity: ${isOpen ? 1 : 0};
height: ${isOpen ? 'auto' : 0};
`;
return (
<Fragment key={index}>
@ -136,14 +149,21 @@ export const SpaceCopyResultDetails = (props: Props) => {
key={index}
alignItems="center"
gutterSize="s"
className="spcCopyToSpaceResultDetails__row"
css={css`
margin-bottom: ${euiTheme.size.xs};
`}
>
<EuiFlexItem grow={false}>
<EuiToolTip position="top" content={getSavedObjectLabel(type)}>
<EuiIcon aria-label={getSavedObjectLabel(type)} type={icon} size="s" />
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={5} className="spcCopyToSpaceResultDetails__savedObjectName">
<EuiFlexItem
grow={5}
css={css`
min-width: 0;
`}
>
<EuiText size="s">
<p className="eui-textTruncate" title={name}>
{name}
@ -161,7 +181,7 @@ export const SpaceCopyResultDetails = (props: Props) => {
/>
</EuiFlexItem>
)}
<EuiFlexItem className="spcCopyToSpaceResultDetails__statusIndicator" grow={false}>
<EuiFlexItem grow={false}>
<div className="eui-textRight">
<CopyStatusIndicator
summarizedCopyResult={props.summarizedCopyResult}
@ -174,8 +194,12 @@ export const SpaceCopyResultDetails = (props: Props) => {
</div>
</EuiFlexItem>
</EuiFlexGroup>
<div className={'spcCopyToSpaceResultDetails__selectControl' + selectContainerClass}>
<div className="spcCopyToSpaceResultDetails__selectControl__childWrapper">
<div
css={css`
margin-left: ${euiTheme.size.l};
`}
>
<div css={childWrapperStyles(selectProps.options.length > 0 && isOverwritePending)}>
<EuiSuperSelect
options={selectProps.options}
valueOfSelected={destinationMap.get(`${type}:${id}`)}

View file

@ -1,3 +0,0 @@
.spcToggleAllFeatures__changeAllLink {
margin-left: $euiSizeS;
}

View file

@ -5,9 +5,14 @@
* 2.0.
*/
import './toggle_all_features.scss';
import { EuiContextMenuItem, EuiContextMenuPanel, EuiLink, EuiPopover } from '@elastic/eui';
import {
EuiContextMenuItem,
EuiContextMenuPanel,
EuiLink,
EuiPopover,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { Component } from 'react';
import { i18n } from '@kbn/i18n';
@ -42,20 +47,31 @@ const options: ToggleOption[] = [
},
];
const ToggleButton = ({ onClick }: { onClick: () => void }) => {
const { euiTheme } = useEuiTheme();
return (
<EuiLink
onClick={onClick}
css={css`
margin-left: ${euiTheme.size.s};
`}
>
<FormattedMessage
id="xpack.spaces.management.toggleAllFeaturesLink"
defaultMessage="(change all)"
/>
</EuiLink>
);
};
export class ToggleAllFeatures extends Component<Props, State> {
public state = {
isPopoverOpen: false,
};
public render() {
const button = (
<EuiLink onClick={this.onButtonClick} className={'spcToggleAllFeatures__changeAllLink'}>
<FormattedMessage
id="xpack.spaces.management.toggleAllFeaturesLink"
defaultMessage="(change all)"
/>
</EuiLink>
);
const button = <ToggleButton onClick={this.onButtonClick} />;
const items = options.map((item) => {
return (

View file

@ -5,29 +5,10 @@ exports[`it renders without blowing up 1`] = `
hasBorder={true}
hasShadow={false}
>
<EuiFlexGroup
alignItems="baseline"
gutterSize="s"
responsive={false}
>
<EuiFlexItem
grow={false}
>
<EuiTitle
size="s"
>
<h2>
<EuiIcon
className="spcSectionPanel__collapsiblePanelLogo"
size="xl"
type="logoElasticsearch"
/>
Elasticsearch
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<SectionPanelTitle
iconType="logoElasticsearch"
title="Elasticsearch"
/>
<EuiSpacer />
<p>
child

View file

@ -1,4 +0,0 @@
.spcSectionPanel__collapsiblePanelLogo {
margin-right: $euiSizeS;
vertical-align: text-bottom;
}

View file

@ -6,7 +6,8 @@
*/
import type { IconType } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import { EuiFlexGroup, EuiIcon, EuiPanel, EuiSpacer, EuiTitle, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import type { ReactNode } from 'react';
import React, { Component, Fragment } from 'react';
@ -16,6 +17,37 @@ interface Props {
dataTestSubj?: string;
}
const SectionPanelTitle = ({
iconType,
title,
}: {
iconType?: IconType;
title: string | ReactNode;
}) => {
const { euiTheme } = useEuiTheme();
return (
<EuiFlexGroup alignItems={'baseline'} gutterSize="s" responsive={false}>
<EuiTitle size="s">
<h2>
{iconType && (
<Fragment>
<EuiIcon
type={iconType}
size={'xl'}
css={css`
vertical-align: text-bottom;
margin-right: ${euiTheme.size.s};
`}
/>
</Fragment>
)}
{title}
</h2>
</EuiTitle>
</EuiFlexGroup>
);
};
export class SectionPanel extends Component<React.PropsWithChildren<Props>, {}> {
public render() {
return (
@ -31,26 +63,7 @@ export class SectionPanel extends Component<React.PropsWithChildren<Props>, {}>
return null;
}
return (
<EuiFlexGroup alignItems={'baseline'} gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<EuiTitle size="s">
<h2>
{this.props.iconType && (
<Fragment>
<EuiIcon
type={this.props.iconType}
size={'xl'}
className={'spcSectionPanel__collapsiblePanelLogo'}
/>{' '}
</Fragment>
)}
{this.props.title}
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
);
return <SectionPanelTitle iconType={this.props.iconType} title={this.props.title as string} />;
};
public getForm = () => {

View file

@ -4,6 +4,18 @@ exports[`ManageSpacesButton doesn't render if user profile forbids managing spac
exports[`ManageSpacesButton renders as expected 1`] = `
<EuiButton
css={
Object {
"map": undefined,
"name": "e9jljh",
"next": undefined,
"styles": "
margin: 12px;
width: calc(100% - 12px * 2);
",
"toString": [Function],
}
}
data-test-subj="manageSpaces"
onClick={[Function]}
size="s"

View file

@ -5,16 +5,16 @@
* 2.0.
*/
import { EuiButton } from '@elastic/eui';
import { EuiButton, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import type { CSSProperties } from 'react';
import React, { Component } from 'react';
import React from 'react';
import type { ApplicationStart, Capabilities } from '@kbn/core/public';
import { FormattedMessage } from '@kbn/i18n-react';
interface Props {
isDisabled?: boolean;
className?: string;
size?: 's' | 'm';
style?: CSSProperties;
onClick?: () => void;
@ -22,34 +22,43 @@ interface Props {
navigateToApp: ApplicationStart['navigateToApp'];
}
export class ManageSpacesButton extends Component<Props, {}> {
public render() {
if (!this.props.capabilities.spaces.manage) {
return null;
export const ManageSpacesButton: React.FC<Props> = ({
isDisabled,
size,
style,
onClick,
capabilities,
navigateToApp,
}) => {
const { euiTheme } = useEuiTheme();
const navigateToManageSpaces = () => {
if (onClick) {
onClick();
}
return (
<EuiButton
size={this.props.size || 's'}
className={this.props.className}
isDisabled={this.props.isDisabled}
onClick={this.navigateToManageSpaces}
style={this.props.style}
data-test-subj="manageSpaces"
>
<FormattedMessage
id="xpack.spaces.manageSpacesButton.manageSpacesButtonLabel"
defaultMessage="Manage spaces"
/>
</EuiButton>
);
navigateToApp('management', { path: 'kibana/spaces' });
};
if (!capabilities.spaces.manage) {
return null;
}
private navigateToManageSpaces = () => {
if (this.props.onClick) {
this.props.onClick();
}
this.props.navigateToApp('management', { path: 'kibana/spaces' });
};
}
return (
<EuiButton
size={size || 's'}
isDisabled={isDisabled}
onClick={navigateToManageSpaces}
style={style}
data-test-subj="manageSpaces"
css={css`
margin: ${euiTheme.size.m};
width: calc(100% - ${euiTheme.size.m} * 2);
`}
>
<FormattedMessage
id="xpack.spaces.manageSpacesButton.manageSpacesButtonLabel"
defaultMessage="Manage spaces"
/>
</EuiButton>
);
};

View file

@ -1,8 +0,0 @@
.spcDescription {
max-width: $euiSizeL * 10;
}
.spcDescription__text,
.spcDescription__manageButtonWrapper {
padding: $euiSizeM;
}

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import './spaces_description.scss';
import { EuiContextMenuPanel, EuiText } from '@elastic/eui';
import { EuiContextMenuPanel, EuiText, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import type { FC } from 'react';
import React from 'react';
@ -26,9 +25,13 @@ interface Props {
}
export const SpacesDescription: FC<Props> = (props: Props) => {
const { euiTheme } = useEuiTheme();
const panelProps = {
id: props.id,
className: 'spcDescription',
css: css`
max-width: calc(${euiTheme.size.l} * 10);
`,
title: 'Spaces',
};
@ -38,10 +41,19 @@ export const SpacesDescription: FC<Props> = (props: Props) => {
return (
<EuiContextMenuPanel {...panelProps}>
<EuiText className="spcDescription__text">
<EuiText
css={css`
padding: ${euiTheme.size.m};
`}
>
<p>{props.isLoading ? spacesLoadingMessage : getSpacesFeatureDescription()}</p>
</EuiText>
<div key="manageSpacesButton" className="spcDescription__manageButtonWrapper">
<div
key="manageSpacesButton"
css={css`
padding: ${euiTheme.size.m};
`}
>
<ManageSpacesButton
size="s"
style={{ width: `100%` }}

View file

@ -1,22 +0,0 @@
.spcMenu {
max-width: $euiSizeL * 10;
}
.spcMenu__spacesList {
max-height: $euiSizeXL * 10;
@include euiYScrollWithShadows;
}
.spcMenu__searchFieldWrapper {
padding: $euiSizeM;
}
.spcMenu__manageButton {
margin: $euiSizeM;
width: calc(100% - #{$euiSizeM*2});
}
.spcMenu__item {
margin-left: $euiSizeM;
}

View file

@ -5,21 +5,21 @@
* 2.0.
*/
import './spaces_menu.scss';
import type { ExclusiveUnion } from '@elastic/eui';
import type { ExclusiveUnion, WithEuiThemeProps } from '@elastic/eui';
import {
EuiLoadingSpinner,
EuiPopoverFooter,
EuiPopoverTitle,
EuiSelectable,
EuiText,
withEuiTheme,
} from '@elastic/eui';
import type { EuiSelectableOption } from '@elastic/eui/src/components/selectable';
import type {
EuiSelectableOnChangeEvent,
EuiSelectableSearchableSearchProps,
} from '@elastic/eui/src/components/selectable/selectable';
import { css } from '@emotion/react';
import React, { Component, Fragment, lazy, Suspense } from 'react';
import type { ApplicationStart, Capabilities } from '@kbn/core/public';
@ -52,9 +52,10 @@ interface Props {
allowSolutionVisibility: boolean;
eventTracker: EventTracker;
}
class SpacesMenuUI extends Component<Props> {
class SpacesMenuUI extends Component<Props & WithEuiThemeProps> {
public render() {
const spaceOptions: EuiSelectableOption[] = this.getSpaceOptions();
const { euiTheme } = this.props.theme;
const noSpacesMessage = (
<EuiText color="subdued" className="eui-textCenter">
@ -96,7 +97,9 @@ class SpacesMenuUI extends Component<Props> {
defaultMessage: 'Spaces',
})}
id={this.props.id}
className={'spcMenu'}
css={css`
max-width: calc(${euiTheme.size.l} * 10);
`}
title={i18n.translate('xpack.spaces.navControl.spacesMenu.changeCurrentSpaceTitle', {
defaultMessage: 'Change current space',
})}
@ -217,7 +220,6 @@ class SpacesMenuUI extends Component<Props> {
return (
<ManageSpacesButton
key="manageSpacesButton"
className="spcMenu__manageButton"
size="s"
onClick={this.props.onClickManageSpaceBtn}
capabilities={this.props.capabilities}
@ -227,4 +229,4 @@ class SpacesMenuUI extends Component<Props> {
};
}
export const SpacesMenu = injectI18n(SpacesMenuUI);
export const SpacesMenu = withEuiTheme(injectI18n(SpacesMenuUI));

View file

@ -1,3 +0,0 @@
.spcShareToSpace__spacesList {
margin-top: $euiSizeXS;
}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import './selectable_spaces_control.scss';
import type { EuiSelectableOption } from '@elastic/eui';
import {
EuiBadge,
@ -18,7 +16,9 @@ import {
EuiLoadingSpinner,
EuiSelectable,
EuiText,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { lazy, Suspense } from 'react';
import { i18n } from '@kbn/i18n';
@ -107,6 +107,7 @@ export const SelectableSpacesControl = (props: Props) => {
prohibitedSpaces,
} = props;
const { services } = useSpaces();
const { euiTheme } = useEuiTheme();
const { application, docLinks } = services;
const { selectedSpaceIds, initiallySelectedSpaceIds } = shareOptions;
@ -235,7 +236,9 @@ export const SelectableSpacesControl = (props: Props) => {
listProps={{
bordered: true,
rowHeight: ROW_HEIGHT,
className: 'spcShareToSpace__spacesList',
css: css`
margin-top: ${euiTheme.size.xs};
`,
'data-test-subj': 'sts-form-space-selector',
}}
height="full"

View file

@ -1,3 +0,0 @@
.spcShareToSpace__flyoutBodyWrapper {
padding: $euiSizeL;
}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import './share_to_space_flyout_internal.scss';
import {
EuiButton,
EuiButtonEmpty,
@ -20,7 +18,9 @@ import {
EuiSpacer,
EuiText,
EuiTitle,
useEuiTheme,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { lazy, Suspense, useEffect, useMemo, useState } from 'react';
import type { ToastsStart } from '@kbn/core/public';
@ -161,6 +161,7 @@ function createDefaultChangeSpacesHandler(
export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => {
const { spacesManager, spacesDataPromise, services } = useSpaces();
const { euiTheme } = useEuiTheme();
const { notifications } = services;
const toastNotifications = notifications!.toasts;
@ -487,7 +488,10 @@ export const ShareToSpaceFlyoutInternal = (props: ShareToSpaceFlyoutProps) => {
<EuiFlexGroup
direction="column"
gutterSize="none"
className="spcShareToSpace__flyoutBodyWrapper eui-yScroll"
className="eui-yScroll"
css={css({
padding: euiTheme.size.l,
})}
responsive={false}
>
<EuiFlexItem grow={false}>

View file

@ -2,7 +2,17 @@
exports[`it renders with custom logo 1`] = `
<_KibanaPageTemplate
className="spcSpaceSelector"
css={
Object {
"map": undefined,
"name": "1si5isr",
"next": undefined,
"styles": "
background-color: transparent;
",
"toString": [Function],
}
}
data-test-subj="kibanaSpaceSelector"
>
<EuiPortal>
@ -34,8 +44,21 @@ exports[`it renders with custom logo 1`] = `
color="subdued"
>
<h1
className="eui spcSpaceSelector__pageHeader"
tabIndex={0}
css={
Object {
"map": undefined,
"name": "1tq4o89",
"next": undefined,
"styles": "
&:focus {
outline: none;
text-decoration: underline;
}
",
"toString": [Function],
}
}
tabIndex={-1}
>
<MemoizedFormattedMessage
defaultMessage="Select your space"
@ -62,7 +85,17 @@ exports[`it renders with custom logo 1`] = `
exports[`it renders without crashing 1`] = `
<_KibanaPageTemplate
className="spcSpaceSelector"
css={
Object {
"map": undefined,
"name": "1si5isr",
"next": undefined,
"styles": "
background-color: transparent;
",
"toString": [Function],
}
}
data-test-subj="kibanaSpaceSelector"
>
<EuiPortal>
@ -93,8 +126,21 @@ exports[`it renders without crashing 1`] = `
color="subdued"
>
<h1
className="eui spcSpaceSelector__pageHeader"
tabIndex={0}
css={
Object {
"map": undefined,
"name": "1tq4o89",
"next": undefined,
"styles": "
&:focus {
outline: none;
text-decoration: underline;
}
",
"toString": [Function],
}
}
tabIndex={-1}
>
<MemoizedFormattedMessage
defaultMessage="Select your space"

View file

@ -1,10 +0,0 @@
.spaceCard {
width: $euiSizeL * 10 !important; // sass-lint:disable-line no-important
min-height: $euiSize * 12.5; // 200px
// SASSTODO: Fix EuiCard to truncate or forcewrap long text
.euiCard__content {
overflow: hidden;
}
}

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import './space_card.scss';
import { EuiCard, EuiLoadingSpinner, EuiTextColor } from '@elastic/eui';
import { EuiCard, EuiLoadingSpinner, EuiTextColor, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import React, { lazy, Suspense } from 'react';
import type { Space } from '../../../common';
@ -25,10 +24,18 @@ interface Props {
}
export const SpaceCard = (props: Props) => {
const { serverBasePath, space } = props;
const { euiTheme } = useEuiTheme();
return (
<EuiCard
className="spaceCard"
css={css`
width: calc(${euiTheme.size.l} * 10) !important;
min-height: calc(${euiTheme.size.base} * 12.5); /* 200px */
.euiCard__content {
overflow: hidden;
}
`}
data-test-subj={`space-card-${space.id}`}
icon={renderSpaceAvatar(space)}
title={space.name}

View file

@ -1,4 +0,0 @@
.spaceCards {
max-width: 1200px;
margin: auto;
}

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import './space_cards.scss';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React, { Component } from 'react';
@ -21,7 +19,7 @@ interface Props {
export class SpaceCards extends Component<Props, {}> {
public render() {
return (
<div className="spaceCards">
<div>
<EuiFlexGroup gutterSize="l" justifyContent="center" wrap responsive={false}>
{this.props.spaces.map(this.renderSpace)}
</EuiFlexGroup>

View file

@ -1,7 +1,3 @@
.spcSpaceSelector {
background: transparent;
}
.spcSelectorBackground {
@include kibanaFullScreenGraphics;
}
@ -10,23 +6,3 @@
z-index: -1;
pointer-events: none;
}
// Fix forced focus outline on text that isn't a link to just be an underline
.spcSpaceSelector__pageHeader {
&:focus {
outline: none;
text-decoration: underline;
}
}
.spcSpaceSelector__searchHolder {
width: $euiFormMaxWidth; // make sure it's as wide as our default form element width
max-width: 100%;
margin-inline: auto;
}
.spcSpaceSelector__errorPanel {
text-align: center;
margin-inline: auto;
max-width: 700px;
}

View file

@ -18,6 +18,7 @@ import {
EuiTextColor,
EuiTitle,
} from '@elastic/eui';
import { css } from '@emotion/react';
import React, { Component, Fragment } from 'react';
import ReactDOM from 'react-dom';
import type { Observable, Subscription } from 'rxjs';
@ -29,6 +30,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaSolutionAvatar } from '@kbn/shared-ux-avatar-solution';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { euiThemeVars } from '@kbn/ui-theme';
import { SpaceCards } from './components';
import type { Space } from '../../common';
@ -107,6 +109,12 @@ export class SpaceSelector extends Component<Props, State> {
public render() {
const { spaces, searchTerm } = this.state;
const panelStyles = css`
text-align: center;
margin-inline: auto;
max-width: 700px;
`;
let filteredSpaces = spaces;
if (searchTerm) {
filteredSpaces = spaces.filter(
@ -117,7 +125,12 @@ export class SpaceSelector extends Component<Props, State> {
}
return (
<KibanaPageTemplate className="spcSpaceSelector" data-test-subj="kibanaSpaceSelector">
<KibanaPageTemplate
css={css`
background-color: transparent;
`}
data-test-subj="kibanaSpaceSelector"
>
{/* Portal the fixed background graphic so it doesn't affect page positioning or overlap on top of global banners */}
<EuiPortal>
<div
@ -143,9 +156,13 @@ export class SpaceSelector extends Component<Props, State> {
<EuiSpacer size="xxl" />
<EuiTextColor color="subdued">
<h1
// plain `eui` class undos forced focus style on non-EUI components
className="eui spcSpaceSelector__pageHeader"
tabIndex={0}
css={css`
&:focus {
outline: none;
text-decoration: underline;
}
`}
tabIndex={-1}
ref={this.setHeaderRef}
>
<FormattedMessage
@ -174,7 +191,7 @@ export class SpaceSelector extends Component<Props, State> {
{!this.state.loading && !this.state.error && filteredSpaces.length === 0 && (
<Fragment>
<EuiSpacer />
<EuiPanel className="spcSpaceSelector__errorPanel" color="subdued">
<EuiPanel css={panelStyles} color="subdued">
<EuiTitle size="xs">
<h2>
{i18n.translate(
@ -193,7 +210,7 @@ export class SpaceSelector extends Component<Props, State> {
{!this.state.loading && this.state.error && (
<Fragment>
<EuiSpacer />
<EuiPanel color="danger" className="spcSpaceSelector__errorPanel">
<EuiPanel css={panelStyles} color="danger">
<EuiText size="s" color="danger">
<h2>
<FormattedMessage
@ -228,7 +245,13 @@ export class SpaceSelector extends Component<Props, State> {
return (
<>
<div className="spcSpaceSelector__searchHolder">
<div
css={css`
width: ${euiThemeVars.euiFormMaxWidth};
max-width: 100%;
margin-inline: auto;
`}
>
<EuiFieldSearch
placeholder={inputLabel}
aria-label={inputLabel}

View file

@ -1,9 +1,9 @@
{
"extends": "../../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"outDir": "target/types"
},
"include": ["common/**/*", "public/**/*", "server/**/*"],
"include": ["common/**/*", "public/**/*", "server/**/*", "../../../../../typings/**/*"],
"kbn_references": [
"@kbn/features-plugin",
"@kbn/licensing-plugin",
@ -55,9 +55,7 @@
"@kbn/ui-theme",
"@kbn/core-chrome-browser",
"@kbn/core-lifecycle-server",
"@kbn/core-user-profile-browser-mocks",
"@kbn/core-user-profile-browser-mocks"
],
"exclude": [
"target/**/*",
]
"exclude": ["target/**/*"]
}