From 37e2b60c7465ad7eb5e3661817e1f5370d2dfeb4 Mon Sep 17 00:00:00 2001 From: Weronika Olejniczak Date: Fri, 11 Oct 2024 13:59:01 +0200 Subject: [PATCH] chore(code-connect): integrate the most used components --- .../eui/src/components/badge/badge.figma.tsx | 66 +++++ .../src/components/badge/badge.stories.tsx | 76 ++++-- .../src/components/button/button.figma.tsx | 148 +++++++++++ .../src/components/button/button.stories.tsx | 81 +++++- .../button_empty/button_empty.stories.tsx | 78 +++++- .../button_group/button_group.stories.tsx | 135 +++++++--- .../components/call_out/call_out.figma.tsx | 62 +++++ .../components/call_out/call_out.stories.tsx | 70 ++++- .../components/combo_box/combo_box.figma.tsx | 71 ++++++ .../combo_box/combo_box.stories.tsx | 239 +++++++++++------- .../form/field_text/field_text.figma.tsx | 64 +++++ .../form/field_text/field_text.stories.tsx | 129 +++++++--- .../components/form/select/select.figma.tsx | 69 +++++ .../components/form/select/select.stories.tsx | 103 +++++++- .../eui/src/components/icon/icon.figma.tsx | 38 +++ .../eui/src/components/icon/icon.stories.tsx | 29 ++- .../eui/src/components/panel/panel.figma.tsx | 55 ++++ .../src/components/panel/panel.stories.tsx | 52 +++- .../eui/src/components/text/text.figma.tsx | 24 ++ .../eui/src/components/text/text.stories.tsx | 35 ++- .../components/tool_tip/tool_tip.figma.tsx | 37 +++ .../components/tool_tip/tool_tip.stories.tsx | 74 ++++-- 22 files changed, 1486 insertions(+), 249 deletions(-) create mode 100644 packages/eui/src/components/badge/badge.figma.tsx create mode 100644 packages/eui/src/components/button/button.figma.tsx create mode 100644 packages/eui/src/components/call_out/call_out.figma.tsx create mode 100644 packages/eui/src/components/combo_box/combo_box.figma.tsx create mode 100644 packages/eui/src/components/form/field_text/field_text.figma.tsx create mode 100644 packages/eui/src/components/form/select/select.figma.tsx create mode 100644 packages/eui/src/components/icon/icon.figma.tsx create mode 100644 packages/eui/src/components/panel/panel.figma.tsx create mode 100644 packages/eui/src/components/text/text.figma.tsx create mode 100644 packages/eui/src/components/tool_tip/tool_tip.figma.tsx diff --git a/packages/eui/src/components/badge/badge.figma.tsx b/packages/eui/src/components/badge/badge.figma.tsx new file mode 100644 index 000000000000..995532768d60 --- /dev/null +++ b/packages/eui/src/components/badge/badge.figma.tsx @@ -0,0 +1,66 @@ +/* + * 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. + */ + +// eslint-disable-next-line +// @ts-nocheck + +/** + * `iconType` expects an enum member, a string or ComponentType + * `iconSide` expects "left" or "right" or undefined + * + * I cannot use `as const` to narrow down the type because the value is serialized, so the code snippet would be + * `iconSide={'left' as const}` + * + * figma.instance returns a a JSX.Element, not ComponentType or string. The code is not executed so it doesn't matter. + * + * `props` must be an object literal so we cannot use assertion there either. + */ + +import React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiBadge } from './badge'; + +figma.connect(EuiBadge, 'node-id=31918-390303', { + props: { + children: figma.boolean('Icon only', { + true: undefined, + false: figma.string('Text'), + }), + color: figma.enum('Color', { + Default: undefined, + Hollow: 'hollow', + Primary: 'primary', + Accent: 'accent', + Success: 'success', + Danger: 'danger', + Warning: 'warning', + }), + isDisabled: figma.boolean('Disabled'), + iconType: figma.boolean('Icon only', { + true: figma.instance('⮑ Icon'), + false: figma.boolean('Icon left', { + true: figma.instance('⮑ Icon left'), + false: figma.boolean('Icon right', { + true: figma.instance('⮑ Icon right'), + false: undefined, + }), + }), + }), + iconSide: figma.boolean('Icon left', { + true: 'left', + false: figma.boolean('Icon right', { + true: 'right', + false: undefined, + }), + }), + }, + example: ({ children, ...props }) => ( + {children} + ), +}); diff --git a/packages/eui/src/components/badge/badge.stories.tsx b/packages/eui/src/components/badge/badge.stories.tsx index 608c1f5f0fd9..c246757d0564 100644 --- a/packages/eui/src/components/badge/badge.stories.tsx +++ b/packages/eui/src/components/badge/badge.stories.tsx @@ -6,25 +6,12 @@ * Side Public License, v 1. */ +import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; import { EuiBadge, EuiBadgeProps, COLORS } from './badge'; -const meta: Meta = { - title: 'Display/EuiBadge/EuiBadge', - component: EuiBadge, - argTypes: { - iconType: { control: 'text' }, - }, - args: { - // Component defaults - iconSide: 'left', - isDisabled: false, - color: 'default', - }, -}; - -export default meta; type Story = StoryObj; export const Playground: Story = { @@ -37,6 +24,9 @@ export const Playground: Story = { options: COLORS, }, }, + render: ({ children, ...args }: EuiBadgeProps) => ( + {children} + ), }; export const CustomColors: Story = { @@ -50,3 +40,59 @@ export const CustomColors: Story = { color: '#0000FF', }, }; + +const meta: Meta = { + title: 'Display/EuiBadge/EuiBadge', + component: EuiBadge, + argTypes: { + iconType: { control: 'text' }, + }, + args: { + // Component defaults + iconSide: 'left', + isDisabled: false, + color: 'default', + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=31918-390303&node-type=frame&m=dev', + examples: [Playground], + props: { + children: figma.boolean('Icon only', { + true: undefined, + false: figma.string('Text'), + }), + color: figma.enum('Color', { + Default: undefined, + Hollow: 'hollow', + Primary: 'primary', + Accent: 'accent', + Success: 'success', + Danger: 'danger', + Warning: 'warning', + }), + isDisabled: figma.boolean('Disabled'), + iconType: figma.boolean('Icon only', { + true: figma.instance('⮑ Icon'), + false: figma.boolean('Icon left', { + true: figma.instance('⮑ Icon left'), + false: figma.boolean('Icon right', { + true: figma.instance('⮑ Icon right'), + false: undefined, + }), + }), + }), + iconSide: figma.boolean('Icon left', { + true: 'left', + false: figma.boolean('Icon right', { + true: 'right', + false: undefined, + }), + }), + }, + }, + }, +}; + +export default meta; diff --git a/packages/eui/src/components/button/button.figma.tsx b/packages/eui/src/components/button/button.figma.tsx new file mode 100644 index 000000000000..5fa3ed790de9 --- /dev/null +++ b/packages/eui/src/components/button/button.figma.tsx @@ -0,0 +1,148 @@ +/* + * 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. + */ + +// eslint-disable-next-line +// @ts-nocheck + +/** + * `iconType` expects an enum member, a string or ComponentType + * `iconSide` expects "left" or "right" or undefined + * + * I cannot use `as const` to narrow down the type below because the value is serialized, + * so the code snippet would be `iconSide={'left' as const}`, + * and figma.instance is a JSX.Element, not ComponentType or string but it's a conflict between + * Code Connect API and the icon implementation (it's not render props pattern). I don't think there's an alternative, + * at the same time it doesn't matter much because Figma files are not executed, they're parsed as string. + */ + +import React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiButton } from './button'; +import { EuiButtonEmpty } from './button_empty'; +import { EuiButtonGroup } from './button_group'; + +/* Example: reusing the same props across multiple component connections */ +const sharedProps = { + children: figma.boolean('Icon only', { + true: undefined, + false: figma.textContent('Text'), + }), + color: figma.enum('Color', { + 'Primary*': undefined, + Neutral: 'text', + Success: 'success', + Warning: 'warning', + Danger: 'danger', + Accent: 'accent', + }), + isDisabled: figma.boolean('Disabled'), + isLoading: figma.boolean('Loading'), + iconType: figma.boolean('Icon only', { + true: figma.instance('⮑ Icon'), + false: figma.boolean('Icon left', { + true: figma.instance('⮑ Icon left'), + false: figma.boolean('Icon right', { + true: figma.instance('⮑ Icon right'), + false: undefined, + }), + }), + }), + iconSide: figma.boolean('Icon left', { + true: 'left', + false: figma.boolean('Icon right', { + true: 'right', + false: figma.boolean('Loading', { + true: figma.boolean('Left spinner', { + true: 'left', + false: figma.boolean('Right spinner', { + true: 'right', + false: undefined, + }), + }), + false: undefined, + }), + }), + }), + size: figma.enum('Size', { + 'Medium*': 'm', + Small: 's', + // Discrepancy between Figma and EUI + // 'Extra Small': 'extra-small', + }), +}; + +/* Basic example */ +figma.connect(EuiButton, 'node-id=31735-391399', { + props: { + ...sharedProps, + fill: figma.enum('Style', { + 'Default*': undefined, + Filled: true, + }), + }, + example: ({ children, ...props }) => ( + {}} {...props}> + {children} + + ), +}); + +/* Example: Self-closing tags example (doesn't work, error: The Figma Variant "Icon only" does not have an option for true) */ +/* figma.connect(EuiButton, 'node-id=31735-391399', { + variant: { 'Icon only': true }, + props: sharedProps, + example: (props) => ( + {}} {...props} /> + ), +}); */ + +/* Example: Figma variant being a separate component in code */ +figma.connect(EuiButtonEmpty, 'node-id=31735-391399', { + variant: { Style: 'Empty' }, + props: sharedProps, + example: (props) => ( + {}} {...props}> + {children} + + ), +}); + +/* Example: static, non-controllable props to showcase the usage */ +figma.connect(EuiButtonGroup, 'node-id=31735-392753', { + props: { + buttonSize: figma.enum('Size', { + 'Small*': 's', + Medium: 'm', + Compressed: 'compressed', + }), + color: figma.enum('Color', { + 'Neutral*': 'text', + Primary: 'primary', + // Discrepancy between Figma and EUI + // Lack of "accent", "success", "warning", "danger" in Figma + }), + isDisabled: figma.boolean('Disabled'), + isFullWidth: figma.boolean('Full width'), + isIconOnly: figma.boolean('Icon only'), + }, + example: (props) => ( + {}} + options={[ + { id: '0', label: 'Button' }, + { id: '1', label: 'Button' }, + { id: '2', label: 'Button' }, + ]} + {...props} + /> + ), +}); diff --git a/packages/eui/src/components/button/button.stories.tsx b/packages/eui/src/components/button/button.stories.tsx index f6135409d974..d9bc9a2062b5 100644 --- a/packages/eui/src/components/button/button.stories.tsx +++ b/packages/eui/src/components/button/button.stories.tsx @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; import { @@ -15,6 +17,18 @@ import { import { EuiButton, Props as EuiButtonProps } from './button'; +type Story = StoryObj; + +export const Playground: Story = { + args: { + children: 'Button', + }, + render: ({ children, ...args }: EuiButtonProps) => ( + {children} + ), +}; +disableStorybookControls(Playground, ['buttonRef']); + const meta: Meta = { title: 'Navigation/EuiButton', component: EuiButton, @@ -37,15 +51,66 @@ const meta: Meta = { isLoading: false, isSelected: false, }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=31735-391399&node-type=frame&m=dev', + examples: [Playground], + props: { + children: figma.boolean('Icon only', { + true: undefined, + false: figma.textContent('Text'), + }), + color: figma.enum('Color', { + 'Primary*': undefined, + Neutral: 'text', + Success: 'success', + Warning: 'warning', + Danger: 'danger', + Accent: 'accent', + }), + fill: figma.enum('Style', { + 'Default*': undefined, + Filled: true, + }), + isDisabled: figma.boolean('Disabled'), + isLoading: figma.boolean('Loading'), + iconType: figma.boolean('Icon only', { + true: figma.instance('⮑ Icon'), + false: figma.boolean('Icon left', { + true: figma.instance('⮑ Icon left'), + false: figma.boolean('Icon right', { + true: figma.instance('⮑ Icon right'), + false: undefined, + }), + }), + }), + iconSide: figma.boolean('Icon left', { + true: 'left', + false: figma.boolean('Icon right', { + true: 'right', + false: figma.boolean('Loading', { + true: figma.boolean('Left spinner', { + true: 'left', + false: figma.boolean('Right spinner', { + true: 'right', + false: undefined, + }), + }), + false: undefined, + }), + }), + }), + size: figma.enum('Size', { + 'Medium*': 'm', + Small: 's', + // Discrepancy between Figma and EUI + // 'Extra Small': 'extra-small', + }), + }, + }, + }, }; enableFunctionToggleControls(meta, ['onClick']); export default meta; -type Story = StoryObj; - -export const Playground: Story = { - args: { - children: 'Button', - }, -}; -disableStorybookControls(Playground, ['buttonRef']); diff --git a/packages/eui/src/components/button/button_empty/button_empty.stories.tsx b/packages/eui/src/components/button/button_empty/button_empty.stories.tsx index 66602e0bcc46..7808e6eec687 100644 --- a/packages/eui/src/components/button/button_empty/button_empty.stories.tsx +++ b/packages/eui/src/components/button/button_empty/button_empty.stories.tsx @@ -6,11 +6,26 @@ * Side Public License, v 1. */ +import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; + import { disableStorybookControls } from '../../../../.storybook/utils'; import { EuiButtonEmpty, EuiButtonEmptyProps } from './button_empty'; +type Story = StoryObj; + +export const Playground: Story = { + args: { + children: 'Tertiary action', + }, + render: ({ children, ...args }: EuiButtonEmptyProps) => ( + {children} + ), +}; +disableStorybookControls(Playground, ['buttonRef']); + const meta: Meta = { title: 'Navigation/EuiButtonEmpty', component: EuiButtonEmpty, @@ -32,14 +47,61 @@ const meta: Meta = { isLoading: false, isSelected: false, }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=31735-391399&node-type=frame&m=dev', + examples: [{ example: Playground, variant: { Style: 'Empty' } }], + props: { + children: figma.boolean('Icon only', { + true: undefined, + false: figma.textContent('Text'), + }), + color: figma.enum('Color', { + 'Primary*': 'primary', + Neutral: 'text', + Success: 'success', + Warning: 'warning', + Danger: 'danger', + Accent: 'accent', + }), + iconType: figma.boolean('Icon only', { + true: figma.instance('⮑ Icon'), + false: figma.boolean('Icon left', { + true: figma.instance('⮑ Icon left'), + false: figma.boolean('Icon right', { + true: figma.instance('⮑ Icon right'), + false: undefined, + }), + }), + }), + iconSide: figma.boolean('Icon left', { + true: 'left', + false: figma.boolean('Icon right', { + true: 'right', + false: figma.boolean('Loading', { + true: figma.boolean('Left spinner', { + true: 'left', + false: figma.boolean('Right spinner', { + true: 'right', + false: undefined, + }), + }), + false: undefined, + }), + }), + }), + isDisabled: figma.boolean('Disabled'), + isLoading: figma.boolean('Loading'), + size: figma.enum('Size', { + 'Medium*': 'm', + Small: 's', + // TODO: document discrepancy between Figma and EUI + // 'Extra Small': 'extra-small', + }), + }, + }, + }, }; export default meta; -type Story = StoryObj; - -export const Playground: Story = { - args: { - children: 'Tertiary action', - }, -}; -disableStorybookControls(Playground, ['buttonRef']); diff --git a/packages/eui/src/components/button/button_group/button_group.stories.tsx b/packages/eui/src/components/button/button_group/button_group.stories.tsx index 9f9a3fe1bd45..3bf02311b4dc 100644 --- a/packages/eui/src/components/button/button_group/button_group.stories.tsx +++ b/packages/eui/src/components/button/button_group/button_group.stories.tsx @@ -7,7 +7,10 @@ */ import React, { useState } from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; +import { useArgs } from '@storybook/preview-api'; + import { disableStorybookControls } from '../../../../.storybook/utils'; import { @@ -16,40 +19,6 @@ import { EuiButtonGroupOptionProps, } from './button_group'; -const meta: Meta = { - title: 'Navigation/EuiButtonGroup', - // @ts-ignore This still works for Storybook controls, even though Typescript complains - component: EuiButtonGroup, - argTypes: { - type: { - options: ['single', 'multi'], - control: { type: 'radio' }, - }, - idSelected: { - control: 'text', - if: { arg: 'type', eq: 'single' }, - }, - idToSelectedMap: { - control: 'object', - if: { arg: 'type', eq: 'multi' }, - }, - options: { - control: 'array', - }, - }, - args: { - // Component defaults - type: 'single', - buttonSize: 's', - color: 'text', - isDisabled: false, - isFullWidth: false, - isIconOnly: false, - }, -}; -disableStorybookControls(meta, ['type']); - -export default meta; type Story = StoryObj; const options: EuiButtonGroupOptionProps[] = [ @@ -67,20 +36,44 @@ const options: EuiButtonGroupOptionProps[] = [ }, ]; -const StatefulEuiButtonGroupSingle = (props: any) => { - const [idSelected, setIdSelected] = useState(props.idSelected); +/* Notice how within the same file we have 2 component wrappers, + - one for single selection + - and one for multi selection + + Using these wrappers as-is in render function will result in an incorrect code snippet: + + ```tsx + + ``` + + Component name should be EuiButtonGroup and there are important props missing + like `type`, `options`, `onChange`, `idSelected` / `idToSelectedMap`. + + Instead, 1) we could leverage args and useArgs utility hook from Storybook. + to synchronize the sandbox component state with Storybook controls panel. + (Source: https://storybook.js.org/docs/writing-stories/args#setting-args-from-within-a-story) +*/ + +const StatefulEuiButtonGroupSingle = (props: EuiButtonGroupProps) => { + const [{ idSelected }, updateArgs] = useArgs(); + + const onChange = (idSelected: EuiButtonGroupProps['idSelected']) => { + updateArgs({ idSelected }); + }; return ( - setIdSelected(id)} - idSelected={idSelected} - /> + ); }; export const SingleSelection: Story = { - render: ({ ...args }) => , + render: (args: EuiButtonGroupProps) => ( + + ), args: { legend: 'EuiButtonGroup - single selection', options, @@ -157,3 +150,61 @@ export const WithTooltips: Story = { idToSelectedMap: { button1: true }, }, }; + +const meta: Meta = { + title: 'Navigation/EuiButtonGroup', + // @ts-ignore This still works for Storybook controls, even though Typescript complains + component: EuiButtonGroup, + argTypes: { + type: { + options: ['single', 'multi'], + control: { type: 'radio' }, + }, + idSelected: { + control: 'text', + if: { arg: 'type', eq: 'single' }, + }, + idToSelectedMap: { + control: 'object', + if: { arg: 'type', eq: 'multi' }, + }, + options: { + control: 'array', + }, + }, + args: { + // Component defaults + type: 'single', + buttonSize: 's', + color: 'text', + isDisabled: false, + isFullWidth: false, + isIconOnly: false, + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=31735-392753&node-type=frame&m=dev', + examples: [SingleSelection], + props: { + buttonSize: figma.enum('Size', { + 'Small*': 's', + Medium: 'm', + Compressed: 'compressed', + }), + color: figma.enum('Color', { + 'Neutral*': 'text', + Primary: 'primary', + // TODO: document discrepancy between Figma and EUI + // accent, success, warning, danger + }), + isDisabled: figma.boolean('Disabled'), + isFullWidth: figma.boolean('Full width'), + isIconOnly: figma.boolean('Icon only'), + }, + }, + }, +}; +disableStorybookControls(meta, ['type']); + +export default meta; diff --git a/packages/eui/src/components/call_out/call_out.figma.tsx b/packages/eui/src/components/call_out/call_out.figma.tsx new file mode 100644 index 000000000000..775fb9ce2cb0 --- /dev/null +++ b/packages/eui/src/components/call_out/call_out.figma.tsx @@ -0,0 +1,62 @@ +/* + * 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. + */ + +// eslint-disable-next-line +// @ts-nocheck + +/** + * `iconType` expects an enum member, a string or ComponentType + * + * figma.instance is a JSX.Element, not ComponentType or string but it's a conflict between + * Code Connect API and the icon implementation (it's not render props pattern). I don't think there's an alternative, + * at the same time it doesn't matter much because Figma files are not executed, they're parsed as string. + */ + +import React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiCallOut } from './call_out'; + +figma.connect(EuiCallOut, 'node-id=32350-392160', { + props: { + children: figma.nestedProps('Children', { + text: figma.children('Callout text'), + }), + color: figma.enum('Color', { + Success: 'success', + Danger: 'danger', + Warning: 'warning', + Primary: 'primary', + }), + iconType: figma.boolean('⮑ Icon', { + true: figma.instance('⮑ Icon glyph'), + false: undefined, + }), + onDismiss: figma.boolean('Dismiss', { + true: () => {}, + false: undefined, + }), + size: figma.enum('Size', { + Medium: 'm', + Small: 's', + }), + title: figma.boolean('Title', { + true: figma.nestedProps('Callout title', { + text: figma.string('Text'), + }), + false: { + text: undefined, + }, + }), + }, + example: ({ children, title, ...props }) => ( + + {children.text} + + ), +}); diff --git a/packages/eui/src/components/call_out/call_out.stories.tsx b/packages/eui/src/components/call_out/call_out.stories.tsx index 7f40c86a6550..748af0ce3c4f 100644 --- a/packages/eui/src/components/call_out/call_out.stories.tsx +++ b/packages/eui/src/components/call_out/call_out.stories.tsx @@ -6,10 +6,40 @@ * Side Public License, v 1. */ +import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; import { EuiCallOut, EuiCallOutProps } from './call_out'; +type Story = StoryObj; + +// It's hard to align component props with Figma property mapping +/* type Story = StoryObj< + EuiCallOutProps & { + title: { text: string }; + children: { text: string }; + } +>; */ + +export const Playground: Story = { + args: { + title: 'Callout title', + children: 'Callout text', + }, + render: ({ children, title, ...props }) => ( + + {children} + + ), + // This would have to be the render function with nested properties: + /* render: ({ children, title, ...props }) => ( + + {children.text} + + ), */ +}; + const meta: Meta = { title: 'Display/EuiCallOut', component: EuiCallOut, @@ -22,14 +52,38 @@ const meta: Meta = { heading: 'p', size: 'm', }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=32350-392160&node-type=frame&m=dev', + examples: [Playground], + props: { + children: figma.instance('Children'), + color: figma.enum('Color', { + Success: 'success', + Danger: 'danger', + Warning: 'warning', + Primary: 'primary', + }), + iconType: figma.boolean('⮑ Icon', { + true: figma.instance('⮑ Icon glyph'), + false: undefined, + }), + onDismiss: figma.boolean('Dismiss', { + true: () => {}, + false: undefined, + }), + size: figma.enum('Size', { + Medium: 'm', + Small: 's', + }), + // This has to differ from the standalone Figma config to align with Storybook + title: figma.boolean('Title', { + true: 'Title', + }), + }, + }, + }, }; export default meta; -type Story = StoryObj; - -export const Playground: Story = { - args: { - title: 'Callout title', - children: 'Callout text', - }, -}; diff --git a/packages/eui/src/components/combo_box/combo_box.figma.tsx b/packages/eui/src/components/combo_box/combo_box.figma.tsx new file mode 100644 index 000000000000..d47eeabc0431 --- /dev/null +++ b/packages/eui/src/components/combo_box/combo_box.figma.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 React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiComboBox } from './combo_box'; +import { EuiFormRow } from '../form'; + +figma.connect(EuiComboBox, 'node-id=15883-161301', { + props: { + ariaLabel: figma.boolean('Label', { + true: undefined, + false: 'Meaningful label', + }), + compressed: figma.boolean('Compressed'), + error: figma.nestedProps('📦Form Row / Error text', { + text: figma.textContent('Text'), + }), + helpText: figma.boolean('Help text', { + true: figma.nestedProps('📦 Form Row / Help text', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + isDisabled: figma.enum('State', { + Disabled: true, + }), + isInvalid: figma.enum('State', { + Invalid: true, + }), + label: figma.boolean('Label', { + true: figma.nestedProps('📦 Form Row / Label', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + }, + example: ({ ariaLabel, error, helpText, isInvalid, label, ...props }) => ( + + {}} + isInvalid={isInvalid} + {...props} + /> + + ), +}); diff --git a/packages/eui/src/components/combo_box/combo_box.stories.tsx b/packages/eui/src/components/combo_box/combo_box.stories.tsx index 880c9d93496e..4e3c385fc964 100644 --- a/packages/eui/src/components/combo_box/combo_box.stories.tsx +++ b/packages/eui/src/components/combo_box/combo_box.stories.tsx @@ -7,6 +7,7 @@ */ import React, { useCallback, useState } from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; import { userEvent, waitFor, within, expect } from '@storybook/test'; @@ -19,7 +20,8 @@ import { EuiCode } from '../code'; import { EuiFlexItem } from '../flex'; import { EuiComboBoxOptionMatcher } from './types'; -import { EuiComboBox, EuiComboBoxProps } from './combo_box'; +import { EuiComboBox as ComboBox, EuiComboBoxProps } from './combo_box'; +import { EuiFormRow } from '../form/form_row'; const options = [ { label: 'Item 1' }, @@ -29,50 +31,75 @@ const options = [ { label: 'Item 5' }, ]; -const meta: Meta> = { - title: 'Forms/EuiComboBox', - // @ts-ignore typescript shenanigans - component: EuiComboBox, - argTypes: { - singleSelection: { - control: 'radio', - options: [false, true, 'asPlainText'], - }, - append: { control: 'text' }, - prepend: { control: 'text' }, - // Storybook is skipping the Pick<> props from EuiComboBoxList for some annoying reason - onCreateOption: { control: 'boolean' }, // Set to a true/false for ease of testing - customOptionText: { control: 'text' }, - renderOption: { control: 'function' }, - }, - args: { - // Pass options in by default for ease of testing - options: options, - selectedOptions: [options[0]], - // Component defaults - delimiter: ',', - sortMatchesBy: 'none', - singleSelection: false, - noSuggestions: false, - async: false, - isCaseSensitive: false, - isClearable: true, - isDisabled: false, - isInvalid: false, - isLoading: false, - autoFocus: false, - compressed: false, - fullWidth: false, - onCreateOption: undefined, // Override Storybook's default callback - }, +const EuiComboBox = ({ + singleSelection, + onCreateOption, + onChange, + ...args +}: EuiComboBoxProps<{}>) => { + const [selectedOptions, setSelectedOptions] = useState(args.selectedOptions); + const handleOnChange: EuiComboBoxProps<{}>['onChange'] = ( + options, + ...args + ) => { + setSelectedOptions(options); + onChange?.(options, ...args); + }; + const _onCreateOption: EuiComboBoxProps<{}>['onCreateOption'] = ( + searchValue, + ...args + ) => { + const createdOption = { label: searchValue }; + setSelectedOptions((prevState) => + !prevState || singleSelection + ? [createdOption] + : [...prevState, createdOption] + ); + onCreateOption?.(searchValue, ...args); + }; + return ( + + ); }; -enableFunctionToggleControls(meta, ['onChange', 'onCreateOption']); -export default meta; -type Story = StoryObj>; +type Story = StoryObj< + EuiComboBoxProps<{}> & { + ariaLabel: string; + error: { text?: string }; + helpText: { text?: string }; + label: { text?: string }; + } +>; export const Playground: Story = { - render: (args) => , + render: ({ ariaLabel, error, helpText, isInvalid, label, ...props }) => ( + + {}} + isInvalid={isInvalid} + {...props} + /> + + ), }; export const WithTooltip: Story = { @@ -94,7 +121,7 @@ export const WithTooltip: Story = { value: idx, })), }, - render: (args) => , + render: (args) => , play: lokiPlayDecorator(async (context) => { const { bodyElement, step } = context; @@ -158,7 +185,7 @@ export const Groups: Story = { ], autoFocus: true, }, - render: (args) => , + render: (args) => , }; export const NestedOptionsGroups: Story = { @@ -189,49 +216,7 @@ export const NestedOptionsGroups: Story = { ], autoFocus: true, }, - render: (args) => , -}; - -const StatefulComboBox = ({ - singleSelection, - onCreateOption, - onChange, - ...args -}: EuiComboBoxProps<{}>) => { - const [selectedOptions, setSelectedOptions] = useState(args.selectedOptions); - const handleOnChange: EuiComboBoxProps<{}>['onChange'] = ( - options, - ...args - ) => { - setSelectedOptions(options); - onChange?.(options, ...args); - }; - const _onCreateOption: EuiComboBoxProps<{}>['onCreateOption'] = ( - searchValue, - ...args - ) => { - const createdOption = { label: searchValue }; - setSelectedOptions((prevState) => - !prevState || singleSelection - ? [createdOption] - : [...prevState, createdOption] - ); - onCreateOption?.(searchValue, ...args); - }; - return ( - - ); + render: (args) => , }; const StoryCustomMatcher = ({ @@ -263,7 +248,7 @@ const StoryCustomMatcher = ({ matched.


