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 + //