From 14a1759dc7220bb18b05901b430224b093014321 Mon Sep 17 00:00:00 2001
From: Lene Gadewoll
Date: Thu, 21 Mar 2024 10:16:02 +0100
Subject: [PATCH 1/7] [Storybook] add stories for more components (letters I -
L) (#7589)
---
src/components/i18n/i18n.stories.tsx | 87 +++++++
src/components/i18n/i18n.tsx | 8 +-
src/components/i18n/i18n_number.stories.tsx | 50 ++++
src/components/icon/icon.stories.tsx | 29 +++
src/components/image/image.stories.tsx | 34 +++
.../inline_edit/inline_edit_text.stories.tsx | 38 +++
.../inline_edit/inline_edit_title.stories.tsx | 43 ++++
.../inner_text/inner_text.stories.tsx | 59 +++++
.../key_pad_menu/key_pad_menu.stories.tsx | 225 ++++++++++++++++++
.../key_pad_menu_item.stories.tsx | 3 +-
src/components/link/link.stories.tsx | 36 +++
.../list_group/list_group.stories.tsx | 45 ++++
.../list_group/list_group_item.stories.tsx | 37 +++
.../pinnable_list_group.stories.tsx | 55 +++++
.../loading/loading_chart.stories.tsx | 25 ++
.../loading/loading_elastic.stories.tsx | 24 ++
.../loading/loading_logo.stories.tsx | 28 +++
.../loading/loading_spinner.stories.tsx | 24 ++
18 files changed, 848 insertions(+), 2 deletions(-)
create mode 100644 src/components/i18n/i18n.stories.tsx
create mode 100644 src/components/i18n/i18n_number.stories.tsx
create mode 100644 src/components/icon/icon.stories.tsx
create mode 100644 src/components/image/image.stories.tsx
create mode 100644 src/components/inline_edit/inline_edit_text.stories.tsx
create mode 100644 src/components/inline_edit/inline_edit_title.stories.tsx
create mode 100644 src/components/inner_text/inner_text.stories.tsx
create mode 100644 src/components/key_pad_menu/key_pad_menu.stories.tsx
create mode 100644 src/components/link/link.stories.tsx
create mode 100644 src/components/list_group/list_group.stories.tsx
create mode 100644 src/components/list_group/list_group_item.stories.tsx
create mode 100644 src/components/list_group/pinnable_list_group/pinnable_list_group.stories.tsx
create mode 100644 src/components/loading/loading_chart.stories.tsx
create mode 100644 src/components/loading/loading_elastic.stories.tsx
create mode 100644 src/components/loading/loading_logo.stories.tsx
create mode 100644 src/components/loading/loading_spinner.stories.tsx
diff --git a/src/components/i18n/i18n.stories.tsx b/src/components/i18n/i18n.stories.tsx
new file mode 100644
index 00000000000..93f0426598f
--- /dev/null
+++ b/src/components/i18n/i18n.stories.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { hideStorybookControls } from '../../../.storybook/utils';
+import { EuiI18n, EuiI18nProps, I18nTokensShape } from './i18n';
+import { EuiCard } from '../card';
+
+type Props = EuiI18nProps;
+
+const meta: Meta = {
+ title: 'Utilities/EuiI18n',
+ component: EuiI18n,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const SingleToken: Story = {
+ argTypes: {
+ default: { control: { type: 'text' } },
+ ...hideStorybookControls(['children', 'tokens', 'defaults']),
+ },
+ args: {
+ token: 'euiI18nBasic.basicexample',
+ default:
+ 'This is the English copy that would be replaced by a translation defined by the euiI18nBasic.basicexample token.',
+ },
+};
+
+export const Interpolation: Story = {
+ argTypes: {
+ ...hideStorybookControls(['children', 'tokens', 'defaults']),
+ },
+ args: {
+ token: 'euiI18nInterpolation.clickedCount',
+ default: 'Clicked on button {count} times.',
+ values: { count: 3 },
+ },
+};
+
+export const MultipleTokens: Story = {
+ argTypes: {
+ ...hideStorybookControls(['token', 'default']),
+ },
+ args: {
+ tokens: ['euiI18n.title', 'euiI18n.description'],
+ defaults: ['Card title', 'Card description'],
+ },
+ render: ({ tokens, defaults }: I18nTokensShape) => (
+ // eslint-disable-next-line local/i18n
+
+ {([title, description]: string[]) => (
+
+ )}
+
+ ),
+};
+
+export const MultipleTokenInterpolation: Story = {
+ argTypes: {
+ ...hideStorybookControls(['token', 'default']),
+ },
+ args: {
+ tokens: ['euiI18nMulti.title', 'euiI18nMulti.description'],
+ defaults: [
+ 'How often was the {name} cuddled?',
+ 'The {name} was cuddled {count} times.',
+ ],
+ values: { name: 'cat', count: 3 },
+ },
+ render: ({ tokens, defaults, values }: I18nTokensShape) => (
+ // eslint-disable-next-line local/i18n
+
+ {([title, description]: string[]) => (
+
+ )}
+
+ ),
+};
diff --git a/src/components/i18n/i18n.tsx b/src/components/i18n/i18n.tsx
index 2659f36aa8d..4eef8881953 100644
--- a/src/components/i18n/i18n.tsx
+++ b/src/components/i18n/i18n.tsx
@@ -90,13 +90,19 @@ type ResolvedType = T extends (...args: any[]) => any ? ReturnType : T;
interface I18nTokenShape> {
token: string;
default: DEFAULT;
+ /**
+ * Render function that returns a ReactElement
+ */
children?: (x: ResolvedType) => ReactChild;
values?: T;
}
-interface I18nTokensShape {
+export interface I18nTokensShape {
tokens: string[];
defaults: T;
+ /**
+ * Render function that returns a ReactElement
+ */
children: (x: Array) => ReactChild;
values?: Record;
}
diff --git a/src/components/i18n/i18n_number.stories.tsx b/src/components/i18n/i18n_number.stories.tsx
new file mode 100644
index 00000000000..180c4067442
--- /dev/null
+++ b/src/components/i18n/i18n_number.stories.tsx
@@ -0,0 +1,50 @@
+/*
+ * 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, { ReactChild } from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { hideStorybookControls } from '../../../.storybook/utils';
+import { EuiI18nNumber, EuiI18nNumberProps } from './i18n_number';
+import { EuiText } from '../text';
+
+const meta: Meta = {
+ title: 'Utilities/EuiI18nNumber',
+ component: EuiI18nNumber,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const SingleValue: Story = {
+ argTypes: hideStorybookControls(['children', 'values']),
+ args: {
+ value: 99,
+ },
+ render: (args: EuiI18nNumberProps) => (
+
+ Formatted number:
+
+ ),
+};
+
+export const MultipleValues: Story = {
+ argTypes: hideStorybookControls(['value']),
+ args: {
+ values: [0, 1, 2],
+ children: (values: ReactChild[]) => (
+ <>
+ {values.map((value) => (
+
+ Formatted number: {value}
+
+ ))}
+ >
+ ),
+ },
+};
diff --git a/src/components/icon/icon.stories.tsx b/src/components/icon/icon.stories.tsx
new file mode 100644
index 00000000000..c226953ff57
--- /dev/null
+++ b/src/components/icon/icon.stories.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiIcon, EuiIconProps } from './icon';
+
+const meta: Meta = {
+ title: 'Display/EuiIcon',
+ component: EuiIcon,
+ argTypes: {
+ color: { control: { type: 'text' } },
+ },
+ // Component defaults
+ args: {
+ type: 'accessibility',
+ size: 'm',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {};
diff --git a/src/components/image/image.stories.tsx b/src/components/image/image.stories.tsx
new file mode 100644
index 00000000000..f2e25822d96
--- /dev/null
+++ b/src/components/image/image.stories.tsx
@@ -0,0 +1,34 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiImage } from './image';
+import { EuiImageProps } from './image_types';
+
+const meta: Meta = {
+ title: 'Display/EuiImage',
+ component: EuiImage,
+ argTypes: {
+ size: { control: { type: 'text' } },
+ caption: { control: { type: 'text' } },
+ },
+ // Component defaults
+ args: {
+ size: 'original',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ src: 'https://images.unsplash.com/photo-1650253618249-fb0d32d3865c?w=900&h=400&fit=crop&q=60',
+ },
+};
diff --git a/src/components/inline_edit/inline_edit_text.stories.tsx b/src/components/inline_edit/inline_edit_text.stories.tsx
new file mode 100644
index 00000000000..10ab035c653
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_text.stories.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiInlineEditText, EuiInlineEditTextProps } from './inline_edit_text';
+
+const meta: Meta = {
+ title: 'Forms/EuiInlineEditText',
+ component: EuiInlineEditText,
+ // Component defaults
+ args: {
+ size: 'm',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ defaultValue: 'Hello World!',
+ inputAriaLabel: 'Edit text inline',
+ },
+};
+
+export const EditMode: Story = {
+ args: {
+ defaultValue: 'Hello World!',
+ inputAriaLabel: 'Edit text inline',
+ startWithEditOpen: true,
+ },
+};
diff --git a/src/components/inline_edit/inline_edit_title.stories.tsx b/src/components/inline_edit/inline_edit_title.stories.tsx
new file mode 100644
index 00000000000..4a84d6926d8
--- /dev/null
+++ b/src/components/inline_edit/inline_edit_title.stories.tsx
@@ -0,0 +1,43 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import {
+ EuiInlineEditTitle,
+ EuiInlineEditTitleProps,
+} from './inline_edit_title';
+
+const meta: Meta = {
+ title: 'Forms/EuiInlineEditTitle',
+ component: EuiInlineEditTitle,
+ // Component defaults
+ args: {
+ size: 'm',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ heading: 'h1',
+ defaultValue: 'Hello World!',
+ inputAriaLabel: 'Edit title inline',
+ },
+};
+
+export const EditMode: Story = {
+ args: {
+ heading: 'h1',
+ defaultValue: 'Hello World!',
+ inputAriaLabel: 'Edit title inline',
+ startWithEditOpen: true,
+ },
+};
diff --git a/src/components/inner_text/inner_text.stories.tsx b/src/components/inner_text/inner_text.stories.tsx
new file mode 100644
index 00000000000..0a299d70821
--- /dev/null
+++ b/src/components/inner_text/inner_text.stories.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiInnerText, EuiInnerTextProps } from './inner_text';
+import { EuiSpacer } from '../spacer';
+import { EuiCode } from '../code';
+
+const meta: Meta = {
+ title: 'Utilities/EuiInnerText',
+ component: EuiInnerText,
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ parameters: {
+ docs: {
+ source: { language: 'tsx' },
+ },
+ },
+ argTypes: {
+ children: { control: { type: 'text' } },
+ fallback: { control: { type: 'text' } },
+ },
+ args: {
+ // overwrite the type to allow for an useable playground because
+ // so far storybook can't handle displaying function as control input
+ children: 'Simple text' as unknown as any,
+ },
+ render: ({ children, fallback }) => {
+ const content = children as unknown as string;
+
+ return (
+
+ {(ref, innerText) => (
+ <>
+
+ {content || fallback}
+
+
+
+ Output:
+
{' '}
+ {innerText}
+ >
+ )}
+
+ );
+ },
+};
diff --git a/src/components/key_pad_menu/key_pad_menu.stories.tsx b/src/components/key_pad_menu/key_pad_menu.stories.tsx
new file mode 100644
index 00000000000..8f0ca82aa02
--- /dev/null
+++ b/src/components/key_pad_menu/key_pad_menu.stories.tsx
@@ -0,0 +1,225 @@
+/*
+ * 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, { useState } from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { EuiKeyPadMenu, EuiKeyPadMenuProps } from './key_pad_menu';
+import { EuiKeyPadMenuItem, EuiKeyPadMenuItemProps } from './key_pad_menu_item';
+import { EuiIcon } from '../icon';
+
+const meta: Meta = {
+ title: 'Navigation/EuiKeyPadMenu/EuiKeyPadMenu',
+ component: EuiKeyPadMenu,
+};
+
+export default meta;
+type Story = StoryObj;
+
+const onChange = action('onChange');
+
+const StatefulKeyPadMenu = (
+ props: EuiKeyPadMenuProps & { checkableType: 'single' | 'multi' }
+) => {
+ const { children, checkableType, ...rest } = props;
+ const firstItem = Array.isArray(children) ? children[0] : children;
+ const firstItemId: string = firstItem.props?.id ?? '';
+
+ const [selectedItem, setSelectedItem] = useState(firstItemId);
+ const [selectedItems, setSelectedItems] = useState([firstItemId]);
+
+ const handleOnChange = (id: string) => {
+ if (checkableType === 'single') {
+ setSelectedItem(id);
+ } else {
+ setSelectedItems((selectedItems): string[] => {
+ if (selectedItems.includes(id)) {
+ return selectedItems.filter((itemId) => itemId !== id);
+ }
+
+ return [...selectedItems, id];
+ });
+ }
+ };
+
+ return (
+
+ {React.Children.map(children, (child) => {
+ if (!child) return null;
+
+ return (
+ React.isValidElement(child) &&
+ React.cloneElement(child, {
+ onChange: (args: any) => {
+ handleOnChange(child.props.id);
+ onChange(args);
+ },
+ isSelected:
+ checkableType === 'single'
+ ? selectedItem === child.props.id
+ : selectedItems.includes(child.props.id),
+ } as Partial)
+ );
+ })}
+
+ );
+};
+
+export const Playground: Story = {
+ args: {
+ children: [
+
+
+ ,
+
+
+ ,
+
+
+ ,
+
+
+ ,
+
+
+ ,
+
+
+ ,
+ ],
+ },
+};
+
+export const CheckableSingle: Story = {
+ args: {
+ children: [
+ ,
+ ,
+ ,
+ ,
+ ,
+ ,
+ ],
+ checkable: {
+ legend: 'Single checkable EuiKeyPadMenu',
+ },
+ },
+ render: (args) => ,
+};
+
+export const CheckableMulti: Story = {
+ args: {
+ children: [
+ ,
+ ,
+ ,
+ ,
+ ,
+ ,
+ ],
+ checkable: {
+ legend: 'Multi checkable EuiKeyPadMenu',
+ },
+ },
+ render: (args) => ,
+};
diff --git a/src/components/key_pad_menu/key_pad_menu_item.stories.tsx b/src/components/key_pad_menu/key_pad_menu_item.stories.tsx
index 285048301b7..6a7412f45c2 100644
--- a/src/components/key_pad_menu/key_pad_menu_item.stories.tsx
+++ b/src/components/key_pad_menu/key_pad_menu_item.stories.tsx
@@ -14,9 +14,10 @@ import { EuiIcon } from '../icon';
import { EuiKeyPadMenuItem, EuiKeyPadMenuItemProps } from './key_pad_menu_item';
const meta: Meta = {
- title: 'Navigation/EuiKeyPadMenuItem',
+ title: 'Navigation/EuiKeyPadMenu/EuiKeyPadMenuItem',
component: EuiKeyPadMenuItem as any,
argTypes: {
+ label: { control: { type: 'text' } },
checkable: { options: [undefined, 'multi', 'single'] },
},
args: {
diff --git a/src/components/link/link.stories.tsx b/src/components/link/link.stories.tsx
new file mode 100644
index 00000000000..e8ea73fa8d8
--- /dev/null
+++ b/src/components/link/link.stories.tsx
@@ -0,0 +1,36 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiLink, EuiLinkProps } from './link';
+
+const meta: Meta = {
+ title: 'Navigation/EuiLink',
+ component: EuiLink,
+ argTypes: {
+ // setting up native HTML attributes to ensure they show up as control
+ target: { control: { type: 'text' } },
+ rel: { control: { type: 'text' } },
+ disabled: { control: { type: 'boolean' } },
+ },
+ args: {
+ color: 'primary',
+ type: 'button',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children: 'Elastic website',
+ href: 'http://www.elastic.co/',
+ },
+};
diff --git a/src/components/list_group/list_group.stories.tsx b/src/components/list_group/list_group.stories.tsx
new file mode 100644
index 00000000000..92e2db2eccf
--- /dev/null
+++ b/src/components/list_group/list_group.stories.tsx
@@ -0,0 +1,45 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { moveStorybookControlsToCategory } from '../../../.storybook/utils';
+import { EuiListGroup, EuiListGroupProps } from './list_group';
+import { EuiListGroupItem } from './list_group_item';
+
+const meta: Meta = {
+ title: 'Display/EuiListGroup/EuiListGroup',
+ component: EuiListGroup,
+ argTypes: moveStorybookControlsToCategory(
+ ['color', 'size'],
+ 'EuiListGroupItem props'
+ ),
+ args: {
+ flush: false,
+ bordered: false,
+ gutterSize: 's',
+ wrapText: false,
+ maxWidth: true,
+ showToolTips: false,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children: [
+ ,
+ ,
+ ,
+ ,
+ ],
+ },
+};
diff --git a/src/components/list_group/list_group_item.stories.tsx b/src/components/list_group/list_group_item.stories.tsx
new file mode 100644
index 00000000000..6d733b8aada
--- /dev/null
+++ b/src/components/list_group/list_group_item.stories.tsx
@@ -0,0 +1,37 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { disableStorybookControls } from '../../../.storybook/utils';
+import { EuiListGroupItem, EuiListGroupItemProps } from './list_group_item';
+
+const meta: Meta = {
+ title: 'Display/EuiListGroup/EuiListGroupItem',
+ component: EuiListGroupItem,
+ argTypes: {
+ ...disableStorybookControls(['buttonRef']),
+ iconType: {
+ control: { type: 'text' },
+ },
+ },
+ args: {
+ size: 'm',
+ color: 'text',
+ showToolTip: false,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ label: 'Link group item',
+ },
+};
diff --git a/src/components/list_group/pinnable_list_group/pinnable_list_group.stories.tsx b/src/components/list_group/pinnable_list_group/pinnable_list_group.stories.tsx
new file mode 100644
index 00000000000..0b21dd5037f
--- /dev/null
+++ b/src/components/list_group/pinnable_list_group/pinnable_list_group.stories.tsx
@@ -0,0 +1,55 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { moveStorybookControlsToCategory } from '../../../../.storybook/utils';
+import {
+ EuiPinnableListGroup,
+ EuiPinnableListGroupProps,
+} from './pinnable_list_group';
+
+const meta: Meta = {
+ title: 'Display/EuiPinnableListGroup',
+ component: EuiPinnableListGroup,
+ argTypes: moveStorybookControlsToCategory(
+ [
+ 'bordered',
+ 'color',
+ 'flush',
+ 'gutterSize',
+ 'maxWidth',
+ 'showToolTips',
+ 'size',
+ 'wrapText',
+ ],
+ 'EuiListGroup props'
+ ),
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ listItems: [
+ {
+ label: 'First item with pinned: true',
+
+ pinned: true,
+ },
+ { label: 'Second item with iconType', iconType: 'home' },
+ { label: 'Third item with isActive: true', isActive: true },
+ {
+ label: 'Fourth item with extraAction',
+ extraAction: { iconType: 'bell', alwaysShow: true },
+ },
+ ],
+ onPinClick: () => {},
+ },
+};
diff --git a/src/components/loading/loading_chart.stories.tsx b/src/components/loading/loading_chart.stories.tsx
new file mode 100644
index 00000000000..f4950997621
--- /dev/null
+++ b/src/components/loading/loading_chart.stories.tsx
@@ -0,0 +1,25 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiLoadingChart, EuiLoadingChartProps } from './loading_chart';
+
+const meta: Meta = {
+ title: 'Display/EuiLoadingChart',
+ component: EuiLoadingChart,
+ args: {
+ size: 'm',
+ mono: false,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {};
diff --git a/src/components/loading/loading_elastic.stories.tsx b/src/components/loading/loading_elastic.stories.tsx
new file mode 100644
index 00000000000..1c5f51ed7e8
--- /dev/null
+++ b/src/components/loading/loading_elastic.stories.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiLoadingElastic, EuiLoadingElasticProps } from './loading_elastic';
+
+const meta: Meta = {
+ title: 'Display/EuiLoadingElastic',
+ component: EuiLoadingElastic,
+ args: {
+ size: 'm',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {};
diff --git a/src/components/loading/loading_logo.stories.tsx b/src/components/loading/loading_logo.stories.tsx
new file mode 100644
index 00000000000..2ac968ef78e
--- /dev/null
+++ b/src/components/loading/loading_logo.stories.tsx
@@ -0,0 +1,28 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiLoadingLogo, EuiLoadingLogoProps } from './loading_logo';
+
+const meta: Meta = {
+ title: 'Display/EuiLoadingLogo',
+ component: EuiLoadingLogo,
+ argTypes: {
+ logo: { control: { type: 'text' } },
+ },
+ args: {
+ size: 'm',
+ logo: 'logoKibana',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {};
diff --git a/src/components/loading/loading_spinner.stories.tsx b/src/components/loading/loading_spinner.stories.tsx
new file mode 100644
index 00000000000..09026f23311
--- /dev/null
+++ b/src/components/loading/loading_spinner.stories.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiLoadingSpinner, EuiLoadingSpinnerProps } from './loading_spinner';
+
+const meta: Meta = {
+ title: 'Display/EuiLoadingSpinner',
+ component: EuiLoadingSpinner,
+ args: {
+ size: 'm',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {};
From d75bccd8f11687dc7e0c3d5f7b94068683cd17b4 Mon Sep 17 00:00:00 2001
From: Cee Chen <549407+cee-chen@users.noreply.github.com>
Date: Thu, 21 Mar 2024 07:46:01 -0700
Subject: [PATCH 2/7] [Emotion] Memoize `EuiPanel` + color/padding hook
utilities + `EuiResizableContainer/Panel` (#7584)
---
.stylelintrc.js | 3 +
src/components/panel/panel.tsx | 5 +-
.../resizable_container/resizable_button.tsx | 5 +-
.../resizable_collapse_button.tsx | 5 +-
.../resizable_container.styles.ts | 20 ++-
.../resizable_container.tsx | 4 +-
.../resizable_panel.styles.ts | 20 +--
.../resizable_container/resizable_panel.tsx | 19 +--
.../mixins/__snapshots__/_color.test.ts.snap | 32 ++---
.../__snapshots__/_padding.test.ts.snap | 72 ++++++----
src/global_styling/mixins/_color.ts | 133 +++++++++---------
src/global_styling/mixins/_padding.test.ts | 4 +-
src/global_styling/mixins/_padding.ts | 92 ++++++++----
src/services/theme/style_memoization.tsx | 13 +-
.../amsterdam/global_styling/mixins/button.ts | 2 +-
15 files changed, 230 insertions(+), 199 deletions(-)
diff --git a/.stylelintrc.js b/.stylelintrc.js
index 800fcafa1ff..42adc41f28c 100644
--- a/.stylelintrc.js
+++ b/.stylelintrc.js
@@ -154,6 +154,9 @@ module.exports = {
// This is set to deprecate after stylelint v16, but in the meanwhile, is helpful
// for finding extraneous semicolons after utils that already output semicolons (e.g. logicalCSS())
'no-extra-semicolons': true,
+
+ // Emotion uses the `label` property to generate the output className string
+ 'property-no-unknown': [true, { ignoreProperties: 'label' }],
},
},
],
diff --git a/src/components/panel/panel.tsx b/src/components/panel/panel.tsx
index 7cdaae3e9f4..b7eb932f53f 100644
--- a/src/components/panel/panel.tsx
+++ b/src/components/panel/panel.tsx
@@ -13,7 +13,7 @@ import React, {
Ref,
} from 'react';
import classNames from 'classnames';
-import { useEuiTheme } from '../../services';
+import { useEuiMemoizedStyles } from '../../services';
import {
useEuiBackgroundColorCSS,
useEuiPaddingCSS,
@@ -107,12 +107,11 @@ export const EuiPanel: FunctionComponent = ({
element,
...rest
}) => {
- const euiTheme = useEuiTheme();
// Shadows are only allowed when there's a white background (plain)
const canHaveShadow = !hasBorder && color === 'plain';
const canHaveBorder = color === 'plain' || color === 'transparent';
- const styles = euiPanelStyles(euiTheme);
+ const styles = useEuiMemoizedStyles(euiPanelStyles);
const cssStyles = [
styles.euiPanel,
grow && styles.grow,
diff --git a/src/components/resizable_container/resizable_button.tsx b/src/components/resizable_container/resizable_button.tsx
index a25ca3c145b..11cdb0df2c0 100644
--- a/src/components/resizable_container/resizable_button.tsx
+++ b/src/components/resizable_container/resizable_button.tsx
@@ -18,7 +18,7 @@ import classNames from 'classnames';
import { CommonProps } from '../common';
import { EuiI18n } from '../i18n';
-import { useEuiTheme, useGeneratedHtmlId } from '../../services';
+import { useEuiMemoizedStyles, useGeneratedHtmlId } from '../../services';
import { useEuiResizableContainerContext } from './context';
import {
@@ -75,8 +75,7 @@ export const EuiResizableButton = forwardRef<
const resizeDirection = isHorizontal ? 'horizontal' : 'vertical';
- const euiTheme = useEuiTheme();
- const styles = euiResizableButtonStyles(euiTheme);
+ const styles = useEuiMemoizedStyles(euiResizableButtonStyles);
const cssStyles = [
styles.euiResizableButton,
styles[indicator],
diff --git a/src/components/resizable_container/resizable_collapse_button.tsx b/src/components/resizable_container/resizable_collapse_button.tsx
index d84f5c68d7e..b7ee4ab9981 100644
--- a/src/components/resizable_container/resizable_collapse_button.tsx
+++ b/src/components/resizable_container/resizable_collapse_button.tsx
@@ -9,7 +9,7 @@
import React, { FunctionComponent } from 'react';
import classNames from 'classnames';
-import { useEuiTheme } from '../../services';
+import { useEuiMemoizedStyles } from '../../services';
import { EuiButtonIcon, EuiButtonIconPropsForButton } from '../button';
import { euiScreenReaderOnlyStyles } from '../accessibility/screen_reader_only/screen_reader_only.styles';
@@ -56,8 +56,7 @@ export const EuiResizableCollapseButton: FunctionComponent<
const screenReaderOnlyStyles =
euiScreenReaderOnlyStyles(showOnFocus).euiScreenReaderOnly;
- const euiTheme = useEuiTheme();
- const styles = euiResizableCollapseButtonStyles(euiTheme);
+ const styles = useEuiMemoizedStyles(euiResizableCollapseButtonStyles);
const collapsedStyles = [
styles.collapsed.collapsed,
diff --git a/src/components/resizable_container/resizable_container.styles.ts b/src/components/resizable_container/resizable_container.styles.ts
index 9df0455bdef..2c7ed90a116 100644
--- a/src/components/resizable_container/resizable_container.styles.ts
+++ b/src/components/resizable_container/resizable_container.styles.ts
@@ -9,15 +9,13 @@
import { css } from '@emotion/react';
import { logicalCSS } from '../../global_styling';
-export const euiResizableContainerStyles = () => {
- return {
- euiResizableContainer: css`
- display: flex;
- ${logicalCSS('width', '100%')}
- `,
- horizontal: css``,
- vertical: css`
- flex-direction: column;
- `,
- };
+export const euiResizableContainerStyles = {
+ euiResizableContainer: css`
+ display: flex;
+ ${logicalCSS('width', '100%')}
+ `,
+ horizontal: css``,
+ vertical: css`
+ flex-direction: column;
+ `,
};
diff --git a/src/components/resizable_container/resizable_container.tsx b/src/components/resizable_container/resizable_container.tsx
index ecf65c50c14..a94183c7af5 100644
--- a/src/components/resizable_container/resizable_container.tsx
+++ b/src/components/resizable_container/resizable_container.tsx
@@ -43,7 +43,7 @@ import {
ResizeTrigger,
} from './types';
-import { euiResizableContainerStyles } from './resizable_container.styles';
+import { euiResizableContainerStyles as styles } from './resizable_container.styles';
export interface EuiResizableContainerProps
extends Omit, 'children'>,
@@ -108,8 +108,6 @@ export const EuiResizableContainer: FunctionComponent<
const isHorizontal = direction === 'horizontal';
const classes = classNames('euiResizableContainer', className);
-
- const styles = euiResizableContainerStyles();
const cssStyles = [styles.euiResizableContainer, styles[direction]];
const [actions, reducerState] = useContainerCallbacks({
diff --git a/src/components/resizable_container/resizable_panel.styles.ts b/src/components/resizable_container/resizable_panel.styles.ts
index 9ae6772b549..cda10b5402f 100644
--- a/src/components/resizable_container/resizable_panel.styles.ts
+++ b/src/components/resizable_container/resizable_panel.styles.ts
@@ -11,22 +11,16 @@ import {
logicalCSS,
logicalCSSWithFallback,
euiScrollBarStyles,
- euiPaddingSizeCSS,
} from '../../global_styling';
import { UseEuiTheme } from '../../services';
-export const euiResizablePanelStyles = (euiThemeContext: UseEuiTheme) => {
- return {
- euiResizablePanel: css`
- position: relative;
- `,
- collapsed: css`
- overflow: hidden;
- `,
- paddingSizes: {
- ...euiPaddingSizeCSS(euiThemeContext),
- },
- };
+export const euiResizablePanelStyles = {
+ euiResizablePanel: css`
+ position: relative;
+ `,
+ collapsed: css`
+ overflow: hidden;
+ `,
};
export const euiResizablePanelContentStyles = (
diff --git a/src/components/resizable_container/resizable_panel.tsx b/src/components/resizable_container/resizable_panel.tsx
index 6de2e40b0a6..2b06901a5ee 100644
--- a/src/components/resizable_container/resizable_panel.tsx
+++ b/src/components/resizable_container/resizable_panel.tsx
@@ -17,8 +17,12 @@ import React, {
} from 'react';
import classNames from 'classnames';
-import { useGeneratedHtmlId, useEuiTheme } from '../../services';
-import { logicalSizeStyle, euiPaddingSize } from '../../global_styling';
+import { useGeneratedHtmlId, useEuiMemoizedStyles } from '../../services';
+import {
+ logicalSizeStyle,
+ useEuiPaddingSize,
+ useEuiPaddingCSS,
+} from '../../global_styling';
import { CommonProps } from '../common';
import { useEuiResizableContainerContext } from './context';
@@ -33,7 +37,7 @@ import {
} from './types';
import { EuiResizableCollapseButton } from './resizable_collapse_button';
import {
- euiResizablePanelStyles,
+ euiResizablePanelStyles as styles,
euiResizablePanelContentStyles,
} from './resizable_panel.styles';
@@ -233,16 +237,13 @@ export const EuiResizablePanel: FunctionComponent = ({
const axis = isHorizontal ? 'horizontal' : 'vertical';
- const euiTheme = useEuiTheme();
-
- const styles = euiResizablePanelStyles(euiTheme);
const cssStyles = [
styles.euiResizablePanel,
isCollapsed && styles.collapsed,
- styles.paddingSizes[wrapperPadding],
+ useEuiPaddingCSS()[wrapperPadding],
wrapperProps?.css,
];
- const contentStyles = euiResizablePanelContentStyles(euiTheme);
+ const contentStyles = useEuiMemoizedStyles(euiResizablePanelContentStyles);
const contentCssStyles = [
contentStyles.euiResizablePanel__content,
scrollable && contentStyles.scrollable,
@@ -261,7 +262,7 @@ export const EuiResizablePanel: FunctionComponent = ({
: logicalSizeStyle('100%', `${size || innerSize}%`)),
};
- const padding = euiPaddingSize(euiTheme, paddingSize) || '0px';
+ const padding = useEuiPaddingSize(paddingSize) || '0px';
useEffect(() => {
if (!registration) return;
diff --git a/src/global_styling/mixins/__snapshots__/_color.test.ts.snap b/src/global_styling/mixins/__snapshots__/_color.test.ts.snap
index 4bb1d1a0dc4..1f82e8f08d5 100644
--- a/src/global_styling/mixins/__snapshots__/_color.test.ts.snap
+++ b/src/global_styling/mixins/__snapshots__/_color.test.ts.snap
@@ -32,34 +32,34 @@ exports[`useEuiBackgroundColor mixin returns a calculated background version for
exports[`useEuiBackgroundColor mixin returns a calculated background version for each color: warning 1`] = `"#fff9e8"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: accent 1`] = `"background-color:#feedf5;;label:accent;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: accent 1`] = `"background-color:#feedf5;label:accent;"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: danger 1`] = `"background-color:#f8e9e9;;label:danger;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: danger 1`] = `"background-color:#f8e9e9;label:danger;"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: plain 1`] = `"background-color:#FFF;;label:plain;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: plain 1`] = `"background-color:#FFF;label:plain;"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: primary 1`] = `"background-color:#e6f1fa;;label:primary;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: primary 1`] = `"background-color:#e6f1fa;label:primary;"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: subdued 1`] = `"background-color:#f7f8fc;;label:subdued;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: subdued 1`] = `"background-color:#f7f8fc;label:subdued;"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: success 1`] = `"background-color:#e6f9f7;;label:success;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: success 1`] = `"background-color:#e6f9f7;label:success;"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: transparent 1`] = `"background-color:transparent;;label:transparent;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: transparent 1`] = `"background-color:transparent;label:transparent;"`;
-exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: warning 1`] = `"background-color:#fff9e8;;label:warning;"`;
+exports[`useEuiBackgroundColorCSS hook returns an object of Emotion background-color properties for each color: warning 1`] = `"background-color:#fff9e8;label:warning;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: accent 1`] = `"border-color:#f9b8d6;;label:accent;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: accent 1`] = `"border-color:#f9b8d6;label:accent;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: danger 1`] = `"border-color:#e5a9a5;;label:danger;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: danger 1`] = `"border-color:#e5a9a5;label:danger;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: plain 1`] = `"border-color:#D3DAE6;;label:plain;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: plain 1`] = `"border-color:#D3DAE6;label:plain;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: primary 1`] = `"border-color:#99c9eb;;label:primary;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: primary 1`] = `"border-color:#99c9eb;label:primary;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: subdued 1`] = `"border-color:#D3DAE6;;label:subdued;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: subdued 1`] = `"border-color:#D3DAE6;label:subdued;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: success 1`] = `"border-color:#99e5e1;;label:success;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: success 1`] = `"border-color:#99e5e1;label:success;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: transparent 1`] = `"border-color:#D3DAE6;;label:transparent;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: transparent 1`] = `"border-color:#D3DAE6;label:transparent;"`;
-exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: warning 1`] = `"border-color:#fedc72;;label:warning;"`;
+exports[`useEuiBorderColorCSS hook returns an object of Emotion border-color properties for each color: warning 1`] = `"border-color:#fedc72;label:warning;"`;
diff --git a/src/global_styling/mixins/__snapshots__/_padding.test.ts.snap b/src/global_styling/mixins/__snapshots__/_padding.test.ts.snap
index c40faeb1fca..34548de54ad 100644
--- a/src/global_styling/mixins/__snapshots__/_padding.test.ts.snap
+++ b/src/global_styling/mixins/__snapshots__/_padding.test.ts.snap
@@ -1,76 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: l 1`] = `"padding-block-end:24px;;label:l;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for all sides, for each size: l 1`] = `"padding:24px;label:l;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: m 1`] = `"padding-block-end:16px;;label:m;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for all sides, for each size: m 1`] = `"padding:16px;label:m;"`;
+
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for all sides, for each size: none 1`] = `null`;
+
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for all sides, for each size: s 1`] = `"padding:8px;label:s;"`;
+
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for all sides, for each size: xl 1`] = `"padding:32px;label:xl;"`;
+
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for all sides, for each size: xs 1`] = `"padding:4px;label:xs;"`;
+
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: l 1`] = `"padding-block-end:24px;label:l;"`;
+
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: m 1`] = `"padding-block-end:16px;label:m;"`;
exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: none 1`] = `null`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: s 1`] = `"padding-block-end:8px;;label:s;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: s 1`] = `"padding-block-end:8px;label:s;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: xl 1`] = `"padding-block-end:32px;;label:xl;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: xl 1`] = `"padding-block-end:32px;label:xl;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: xs 1`] = `"padding-block-end:4px;;label:xs;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: bottom, for each size: xs 1`] = `"padding-block-end:4px;label:xs;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: l 1`] = `"padding-inline:24px;;label:l;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: l 1`] = `"padding-inline:24px;label:l;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: m 1`] = `"padding-inline:16px;;label:m;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: m 1`] = `"padding-inline:16px;label:m;"`;
exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: none 1`] = `null`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: s 1`] = `"padding-inline:8px;;label:s;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: s 1`] = `"padding-inline:8px;label:s;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: xl 1`] = `"padding-inline:32px;;label:xl;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: xl 1`] = `"padding-inline:32px;label:xl;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: xs 1`] = `"padding-inline:4px;;label:xs;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: horizontal, for each size: xs 1`] = `"padding-inline:4px;label:xs;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: l 1`] = `"padding-inline-start:24px;;label:l;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: l 1`] = `"padding-inline-start:24px;label:l;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: m 1`] = `"padding-inline-start:16px;;label:m;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: m 1`] = `"padding-inline-start:16px;label:m;"`;
exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: none 1`] = `null`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: s 1`] = `"padding-inline-start:8px;;label:s;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: s 1`] = `"padding-inline-start:8px;label:s;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: xl 1`] = `"padding-inline-start:32px;;label:xl;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: xl 1`] = `"padding-inline-start:32px;label:xl;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: xs 1`] = `"padding-inline-start:4px;;label:xs;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: left, for each size: xs 1`] = `"padding-inline-start:4px;label:xs;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: l 1`] = `"padding-inline-end:24px;;label:l;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: l 1`] = `"padding-inline-end:24px;label:l;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: m 1`] = `"padding-inline-end:16px;;label:m;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: m 1`] = `"padding-inline-end:16px;label:m;"`;
exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: none 1`] = `null`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: s 1`] = `"padding-inline-end:8px;;label:s;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: s 1`] = `"padding-inline-end:8px;label:s;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: xl 1`] = `"padding-inline-end:32px;;label:xl;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: xl 1`] = `"padding-inline-end:32px;label:xl;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: xs 1`] = `"padding-inline-end:4px;;label:xs;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: right, for each size: xs 1`] = `"padding-inline-end:4px;label:xs;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: l 1`] = `"padding-block-start:24px;;label:l;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: l 1`] = `"padding-block-start:24px;label:l;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: m 1`] = `"padding-block-start:16px;;label:m;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: m 1`] = `"padding-block-start:16px;label:m;"`;
exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: none 1`] = `null`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: s 1`] = `"padding-block-start:8px;;label:s;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: s 1`] = `"padding-block-start:8px;label:s;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: xl 1`] = `"padding-block-start:32px;;label:xl;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: xl 1`] = `"padding-block-start:32px;label:xl;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: xs 1`] = `"padding-block-start:4px;;label:xs;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: top, for each size: xs 1`] = `"padding-block-start:4px;label:xs;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: l 1`] = `"padding-block:24px;;label:l;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: l 1`] = `"padding-block:24px;label:l;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: m 1`] = `"padding-block:16px;;label:m;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: m 1`] = `"padding-block:16px;label:m;"`;
exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: none 1`] = `null`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: s 1`] = `"padding-block:8px;;label:s;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: s 1`] = `"padding-block:8px;label:s;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: xl 1`] = `"padding-block:32px;;label:xl;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: xl 1`] = `"padding-block:32px;label:xl;"`;
-exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: xs 1`] = `"padding-block:4px;;label:xs;"`;
+exports[`useEuiPaddingCSS hook returns an object of Emotion padding properties for side: vertical, for each size: xs 1`] = `"padding-block:4px;label:xs;"`;
exports[`useEuiPaddingSize returns a static padding value for each size l 1`] = `"24px"`;
diff --git a/src/global_styling/mixins/_color.ts b/src/global_styling/mixins/_color.ts
index 80c35b5c336..74087f584ff 100644
--- a/src/global_styling/mixins/_color.ts
+++ b/src/global_styling/mixins/_color.ts
@@ -6,15 +6,14 @@
* Side Public License, v 1.
*/
-import { useMemo } from 'react';
-import { css } from '@emotion/react';
+import { css, SerializedStyles } from '@emotion/react';
import {
shade,
tint,
tintOrShade,
transparentize,
- useEuiTheme,
UseEuiTheme,
+ useEuiMemoizedStyles,
} from '../../services';
export const BACKGROUND_COLORS = [
@@ -37,6 +36,9 @@ export interface _EuiBackgroundColorOptions {
method?: 'opaque' | 'transparent';
}
+/**
+ * @returns A single background color with optional alpha transparency
+ */
export const euiBackgroundColor = (
{ euiTheme, colorMode }: UseEuiTheme,
color: _EuiBackgroundColor,
@@ -70,47 +72,63 @@ export const euiBackgroundColor = (
}
};
+/**
+ * @returns An object map of color keys to color values, categorized by
+ * opaque (default) vs transparency (hover/focus states) methods.
+ * e.g. {
+ * opaque: { danger: '#000', success: '#fff', ... },
+ * transparent: { danger: 'rgba(0,0,0,0.1)', success: 'rgba(255,255,255,0.1)', ... },
+ * }
+ */
+const _euiBackgroundColorMap = (euiThemeContext: UseEuiTheme) => ({
+ opaque: BACKGROUND_COLORS.reduce(
+ (acc, color) => ({
+ ...acc,
+ [color]: euiBackgroundColor(euiThemeContext, color),
+ }),
+ {} as Record<_EuiBackgroundColor, string>
+ ),
+ transparent: BACKGROUND_COLORS.reduce(
+ (acc, color) => ({
+ ...acc,
+ [color]: euiBackgroundColor(euiThemeContext, color, {
+ method: 'transparent',
+ }),
+ }),
+ {} as Record<_EuiBackgroundColor, string>
+ ),
+});
+
export const useEuiBackgroundColor = (
color: _EuiBackgroundColor,
{ method }: _EuiBackgroundColorOptions = {}
) => {
- const euiTheme = useEuiTheme();
- return euiBackgroundColor(euiTheme, color, { method });
+ const backgroundColorMap = useEuiMemoizedStyles(_euiBackgroundColorMap);
+ return backgroundColorMap[method || 'opaque'][color];
};
-export const useEuiBackgroundColorCSS = () => {
- const euiThemeContext = useEuiTheme();
-
- return useMemo(
- () => ({
- transparent: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'transparent')};
- `,
- plain: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'plain')};
- `,
- subdued: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'subdued')};
- `,
- accent: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'accent')};
- `,
- primary: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'primary')};
- `,
- success: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'success')};
- `,
- warning: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'warning')};
- `,
- danger: css`
- background-color: ${euiBackgroundColor(euiThemeContext, 'danger')};
+/**
+ * @returns An object map of color keys to CSS,
+ * e.g. { danger: css``, success: css``, ... }
+ */
+const _euiBackgroundColors = (euiThemeContext: UseEuiTheme) =>
+ BACKGROUND_COLORS.reduce(
+ (acc, color) => ({
+ ...acc,
+ [color]: css`
+ background-color: ${euiBackgroundColor(euiThemeContext, color)};
+ label: ${color};
`,
}),
- [euiThemeContext]
+ {} as Record<_EuiBackgroundColor, SerializedStyles>
);
-};
+
+export const useEuiBackgroundColorCSS = () =>
+ useEuiMemoizedStyles(_euiBackgroundColors);
+
+/**
+ * Border colors
+ */
export const euiBorderColor = (
{ euiTheme, colorMode }: UseEuiTheme,
@@ -128,36 +146,21 @@ export const euiBorderColor = (
}
};
-export const useEuiBorderColorCSS = () => {
- const euiThemeContext = useEuiTheme();
-
- return useMemo(
- () => ({
- transparent: css`
- border-color: ${euiBorderColor(euiThemeContext, 'transparent')};
- `,
- plain: css`
- border-color: ${euiBorderColor(euiThemeContext, 'plain')};
- `,
- subdued: css`
- border-color: ${euiBorderColor(euiThemeContext, 'subdued')};
- `,
- accent: css`
- border-color: ${euiBorderColor(euiThemeContext, 'accent')};
- `,
- primary: css`
- border-color: ${euiBorderColor(euiThemeContext, 'primary')};
- `,
- success: css`
- border-color: ${euiBorderColor(euiThemeContext, 'success')};
- `,
- warning: css`
- border-color: ${euiBorderColor(euiThemeContext, 'warning')};
- `,
- danger: css`
- border-color: ${euiBorderColor(euiThemeContext, 'danger')};
+/**
+ * @returns An object map of color keys to CSS,
+ * e.g. { danger: css``, success: css``, ... }
+ */
+const _euiBorderColors = (euiThemeContext: UseEuiTheme) =>
+ BACKGROUND_COLORS.reduce(
+ (acc, color) => ({
+ ...acc,
+ [color]: css`
+ border-color: ${euiBorderColor(euiThemeContext, color)};
+ label: ${color};
`,
}),
- [euiThemeContext]
+ {} as Record<_EuiBackgroundColor, SerializedStyles>
);
-};
+
+export const useEuiBorderColorCSS = () =>
+ useEuiMemoizedStyles(_euiBorderColors);
diff --git a/src/global_styling/mixins/_padding.test.ts b/src/global_styling/mixins/_padding.test.ts
index 4dcbcfb54a0..f4035e3de12 100644
--- a/src/global_styling/mixins/_padding.test.ts
+++ b/src/global_styling/mixins/_padding.test.ts
@@ -23,8 +23,8 @@ describe('useEuiPaddingSize returns a static padding value', () => {
});
describe('useEuiPaddingCSS hook returns an object of Emotion padding properties', () => {
- LOGICAL_SIDES.forEach((side) => {
- describe(`for side: ${side},`, () => {
+ [...LOGICAL_SIDES, undefined].forEach((side) => {
+ describe(side ? `for side: ${side},` : 'for all sides,', () => {
const sizes = renderHook(() => useEuiPaddingCSS(side)).result.current;
describe('for each size:', () => {
diff --git a/src/global_styling/mixins/_padding.ts b/src/global_styling/mixins/_padding.ts
index ee2129d74cd..d79177a2b82 100644
--- a/src/global_styling/mixins/_padding.ts
+++ b/src/global_styling/mixins/_padding.ts
@@ -6,13 +6,17 @@
* Side Public License, v 1.
*/
-import { css } from '@emotion/react';
-import { useEuiTheme, UseEuiTheme } from '../../services/theme';
-import { logicalSide, LogicalSides } from '../functions';
+import { css, SerializedStyles } from '@emotion/react';
+import { useEuiMemoizedStyles, UseEuiTheme } from '../../services/theme';
+import { LogicalSides } from '../functions';
export const PADDING_SIZES = ['none', 'xs', 's', 'm', 'l', 'xl'] as const;
export type EuiPaddingSize = (typeof PADDING_SIZES)[number];
+/**
+ * Get a single padding size
+ */
+
export const euiPaddingSize = (
{ euiTheme }: UseEuiTheme,
size: EuiPaddingSize
@@ -27,38 +31,64 @@ export const euiPaddingSize = (
}
};
-export const euiPaddingSizeCSS = (
- euiThemeContext: UseEuiTheme,
- side?: LogicalSides
-) => {
- const property = side ? `padding-${logicalSide[side]}` : 'padding';
+/**
+ * @returns An object map of padding size keys to padding values,
+ * e.g. { s: '8px', m: '16px', ... }
+ */
+const _getEuiPaddingSize = (euiThemeContext: UseEuiTheme) =>
+ PADDING_SIZES.reduce(
+ (stylesAcc, size) => ({
+ ...stylesAcc,
+ [size]: size === 'none' ? null : euiPaddingSize(euiThemeContext, size),
+ }),
+ {} as Record
+ );
- return {
- none: null,
- xs: css`
- ${property}: ${euiPaddingSize(euiThemeContext, 'xs')};
- `,
- s: css`
- ${property}: ${euiPaddingSize(euiThemeContext, 's')};
- `,
- m: css`
- ${property}: ${euiPaddingSize(euiThemeContext, 'm')};
- `,
- l: css`
- ${property}: ${euiPaddingSize(euiThemeContext, 'l')};
- `,
- xl: css`
- ${property}: ${euiPaddingSize(euiThemeContext, 'xl')};
- `,
- };
+export const useEuiPaddingSize = (size: EuiPaddingSize) => {
+ const sizes = useEuiMemoizedStyles(_getEuiPaddingSize);
+ return sizes[size];
};
-export const useEuiPaddingSize = (size: EuiPaddingSize) => {
- const euiTheme = useEuiTheme();
- return euiPaddingSize(euiTheme, size);
+/**
+ * @returns An object map of all padding sizes for all padding sides properties
+ * e.g., {
+ * padding: { s: css`padding-size: 8px`, ... }
+ * left: { s: css`padding-inline-start: 8px`, ... }
+ * }
+ */
+const _euiPaddingSidesAndSizes = (euiThemeContext: UseEuiTheme) => {
+ const sizesMap = _getEuiPaddingSize(euiThemeContext);
+
+ type SizeStylesMap = Record;
+
+ // The `_` prefix stops Emotion from applying the function name as a label
+ const _generateSizeStyles = (cssProperty: string) =>
+ Object.fromEntries(
+ Object.entries(sizesMap).map(([sizeKey, sizeValue]) => [
+ sizeKey,
+ sizeValue === null
+ ? null
+ : css`
+ ${cssProperty}: ${sizeValue};
+ label: ${sizeKey};
+ `,
+ ])
+ ) as SizeStylesMap;
+
+ const sidesMap: Record = {
+ padding: _generateSizeStyles('padding'),
+ vertical: _generateSizeStyles('padding-block'),
+ top: _generateSizeStyles('padding-block-start'),
+ bottom: _generateSizeStyles('padding-block-end'),
+ horizontal: _generateSizeStyles('padding-inline'),
+ left: _generateSizeStyles('padding-inline-start'),
+ right: _generateSizeStyles('padding-inline-end'),
+ };
+
+ return sidesMap;
};
export const useEuiPaddingCSS = (side?: LogicalSides) => {
- const euiTheme = useEuiTheme();
- return euiPaddingSizeCSS(euiTheme, side);
+ const memoizedSideMap = useEuiMemoizedStyles(_euiPaddingSidesAndSizes);
+ return memoizedSideMap[side || 'padding'];
};
diff --git a/src/services/theme/style_memoization.tsx b/src/services/theme/style_memoization.tsx
index 966c575b327..de8d8cd59dd 100644
--- a/src/services/theme/style_memoization.tsx
+++ b/src/services/theme/style_memoization.tsx
@@ -16,17 +16,12 @@ import React, {
useCallback,
forwardRef,
} from 'react';
-import type { SerializedStyles, CSSObject } from '@emotion/react';
import { useUpdateEffect } from '../hooks';
import { useEuiTheme, UseEuiTheme } from './hooks';
-type Styles = SerializedStyles | CSSObject | string | null;
-type StylesMaps = Record>;
-// NOTE: We're specifically using a WeakMap instead of a Map in order to allow
-// unmounted components to have their styles garbage-collected by the browser
-// @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
-type MemoizedStylesMap = WeakMap;
+type StylesMap = Record; // Typically an object of serialized css`` styles, but can have any amount of nesting, so it's not worth it to try and strictly type this
+type MemoizedStylesMap = WeakMap;
export const EuiThemeMemoizedStylesContext = createContext(
new WeakMap()
@@ -85,7 +80,7 @@ const getMemoizedStyles = (
* per-theme
*/
export const useEuiMemoizedStyles = <
- T extends (theme: UseEuiTheme) => StylesMaps
+ T extends (theme: UseEuiTheme) => StylesMap
>(
stylesGenerator: T
): ReturnType => {
@@ -97,7 +92,7 @@ export const useEuiMemoizedStyles = <
[stylesGenerator, memoizedStyles, euiThemeContext]
);
- return memoizedComponentStyles as ReturnType;
+ return memoizedComponentStyles;
};
/**
diff --git a/src/themes/amsterdam/global_styling/mixins/button.ts b/src/themes/amsterdam/global_styling/mixins/button.ts
index 373184fde10..3a94d5609a2 100644
--- a/src/themes/amsterdam/global_styling/mixins/button.ts
+++ b/src/themes/amsterdam/global_styling/mixins/button.ts
@@ -242,7 +242,7 @@ const euiButtonDisplaysColors = (euiThemeContext: UseEuiTheme) => {
* @returns string
*/
export const useEuiButtonFocusCSS = () =>
- useEuiMemoizedStyles(euiButtonFocusCSS);
+ useEuiMemoizedStyles(euiButtonFocusCSS);
const euiButtonFocusAnimation = keyframes`
50% {
From c9a6c36e0dc0b9b3853a548e5f04a5823bd282b5 Mon Sep 17 00:00:00 2001
From: Cee Chen <549407+cee-chen@users.noreply.github.com>
Date: Thu, 21 Mar 2024 13:51:19 -0700
Subject: [PATCH 3/7] [docs][EuiCode] Fix moved Kibana repo link (#7608)
---
src-docs/src/views/code/code_example.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src-docs/src/views/code/code_example.js b/src-docs/src/views/code/code_example.js
index 1a0d7907ec1..7fbe9103dcf 100644
--- a/src-docs/src/views/code/code_example.js
+++ b/src-docs/src/views/code/code_example.js
@@ -91,7 +91,7 @@ export const CodeExample = {
or want to print long code (e.g., printing JSON from an API), we
recommend installing a version of Monaco. If you are building within
the Kibana platform, you can use their{' '}
-
+
CodeEditor
.
From 40f0b0dd549c1629fed0c2604cf46fad3556c831 Mon Sep 17 00:00:00 2001
From: Cee Chen <549407+cee-chen@users.noreply.github.com>
Date: Thu, 21 Mar 2024 13:51:56 -0700
Subject: [PATCH 4/7] [EuiTextArea] Fix extra 2-3px of height caused by
`display: inline-block` (#7607)
---
changelogs/upcoming/7607.md | 3 +++
src/components/form/text_area/_text_area.scss | 5 +++++
2 files changed, 8 insertions(+)
create mode 100644 changelogs/upcoming/7607.md
diff --git a/changelogs/upcoming/7607.md b/changelogs/upcoming/7607.md
new file mode 100644
index 00000000000..ba7a04f6937
--- /dev/null
+++ b/changelogs/upcoming/7607.md
@@ -0,0 +1,3 @@
+**Bug fixes**
+
+- Fixed `EuiTextArea`'s CSS box model to no longer render a few extra pixels of strut height
diff --git a/src/components/form/text_area/_text_area.scss b/src/components/form/text_area/_text_area.scss
index 4144e1dd86d..3122fd44140 100644
--- a/src/components/form/text_area/_text_area.scss
+++ b/src/components/form/text_area/_text_area.scss
@@ -2,6 +2,11 @@
@include euiFormControlStyle;
line-height: $euiLineHeight; // give more spacing between multiple lines
+ //
If listProps.isVirtualized is set to{' '}
- false, each row will fit its contents and removes
- all scrolling. Therefore, we recommend having a large enough
- container to accommodate all options. You can also remove truncation
- by setting {'textWrap="wrap"'} when
- virtualization is off.
+ false, each row will fit its content. You can
+ also remove truncation by setting{' '}
+ {'textWrap="wrap"'} when virtualization is off.
+ Note that while this is useful for dynamic options, it can also be
+ computationally expensive as all off-screen options will be
+ rendered to the DOM. We do not recommend turning off virtualization
+ for high numbers of options, but if you absolutely must do so, we
+ suggest using methods such as async loading more options.
Custom content
diff --git a/src/components/selectable/selectable.spec.tsx b/src/components/selectable/selectable.spec.tsx
index f477641bbd1..9ac3ee00c80 100644
--- a/src/components/selectable/selectable.spec.tsx
+++ b/src/components/selectable/selectable.spec.tsx
@@ -253,6 +253,22 @@ describe('EuiSelectable', () => {
});
});
+ describe('isVirtualization={false}', () => {
+ it('correctly scrolls keyboard navigated elements into view', () => {
+ cy.realMount(
+
+ );
+ cy.get('.euiSelectableList__list').invoke('scrollTop').should('eq', 0);
+
+ cy.realPress('Tab');
+ cy.realPress('ArrowUp');
+ cy.get('.euiSelectableList__list').invoke('scrollTop').should('eq', 48);
+ });
+ });
+
describe('truncation', () => {
const sharedProps = {
style: { width: 240 },
diff --git a/src/components/selectable/selectable_list/__snapshots__/selectable_list.test.tsx.snap b/src/components/selectable/selectable_list/__snapshots__/selectable_list.test.tsx.snap
index a388f1c50ad..e338cf3b75d 100644
--- a/src/components/selectable/selectable_list/__snapshots__/selectable_list.test.tsx.snap
+++ b/src/components/selectable/selectable_list/__snapshots__/selectable_list.test.tsx.snap
@@ -10,7 +10,6 @@ exports[`EuiSelectableListItem group labels handles updating aria attrs correctl
tabindex="-1"
>
`;
-exports[`EuiSelectableListItem props isVirtualized can be false 1`] = `
+exports[`EuiSelectableListItem props isVirtualized={false} renders 1`] = `
ul:focus {
diff --git a/src/components/selectable/selectable_list/selectable_list.test.tsx b/src/components/selectable/selectable_list/selectable_list.test.tsx
index 847ed388948..897721906bd 100644
--- a/src/components/selectable/selectable_list/selectable_list.test.tsx
+++ b/src/components/selectable/selectable_list/selectable_list.test.tsx
@@ -227,16 +227,33 @@ describe('EuiSelectableListItem', () => {
expect(container.firstChild).toMatchSnapshot();
});
- test('isVirtualized can be false', () => {
- const { container } = render(
-
- );
+ describe('isVirtualized={false}', () => {
+ it('renders', () => {
+ const { container } = render(
+
+ );
- expect(container.firstChild).toMatchSnapshot();
+ expect(container.firstChild).toMatchSnapshot();
+ });
+
+ it('maintains the passed height when false', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.querySelector('.euiSelectableList__list')).toHaveStyle(
+ 'block-size: 300px'
+ );
+ });
});
test('searchable enables correct screen reader instructions', () => {
diff --git a/src/components/selectable/selectable_list/selectable_list.tsx b/src/components/selectable/selectable_list/selectable_list.tsx
index 9812d3a9e77..802c91dc77a 100644
--- a/src/components/selectable/selectable_list/selectable_list.tsx
+++ b/src/components/selectable/selectable_list/selectable_list.tsx
@@ -244,17 +244,30 @@ export class EuiSelectableList
extends Component<
};
componentDidUpdate(prevProps: EuiSelectableListProps) {
- const { activeOptionIndex, visibleOptions, options } = this.props;
+ const { isVirtualized, activeOptionIndex, visibleOptions, options } =
+ this.props;
- if (this.listBoxRef && this.props.searchable !== true) {
- this.listBoxRef.setAttribute(
- 'aria-activedescendant',
- `${this.props.makeOptionId(activeOptionIndex)}`
- );
- }
+ if (prevProps.activeOptionIndex !== activeOptionIndex) {
+ const { makeOptionId } = this.props;
- if (this.listRef && typeof activeOptionIndex !== 'undefined') {
- this.listRef.scrollToItem(activeOptionIndex, 'auto');
+ if (this.listBoxRef && this.props.searchable !== true) {
+ this.listBoxRef.setAttribute(
+ 'aria-activedescendant',
+ makeOptionId(activeOptionIndex)
+ );
+ }
+
+ if (typeof activeOptionIndex !== 'undefined') {
+ if (isVirtualized) {
+ this.listRef?.scrollToItem(activeOptionIndex, 'auto');
+ } else {
+ const activeOptionId = `#${makeOptionId(activeOptionIndex)}`;
+ const activeOptionEl = this.listBoxRef?.querySelector(activeOptionId);
+ if (activeOptionEl) {
+ activeOptionEl.scrollIntoView({ block: 'nearest' });
+ }
+ }
+ }
}
if (
@@ -609,10 +622,12 @@ export class EuiSelectableList extends Component<
...rest
} = this.props;
+ const heightIsFull = forcedHeight === 'full';
+
const classes = classNames(
'euiSelectableList',
{
- 'euiSelectableList-fullHeight': forcedHeight === 'full',
+ 'euiSelectableList-fullHeight': heightIsFull,
'euiSelectableList-bordered': bordered,
},
className
@@ -625,6 +640,7 @@ export class EuiSelectableList extends Component<
) : (
From 1f342fa7d306af7756129ab5ae10d5ccfd7ffbe8 Mon Sep 17 00:00:00 2001
From: Lene Gadewoll
Date: Fri, 22 Mar 2024 18:40:34 +0100
Subject: [PATCH 6/7] [Storybook] Add stories for more components (letters M -
O) - Part 1 (#7596)
---
.../modal/confirm_modal.stories.tsx | 61 ++++++++
src/components/modal/modal.stories.tsx | 142 ++++++++++++++++++
src/components/modal/modal_body.stories.tsx | 35 +++++
src/components/modal/modal_footer.stories.tsx | 35 +++++
src/components/modal/modal_header.stories.tsx | 35 +++++
.../modal/modal_header_title.stories.tsx | 48 ++++++
.../overlay_mask/overlay_mask.stories.tsx | 39 +++++
7 files changed, 395 insertions(+)
create mode 100644 src/components/modal/confirm_modal.stories.tsx
create mode 100644 src/components/modal/modal.stories.tsx
create mode 100644 src/components/modal/modal_body.stories.tsx
create mode 100644 src/components/modal/modal_footer.stories.tsx
create mode 100644 src/components/modal/modal_header.stories.tsx
create mode 100644 src/components/modal/modal_header_title.stories.tsx
create mode 100644 src/components/overlay_mask/overlay_mask.stories.tsx
diff --git a/src/components/modal/confirm_modal.stories.tsx b/src/components/modal/confirm_modal.stories.tsx
new file mode 100644
index 00000000000..a77e9efe426
--- /dev/null
+++ b/src/components/modal/confirm_modal.stories.tsx
@@ -0,0 +1,61 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { moveStorybookControlsToCategory } from '../../../.storybook/utils';
+import { BUTTON_COLORS } from '../../themes/amsterdam/global_styling/mixins';
+import {
+ CANCEL_BUTTON,
+ CONFIRM_BUTTON,
+ EuiConfirmModal,
+ EuiConfirmModalProps,
+} from './confirm_modal';
+
+const meta: Meta = {
+ title: 'Layout/EuiConfirmModal',
+ component: EuiConfirmModal,
+ argTypes: {
+ // TODO: the `maxWidth` prop takes many different types (bool, string, number)
+ title: { control: { type: 'text' } },
+ confirmButtonText: { control: { type: 'text' } },
+ cancelButtonText: { control: { type: 'text' } },
+ defaultFocusedButton: {
+ control: { type: 'radio' },
+ options: [undefined, CONFIRM_BUTTON, CANCEL_BUTTON],
+ },
+ buttonColor: { control: { type: 'select' }, options: BUTTON_COLORS },
+ ...moveStorybookControlsToCategory(
+ ['role', 'maxWidth', 'initialFocus'],
+ 'EuiModal props'
+ ),
+ },
+ args: {
+ buttonColor: 'primary',
+ role: 'alertdialog',
+ maxWidth: true,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+const onCancel = action('onCancel');
+const onConfirm = action('onConfirm');
+
+export const Playground: Story = {
+ args: {
+ title: 'Confirm modal title',
+ children: 'Confirm modal content',
+ confirmButtonText: 'Confirm',
+ cancelButtonText: 'Cancel',
+ onCancel,
+ onConfirm,
+ },
+};
diff --git a/src/components/modal/modal.stories.tsx b/src/components/modal/modal.stories.tsx
new file mode 100644
index 00000000000..90900090b63
--- /dev/null
+++ b/src/components/modal/modal.stories.tsx
@@ -0,0 +1,142 @@
+/*
+ * 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, { MouseEvent, useState } from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { EuiButton, EuiButtonEmpty } from '../button';
+import { EuiFieldText, EuiForm, EuiFormRow } from '../form';
+
+import { EuiModalHeader } from './modal_header';
+import { EuiModalHeaderTitle } from './modal_header_title';
+import { EuiModalBody } from './modal_body';
+import { EuiModalFooter } from './modal_footer';
+import { EuiModal, EuiModalProps } from './modal';
+
+const meta: Meta = {
+ title: 'Layout/EuiModal/EuiModal',
+ component: EuiModal,
+ argTypes: {
+ // TODO: the `maxWidth` prop takes many different types (bool, string, number)
+ },
+ // Component defaults
+ args: {
+ role: 'dialog',
+ maxWidth: true,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+const onClose = action('onClose');
+
+export const Playground: Story = {
+ args: {
+ children: (
+ <>
+
+ Modal title
+
+
+ Modal body
+
+
+ Modal footer
+
+ >
+ ),
+ },
+};
+
+export const ToggleExample: Story = {
+ args: {
+ children: (
+ <>
+
+ Modal title
+
+
+ Modal body
+
+
+
+ Modal footer
+
+
+ >
+ ),
+ },
+ render: (args) => ,
+};
+
+export const InitialFocus: Story = {
+ args: {
+ initialFocus: '[name=popfirst]',
+ },
+ render: (args) => {
+ const handleOnSubmit = (e: MouseEvent) => {
+ e.preventDefault();
+ action('onSubmit')();
+ };
+ return (
+
+
+
+ Modal title
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ Save
+
+
+
+ );
+ },
+};
+
+/* Story content components */
+
+const StatefulModal = (props: EuiModalProps) => {
+ const [isOpen, setIsOpen] = useState(true);
+
+ return (
+ <>
+ setIsOpen(!isOpen)}>
+ Toggle Modal
+
+ {isOpen && (
+ {
+ setIsOpen(false);
+ onClose(...args);
+ }}
+ />
+ )}
+ >
+ );
+};
diff --git a/src/components/modal/modal_body.stories.tsx b/src/components/modal/modal_body.stories.tsx
new file mode 100644
index 00000000000..7011fe52bca
--- /dev/null
+++ b/src/components/modal/modal_body.stories.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { EuiModal } from './modal';
+import { EuiModalBody, EuiModalBodyProps } from './modal_body';
+
+const meta: Meta = {
+ title: 'Layout/EuiModal/EuiModalBody',
+ component: EuiModalBody,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children: 'Modal body',
+ },
+};
diff --git a/src/components/modal/modal_footer.stories.tsx b/src/components/modal/modal_footer.stories.tsx
new file mode 100644
index 00000000000..4f37c48fad0
--- /dev/null
+++ b/src/components/modal/modal_footer.stories.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { EuiModal } from './modal';
+import { EuiModalFooter, EuiModalFooterProps } from './modal_footer';
+
+const meta: Meta = {
+ title: 'Layout/EuiModal/EuiModalFooter',
+ component: EuiModalFooter,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children: 'Modal footer',
+ },
+};
diff --git a/src/components/modal/modal_header.stories.tsx b/src/components/modal/modal_header.stories.tsx
new file mode 100644
index 00000000000..78da94525e9
--- /dev/null
+++ b/src/components/modal/modal_header.stories.tsx
@@ -0,0 +1,35 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { EuiModal } from './modal';
+import { EuiModalHeader, EuiModalHeaderProps } from './modal_header';
+
+const meta: Meta = {
+ title: 'Layout/EuiModal/EuiModalHeader',
+ component: EuiModalHeader,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children: 'Modal header',
+ },
+};
diff --git a/src/components/modal/modal_header_title.stories.tsx b/src/components/modal/modal_header_title.stories.tsx
new file mode 100644
index 00000000000..372dd2b1d45
--- /dev/null
+++ b/src/components/modal/modal_header_title.stories.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { EuiModal } from './modal';
+import { EuiModalHeader } from './modal_header';
+import {
+ EuiModalHeaderTitle,
+ EuiModalHeaderTitleProps,
+} from './modal_header_title';
+
+const meta: Meta = {
+ title: 'Layout/EuiModal/EuiModalHeaderTitle',
+ component: EuiModalHeaderTitle,
+ argTypes: {
+ component: { control: { type: 'text' } },
+ },
+ // Component defaults
+ args: {
+ component: 'h1',
+ },
+ decorators: [
+ (Story) => (
+
+
+
+
+
+ ),
+ ],
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children: 'Modal header title',
+ },
+};
diff --git a/src/components/overlay_mask/overlay_mask.stories.tsx b/src/components/overlay_mask/overlay_mask.stories.tsx
new file mode 100644
index 00000000000..76199096d1a
--- /dev/null
+++ b/src/components/overlay_mask/overlay_mask.stories.tsx
@@ -0,0 +1,39 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { disableStorybookControls } from '../../../.storybook/utils';
+import { EuiOverlayMask, EuiOverlayMaskProps } from './overlay_mask';
+import { EuiHeader } from '../header';
+
+const meta: Meta = {
+ title: 'Utilities/EuiOverlayMask',
+ component: EuiOverlayMask,
+ argTypes: {
+ children: { control: { type: 'text' } },
+ ...disableStorybookControls(['maskRef']),
+ },
+ // Component defaults
+ args: {
+ headerZindexLocation: 'above',
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ render: (args) => (
+ <>
+
+
+ >
+ ),
+};
From d2a267dd8fbf2468a927fda3cc4de7b91091a3e9 Mon Sep 17 00:00:00 2001
From: Lene Gadewoll
Date: Fri, 22 Mar 2024 19:41:50 +0100
Subject: [PATCH 7/7] [Storybook] Add stories for more components (letters M -
O) - Part 2 (#7605)
---
src/components/mark/mark.stories.tsx | 29 +++++
.../markdown_editor.stories.tsx | 72 +++++++++++++
.../markdown_format.stories.tsx | 71 ++++++++++++
.../mutation_observer.stories.tsx | 102 ++++++++++++++++++
...tion_observer.ts => mutation_observer.tsx} | 0
.../resize_observer.stories.tsx | 93 ++++++++++++++++
.../outside_click_detector.stories.tsx | 51 +++++++++
...detector.ts => outside_click_detector.tsx} | 0
8 files changed, 418 insertions(+)
create mode 100644 src/components/mark/mark.stories.tsx
create mode 100644 src/components/markdown_editor/markdown_editor.stories.tsx
create mode 100644 src/components/markdown_editor/markdown_format.stories.tsx
create mode 100644 src/components/observer/mutation_observer/mutation_observer.stories.tsx
rename src/components/observer/mutation_observer/{mutation_observer.ts => mutation_observer.tsx} (100%)
create mode 100644 src/components/observer/resize_observer/resize_observer.stories.tsx
create mode 100644 src/components/outside_click_detector/outside_click_detector.stories.tsx
rename src/components/outside_click_detector/{outside_click_detector.ts => outside_click_detector.tsx} (100%)
diff --git a/src/components/mark/mark.stories.tsx b/src/components/mark/mark.stories.tsx
new file mode 100644
index 00000000000..65edfe8b197
--- /dev/null
+++ b/src/components/mark/mark.stories.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiMark, EuiMarkProps } from './mark';
+
+const meta: Meta = {
+ title: 'Utilities/EuiMark',
+ component: EuiMark,
+ // Component defaults
+ args: {
+ hasScreenReaderHelpText: true,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children: 'Marked text',
+ },
+};
diff --git a/src/components/markdown_editor/markdown_editor.stories.tsx b/src/components/markdown_editor/markdown_editor.stories.tsx
new file mode 100644
index 00000000000..32add3e8f3d
--- /dev/null
+++ b/src/components/markdown_editor/markdown_editor.stories.tsx
@@ -0,0 +1,72 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiMarkdownEditor, EuiMarkdownEditorProps } from './markdown_editor';
+import {
+ defaultParsingPlugins,
+ defaultProcessingPlugins,
+ defaultUiPlugins,
+} from './plugins/markdown_default_plugins';
+import { MODE_EDITING, MODE_VIEWING } from './markdown_modes';
+
+const initialContent = `## Hello world!
+
+Basic "GitHub flavored" markdown will work as you'd expect.
+
+The editor also ships with some built in plugins. For example it can handle checkboxes. Notice how they toggle state even in the preview mode.
+
+- [ ] Checkboxes
+- [x] Can be filled
+- [ ] Or empty
+
+It can also handle emojis! :smile:
+
+And it can render !{tooltip[tooltips like this](Look! I'm a very helpful tooltip content!)}
+`;
+
+const meta: Meta = {
+ title: 'Editors & Syntax/EuiMarkdownEditor/EuiMarkdownEditor',
+ component: EuiMarkdownEditor,
+ // Component defaults
+ args: {
+ height: 250,
+ maxHeight: 500,
+ autoExpandPreview: true,
+ parsingPluginList: defaultParsingPlugins,
+ processingPluginList: defaultProcessingPlugins,
+ uiPlugins: defaultUiPlugins,
+ errors: [],
+ initialViewMode: MODE_EDITING,
+ dropHandlers: [],
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ value: initialContent,
+ },
+};
+
+export const ViewMode: Story = {
+ args: {
+ value: initialContent,
+ initialViewMode: MODE_VIEWING,
+ },
+};
+
+export const Errors: Story = {
+ args: {
+ value: initialContent,
+ errors: ['An error happened.', 'Woops, another error happened.'],
+ },
+};
diff --git a/src/components/markdown_editor/markdown_format.stories.tsx b/src/components/markdown_editor/markdown_format.stories.tsx
new file mode 100644
index 00000000000..1092ffba1a7
--- /dev/null
+++ b/src/components/markdown_editor/markdown_format.stories.tsx
@@ -0,0 +1,71 @@
+/*
+ * 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 type { Meta, StoryObj } from '@storybook/react';
+
+import {
+ hideStorybookControls,
+ moveStorybookControlsToCategory,
+} from '../../../.storybook/utils';
+import { EuiMarkdownFormat, EuiMarkdownFormatProps } from './markdown_format';
+import {
+ defaultParsingPlugins,
+ defaultProcessingPlugins,
+} from './plugins/markdown_default_plugins';
+import { ALIGNMENTS } from '../text/text_align';
+
+const initialContent = `## Hello world!
+
+Basic "GitHub flavored" markdown will work as you'd expect.
+
+The editor also ships with some built in plugins. For example it can handle checkboxes. Notice how they toggle state even in the preview mode.
+
+- [ ] Checkboxes
+- [x] Can be filled
+- [ ] Or empty
+
+It can also handle emojis! :smile:
+
+And it can render !{tooltip[tooltips like this](Look! I'm a very helpful tooltip content!)}
+`;
+
+const meta: Meta = {
+ title: 'Editors & Syntax/EuiMarkdownEditor/EuiMarkdownFormat',
+ component: EuiMarkdownFormat,
+ argTypes: {
+ color: { control: { type: 'text' } },
+ grow: { control: { type: 'boolean' } },
+ textAlign: {
+ control: { type: 'radio' },
+ options: [undefined, ...ALIGNMENTS],
+ },
+ ...hideStorybookControls(['aria-label']),
+ },
+ // Component defaults
+ args: {
+ textSize: 'm',
+ parsingPluginList: defaultParsingPlugins,
+ processingPluginList: defaultProcessingPlugins,
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ // TODO: move this to the component level once utils are updated to fully support merged configs (#7583)
+ argTypes: {
+ ...moveStorybookControlsToCategory(
+ ['textAlign', 'color', 'grow'],
+ 'EuiText props'
+ ),
+ },
+ args: {
+ children: initialContent,
+ },
+};
diff --git a/src/components/observer/mutation_observer/mutation_observer.stories.tsx b/src/components/observer/mutation_observer/mutation_observer.stories.tsx
new file mode 100644
index 00000000000..51f7394b875
--- /dev/null
+++ b/src/components/observer/mutation_observer/mutation_observer.stories.tsx
@@ -0,0 +1,102 @@
+/*
+ * 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, { useState } from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { _EuiButtonColor } from '../../../themes/amsterdam/global_styling/mixins';
+import { EuiFlexGroup, EuiFlexItem } from '../../flex';
+import { EuiSpacer } from '../../spacer';
+import { EuiButton, EuiButtonEmpty } from '../../button';
+import { EuiPanel } from '../../panel';
+import {
+ EuiMutationObserver,
+ EuiMutationObserverProps,
+} from './mutation_observer';
+
+const meta: Meta = {
+ title: 'Utilities/EuiMutationObserver',
+ component: EuiMutationObserver,
+};
+
+export default meta;
+type Story = StoryObj;
+
+const StatefulPlayground = ({
+ onMutation,
+ ...rest
+}: EuiMutationObserverProps) => {
+ const [lastMutation, setLastMutation] = useState('no changes detected');
+ const [buttonColor, setButtonColor] = useState<_EuiButtonColor>('primary');
+ const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
+
+ const toggleButtonColor = () => {
+ setButtonColor(buttonColor === 'primary' ? 'warning' : 'primary');
+ };
+
+ const addItem = () => {
+ setItems([...items, `Item ${items.length + 1}`]);
+ };
+
+ const handleOnMutation = (
+ mutationRecords: MutationRecord[],
+ mutationObserver: MutationObserver
+ ) => {
+ const [{ type }] = mutationRecords;
+ setLastMutation(
+ type === 'attributes' ? 'button class name changed' : 'DOM tree changed'
+ );
+ onMutation(mutationRecords, mutationObserver);
+ };
+
+ return (
+ <>
+ {lastMutation}
+
+
+
+
+ {(mutationRef) => (
+
+
+ Toggle button color
+
+
+
+
+
+
+
+
+ {items.map((item) => (
+ - {item}
+ ))}
+
+
+ add item
+
+
+
+
+ )}
+
+ >
+ );
+};
+
+export const Playground: Story = {
+ render: (args) => ,
+};
diff --git a/src/components/observer/mutation_observer/mutation_observer.ts b/src/components/observer/mutation_observer/mutation_observer.tsx
similarity index 100%
rename from src/components/observer/mutation_observer/mutation_observer.ts
rename to src/components/observer/mutation_observer/mutation_observer.tsx
diff --git a/src/components/observer/resize_observer/resize_observer.stories.tsx b/src/components/observer/resize_observer/resize_observer.stories.tsx
new file mode 100644
index 00000000000..f2baa73e127
--- /dev/null
+++ b/src/components/observer/resize_observer/resize_observer.stories.tsx
@@ -0,0 +1,93 @@
+/*
+ * 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, { useState } from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+
+import { EuiPaddingSize } from '../../../global_styling';
+import { EuiText } from '../../text';
+import { EuiCode } from '../../code';
+import { EuiSpacer } from '../../spacer';
+import { EuiButton, EuiButtonEmpty } from '../../button';
+import { EuiPanel } from '../../panel';
+import { EuiResizeObserver, EuiResizeObserverProps } from './resize_observer';
+
+const meta: Meta = {
+ title: 'Utilities/EuiResizeObserver',
+ component: EuiResizeObserver,
+};
+
+export default meta;
+type Story = StoryObj;
+
+const StatefulPlayground = ({ onResize, ...rest }: EuiResizeObserverProps) => {
+ const [paddingSize, setPaddingSize] = useState('s');
+ const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
+ const [height, setHeight] = useState(0);
+ const [width, setWidth] = useState(0);
+
+ const togglePaddingSize = () => {
+ setPaddingSize((paddingSize) => (paddingSize === 's' ? 'l' : 's'));
+ };
+
+ const addItem = () => {
+ setItems((items) => [...items, `Item ${items.length + 1}`]);
+ };
+
+ const handleOnResize = ({
+ height,
+ width,
+ }: {
+ height: number;
+ width: number;
+ }) => {
+ setHeight(height);
+ setWidth(width);
+ onResize({ height, width });
+ };
+
+ return (
+ <>
+
+
+
+ height: {height}; width: {width}
+
+
+
+
+
+
+
+ Toggle container padding
+
+
+
+
+
+ {(resizeRef) => (
+
+
+
+ {items.map((item) => (
+ - {item}
+ ))}
+
+
+ add item
+
+
+ )}
+
+ >
+ );
+};
+
+export const Playground: Story = {
+ render: (args) => ,
+};
diff --git a/src/components/outside_click_detector/outside_click_detector.stories.tsx b/src/components/outside_click_detector/outside_click_detector.stories.tsx
new file mode 100644
index 00000000000..4dbded25f6e
--- /dev/null
+++ b/src/components/outside_click_detector/outside_click_detector.stories.tsx
@@ -0,0 +1,51 @@
+/*
+ * 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 from 'react';
+import type { Meta, StoryObj } from '@storybook/react';
+import { action } from '@storybook/addon-actions';
+
+import { EuiText } from '../text';
+import {
+ EuiOutsideClickDetector,
+ EuiOutsideClickDetectorProps,
+} from './outside_click_detector';
+
+const meta: Meta = {
+ title: 'Utilities/EuiOutsideClickDetector',
+ component: EuiOutsideClickDetector,
+ argTypes: {
+ children: { control: { type: 'text' } },
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const Playground: Story = {
+ args: {
+ children:
+ // cast type here to ensure the control table and output are connected and useful
+ // TODO: remove once the control table can handle more complex types
+ 'Click anywhere outside of this text to trigger an alert' as unknown as any,
+ onOutsideClick: (e: Event) => {
+ action('onOutsideClick')(e);
+ window.alert('Clicked outside');
+ },
+ },
+ render: ({ children, ...rest }: EuiOutsideClickDetectorProps) => {
+ const content = (
+
+ {children}
+
+ );
+ return (
+ {content}
+ );
+ },
+};
diff --git a/src/components/outside_click_detector/outside_click_detector.ts b/src/components/outside_click_detector/outside_click_detector.tsx
similarity index 100%
rename from src/components/outside_click_detector/outside_click_detector.ts
rename to src/components/outside_click_detector/outside_click_detector.tsx