Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Storybook] Add story code snippets #7716

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d6a6dfa
feat: add custom code-sippet addon panel
mgadewoll May 24, 2024
b9b032b
refactor: renames prettier config file to js file for extension usage
mgadewoll May 24, 2024
47d236f
feat: add and use jsxDecorator
mgadewoll May 24, 2024
8839a4c
fix: prevent fewer hooks called error on euiTheme
mgadewoll May 24, 2024
3bce2bc
docs(storybook): updates stories to ensure code-snippet generation
mgadewoll May 24, 2024
c040d8a
docs(storybook): various story adjustments
mgadewoll May 24, 2024
e3d082e
docs(storybook): skip code snippet generation for components that hav…
mgadewoll May 24, 2024
07258c5
refactor: update style object construction regex
mgadewoll May 24, 2024
b7c574c
chore: update yarn lock
mgadewoll Jun 7, 2024
c6cc95e
build: update prettier types dependency to correct version
mgadewoll Jun 7, 2024
e76dd6f
refactor: update to exclude empty object and array values from props
mgadewoll Jun 7, 2024
23d5ac7
fix: ensure default tags with emotion css are output correctly
mgadewoll Jun 7, 2024
13d1797
refactor: rename emotion object data key
mgadewoll Jun 7, 2024
52b8460
docs(storybook): revert custom opening handling for EuiheaderAlert fl…
mgadewoll Jun 12, 2024
1910e88
feat(code-snippet-addon): show/hide addon conditionally based on skip…
mgadewoll Jun 13, 2024
7cb1118
feat(code-snippet-addon): enable opening the addon panel based on lin…
mgadewoll Jun 13, 2024
c2c8e3f
refactor(code-snippet): move event setup to addon directory
mgadewoll Jun 13, 2024
e6f643e
refactor(code-snippet): remove obsolete double quote transform since …
mgadewoll Jun 13, 2024
471b7a3
docs: add addon README
mgadewoll Jun 13, 2024
03d2055
docs: update addon readme
mgadewoll Jun 13, 2024
35e412b
chore(code-snippet): cleanup
mgadewoll Jun 17, 2024
38ca6be
chore(code-snippet): remove unused skip code
mgadewoll Jun 17, 2024
bfd2266
docs: add documentation on manual code snippet
mgadewoll Jun 17, 2024
2d9e1b1
docs(code-snippet): update links
mgadewoll Jun 17, 2024
4b56424
docs(code-snippet): update readme
mgadewoll Jun 19, 2024
118adc8
refactor: update .match usages
mgadewoll Jun 19, 2024
4f6e4ac
move jsx constants and change them to be Sets
mgadewoll Jun 19, 2024
57ad5fe
refactor: tighten types where possible
mgadewoll Jun 19, 2024
5643d07
fix(code-snippet): fix isFragment type guards
mgadewoll Jun 19, 2024
0267410
docs(storybook): ensure EuiSpacer args are spread to component
mgadewoll Jun 19, 2024
890f288
docs(storybook): skip snippet generation for EuiInnerText
mgadewoll Jun 19, 2024
3d7c6be
feat(code-snippet): add support for snippet value overrides
mgadewoll Jun 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 0 additions & 7 deletions packages/eui/.prettierrc

This file was deleted.

7 changes: 7 additions & 0 deletions packages/eui/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
parser: "typescript",
printWidth: 80,
semi: true,
singleQuote: true,
trailingComma: "es5"
}
197 changes: 197 additions & 0 deletions packages/eui/.storybook/addons/code-snippet/README.md
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This README was incredibly thorough and helpful in understanding the high level approach - huge 👏 s for it!!

Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Storybook code-snippet addon

## Description

> This is an internal EUI Storybook addon which adds code snippets to EUI stories.

The purpose of this addon is to improve the developer experience by providing code snippets with dynamically updated props based on the story controls.

This addon is provided as additional story panel next to the available panels for "Controls", "Actions" and "Interactions".

