diff --git a/src/core/public/_mixins.scss b/src/core/public/_mixins.scss deleted file mode 100644 index 0c6a8571f9e75..0000000000000 --- a/src/core/public/_mixins.scss +++ /dev/null @@ -1,10 +0,0 @@ -@mixin kibanaFullBodyHeight($additionalOffset: 0) { - // The `--kbnAppHeadersOffset` CSS variable is automatically updated by - // styles/rendering/_base.scss, based on whether the Kibana chrome has a - // header banner, app menu, and is visible or hidden - height: calc( - 100vh - - var(--kbnAppHeadersOffset, var(--euiFixedHeadersOffset, 0)) - - #{$additionalOffset} - ); -} diff --git a/src/core/public/cssUtils.ts b/src/core/public/css_utils.ts similarity index 59% rename from src/core/public/cssUtils.ts rename to src/core/public/css_utils.ts index a5423ec98cf3d..56f1f42dcf1fe 100644 --- a/src/core/public/cssUtils.ts +++ b/src/core/public/css_utils.ts @@ -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'; @@ -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 // 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) => { @@ -79,3 +79,40 @@ export const fullScreenGraphicsMixinStyles = (euiZLevel: number, euiTheme: UseEu }, }); }; + +type StyleMap = Record< + string, + Interpolation | ((theme: UseEuiTheme) => Interpolation) +>; + +type StaticStyleMap = Record>; + +/** + * 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) => { + const euiThemeContext = useEuiTheme(); + const outputStyles = useMemo(() => { + return Object.entries(styleMap).reduce((acc, [key, value]) => { + acc[key] = typeof value === 'function' ? value(euiThemeContext) : value; + return acc; + }, {}); + }, [euiThemeContext, styleMap]); + return outputStyles; +}; diff --git a/src/core/public/index.scss b/src/core/public/index.scss index db05def14bc0c..18fc47a14397d 100644 --- a/src/core/public/index.scss +++ b/src/core/public/index.scss @@ -1,3 +1,2 @@ @import './css_variables'; -@import './mixins'; @import './styles/index'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 0f3d8b7be280c..7a6ab3fd3f3c0 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -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'; diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_app/_dashboard_app.scss b/src/platform/plugins/shared/dashboard/public/dashboard_app/_dashboard_app.scss index dfef1bf0c4ec9..45a2d66e18b63 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_app/_dashboard_app.scss +++ b/src/platform/plugins/shared/dashboard/public/dashboard_app/_dashboard_app.scss @@ -1,5 +1,3 @@ -@import '../../../../../../../src/core/public/mixins'; - .dshUnsavedListingItem { margin-top: $euiSizeM; } diff --git a/src/platform/plugins/shared/discover/public/application/context/context_app.tsx b/src/platform/plugins/shared/discover/public/application/context/context_app.tsx index a638058c6afca..dcf9e702477da 100644 --- a/src/platform/plugins/shared/discover/public/application/context/context_app.tsx +++ b/src/platform/plugins/shared/discover/public/application/context/context_app.tsx @@ -265,11 +265,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 +}; diff --git a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx index 6f36a3d32e505..b9d2e149e8e87 100644 --- a/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/platform/plugins/shared/discover/public/application/main/components/layout/discover_layout.tsx @@ -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)} } `} > diff --git a/x-pack/platform/plugins/private/painless_lab/public/application/components/main.tsx b/x-pack/platform/plugins/private/painless_lab/public/application/components/main.tsx index ab7b27400f821..60cbbe577a6cd 100644 --- a/x-pack/platform/plugins/private/painless_lab/public/application/components/main.tsx +++ b/x-pack/platform/plugins/private/painless_lab/public/application/components/main.tsx @@ -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'; @@ -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 }, @@ -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); @@ -41,9 +69,9 @@ export const Main: React.FunctionComponent = () => { }; return ( -
- - +
+ +

{i18n.translate('xpack.painlessLab.title', { diff --git a/x-pack/platform/plugins/private/painless_lab/public/application/components/output_pane/output_pane.tsx b/x-pack/platform/plugins/private/painless_lab/public/application/components/output_pane/output_pane.tsx index 37f076f365bfe..83dc6fc71146c 100644 --- a/x-pack/platform/plugins/private/painless_lab/public/application/components/output_pane/output_pane.tsx +++ b/x-pack/platform/plugins/private/painless_lab/public/application/components/output_pane/output_pane.tsx @@ -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 = ({ isLoading, response }) => { + const styles = useMemoizedStyles(componentStyles); const outputTabLabel = ( @@ -47,9 +71,9 @@ export const OutputPane: FunctionComponent = ({ isLoading, response }) => ); return ( -
+
= ({ }, ]} /> - -
); diff --git a/x-pack/platform/plugins/private/painless_lab/public/index.scss b/x-pack/platform/plugins/private/painless_lab/public/index.scss deleted file mode 100644 index 29a5761255278..0000000000000 --- a/x-pack/platform/plugins/private/painless_lab/public/index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'styles/index'; diff --git a/x-pack/platform/plugins/private/painless_lab/public/index.ts b/x-pack/platform/plugins/private/painless_lab/public/index.ts index 7598c0dfee20f..b77b5ac01c5e0 100644 --- a/x-pack/platform/plugins/private/painless_lab/public/index.ts +++ b/x-pack/platform/plugins/private/painless_lab/public/index.ts @@ -5,7 +5,6 @@ * 2.0. */ -import './styles/_index.scss'; import { PainlessLabUIPlugin } from './plugin'; export function plugin() { diff --git a/x-pack/platform/plugins/private/painless_lab/public/styles/_index.scss b/x-pack/platform/plugins/private/painless_lab/public/styles/_index.scss deleted file mode 100644 index e67d7fb01fa31..0000000000000 --- a/x-pack/platform/plugins/private/painless_lab/public/styles/_index.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import '../../../../../../../src/core/public/mixins'; - -/** - * This is a very brittle way of preventing the editor and other content from disappearing - * behind the bottom bar. - */ -$bottomBarHeight: $euiSize * 3; - -.painlessLabBottomBarPlaceholder { - height: $bottomBarHeight; -} - -.painlessLabLeftPane { - padding-top: $euiSizeM; - background-color: $euiColorEmptyShade; -} - -.painlessLabRightPane { - background-color: $euiColorEmptyShade; - padding: $euiSizeS; - border-left: $euiBorderThin; - height: 100%; -} - -.painlessLabRightPane__tabs { - display: flex; - flex-direction: column; - height: 100%; - - [role='tabpanel'] { - height: 100%; - overflow-y: auto; - } -} - -// adding dev tool top bar + bottom bar height to the body offset -// (they're both the same height, hence the x2) -$bodyOffset: $bottomBarHeight * 2; - -.painlessLabMainContainer { - @include kibanaFullBodyHeight($bodyOffset); -} - -.painlessLabPanelsContainer { - // The panels container should adopt the height of the main container - height: 100%; -} diff --git a/x-pack/platform/plugins/shared/maps/public/_index.scss b/x-pack/platform/plugins/shared/maps/public/_index.scss index 77541eb21d2e5..45b50cbf7f141 100644 --- a/x-pack/platform/plugins/shared/maps/public/_index.scss +++ b/x-pack/platform/plugins/shared/maps/public/_index.scss @@ -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'; \ No newline at end of file +@import 'react_embeddable/index'; diff --git a/x-pack/platform/plugins/shared/maps/public/_main.scss b/x-pack/platform/plugins/shared/maps/public/_main.scss deleted file mode 100644 index 3089ed516ca34..0000000000000 --- a/x-pack/platform/plugins/shared/maps/public/_main.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import '../../../../../../src/core/public/mixins'; - -// sass-lint:disable no-ids -#maps-plugin { - @include kibanaFullBodyHeight(); - - display: flex; - flex-direction: column; - width: 100%; - overflow: hidden; -} - -.mapFullScreen { - // sass-lint:disable no-important - height: 100vh !important; -} - -#react-maps-root { - flex-grow: 1; - display: flex; - flex-direction: column; -} - -.euiColorPicker__emptySwatch { - position: relative; -} diff --git a/x-pack/platform/plugins/shared/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/platform/plugins/shared/maps/public/routes/map_page/map_app/map_app.tsx index 5ac414d54c274..e56404848144c 100644 --- a/x-pack/platform/plugins/shared/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/platform/plugins/shared/maps/public/routes/map_page/map_app/map_app.tsx @@ -14,6 +14,7 @@ import { AppMountParameters, KibanaExecutionContext, ScopedHistory, + kibanaFullBodyHeightCss, } from '@kbn/core/public'; import { Adapters } from '@kbn/inspector-plugin/public'; import { Subscription } from 'rxjs'; @@ -27,6 +28,7 @@ import { SavedQuery, syncGlobalQueryStateWithUrl, } from '@kbn/data-plugin/public'; +import { css } from '@emotion/react'; import { createKbnUrlStateStorage, withNotifyOnErrors, @@ -63,6 +65,23 @@ import { import { waitUntilTimeLayersLoad$ } from './wait_until_time_layers_load'; import { RefreshConfig as MapRefreshConfig, ParsedMapStateJSON } from '../saved_map'; +const styles = { + wrapper: css([ + { + display: 'flex', + flexDirection: 'column', + }, + kibanaFullBodyHeightCss(), + ]), + fullScreen: css({ + height: '100vh !important', + }), + reactMapsRoot: css({ + flexGrow: 1, + display: 'flex', + flexDirection: 'column', + }), +}; export interface Props { savedMap: SavedMap; // saveCounter used to trigger MapApp render after SaveMap.save @@ -578,10 +597,10 @@ export class MapApp extends React.Component { } return ( -
+
{this._renderTopNav()}

{`screenTitle placeholder`}

-
+
{this._renderLegacyUrlConflict()} + css([ + { + overflow: 'hidden', + flexShrink: 1, + }, // adding dev tool top bar to the body offset + kibanaFullBodyHeightCss(`(${euiTheme.size.base} * 3)`), + ]), +}; + export const App = () => { const { getLicenseStatus, notifications } = useAppContext(); @@ -82,9 +103,11 @@ export const App = () => { return null; }; + const styles = useMemoizedStyles(componentStyles); + return ( <> - + {renderLicenseWarning()}