Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions src/core/public/_mixins.scss

This file was deleted.

51 changes: 44 additions & 7 deletions src/core/public/cssUtils.ts → src/core/public/css_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

// This file replaces scss core/public/_mixins.scss

import { css, keyframes } from '@emotion/react';
import { COLOR_MODES_STANDARD, UseEuiTheme, euiCanAnimate } from '@elastic/eui';
import { Interpolation, Theme, css, keyframes } from '@emotion/react';
import { COLOR_MODES_STANDARD, UseEuiTheme, euiCanAnimate, useEuiTheme } from '@elastic/eui';
import { useMemo } from 'react';
import bg_top_branded from './styles/core_app/images/bg_top_branded.svg';
import bg_top_branded_dark from './styles/core_app/images/bg_top_branded_dark.svg';
import bg_bottom_branded from './styles/core_app/images/bg_bottom_branded.svg';
Expand All @@ -19,11 +20,10 @@ import bg_bottom_branded_dark from './styles/core_app/images/bg_bottom_branded_d
// The `--kbnAppHeadersOffset` CSS variable is automatically updated by
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File renamed because the linter complained about the name not following snake_case format.

// styles/rendering/_base.scss, based on whether the Kibana chrome has a
// header banner, app menu, and is visible or hidden
export const kibanaFullBodyHeightCss = (additionalOffset = 0) => css`
height: calc(
100vh - var(--kbnAppHeadersOffset, var(--euiFixedHeadersOffset, 0)) - ${additionalOffset}px
);
`;
export const kibanaFullBodyHeightCss = (additionalOffset = '0px') =>
css({
height: `calc(100vh - var(--kbnAppHeadersOffset, var(--euiFixedHeadersOffset, 0)) - ${additionalOffset})`,
});

export const fullScreenGraphicsMixinStyles = (euiZLevel: number, euiTheme: UseEuiTheme) => {
const lightOrDarkTheme = (lightSvg: any, darkSvg: any) => {
Expand Down Expand Up @@ -79,3 +79,40 @@ export const fullScreenGraphicsMixinStyles = (euiZLevel: number, euiTheme: UseEu
},
});
};

type StyleMap = Record<
string,
Interpolation<Theme> | ((theme: UseEuiTheme) => Interpolation<Theme>)
>;

type StaticStyleMap = Record<string, Interpolation<Theme>>;

/**
* Custom hook to reduce boilerplate when working with Emotion styles that may depend on
* the EUI theme.
*
* Accepts a map of styles where each entry is either a static Emotion style (via `css`)
* or a function that returns styles based on the current `euiTheme`.
*
* It returns a memoized version of the style map with all values resolved to static
* Emotion styles, allowing components to use a clean and unified object for styling.
*
* This helps simplify component code by centralizing theme-aware style logic.
*
* Example usage:
* const componentStyles = {
* container: css({ overflow: hidden }),
* leftPane: ({ euiTheme }) => css({ paddingTop: euiTheme.size.m }),
* }
* const styles = useMemoizedStyles(componentStyles);
*/
export const useMemoizedStyles = (styleMap: StyleMap) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is neat!

Not intended as a blocker, but I wonder if we don't achieve a decent enough developer UX when grabbing the euiTheme object from css prop on components (e.g.).

Then to get the style definition "statically" colocated outside components we could use TS like:

// css_utils.ts
import type { Attributes } from 'react';

// Helper type for getting CSS attribute from React
export type CSSAttribute = Required<Attributes>['css'];
export type StyleMap = Record<string, CSSAttribute>;

// somewhere_else.ts
import type { CSSAttribute, StyleMap } from 'css_utils'

const styles: CSSAttribute = ({ euiTheme }) => ({
  '& + &': { marginTop: euiTheme.size.s },
  backgroundColor: euiTheme.colors.backgroundBasePrimary,
  body: '1vh',
  // ...
});

<MyComponent css={styles} />