The basis for the code snippet generation is based on Storybooks [`Source`](https://storybook.js.org/docs/writing-docs/doc-blocks#source) block. The internally used [`jsxDecorator`](https://github.com/storybookjs/storybook/blob/2bff7a1c156bbd42ab381f84b8a55a07694e7e53/code/renderers/react/src/docs/jsxDecorator.tsx) file was copied and then adjusted and extended to fit the specific needs for EUI. The main functionality to generate a jsx string from react elements comes from the [`react-element-to-jsx-string`](https://github.com/algolia/react-element-to-jsx-strin) package.

## Concept

The `code-snippet` addon follows the [official guides](https://storybook.js.org/docs/addons/writing-addons) to create a Storybook addon. The only real difference is that this addon is not released separately but simply added and used internally.

The addon is defined and registered in `manager.ts` this ensures it's available in Storybook. Storybook handles most of the rendered output (e.g. tab list and tab buttons), the only custom content is what is passed via the `render` key on the addon config. This content will be output as child of the addon panel that Storybook renders.

```ts
// Register a addon
addons.register(ADDON_ID, (api: API) => {
// Register a panel
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'Code Snippet',
match: ({ viewMode }) => viewMode === 'story',
render: Panel,
});
});
```

The main code snippet generation functionality is done in `jsx_decorator.tsx`. It's used as a decorator for every story in `preview.tsx`.


```ts
import { customJsxDecorator } from './addons/code-snippet/decorators/jsx_decorator';

const preview: Preview = {
decorators: [
customJsxDecorator,
]
}
```

This decorator generates the code snippet as a `string` and sends it via Storybooks [Channel events](https://storybook.js.org/docs/addons/addons-api#usechannel) to the custom addon panel which outputs the code string to the panel which updates its state on receiving the event ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/components/panel.tsx#L58)).

```ts
channel.emit(EVENTS.SNIPPET_RENDERED, {
id,
source: jsx,
args: unmappedArgs,
});
```

![Dimensions](https://github.com/elastic/eui/assets/44670957/9bb087f5-82bd-4b55-8264-5decc0a36cff)

## Differences to the Storybook `jsxDecorator`

The main changes/additions to the original `jsxDecorator` from Storybook are to ensure the generator outputs clean and EUI relevant code snippets.

Additional features added:
- renames Emotion wrappers to the actual component name (whenever we use `css` on a component in a story it will be an Emotion-wrapped component)
- renames stateful wrappers that start with the wording Stateful (requires us to follow an agreed naming convention)
- removes obsolete fragment wrappers (but keeps required ones)
- removes story specific wrappers (e.g. layout or styling)
- keep related wrappers (e.g. parent & subcomponent or related by name)
- resolves any other unexpected wrapper we might add to structure complex stories
- renames internal component names that start with _underscore (e.g. `<_Component>` is changed to `<Component>`)
- ensures `css` attribute is output properly and not as resolved Emotion object
- ensures boolean props are output in a meaningful way (generally as shorthand but it keeps specifically defined `false` values where `false` has a meaning)
- ensures project specific formatting via `prettier`
- supports adding manual code snippets


## How it works

The generation happens in different stages:

1. `pre-conversion`: determine what react element should be passed to react-element-to-jsx-string and with which options
2. `conversion`: pass react elements to react-element-to-jsx-string
3. `post-conversion`: do additional replacements on the returned string
4. `formatting`: format the result using prettier

### 1. Pre-conversion

Before passing a React element to the `react-element-to-jsx-string` package functionality, we first determine:

1. Should a story be skipped? ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/utils.ts#L196))
- a story may be skipped:
- by using `parameters.codeSnippet.skip` ([example](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/src/components/drag_and_drop/drag_drop_context.stories.tsx#L31))
- by returning an anonymous function from story `render`
2. Is a manual code snippet provided? ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/jsx_decorator.tsx#L100)) ([example](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/src/components/text_diff/text_diff.stories.tsx#L24))

3. What React element should be used? (only a single React element can be passed to `react-element-to-jsx-string`) ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/jsx_decorator.tsx#L146))

1. Check if the outer element should be resolved due to manual flagging via `parameters.codeSnippet.resolveChildren`. The children would be used instead. ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L214)).
2. We check the story react element for some base conditions ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L226)) for which we return the current element. Otherwise we move to the elements `children`:
- Is the element the story component?
- Is the element the stories parent? (We usually want to show Parent & subcomponents together)
- Is the element a subcomponent?
- Is the element a stateful wrapper? (To add interactivity we usually wrap stories in stateful wrappers that are not relevant for the snippet)
- Is the element a React.Fragment? (where obsolete we would want to remove wrapping fragments)
3. If the element is an array we resolve for the children ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L235)).