- ); }; + +const meta: Meta> = { + title: 'Forms/EuiComboBox', + // @ts-ignore typescript shenanigans + component: ComboBox, + argTypes: { + singleSelection: { + control: 'radio', + options: [false, true, 'asPlainText'], + }, + append: { control: 'text' }, + prepend: { control: 'text' }, + // Storybook is skipping the Pick<> props from EuiComboBoxList for some annoying reason + onCreateOption: { control: 'boolean' }, // Set to a true/false for ease of testing + customOptionText: { control: 'text' }, + renderOption: { control: 'function' }, + }, + args: { + // Pass options in by default for ease of testing + options: options, + selectedOptions: [options[0]], + // Component defaults + delimiter: ',', + sortMatchesBy: 'none', + singleSelection: false, + noSuggestions: false, + async: false, + isCaseSensitive: false, + isClearable: true, + isDisabled: false, + isInvalid: false, + isLoading: false, + autoFocus: false, + compressed: false, + fullWidth: false, + onCreateOption: undefined, // Override Storybook's default callback + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=15883-161301&node-type=frame&m=dev', + examples: [Playground], + props: { + ariaLabel: figma.boolean('Label', { + true: undefined, + false: 'Meaningful label', + }), + compressed: figma.boolean('Compressed'), + error: figma.nestedProps('📦Form Row / Error text', { + text: figma.textContent('Text'), + }), + helpText: figma.boolean('Help text', { + true: figma.nestedProps('📦 Form Row / Help text', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + isDisabled: figma.enum('State', { + Disabled: true, + }), + isInvalid: figma.enum('State', { + Invalid: true, + }), + label: figma.boolean('Label', { + true: figma.nestedProps('📦 Form Row / Label', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + }, + }, + }, +}; +enableFunctionToggleControls(meta, ['onChange', 'onCreateOption']); + +export default meta; diff --git a/packages/eui/src/components/form/field_text/field_text.figma.tsx b/packages/eui/src/components/form/field_text/field_text.figma.tsx new file mode 100644 index 000000000000..0f81cd8b349c --- /dev/null +++ b/packages/eui/src/components/form/field_text/field_text.figma.tsx @@ -0,0 +1,64 @@ +/* + * 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 figma from '@figma/code-connect'; + +import { EuiFieldText } from './field_text'; +import { EuiFormRow } from '../form_row'; + +figma.connect(EuiFieldText, 'node-id=13676-796', { + props: { + ariaLabel: figma.boolean('Label', { + true: undefined, + false: 'Meaningful label', + }), + compressed: figma.boolean('Compressed'), + error: figma.nestedProps('📦Form Row / Error text', { + text: figma.textContent('Text'), + }), + helpText: figma.boolean('Help text', { + true: figma.nestedProps('📦 Form Row / Help text', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + isDisabled: figma.enum('State', { + Disabled: true, + }), + isInvalid: figma.enum('State', { + Invalid: true, + }), + label: figma.boolean('Label', { + true: figma.nestedProps('📦 Form Row / Label', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + }, + example: ({ ariaLabel, error, helpText, isInvalid, label, ...props }) => ( + + {}} + isInvalid={isInvalid} + {...props} + /> + + ), +}); diff --git a/packages/eui/src/components/form/field_text/field_text.stories.tsx b/packages/eui/src/components/form/field_text/field_text.stories.tsx index 70ead83c42d4..9c23df495de8 100644 --- a/packages/eui/src/components/form/field_text/field_text.stories.tsx +++ b/packages/eui/src/components/form/field_text/field_text.stories.tsx @@ -7,45 +7,46 @@ */ import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; + import { disableStorybookControls, moveStorybookControlsToCategory, } from '../../../../.storybook/utils'; +import { EuiFormRow } from '../form_row'; import { EuiFieldText, EuiFieldTextProps } from './field_text'; -const meta: Meta = { - title: 'Forms/EuiFieldText', - component: EuiFieldText, - argTypes: { - // For quicker/easier QA - icon: { control: 'text' }, - prepend: { control: 'text' }, - append: { control: 'text' }, - value: { control: 'text' }, - }, - args: { - // Component defaults - compressed: false, - fullWidth: false, - isInvalid: false, - isLoading: false, - disabled: false, - readOnly: false, - controlOnly: false, - // Added for easier testing - placeholder: 'EuiFieldText', - id: '', - name: '', - }, -}; +// We need to add some args to make the snippet fully accurate +// Here, args do not map 1:1 to props +type Story = StoryObj< + EuiFieldTextProps & { + ariaLabel: string; + error: { text?: string }; + helpText: { text?: string }; + label: { text?: string }; + } +>; -export default meta; -type Story = StoryObj; -disableStorybookControls(meta, ['inputRef']); - -export const Playground: Story = {}; +export const Playground: Story = { + render: ({ ariaLabel, error, helpText, isInvalid, label, ...props }) => ( + + {}} + isInvalid={isInvalid} + {...props} + /> + + ), +}; export const IconShape: Story = { parameters: { @@ -98,3 +99,71 @@ export const AutoFill: Story = { name: 'autofill-test', }, }; + +const meta: Meta = { + title: 'Forms/EuiFieldText', + component: EuiFieldText, + argTypes: { + // For quicker/easier QA + icon: { control: 'text' }, + prepend: { control: 'text' }, + append: { control: 'text' }, + value: { control: 'text' }, + }, + args: { + // Component defaults + compressed: false, + fullWidth: false, + isInvalid: false, + isLoading: false, + disabled: false, + readOnly: false, + controlOnly: false, + // Added for easier testing + placeholder: 'EuiFieldText', + id: '', + name: '', + }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=13676-796&node-type=frame&m=dev', + examples: [Playground], + props: { + ariaLabel: figma.boolean('Label', { + true: undefined, + false: 'Meaningful label', + }), + compressed: figma.boolean('Compressed'), + error: figma.nestedProps('📦Form Row / Error text', { + text: figma.textContent('Text'), + }), + helpText: figma.boolean('Help text', { + true: figma.nestedProps('📦 Form Row / Help text', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + isDisabled: figma.enum('State', { + Disabled: true, + }), + isInvalid: figma.enum('State', { + Invalid: true, + }), + label: figma.boolean('Label', { + true: figma.nestedProps('📦 Form Row / Label', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + }, + }, + }, +}; + +export default meta; +disableStorybookControls(meta, ['inputRef']); diff --git a/packages/eui/src/components/form/select/select.figma.tsx b/packages/eui/src/components/form/select/select.figma.tsx new file mode 100644 index 000000000000..2c7147976e60 --- /dev/null +++ b/packages/eui/src/components/form/select/select.figma.tsx @@ -0,0 +1,69 @@ +/* + * 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 figma from '@figma/code-connect'; + +import { EuiSelect } from './select'; +import { EuiFormRow } from '../form_row'; + +figma.connect(EuiSelect, 'node-id=15883-129716', { + props: { + ariaLabel: figma.boolean('Label', { + true: undefined, + false: 'Meaningful label', + }), + compressed: figma.boolean('Compressed'), + error: figma.nestedProps('📦Form Row / Error text', { + text: figma.textContent('Text'), + }), + helpText: figma.boolean('Help text', { + true: figma.nestedProps('📦 Form Row / Help text', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + isDisabled: figma.enum('State', { + Disabled: true, + }), + isInvalid: figma.enum('State', { + Invalid: true, + }), + label: figma.boolean('Label', { + true: figma.nestedProps('📦 Form Row / Label', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + }, + example: ({ ariaLabel, error, helpText, isInvalid, label, ...props }) => ( + + {}} + isInvalid={isInvalid} + {...props} + /> + + ), +}); diff --git a/packages/eui/src/components/form/select/select.stories.tsx b/packages/eui/src/components/form/select/select.stories.tsx index deb4e0401f2c..18fc78f4ecb0 100644 --- a/packages/eui/src/components/form/select/select.stories.tsx +++ b/packages/eui/src/components/form/select/select.stories.tsx @@ -8,15 +8,69 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import figma from '@figma/code-connect'; import { disableStorybookControls, enableFunctionToggleControls, } from '../../../../.storybook/utils'; import { EuiIcon } from '../../icon'; +import { EuiFormRow } from '../form_row'; import { EuiSelect, EuiSelectProps } from './select'; +// We need to add story-specific arguments (that do not map directly to component props) +type Story = StoryObj< + EuiSelectProps & { + ariaLabel: string; + error: { text?: string }; + helpText: { text?: string }; + label: { text?: string }; + } +>; + +export const Playground: Story = { + args: { + defaultValue: 'option-2', + options: [ + { value: 'option-1', text: 'Option 1' }, + { value: 'option-2', text: 'Option 2' }, + { value: 'option-3', text: 'Option 3' }, + ], + }, + // Each prop has to be explicitly defined to be present in the Code Connect code snippet + // but if it's not available in the Figma mapping, the parsing will fail if we add it as an explicit story arg + render: ({ + ariaLabel, + error, + helpText, + isInvalid, + label, + /* options - this will fail parsing */ + ...props + }) => ( + + {}} + isInvalid={isInvalid} + {...props} + /> + + ), +}; + const meta: Meta = { title: 'Forms/EuiSelect', component: EuiSelect, @@ -25,6 +79,43 @@ const meta: Meta = { // Excude onMouseUp from controls, as it's not a terribly useful prop to document exclude: ['onMouseUp'], }, + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=15883-129716&node-type=frame&m=dev', + examples: [Playground], + props: { + ariaLabel: figma.boolean('Label', { + true: undefined, + false: 'Meaningful label', + }), + compressed: figma.boolean('Compressed'), + error: figma.nestedProps('📦Form Row / Error text', { + text: figma.textContent('Text'), + }), + helpText: figma.boolean('Help text', { + true: figma.nestedProps('📦 Form Row / Help text', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + isDisabled: figma.enum('State', { + Disabled: true, + }), + isInvalid: figma.enum('State', { + Invalid: true, + }), + label: figma.boolean('Label', { + true: figma.nestedProps('📦 Form Row / Label', { + text: figma.textContent('Text'), + }), + false: { + text: undefined, + }, + }), + }, + }, }, argTypes: { append: { @@ -63,15 +154,3 @@ enableFunctionToggleControls(meta, ['onChange']); disableStorybookControls(meta, ['inputRef']); export default meta; -type Story = StoryObj; - -export const Playground: Story = { - args: { - defaultValue: 'option-2', - options: [ - { value: 'option-1', text: 'Option 1' }, - { value: 'option-2', text: 'Option 2' }, - { value: 'option-3', text: 'Option 3' }, - ], - }, -}; diff --git a/packages/eui/src/components/icon/icon.figma.tsx b/packages/eui/src/components/icon/icon.figma.tsx new file mode 100644 index 000000000000..f71998b8af0a --- /dev/null +++ b/packages/eui/src/components/icon/icon.figma.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. + */ + +// eslint-disable-next-line +// @ts-nocheck + +/** + * `type` expects an enum member, a string or ComponentType + * + * figma.instance is a JSX.Element, not ComponentType or string but it's a conflict between + * Code Connect API and the icon implementation (it's not render props pattern). I don't think there's an alternative, + * at the same time it doesn't matter much because Figma files are not executed, they're parsed as string. + */ + +import React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiIcon } from './icon'; + +figma.connect(EuiIcon, 'node-id=31572-393323', { + props: { + type: figma.instance('Type'), + size: figma.enum('Size', { + 'Small - 12px': 's', + 'Medium* - 16px': 'm', + 'Large - 24px': 'l', + 'X-Large - 32px': 'xl', + 'XX-Large - 40px': 'xxl', + Size6: 'original', + }), + }, + example: (props) => , +}); diff --git a/packages/eui/src/components/icon/icon.stories.tsx b/packages/eui/src/components/icon/icon.stories.tsx index c226953ff576..95f60c48bc37 100644 --- a/packages/eui/src/components/icon/icon.stories.tsx +++ b/packages/eui/src/components/icon/icon.stories.tsx @@ -6,10 +6,18 @@ * Side Public License, v 1. */ +import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; import { EuiIcon, EuiIconProps } from './icon'; +type Story = StoryObj; + +export const Playground: Story = { + render: (props) => , +}; + const meta: Meta = { title: 'Display/EuiIcon', component: EuiIcon, @@ -21,9 +29,24 @@ const meta: Meta = { type: 'accessibility', size: 'm', }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=31572-393323&node-type=frame&m=dev', + examples: [Playground], + props: { + type: figma.instance('Type'), + size: figma.enum('Size', { + 'Small - 12px': 's', + 'Medium* - 16px': 'm', + 'Large - 24px': 'l', + 'X-Large - 32px': 'xl', + 'XX-Large - 40px': 'xxl', + Size6: 'original', + }), + }, + }, + }, }; export default meta; -type Story = StoryObj; - -export const Playground: Story = {}; diff --git a/packages/eui/src/components/panel/panel.figma.tsx b/packages/eui/src/components/panel/panel.figma.tsx new file mode 100644 index 000000000000..bbcf1336a2fe --- /dev/null +++ b/packages/eui/src/components/panel/panel.figma.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 React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiPanel } from './panel'; + +const sharedProps = { + borderRadius: figma.boolean('Border radius', { + true: 'm', + false: 'none', + }), + color: figma.enum('Color', { + 'Plain*': undefined, + Subdued: 'subdued', + Primary: 'primary', + Success: 'success', + Warning: 'warning', + Danger: 'danger', + Accent: 'accent', + Transparent: 'transparent', + }), + children: figma.instance('Content'), + hasBorder: figma.boolean('Border'), + paddingSize: figma.enum('Padding size', { + None: 'none', + Small: 's', + 'Medium*': undefined, + Large: 'l', + }), +}; + +figma.connect(EuiPanel, 'node-id=32642-391756', { + variant: { Shadow: true }, + props: sharedProps, + example: ({ children, ...props }) => ( + {children} + ), +}); + +figma.connect(EuiPanel, 'node-id=32642-391756', { + variant: { Shadow: false }, + props: sharedProps, + example: ({ children, ...props }) => ( + + {children} + + ), +}); diff --git a/packages/eui/src/components/panel/panel.stories.tsx b/packages/eui/src/components/panel/panel.stories.tsx index 03e3296a6377..620eafc13aac 100644 --- a/packages/eui/src/components/panel/panel.stories.tsx +++ b/packages/eui/src/components/panel/panel.stories.tsx @@ -6,14 +6,28 @@ * Side Public License, v 1. */ +import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; +import figma from '@figma/code-connect'; import { disableStorybookControls, enableFunctionToggleControls, } from '../../../.storybook/utils'; + import { EuiPanel, EuiPanelProps } from './panel'; +type Story = StoryObj; + +export const Playground: Story = { + args: { + children: 'Panel content', + }, + render: ({ children, ...props }: EuiPanelProps) => ( + {children} + ), +}; + const meta: Meta = { title: 'Layout/EuiPanel', component: EuiPanel, @@ -33,15 +47,39 @@ const meta: Meta = { hasBorder: false, grow: true, }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=32642-391756&node-type=frame&m=dev', + examples: [Playground], + props: { + borderRadius: figma.boolean('Border radius', { + true: 'm', + false: 'none', + }), + color: figma.enum('Color', { + 'Plain*': undefined, + Subdued: 'subdued', + Primary: 'primary', + Success: 'success', + Warning: 'warning', + Danger: 'danger', + Accent: 'accent', + Transparent: 'transparent', + }), + children: figma.instance('Content'), + hasBorder: figma.boolean('Border'), + paddingSize: figma.enum('Padding size', { + None: 'none', + Small: 's', + 'Medium*': undefined, + Large: 'l', + }), + }, + }, + }, }; enableFunctionToggleControls(meta, ['onClick']); disableStorybookControls(meta, ['panelRef']); export default meta; -type Story = StoryObj; - -export const Playground: Story = { - args: { - children: 'Panel content', - }, -}; diff --git a/packages/eui/src/components/text/text.figma.tsx b/packages/eui/src/components/text/text.figma.tsx new file mode 100644 index 000000000000..e27463e7cf65 --- /dev/null +++ b/packages/eui/src/components/text/text.figma.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 React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiText } from './text'; + +figma.connect(EuiText, 'node-id=32296-391647', { + props: { + children: figma.string('Text'), + size: figma.enum('Size', { + Medium: 'm', + Small: 's', + 'X-Small': 'xs', + }), + }, + example: ({ children, ...props }) => {children}, +}); diff --git a/packages/eui/src/components/text/text.stories.tsx b/packages/eui/src/components/text/text.stories.tsx index e8a613c1ea54..4f20fe667447 100644 --- a/packages/eui/src/components/text/text.stories.tsx +++ b/packages/eui/src/components/text/text.stories.tsx @@ -6,6 +6,8 @@ * Side Public License, v 1. */ +import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; import { faker } from '@faker-js/faker'; @@ -14,6 +16,17 @@ faker.seed(42); import { moveStorybookControlsToCategory } from '../../../.storybook/utils'; import { EuiText, EuiTextProps } from './text'; +type Story = StoryObj; + +export const Playground: Story = { + args: { + children: faker.lorem.sentences(3), + }, + render: ({ children, ...args }: EuiTextProps) => ( + {children} + ), +}; + const meta: Meta = { title: 'Display/EuiText/EuiText', component: EuiText, @@ -27,15 +40,23 @@ const meta: Meta = { textAlign: 'left', component: 'div', }, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=32296-391647&node-type=frame&m=dev', + examples: [Playground], + props: { + children: figma.string('Text'), + size: figma.enum('Size', { + Medium: 'm', + Small: 's', + 'X-Small': 'xs', + }), + }, + }, + }, }; moveStorybookControlsToCategory(meta, ['color'], 'EuiTextColor props'); moveStorybookControlsToCategory(meta, ['textAlign'], 'EuiTextAlign props'); export default meta; -type Story = StoryObj; - -export const Playground: Story = { - args: { - children: faker.lorem.sentences(3), - }, -}; diff --git a/packages/eui/src/components/tool_tip/tool_tip.figma.tsx b/packages/eui/src/components/tool_tip/tool_tip.figma.tsx new file mode 100644 index 000000000000..76d7c166fae5 --- /dev/null +++ b/packages/eui/src/components/tool_tip/tool_tip.figma.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 React from 'react'; +import figma from '@figma/code-connect'; + +import { EuiToolTip } from './tool_tip'; + +figma.connect(EuiToolTip, 'node-id=32039-390707', { + props: { + content: figma.string('Description'), + position: figma.enum('Direction', { + '12:00 ↑': 'bottom', + '11:00': 'bottom', + '10:00': 'right', + '8:00': 'right', + '7:00': 'top', + '6:00 ↓': 'top', + '5:00': 'top', + '4:00': 'left', + '3:00 →': 'left', + '2:00': 'left', + '1::00': 'bottom', + '9:00 ←': 'right', + }), + title: figma.boolean('Title', { + true: figma.string('⮑ Title'), + false: undefined, + }), + }, + example: (props) => , +}); diff --git a/packages/eui/src/components/tool_tip/tool_tip.stories.tsx b/packages/eui/src/components/tool_tip/tool_tip.stories.tsx index 8c4deb062634..0e522ebaf647 100644 --- a/packages/eui/src/components/tool_tip/tool_tip.stories.tsx +++ b/packages/eui/src/components/tool_tip/tool_tip.stories.tsx @@ -7,14 +7,40 @@ */ import React from 'react'; +import figma from '@figma/code-connect'; import type { Meta, StoryObj } from '@storybook/react'; import { enableFunctionToggleControls } from '../../../.storybook/utils'; import { LOKI_SELECTORS } from '../../../.storybook/loki'; import { EuiFlexGroup } from '../flex'; import { EuiButton } from '../button'; + import { EuiToolTip, EuiToolTipProps } from './tool_tip'; +type Story = StoryObj; + +export const Playground: Story = { + args: { + // using autoFocus here as small trick to ensure showing the tooltip on load (e.g. for VRT) + // TODO: uncomment loki play() interactions and remove autoFocus once #7747 is merged + children: Tooltip trigger, + content: 'tooltip content', + }, + render: (props) => , + // play: lokiPlayDecorator(async (context) => { + // const { bodyElement, step } = context; + + // const canvas = within(bodyElement); + + // await step('show tooltip on click', async () => { + // await userEvent.click(canvas.getByRole('button')); + // await waitFor(() => { + // expect(canvas.getByRole('tooltip')).toBeVisible(); + // }); + // }); + // }), +}; + const meta: Meta = { title: 'Display/EuiToolTip', component: EuiToolTip, @@ -23,6 +49,32 @@ const meta: Meta = { loki: { chromeSelector: LOKI_SELECTORS.portal, }, + design: { + type: 'figma', + url: 'https://www.figma.com/design/RzfYLj2xmH9K7gQtbSKygn/Elastic-UI?node-id=32039-390707&node-type=frame&m=dev', + examples: [Playground], + props: { + content: figma.string('Description'), + position: figma.enum('Direction', { + '12:00 ↑': 'bottom', + '11:00': 'bottom', + '10:00': 'right', + '8:00': 'right', + '7:00': 'top', + '6:00 ↓': 'top', + '5:00': 'top', + '4:00': 'left', + '3:00 →': 'left', + '2:00': 'left', + '1::00': 'bottom', + '9:00 ←': 'right', + }), + title: figma.boolean('Title', { + true: figma.string('⮑ Title'), + false: undefined, + }), + }, + }, }, decorators: [ (Story, { args }) => ( @@ -50,25 +102,3 @@ const meta: Meta = { enableFunctionToggleControls(meta, ['onMouseOut']); export default meta; -type Story = StoryObj; - -export const Playground: Story = { - args: { - // using autoFocus here as small trick to ensure showing the tooltip on load (e.g. for VRT) - // TODO: uncomment loki play() interactions and remove autoFocus once #7747 is merged - children: Tooltip trigger, - content: 'tooltip content', - }, - // play: lokiPlayDecorator(async (context) => { - // const { bodyElement, step } = context; - - // const canvas = within(bodyElement); - - // await step('show tooltip on click', async () => { - // await userEvent.click(canvas.getByRole('button')); - // await waitFor(() => { - // expect(canvas.getByRole('tooltip')).toBeVisible(); - // }); - // }); - // }), -};