// or

const styles = {
  container: () => ...
} satisfies StyleMap;

<MyComponent css={styles.container} />

...but I'm not an emotion expert! Perhaps your way is more performant/easier to use.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mbondyra, can you help me answer this question? 😇

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @jloleysens — it's mostly a performance thing 🙂 With this approach, all styles are memoized.

That syntactic sugar you mentioned can actually hurt performance, since the function runs and re-serializes styles on every render—even though it looks cleaner in the JSX. There's also a small mistake in this comment: elastic/eui#6828 (comment) — defining the function outside the component doesn't help, because it still runs on each render as long as it’s a function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation, that makes sense 👍🏻

const euiThemeContext = useEuiTheme();
const outputStyles = useMemo(() => {
return Object.entries(styleMap).reduce<StaticStyleMap>((acc, [key, value]) => {
acc[key] = typeof value === 'function' ? value(euiThemeContext) : value;
return acc;
}, {});
}, [euiThemeContext, styleMap]);
return outputStyles;
};
1 change: 0 additions & 1 deletion src/core/public/index.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
@import './css_variables';
@import './mixins';
@import './styles/index';
6 changes: 5 additions & 1 deletion src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,4 +309,8 @@ export type { CoreSystem } from '@kbn/core-root-browser-internal';

export { __kbnBootstrap__ } from '@kbn/core-root-browser-internal';

export { kibanaFullBodyHeightCss, fullScreenGraphicsMixinStyles } from './cssUtils';
export {
kibanaFullBodyHeightCss,
fullScreenGraphicsMixinStyles,
useMemoizedStyles,
} from './css_utils';
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@import '../../../../../../../src/core/public/mixins';

.dshUnsavedListingItem {
margin-top: $euiSizeM;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,11 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
})}
</h1>
<TopNavMenu {...getNavBarProps()} />
<EuiPage css={dscDocsPageCss}>
<EuiPage css={styles.docsPage}>
<EuiPageBody
panelled
paddingSize="none"
css={dscDocsContentCss}
css={styles.docsContent}
panelProps={{ role: 'main' }}
>
<EuiText
Expand Down Expand Up @@ -325,12 +325,11 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
);
};

const dscDocsPageCss = css`
${kibanaFullBodyHeightCss(54)}; // 54px is the action bar height
`;

const dscDocsContentCss = css`
display: flex;
flex-direction: column;
height: 100%;
`;
const styles = {
docsContent: css({
display: 'flex',
flexDirection: 'column',
height: '100%',
}),
docsPage: kibanaFullBodyHeightCss('54px'), // 54px is the action bar height
};
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
background-color: ${pageBackgroundColor};