4. Once a single React element is determine the node and all its props (+ children) are recursively checked and resolved to ensure expected output:

- skip any obsolete React.Fragments (returning children instead) ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L330))
- ensure Emotion `css` is resolved and reversed as Emotion transforms the input syntax to an Emotion style object. (e.g. resolve `css={({ euiTheme }) => ({})}`) ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L357))
- ensure euiTheme tokens are output as variables (e.g. `someProp=euiTheme.colors.lightShade`) - This step adds the variable in special markes that are removed later. This is to prevent `react-element-to-jsx-string` from assuming a type and formatting unexpectedly ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L400))
- ensure `style` attribute is applied ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L468))
- resolve arrays (this outputs e.g. `someProp={[<SomeComponent />, <SomeOtherComponent />]}` instead of `[]`) ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L495))
- resolve objects (e.g. ensures output like `{ text: 'foobar' color: 'green' }`) ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L504))
- resolve class instances used as values to functions ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L518))
- [_todo_] resolve render functions

### 2. Conversion from React element to string

Once the React element is properly checked and resolved according to expected output needs, it can be passed to the functionality from `react-element-to-jsx-string` which will generate a jsx string based on the React element. ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L249))

```tsx
// example output
<EuiFlexItem
component="div"
css={{
backgroundColor: 'rgba(0, 119, 204, 0.1)'
}}
grow={true}
>
Flex item
</EuiFlexItem>
```

### 3. Post-conversion cleanup

The returned string of the conversion is then cleaned to ensure:

- rename internal Components (e.g. `<_Component>` to `<Component>`) ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L256))
- rename necessary React.Fragment to shorthand (e.g. `<React.Fragment>` to `<>`) [code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L277)
- ensure boolean value shorthand by manually filtering out values of `true` ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L290))
- this is manually handled and not by `react-element-to-jsx-string` because we want to keep some occurrences of `false` values when they have meaning (e.g. `<EuiFlexItem grow={false}>`)
- replace variable markers that were added in "1: Pre-conversion" ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L301))
- remove obsolete function naming ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/render_jsx.tsx#L314))


### 4. Final Formatting

