= {
title: 'Display/EuiCallOut',
@@ -88,16 +101,7 @@ export const WithActions: Story = {
args: {
title,
text,
- actionProps: {
- primary: {
- children: 'Primary action',
- onClick: action('primary onClick'),
- },
- secondary: {
- children: 'Secondary action',
- onClick: action('secondary onClick'),
- },
- },
+ actionProps: defaultActionProps,
},
};
@@ -365,3 +369,103 @@ export const KitchenSinkCustomChildren: Story = {
);
},
};
+
+export const WrapperKitchenSink: Story = {
+ tags: ['vrt-only'],
+ render: function Render() {
+ return (
+
+
+ within EuiPanel
+
+
+
+
+
+
+
+
+
+ within EuiPopover
+
+
+
Show popover}
+ isOpen
+ anchorPosition="downCenter"
+ closePopover={() => {}}
+ aria-label="Popover containing a CallOut"
+ >
+
+ {textLong}
+
+
+
+
+
Show popover}
+ isOpen
+ anchorPosition="downCenter"
+ closePopover={() => {}}
+ aria-label="Popover containing a CallOut"
+ >
+
+
+
+
+
+
+ within EuiFlexGroup
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ },
+};
diff --git a/packages/eui/src/components/call_out/call_out.styles.ts b/packages/eui/src/components/call_out/call_out.styles.ts
index 85847bd6a5db..338cc586ede0 100644
--- a/packages/eui/src/components/call_out/call_out.styles.ts
+++ b/packages/eui/src/components/call_out/call_out.styles.ts
@@ -14,46 +14,9 @@ import {
preventForcedColors,
} from '../../global_styling';
import { UseEuiTheme } from '../../services';
-import { EuiCallOutSize } from './types';
/** Maximum reading width for `text` and `children` slots. */
const TEXT_MAX_WIDTH = 1200;
-const CONTAINER_NAME = 'euiCallOut';
-const CQC_LAYOUTS = ['superNarrow', 'wide'] as const;
-type EuiCallOutLayouts = (typeof CQC_LAYOUTS)[number];
-const CQC_BREAKPOINTS: Record<
- EuiCallOutSize,
- Record
-> = {
- s: {
- superNarrow: '(max-width: 400px)',
- wide: '(min-width: 800px)',
- },
- m: {
- superNarrow: '(max-width: 600px)',
- wide: '(min-width: 1000px)',
- },
-};
-
-const withContainerQuery = ({
- layout,
- styles,
-}: {
- layout: EuiCallOutLayouts;
- styles: string;
-}) => {
- return Object.keys(CQC_BREAKPOINTS)
- .map(
- (sizeKey) => `
- @container ${CONTAINER_NAME}--${sizeKey} ${
- CQC_BREAKPOINTS[sizeKey as EuiCallOutSize][layout]
- } {
- ${styles}
- }
- `
- )
- .join('\n');
-};
export const euiCallOutStyles = (euiThemeContext: UseEuiTheme) => {
const { euiTheme } = euiThemeContext;
@@ -76,11 +39,9 @@ export const euiCallOutStyles = (euiThemeContext: UseEuiTheme) => {
return {
euiCallOut: css`
- container-type: inline-size;
- container-name: ${CONTAINER_NAME};
position: relative;
display: flex;
- inline-size: 100%;
+ max-inline-size: 100%;
align-items: center;
border: ${euiTheme.border.width.thin} solid;
border-radius: ${borderRadius};
@@ -104,14 +65,10 @@ export const euiCallOutStyles = (euiThemeContext: UseEuiTheme) => {
}
&:where([data-size='s']) {
- container-name: ${CONTAINER_NAME} ${CONTAINER_NAME}--s;
-
${logicalShorthandCSS('padding', `${paddingSizes.s} ${paddingSizes.m}`)}
}
&:where([data-size='m']) {
- container-name: ${CONTAINER_NAME} ${CONTAINER_NAME}--m;
-
padding: ${paddingSizes.m};
}
`,
@@ -129,13 +86,10 @@ export const euiCallOutStyles = (euiThemeContext: UseEuiTheme) => {
gap: ${euiTheme.size.m};
}
- ${withContainerQuery({
- layout: 'wide',
- styles: `
- flex-direction: row;
- gap: ${euiTheme.size.xxl};
- `,
- })}
+ &:where([data-layout='wide'] &) {
+ flex-direction: row;
+ gap: ${euiTheme.size.xxl};
+ }
`,
// handles icon + text layout
body: css`
@@ -232,8 +186,7 @@ export const euiCallOutStyles = (euiThemeContext: UseEuiTheme) => {
${logicalCSS('margin-left', euiTheme.size.xl)}
}
- /* uses container query directly as it should apply generically independent of size */
- @container ${CONTAINER_NAME} ${CQC_BREAKPOINTS.s.superNarrow} {
+ &:where([data-layout='superNarrow'] &) {
flex-wrap: wrap;
/* use full width actions */
@@ -243,15 +196,12 @@ export const euiCallOutStyles = (euiThemeContext: UseEuiTheme) => {
}
}
- ${withContainerQuery({
- layout: 'wide',
- styles: `
- ${logicalCSS('margin-left', '0')}
- align-self: center;
- flex-shrink: 0;
- flex-direction: row-reverse;
- `,
- })}
+ &:where([data-layout='wide'] &) {
+ ${logicalCSS('margin-left', '0')}
+ align-self: center;
+ flex-shrink: 0;
+ flex-direction: row-reverse;
+ }
`,
};
};
diff --git a/packages/eui/src/components/call_out/call_out.tsx b/packages/eui/src/components/call_out/call_out.tsx
index ee267d3d4d97..21f063bed1bb 100644
--- a/packages/eui/src/components/call_out/call_out.tsx
+++ b/packages/eui/src/components/call_out/call_out.tsx
@@ -26,6 +26,7 @@ import {
type EuiNotificationIconType,
} from '../notification_icon/notification_icon';
+import { useLayoutObserver } from './use_layout_observer';
import {
EuiCallOutAction,
EuiCallOutActionPrimaryProps,
@@ -133,6 +134,11 @@ export const EuiCallOut = forwardRef(
const { euiTheme } = useEuiTheme();
const color = getCallOutColor(_color);
+ /* Uses resize observer to determine the container width/layout instead of native container queries,
+ because callouts can be placed in containers without defined size (absolute positioned, no-grow flex layout etc.)
+ where container queries would collapse by design instead of adjusting to the content dimensions. */
+ const panelRef = useLayoutObserver(size, ref);
+
const borderColors = useEuiBorderColorCSS();
const styles = useEuiMemoizedStyles(euiCallOutStyles);
const cssStyles = [
@@ -307,7 +313,7 @@ export const EuiCallOut = forwardRef(
// uses custom padding
paddingSize="none"
className={classes}
- panelRef={ref}
+ panelRef={panelRef}
grow={false}
style={{ ...cssVariables, ...style }}
data-size={size}
diff --git a/packages/eui/src/components/call_out/use_layout_observer.ts b/packages/eui/src/components/call_out/use_layout_observer.ts
new file mode 100644
index 000000000000..df2da8e32884
--- /dev/null
+++ b/packages/eui/src/components/call_out/use_layout_observer.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useCallback, useEffect, useRef } from 'react';
+
+import { hasResizeObserver } from '../observer/resize_observer/resize_observer';
+import { EuiCallOutSize } from './types';
+
+const SUPER_NARROW_WIDTH = 400;
+const WIDE_BREAKPOINTS: Record = {
+ s: 800,
+ m: 1000,
+};
+
+/**
+ * Observes the rendered width and sets `data-layout` on the root
+ * element so that CSS can respond to size changes.
+ *
+ * This is an alternative to native CSS container queries. Its purpose is to handle cases where
+ * container queries would collapse if the element is placed inside a container without a defined size.
+ */
+export const useLayoutObserver = (
+ size: EuiCallOutSize,
+ ref: React.ForwardedRef
+): ((node: HTMLDivElement | null) => void) => {
+ const elementRef = useRef(null);
+ const forwardedRefRef = useRef(ref);
+ forwardedRefRef.current = ref;
+
+ const panelRef = useCallback((node: HTMLDivElement | null) => {
+ elementRef.current = node;
+ const fRef = forwardedRefRef.current;
+ if (typeof fRef === 'function') {
+ fRef(node);
+ } else if (fRef) {
+ (fRef as React.MutableRefObject).current = node;
+ }
+ }, []);
+
+ useEffect(() => {
+ const element = elementRef.current;
+ if (!element || !hasResizeObserver) return;
+
+ const wide = WIDE_BREAKPOINTS[size];
+
+ const observer = new ResizeObserver(([entry]) => {
+ const width = entry.borderBoxSize[0].inlineSize;
+
+ if (width <= SUPER_NARROW_WIDTH) {
+ element.setAttribute('data-layout', 'superNarrow');
+ } else if (width >= wide) {
+ element.setAttribute('data-layout', 'wide');
+ } else {
+ element.removeAttribute('data-layout');
+ }
+ });
+
+ observer.observe(element);
+ return () => observer.disconnect();
+ }, [size]);
+
+ return panelRef;
+};
diff --git a/packages/eui/src/components/form/__snapshots__/form.test.tsx.snap b/packages/eui/src/components/form/__snapshots__/form.test.tsx.snap
index 4339ad571243..8122c87a108b 100644
--- a/packages/eui/src/components/form/__snapshots__/form.test.tsx.snap
+++ b/packages/eui/src/components/form/__snapshots__/form.test.tsx.snap
@@ -31,7 +31,7 @@ exports[`EuiForm renders with error callout when isInvalid is "true" 1`] = `
tabindex="-1"
>