diff --git a/addons/a11y/src/components/A11yContext.tsx b/addons/a11y/src/components/A11yContext.tsx index 91466a15a64d..c366c1c8f153 100644 --- a/addons/a11y/src/components/A11yContext.tsx +++ b/addons/a11y/src/components/A11yContext.tsx @@ -23,9 +23,9 @@ interface A11yContextStore { } const colorsByType = [ - convert(themes.normal).color.negative, // VIOLATION, - convert(themes.normal).color.positive, // PASS, - convert(themes.normal).color.warning, // INCOMPLETION, + convert(themes.light).color.negative, // VIOLATION, + convert(themes.light).color.positive, // PASS, + convert(themes.light).color.warning, // INCOMPLETION, ]; export const A11yContext = React.createContext({ diff --git a/addons/interactions/README.md b/addons/interactions/README.md index d3687e084f32..0cd9e62bc3a5 100644 --- a/addons/interactions/README.md +++ b/addons/interactions/README.md @@ -41,22 +41,25 @@ Interactions relies on "instrumented" versions of Jest and Testing Library, that `@storybook/testing-library` instead of their original package. You can then use these libraries in your `play` function. ```js +import { Button } from './Button'; import { expect } from '@storybook/jest'; import { within, userEvent } from '@storybook/testing-library'; export default { title: 'Button', + component: Button, argTypes: { onClick: { action: true }, }, }; -export const Demo = { - play: async ({ args, canvasElement }) => { - const canvas = within(canvasElement); - await userEvent.click(canvas.getByRole('button')); - await expect(args.onClick).toHaveBeenCalled(); - }, +const Template = (args) => +
+ {args.json &&
{JSON.stringify(args.json, null, 2)}
}
); diff --git a/lib/api/src/modules/layout.ts b/lib/api/src/modules/layout.ts index 765b20d9640e..ab73510f2bb1 100644 --- a/lib/api/src/modules/layout.ts +++ b/lib/api/src/modules/layout.ts @@ -1,7 +1,7 @@ import global from 'global'; import pick from 'lodash/pick'; import deepEqual from 'fast-deep-equal'; -import { themes } from '@storybook/theming'; +import { create } from '@storybook/theming'; import type { ThemeVars } from '@storybook/theming'; import { once } from '@storybook/client-logger'; import dedent from 'ts-dedent'; @@ -84,7 +84,7 @@ const defaultState: SubState = { showTabs: true, }, selectedPanel: undefined, - theme: themes.light, + theme: create(), }; export const focusableUIElements = { diff --git a/lib/blocks/src/blocks/ExternalDocsContainer.tsx b/lib/blocks/src/blocks/ExternalDocsContainer.tsx index b260af12af36..1a89c0b0ea09 100644 --- a/lib/blocks/src/blocks/ExternalDocsContainer.tsx +++ b/lib/blocks/src/blocks/ExternalDocsContainer.tsx @@ -58,7 +58,7 @@ export const ExternalDocsContainer: React.FC<{ projectAnnotations: any }> = ({ return ( - {children} + {children} ); }; diff --git a/lib/blocks/src/controls/Text.tsx b/lib/blocks/src/controls/Text.tsx index 5799e8c751ed..602d5923c443 100644 --- a/lib/blocks/src/controls/Text.tsx +++ b/lib/blocks/src/controls/Text.tsx @@ -10,7 +10,20 @@ const Wrapper = styled.label({ display: 'flex', }); -export const TextControl: FC = ({ name, value, onChange, onFocus, onBlur }) => { +const MaxLength = styled.div<{ isMaxed: boolean }>(({ isMaxed }) => ({ + marginLeft: '0.75rem', + paddingTop: '0.35rem', + color: isMaxed ? 'red' : undefined, +})); + +export const TextControl: FC = ({ + name, + value, + onChange, + onFocus, + onBlur, + maxLength, +}) => { const handleChange = (event: ChangeEvent) => { onChange(event.target.value); }; @@ -33,6 +46,7 @@ export const TextControl: FC = ({ name, value, onChange, onFocus, onB = ({ name, value, onChange, onFocus, onB valid={isValid ? null : 'error'} {...{ name, value: isValid ? value : '', onFocus, onBlur }} /> + {maxLength && ( + + {value?.length ?? 0} / {maxLength} + + )} ); }; diff --git a/lib/blocks/src/controls/types.ts b/lib/blocks/src/controls/types.ts index e14833fa20b8..390f8cc8d21c 100644 --- a/lib/blocks/src/controls/types.ts +++ b/lib/blocks/src/controls/types.ts @@ -61,7 +61,9 @@ export interface NormalizedOptionsConfig { } export type TextValue = string; -export interface TextConfig {} +export interface TextConfig { + maxLength?: number; +} export type ControlType = | 'array' diff --git a/lib/core-server/package.json b/lib/core-server/package.json index 2308a115d422..4a1cf69335bb 100644 --- a/lib/core-server/package.json +++ b/lib/core-server/package.json @@ -12,7 +12,7 @@ "repository": { "type": "git", "url": "https://github.com/storybookjs/storybook.git", - "directory": "lib/core" + "directory": "lib/core-server" }, "funding": { "type": "opencollective", diff --git a/lib/manager-webpack5/src/presets/manager-preset.ts b/lib/manager-webpack5/src/presets/manager-preset.ts index b03d68439702..973577bad0d3 100644 --- a/lib/manager-webpack5/src/presets/manager-preset.ts +++ b/lib/manager-webpack5/src/presets/manager-preset.ts @@ -82,6 +82,7 @@ export async function managerWebpack( }) as any as WebpackPluginInstance) : null, new HtmlWebpackPlugin({ + title: 'Storybook loading…', filename: `index.html`, // FIXME: `none` isn't a known option chunksSortMode: 'none' as any, diff --git a/lib/router/src/utils.test.ts b/lib/router/src/utils.test.ts index e7ae39173ade..adda7905ba85 100644 --- a/lib/router/src/utils.test.ts +++ b/lib/router/src/utils.test.ts @@ -34,6 +34,12 @@ describe('getMatch', () => { expect(output).toBe(null); }); + + it('returns null match if "startsWith" part is in the middle', () => { + const output = getMatch('/foo/bar', '/bar', true); + + expect(output).toBe(null); + }); }); describe('parsePath', () => { diff --git a/lib/router/src/utils.ts b/lib/router/src/utils.ts index ed84228bfa98..7d3aeca0c786 100644 --- a/lib/router/src/utils.ts +++ b/lib/router/src/utils.ts @@ -148,11 +148,19 @@ type Match = { path: string }; export const getMatch = memoize(1000)( (current: string, target: string, startsWith = true): Match | null => { - const startsWithTarget = current && startsWith && current.startsWith(target); + if (startsWith) { + const startsWithTarget = current && current.startsWith(target); + if (startsWithTarget) { + return { path: current }; + } + + return null; + } + const currentIsTarget = typeof target === 'string' && current === target; const matchTarget = current && target && current.match(target); - if (startsWithTarget || currentIsTarget || matchTarget) { + if (currentIsTarget || matchTarget) { return { path: current }; } diff --git a/lib/store/src/inferControls.ts b/lib/store/src/inferControls.ts index 084b4c663793..fce183337d32 100644 --- a/lib/store/src/inferControls.ts +++ b/lib/store/src/inferControls.ts @@ -23,9 +23,11 @@ const inferControl = (argType: StrictInputType, name: string, matchers: Controls return { control: { type: 'color' } }; } - logger.warn( - `Addon controls: Control of type color only supports string, received "${controlType}" instead` - ); + if (controlType !== 'enum') { + logger.warn( + `Addon controls: Control of type color only supports string, received "${controlType}" instead` + ); + } } // args that end with date e.g. purchaseDate diff --git a/lib/ui/src/components/layout/container.tsx b/lib/ui/src/components/layout/container.tsx index e8442cee2ec3..eaad17b3d430 100644 --- a/lib/ui/src/components/layout/container.tsx +++ b/lib/ui/src/components/layout/container.tsx @@ -569,10 +569,11 @@ class Layout extends Component { marginTop: -margin, } : { - marginLeft: -margin, + marginLeft: 1, } } axis={isPanelBottom ? 'y' : 'x'} + reverse /> )} diff --git a/lib/ui/src/components/layout/draggers.tsx b/lib/ui/src/components/layout/draggers.tsx index 4424b084fdb0..6fe6dfed7a32 100644 --- a/lib/ui/src/components/layout/draggers.tsx +++ b/lib/ui/src/components/layout/draggers.tsx @@ -3,7 +3,7 @@ import { styled } from '@storybook/theming'; export type Axis = 'x' | 'y'; -const Handle = styled.div<{ isDragging: boolean; axis: Axis }>( +const Handle = styled.div<{ isDragging: boolean; axis: Axis; reverse?: boolean }>( ({ theme, isDragging }) => ({ zIndex: 10, position: 'absolute', @@ -17,7 +17,7 @@ const Handle = styled.div<{ isDragging: boolean; axis: Axis }>( overflow: 'hidden', transition: - 'color 0.2s linear, background-position 0.2s linear, background-size 0.2s linear, background 0.2s linear', + 'color 0.2s linear, background-size 0.2s linear, background 0.2s linear, background-position 0s linear', '&:hover': { color: theme.color.secondary, }, @@ -37,7 +37,7 @@ const Handle = styled.div<{ isDragging: boolean; axis: Axis }>( width: '100%', marginTop: 0, }, - ({ axis, isDragging }) => { + ({ axis, isDragging, reverse = false }) => { if (axis === 'y') { const style = { backgroundImage: `radial-gradient(at center center,rgba(0,0,0,0.2) 0%,transparent 70%,transparent 100%)`, @@ -57,7 +57,7 @@ const Handle = styled.div<{ isDragging: boolean; axis: Axis }>( const style = { backgroundImage: `radial-gradient(at center center,rgba(0,0,0,0.2) 0%,transparent 70%,transparent 100%)`, backgroundSize: '50px 100%', - backgroundPosition: '0 50%', + backgroundPosition: reverse ? '100% 50%' : '0 50%', backgroundRepeat: 'no-repeat', }; return isDragging diff --git a/renderers/react/src/docs/jsxDecorator.test.tsx b/renderers/react/src/docs/jsxDecorator.test.tsx index b32364cb1e95..269c913da115 100644 --- a/renderers/react/src/docs/jsxDecorator.test.tsx +++ b/renderers/react/src/docs/jsxDecorator.test.tsx @@ -290,4 +290,33 @@ describe('jsxDecorator', () => { '
' ); }); + + it('handles stories that trigger Suspense', async () => { + // if a story function uses a hook or other library that triggers suspense, it will throw a Promise until it is resolved + // and then it will return the story content after the promise is resolved + const storyFn = jest.fn(); + storyFn + .mockImplementationOnce(() => { + throw Promise.resolve(); + }) + .mockImplementation(() => { + return
resolved args story
; + }); + const jsx = ''; + const context = makeContext('args', { __isArgsStory: true, jsx }, {}); + expect(() => { + jsxDecorator(storyFn, context); + }).toThrow(Promise); + jsxDecorator(storyFn, context); + await new Promise((r) => setTimeout(r, 0)); + + expect(mockChannel.emit).toHaveBeenCalledTimes(2); + expect(mockChannel.emit).nthCalledWith(1, SNIPPET_RENDERED, 'jsx-test--args', ''); + expect(mockChannel.emit).nthCalledWith( + 2, + SNIPPET_RENDERED, + 'jsx-test--args', + '
\n resolved args story\n
' + ); + }); }); diff --git a/renderers/react/src/docs/jsxDecorator.tsx b/renderers/react/src/docs/jsxDecorator.tsx index 4f513c7a8376..8c1078d43d3d 100644 --- a/renderers/react/src/docs/jsxDecorator.tsx +++ b/renderers/react/src/docs/jsxDecorator.tsx @@ -179,7 +179,6 @@ export const jsxDecorator = ( ) => { const channel = addons.getChannel(); const skip = skipJsxRender(context); - const story = storyFn(); let jsx = ''; @@ -189,6 +188,7 @@ export const jsxDecorator = ( } }); + const story = storyFn(); // We only need to render JSX if the source block is actually going to // consume it. Otherwise it's just slowing us down. if (skip) { diff --git a/yarn.lock b/yarn.lock index 20a55342b8b6..23d9788b5d56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7308,9 +7308,10 @@ __metadata: "@types/puppeteer": ^5.4.0 core-js: ^3.8.2 jest-image-snapshot: ^4.3.0 + puppeteer: ^2.0.0 || ^3.0.0 peerDependencies: "@storybook/addon-storyshots": 7.0.0-alpha.8 - puppeteer: ^2.0.0 || ^3.0.0 + puppeteer: ">=2.0.0" peerDependenciesMeta: puppeteer: optional: true @@ -7423,7 +7424,7 @@ __metadata: core-js: ^3.8.2 estraverse: ^5.2.0 prop-types: ^15.7.2 - react-syntax-highlighter: ^15.4.5 + react-syntax-highlighter: ^15.5.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -22676,7 +22677,7 @@ __metadata: languageName: node linkType: hard -"extract-zip@npm:2.0.1": +"extract-zip@npm:2.0.1, extract-zip@npm:^2.0.0": version: 2.0.1 resolution: "extract-zip@npm:2.0.1" dependencies: @@ -32483,6 +32484,13 @@ __metadata: languageName: node linkType: hard +"mkdirp-classic@npm:^0.5.2": + version: 0.5.3 + resolution: "mkdirp-classic@npm:0.5.3" + checksum: 95371d831d196960ddc3833cc6907e6b8f67ac5501a6582f47dfae5eb0f092e9f8ce88e0d83afcae95d6e2b61a01741ba03714eeafb6f7a6e9dcc158ac85b168 + languageName: node + linkType: hard + "mkdirp-promise@npm:^5.0.1": version: 5.0.1 resolution: "mkdirp-promise@npm:5.0.1" @@ -37105,6 +37113,24 @@ __metadata: languageName: node linkType: hard +"puppeteer@npm:^2.0.0 || ^3.0.0": + version: 3.3.0 + resolution: "puppeteer@npm:3.3.0" + dependencies: + debug: ^4.1.0 + extract-zip: ^2.0.0 + https-proxy-agent: ^4.0.0 + mime: ^2.0.3 + progress: ^2.0.1 + proxy-from-env: ^1.0.0 + rimraf: ^3.0.2 + tar-fs: ^2.0.0 + unbzip2-stream: ^1.3.3 + ws: ^7.2.3 + checksum: 9f8d7f00458425f9ca42580f509f5406ddf27767dbf93080d05157a7882efaf0e32c77e540c8a4d2bd295ab11584a8447d9e6593d6316bf04ce1a16d6fd11b4e + languageName: node + linkType: hard + "puppeteer@npm:^2.1.1": version: 2.1.1 resolution: "puppeteer@npm:2.1.1" @@ -37878,7 +37904,7 @@ __metadata: languageName: node linkType: hard -"react-syntax-highlighter@npm:^15.4.5": +"react-syntax-highlighter@npm:^15.4.5, react-syntax-highlighter@npm:^15.5.0": version: 15.5.0 resolution: "react-syntax-highlighter@npm:15.5.0" dependencies: @@ -42369,6 +42395,18 @@ __metadata: languageName: node linkType: hard +"tar-fs@npm:^2.0.0": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: 871d26a934bfb7beeae4c4d8a09689f530b565f79bd0cf489823ff0efa3705da01278160da10bb006d1a793fa0425cf316cec029b32a9159eacbeaff4965fb6d + languageName: node + linkType: hard + "tar-stream@npm:^1.5.2": version: 1.6.2 resolution: "tar-stream@npm:1.6.2" @@ -42384,7 +42422,7 @@ __metadata: languageName: node linkType: hard -"tar-stream@npm:~2.2.0": +"tar-stream@npm:^2.1.4, tar-stream@npm:~2.2.0": version: 2.2.0 resolution: "tar-stream@npm:2.2.0" dependencies: @@ -43792,7 +43830,7 @@ __metadata: languageName: node linkType: hard -"unbzip2-stream@npm:^1.0.9": +"unbzip2-stream@npm:^1.0.9, unbzip2-stream@npm:^1.3.3": version: 1.4.3 resolution: "unbzip2-stream@npm:1.4.3" dependencies: @@ -46435,7 +46473,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^7.3.1": +"ws@npm:^7.2.3, ws@npm:^7.3.1": version: 7.5.8 resolution: "ws@npm:7.5.8" peerDependencies: @@ -46450,7 +46488,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:^8.1.0, ws@npm:^8.2.3, ws@npm:^8.4.2": +"ws@npm:^8.1.0, ws@npm:^8.4.2": version: 8.6.0 resolution: "ws@npm:8.6.0" peerDependencies: @@ -46465,6 +46503,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:^8.2.3": + version: 8.5.0 + resolution: "ws@npm:8.5.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 0baeee03e97865accda8fad51e8e5fa17d19b8e264529efdf662bbba2acc1c7f1de8316287e6df5cb639231a96009e6d5234b57e6ff36ee2d04e49a0995fec2f + languageName: node + linkType: hard + "ws@npm:~8.2.3": version: 8.2.3 resolution: "ws@npm:8.2.3"