To ensure the formatting is correct after adjusting the string returned from `react-element-to-jsx-string` and to align it with the EUI projects formatting rules, we run `prettier` on the string as a final step. ([code](https://github.com/elastic/eui/blob/03d20559b4262d6a18de5fc8edf4ec3854753995/packages/eui/.storybook/addons/code-snippet/decorators/utils.ts#L207))

## Options

Currently there are two addon specific parameter options added that can be used under the key `codeSnippet` in the parameters config key.

```ts
// meta or story config
const meta = {
title: 'Navigation/EuiButton',
component: EuiButton,
parameters: {
codeSnippet: {
// will skip code snippet generation for the component or story
skip: true,
// Useful for complex story composition wrappers (using the story component as
// nested child and not as direct return for `render`).
// It will skip the outer story wrapper and return the code snippet for its children
// instead. See the story for `EuiHeader/Multiple Fixed Headers` as an example.
resolveChildren: true,
}
}
}
```

## Additional functionality

### Manual code snippets

Instead of using the automatic code snippet generation, we can also provide a manual snippet which will be output instead. This is especially useful when the story content is not actually a component (e.g. a hook). You can see an example of this for the story of `useEuiTextDiff`.

To add the story args to the code snippet, add the defined marker `{{STORY_ARGS}}` to the snippet string. This marker will be replaced automatically with the current story args.

```ts
parameters: {
codeSnippet: {
snippet: `
const [rendered, textDiffObject] = useTextDiff(${STORY_ARGS_MARKER})
`,
},
}
```

🚧 More will follow soon 🚧


## Limitations

1. Currently it's not yet supported to resolve `"render functions"` (either used as children or as any prop value). Components that make use of render functions (specifically for children) are currently (manually) skipped via `parameters.codeSnippet.skip: true` until support is added.

2. Currently the addon uses Storybooks `SyntaxHighlighter` component to output the code snippets. This works generally well but seems to have trouble properly detecting and styling code parts for large snippets. This results in some partially uncolored snippets. Using EUI components does currently not work just out of the box as there seem to be issues with applying Emotion correctly.
116 changes: 116 additions & 0 deletions packages/eui/.storybook/addons/code-snippet/components/panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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, { useEffect, FunctionComponent } from 'react';
import {
useAddonState,
useChannel,
useStorybookApi,
} from '@storybook/manager-api';
import { AddonPanel, SyntaxHighlighter } from '@storybook/components';
import { styled } from '@storybook/theming';
import { STORY_RENDERED } from '@storybook/core-events';

import { ADDON_ID, ADDON_PARAMETER_KEY, EVENTS } from '../constants';
import { addHiddenStyle, clearHiddenStyle } from '../utils/addon_visibility';

const addonTabStyles = (selector: string) => `
${selector} {
display: none;
}
`;

interface PanelProps {
active?: boolean;
}

export const Panel: FunctionComponent<PanelProps> = ({ active, ...rest }) => {
const [addonState, setAddonState] = useAddonState(ADDON_ID, {
code: '',
isLoaded: false,
isSkipped: true,
});
const { code, isLoaded, isSkipped } = addonState;
const storybookApi = useStorybookApi();

useEffect(() => {
const addonTabId = `#tabbutton-${ADDON_ID.split('/').join('-')}-panel`;

/**
* we manually hide the addon tab element initially and show it only if it's not skipped.
* This uses style element injection over classes as we don't have access to the actual elements.
* We would need to wait for the elements to be rendered by Storybook to get them which is less
* consistent as controlling the styles.
* reference: https://storybook.js.org/docs/addons/writing-addons#style-the-addon
*/
if (isSkipped) {
addHiddenStyle(ADDON_ID, addonTabStyles(addonTabId));
} else {
clearHiddenStyle(ADDON_ID);
}
}, [isSkipped]);

const emit = useChannel({
[EVENTS.SNIPPET_RENDERED]: (args) => {
setAddonState((prevState) => ({ ...prevState, code: args.source ?? '' }));
},
[STORY_RENDERED]: (id: string) => {
const parameters = storybookApi.getParameters(id);
const isStorySkipped = parameters?.[ADDON_PARAMETER_KEY]?.skip ?? false;

setAddonState((prevState) => ({
...prevState,
isLoaded: true,
isSkipped: isStorySkipped,
}));
},
});

useEffect(() => {
if (isSkipped || !isLoaded || !active) return;

// emit OPENED event
emit(EVENTS.SNIPPET_PANEL_OPENED);

return () => {
// emit CLOSED event
emit(EVENTS.SNIPPET_PANEL_CLOSED);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSkipped, isLoaded, active]);

if (isSkipped) return null;

const emptyState = <span>No code snippet available</span>;
mgadewoll marked this conversation as resolved.
Show resolved Hide resolved
const loadingState = <span>Loading...</span>;

return (
<AddonPanel active={active ?? false} {...rest}>
{code ? (
<SyntaxHighlighter
language="tsx"
copyable
padded
showLineNumbers={false}
wrapLongLines
>
{code}
</SyntaxHighlighter>
Comment on lines +95 to +103
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any thoughts about dogfooding EuiCodeBlock for this instead of using Storybook's component? 👀

Copy link
Contributor Author

@mgadewoll mgadewoll Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good idea! I think I briefly had that thought while initially building this but went with the Storybook component just because it works out of the box (as they use it for their docs source code) and saved me some time 😅.
Let me have another look how it would work with our component 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I realize now why I took the Storybook component. There are issues using the EUI components because emotion is not properly resolved somehow for the addon panel. 🫠

You have tried to stringify object returned from `css` function. It isn't supposed to be used directly (e.g. as value of the `className` prop), but rather handed to emotion so it can handle it (e.g. as value of `css` prop).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha whoops 🙈 Yeah, Storybook probably needs the Emotion babel preset which is a whole fun setup thing (https://emotion.sh/docs/css-prop). You could the JSX pragma to see if that works?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But no worries if it doesn't, I'm totally fine with the Storybook component as-is!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I had a first brief look at it to set this up but it didn't work as expected and it will need some more digging. I think I'd opt for leaving it as is for now and doing the update as a standalone task because I do think it would be generally good to be able to use our component as the Storybook SyntaxHighlighter has some limitation that it does not always properly highlighting the code.

🗒️ What I tried/noticed:

  • Emotion works fine in the preview but not the manager parts
  • I tried adding the emotion preset and it did absolutely nothing (it also seemed like the root babel config is not used - adding a manual config for Storybook with the preset resulted in custom components with css prop working but the imported EUI files still were broken)
  • adding the JSX pragma worked when it was set in the EUI component files that are imported, not when added in the Storybook files (which is not an option)

) : (
<Container>{isLoaded ? emptyState : loadingState}</Container>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thoughts on using EuiEmptyPrompt or EuiCallOut instead of this container? Am I being too extra here right now? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that's a good idea! Why not reuse what we already have!
Let's see first if we can hide the panel for not available code snippets because we would not need this, if we just hide it 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah wait, for the loading state it would still be used, so yes! I'll check how that'll look like 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ This is blocked due to the same issue around Emotion mentioned here.

)}
</AddonPanel>
);
};

const Container = styled.div(({ theme }) => ({
display: 'flex',
justifyContent: 'flex-start',
margin: 0,
padding: theme.layoutMargin,
}));
37 changes: 37 additions & 0 deletions packages/eui/.storybook/addons/code-snippet/constants.ts
Original file line number Diff line number Diff line change
@@ -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.
*/

/**
* Addon specific constants
*/
export const ADDON_ID = 'storybook/code-snippet';
export const PANEL_ID = `${ADDON_ID}/panel`;

export const EVENTS = {
SNIPPET_RENDERED: `${ADDON_ID}/snippet-rendered`,
SNIPPET_PANEL_OPENED: `${ADDON_ID}/snippet-panel-opened`,
SNIPPET_PANEL_CLOSED: `${ADDON_ID}/snippet-panel-closed`,
};

export const ADDON_PARAMETER_KEY = 'codeSnippet';
export const QUERY_PARAMS = {
SHOW_SNIPPET: 'showSnippet',
};

export const STORY_ARGS_MARKER = '{{STORY_ARGS}}';

/**
* JSX snippet generation constants
*/
// excluded props to not be shown in the code snippet
export const EXCLUDED_PROPS = new Set([
'__EMOTION_TYPE_PLEASE_DO_NOT_USE__',
'key',
]);
// props with 'false' value that should not be removed but shown in the code snippet
export const PRESERVED_FALSE_VALUE_PROPS = new Set(['grow']);