${useEuiBreakpoint(['m', 'l', 'xl'])} {
${kibanaFullBodyHeightCss(TABS_ENABLED ? 32 : undefined)}
${kibanaFullBodyHeightCss(TABS_ENABLED ? '32px' : undefined)}
}
`}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/

import React, { useState, useEffect } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui';
import { css } from '@emotion/react';
import { kibanaFullBodyHeightCss, useMemoizedStyles } from '@kbn/core/public';
import { EuiFlexGroup, EuiFlexItem, EuiTitle, UseEuiTheme } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { formatRequestPayload, formatJson } from '../lib/format';
import { exampleScript } from '../constants';
Expand All @@ -18,6 +20,31 @@ import { MainControls } from './main_controls';
import { Editor } from './editor';
import { RequestFlyout } from './request_flyout';

const mainStyles = {
container: css({
// The panel's container should adopt the height of the main container
height: '100%',
}),
leftPane: ({ euiTheme }: UseEuiTheme) =>
css({
paddingTop: euiTheme.size.m,
backgroundColor: euiTheme.colors.emptyShade,
}),
mainContainer: ({ euiTheme }: UseEuiTheme) => {
/**
* This is a very brittle way of preventing the editor and other content from disappearing
* behind the bottom bar.
*/
const bottomBarHeight = `(${euiTheme.size.base} * 3)`;

// adding dev tool top bar + bottom bar height to the body offset
// (they're both the same height, hence the x2)
const bodyOffset = `(${bottomBarHeight} * 2)`;

return kibanaFullBodyHeightCss(bodyOffset);
},
};

export const Main: React.FunctionComponent = () => {
const {
store: { payload, validation },
Expand All @@ -26,6 +53,7 @@ export const Main: React.FunctionComponent = () => {
links,
} = useAppContext();

const styles = useMemoizedStyles(mainStyles);
const [isRequestFlyoutOpen, setRequestFlyoutOpen] = useState(false);
const { inProgress, response, submit } = useSubmitCode(http);

Expand All @@ -41,9 +69,9 @@ export const Main: React.FunctionComponent = () => {
};

return (
<div className="painlessLabMainContainer">
<EuiFlexGroup className="painlessLabPanelsContainer" responsive={false} gutterSize="none">
<EuiFlexItem className="painlessLabLeftPane">
<div css={styles.mainContainer}>
<EuiFlexGroup responsive={false} gutterSize="none" css={styles.container}>
<EuiFlexItem css={styles.leftPane}>
<EuiTitle className="euiScreenReaderOnly">
<h1>
{i18n.translate('xpack.painlessLab.title', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,44 @@ import {
EuiFlexItem,
EuiLoadingSpinner,
EuiTabbedContent,
UseEuiTheme,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';

import { useMemoizedStyles } from '@kbn/core/public';
import { Response } from '../../types';
import { OutputTab } from './output_tab';
import { ParametersTab } from './parameters_tab';
import { ContextTab } from './context_tab';

const componentStyles = {
rightPane: ({ euiTheme }: UseEuiTheme) =>
css({
backgroundColor: euiTheme.colors.emptyShade,
padding: euiTheme.size.s,
borderLeft: euiTheme.border.thin,
height: '100%',
}),
tabsStyles: css({
display: 'flex',
flexDirection: 'column',
height: '100%',

"[role='tabpanel']": {
height: '100%',
overflowY: 'auto',
},
}),
};

interface Props {
isLoading: boolean;
response?: Response;
}

export const OutputPane: FunctionComponent<Props> = ({ isLoading, response }) => {
const styles = useMemoizedStyles(componentStyles);
const outputTabLabel = (
<EuiFlexGroup gutterSize="s" alignItems="center" responsive={false}>
<EuiFlexItem grow={false}>
Expand All @@ -47,9 +71,9 @@ export const OutputPane: FunctionComponent<Props> = ({ isLoading, response }) =>
);

return (
<div className="painlessLabRightPane">
<div css={styles.rightPane}>
<EuiTabbedContent
className="painlessLabRightPane__tabs"
css={styles.tabsStyles}
data-test-subj={isLoading ? `painlessTabs-loading` : `painlessTabs-loaded`}
size="s"
tabs={[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ export const RequestFlyout: FunctionComponent<Props> = ({
},
]}
/>

<div className="painlessLabBottomBarPlaceholder" />
</EuiFlyoutBody>
</EuiFlyout>
);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* 2.0.
*/

import './styles/_index.scss';
import { PainlessLabUIPlugin } from './plugin';

export function plugin() {
Expand Down

This file was deleted.

3 changes: 1 addition & 2 deletions x-pack/platform/plugins/shared/maps/public/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
// mapChart__legend-isLoading

@import 'mixins';
@import 'main';
@import 'mapbox_hacks';
@import 'connected_components/index';
@import 'components/index';
@import 'classes/index';
@import 'animations';
@import 'react_embeddable/index';
@import 'react_embeddable/index';
26 changes: 0 additions & 26 deletions x-pack/platform/plugins/shared/maps/public/_main.scss

This file was deleted.

Loading