mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-08 11:11:53 +08:00
Merge branch 'next' into shilman/rnstorybook-automigration
This commit is contained in:
commit
f1a3442201
43
.cursor/rules/spy-mocking.mdc
Normal file
43
.cursor/rules/spy-mocking.mdc
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
description: Rules for consistent and type-safe mocking in Vitest tests
|
||||
globs: "**/*.test.{ts,tsx,js,jsx}"
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Spy Mocking Rules for Vitest Tests
|
||||
|
||||
## Mocking Approach
|
||||
|
||||
When mocking packages or files in Vitest-based tests, follow these rules:
|
||||
|
||||
1. Use `vi.mock()` with the `spy: true` option for all package and file mocks
|
||||
2. Place all mocks at the top of the test file before any test cases
|
||||
3. Use `vi.mocked()` to type and access the mocked functions
|
||||
4. Implement mock behaviors in `beforeEach` blocks
|
||||
5. Mock all required dependencies that the test subject uses
|
||||
|
||||
## Mock Implementation Rules
|
||||
|
||||
1. Mock implementations should be placed in `beforeEach` blocks
|
||||
2. Each mock implementation should return a Promise for async functions
|
||||
3. Mock implementations should match the expected return type of the original function
|
||||
4. Use `vi.mocked()` to access and implement mock behaviors
|
||||
5. Mock all required properties and methods that the test subject uses
|
||||
|
||||
## Avoided Patterns
|
||||
|
||||
The following mocking patterns should be avoided:
|
||||
|
||||
1. Direct function mocking without `vi.mocked()`
|
||||
2. Mock implementations outside of `beforeEach` blocks
|
||||
3. Mocking without the `spy: true` option
|
||||
4. Inline mock implementations within test cases
|
||||
5. Mocking only a subset of required dependencies
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Mock at the highest level of abstraction needed
|
||||
2. Keep mock implementations simple and focused
|
||||
3. Use type-safe mocking with `vi.mocked()`
|
||||
4. Document complex mock behaviors
|
||||
5. Group related mocks together
|
34
MIGRATION.md
34
MIGRATION.md
@ -2,6 +2,7 @@
|
||||
|
||||
- [From version 8.x to 9.0.0](#from-version-8x-to-900)
|
||||
- [React-Native config dir renamed](#react-native-config-dir-renamed)
|
||||
- [Addon viewport and addon backgrounds synchronized configuration and use globals](#addon-viewport-and-addon-backgrounds-synchronized-configuration-and-use-globals)
|
||||
- [Manager builder removed alias for `util`, `assert` and `process`](#manager-builder-removed-alias-for-util-assert-and-process)
|
||||
- [Actions addon moved to core](#actions-addon-moved-to-core)
|
||||
- [Dropped support for legacy packages](#dropped-support-for-legacy-packages)
|
||||
@ -125,17 +126,17 @@
|
||||
- [Tab addons cannot manually route, Tool addons can filter their visibility via tabId](#tab-addons-cannot-manually-route-tool-addons-can-filter-their-visibility-via-tabid)
|
||||
- [Removed `config` preset](#removed-config-preset-1)
|
||||
- [From version 7.5.0 to 7.6.0](#from-version-750-to-760)
|
||||
- [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated)
|
||||
- [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated)
|
||||
- [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated)
|
||||
- [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop)
|
||||
- [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react)
|
||||
- [CommonJS with Vite is deprecated](#commonjs-with-vite-is-deprecated)
|
||||
- [Using implicit actions during rendering is deprecated](#using-implicit-actions-during-rendering-is-deprecated)
|
||||
- [typescript.skipBabel deprecated](#typescriptskipbabel-deprecated)
|
||||
- [Primary doc block accepts of prop](#primary-doc-block-accepts-of-prop)
|
||||
- [Addons no longer need a peer dependency on React](#addons-no-longer-need-a-peer-dependency-on-react)
|
||||
- [From version 7.4.0 to 7.5.0](#from-version-740-to-750)
|
||||
- [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated)
|
||||
- [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers)
|
||||
- [`storyStoreV6` and `storiesOf` is deprecated](#storystorev6-and-storiesof-is-deprecated)
|
||||
- [`storyIndexers` is replaced with `experimental_indexers`](#storyindexers-is-replaced-with-experimental_indexers)
|
||||
- [From version 7.0.0 to 7.2.0](#from-version-700-to-720)
|
||||
- [Addon API is more type-strict](#addon-api-is-more-type-strict)
|
||||
- [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated)
|
||||
- [Addon API is more type-strict](#addon-api-is-more-type-strict)
|
||||
- [Addon-controls hideNoControlsWarning parameter is deprecated](#addon-controls-hidenocontrolswarning-parameter-is-deprecated)
|
||||
- [From version 6.5.x to 7.0.0](#from-version-65x-to-700)
|
||||
- [7.0 breaking changes](#70-breaking-changes)
|
||||
- [Dropped support for Node 15 and below](#dropped-support-for-node-15-and-below)
|
||||
@ -161,7 +162,7 @@
|
||||
- [Deploying build artifacts](#deploying-build-artifacts)
|
||||
- [Dropped support for file URLs](#dropped-support-for-file-urls)
|
||||
- [Serving with nginx](#serving-with-nginx)
|
||||
- [Ignore story files from node\_modules](#ignore-story-files-from-node_modules)
|
||||
- [Ignore story files from node_modules](#ignore-story-files-from-node_modules)
|
||||
- [7.0 Core changes](#70-core-changes)
|
||||
- [7.0 feature flags removed](#70-feature-flags-removed)
|
||||
- [Story context is prepared before for supporting fine grained updates](#story-context-is-prepared-before-for-supporting-fine-grained-updates)
|
||||
@ -175,7 +176,7 @@
|
||||
- [Addon-interactions: Interactions debugger is now default](#addon-interactions-interactions-debugger-is-now-default)
|
||||
- [7.0 Vite changes](#70-vite-changes)
|
||||
- [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically)
|
||||
- [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
|
||||
- [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
|
||||
- [7.0 Webpack changes](#70-webpack-changes)
|
||||
- [Webpack4 support discontinued](#webpack4-support-discontinued)
|
||||
- [Babel mode v7 exclusively](#babel-mode-v7-exclusively)
|
||||
@ -226,7 +227,7 @@
|
||||
- [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration)
|
||||
- [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration)
|
||||
- [Autoplay in docs](#autoplay-in-docs)
|
||||
- [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global)
|
||||
- [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global)
|
||||
- [7.0 Deprecations and default changes](#70-deprecations-and-default-changes)
|
||||
- [storyStoreV7 enabled by default](#storystorev7-enabled-by-default)
|
||||
- [`Story` type deprecated](#story-type-deprecated)
|
||||
@ -448,6 +449,15 @@ That makes it easier for RN and React Native Web (RNW) storybooks to co-exist in
|
||||
|
||||
To upgrade, either rename your `.storybook` directory to `.rnstorybook` or if you wish to continue using `.storybook` (not recommended), you can use the [`configPath`](https://github.com/storybookjs/react-native#configpath) option to specify `.storybook` manually.
|
||||
|
||||
### Addon viewport and addon backgrounds synchronized configuration and use globals
|
||||
|
||||
The feature flags: `viewportStoryGlobals` and `backgroundsStoryGlobals` have been removed, please remove these from your `.storybook/main.ts` file.
|
||||
|
||||
See here for the ways you have to configure addon viewports & backgrounds:
|
||||
|
||||
- [New parameters format for addon backgrounds](#new-parameters-format-for-addon-backgrounds)
|
||||
- [New parameters format for addon viewport](#new-parameters-format-for-addon-viewport)
|
||||
|
||||
### Manager builder removed alias for `util`, `assert` and `process`
|
||||
|
||||
These dependencies (often used accidentally) were polyfilled to mocks or browser equivalents by storybook's manager builder.
|
||||
|
@ -127,8 +127,6 @@ const config = defineMain({
|
||||
disableTelemetry: true,
|
||||
},
|
||||
features: {
|
||||
viewportStoryGlobals: true,
|
||||
backgroundsStoryGlobals: true,
|
||||
developmentModeForBuild: true,
|
||||
},
|
||||
viteFinal: async (viteConfig, { configType }) => {
|
||||
|
@ -8,12 +8,12 @@ import { useGlobals, useParameter } from 'storybook/manager-api';
|
||||
|
||||
import { PARAM_KEY as KEY } from '../constants';
|
||||
import { DEFAULT_BACKGROUNDS } from '../defaults';
|
||||
import type { Background, BackgroundMap, Config, GlobalStateUpdate } from '../types';
|
||||
import type { Background, BackgroundMap, BackgroundsParameters, GlobalStateUpdate } from '../types';
|
||||
|
||||
type Link = Parameters<typeof TooltipLinkList>['0']['links'][0];
|
||||
|
||||
export const BackgroundTool = memo(function BackgroundSelector() {
|
||||
const config = useParameter<Config>(KEY);
|
||||
const config = useParameter<BackgroundsParameters['backgrounds']>(KEY);
|
||||
const [globals, updateGlobals, storyGlobals] = useGlobals();
|
||||
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { useEffect } from 'storybook/preview-api';
|
||||
|
||||
import { PARAM_KEY as KEY } from './constants';
|
||||
import { DEFAULT_BACKGROUNDS } from './defaults';
|
||||
import type { Config, GridConfig } from './types';
|
||||
import type { BackgroundsParameters, GridConfig } from './types';
|
||||
import { addBackgroundStyle, addGridStyle, clearStyles, isReduceMotionEnabled } from './utils';
|
||||
|
||||
const defaultGrid: GridConfig = {
|
||||
@ -24,14 +24,14 @@ export const withBackgroundAndGrid: DecoratorFunction = (StoryFn, context) => {
|
||||
options = DEFAULT_BACKGROUNDS,
|
||||
disable,
|
||||
grid = defaultGrid,
|
||||
} = (parameters[KEY] || {}) as Config;
|
||||
} = (parameters[KEY] || {}) as BackgroundsParameters['backgrounds'];
|
||||
const data = globals[KEY] || {};
|
||||
const backgroundName: string | undefined = data.value;
|
||||
const backgroundName: string | undefined = typeof data === 'string' ? data : data.value;
|
||||
|
||||
const item = backgroundName ? options[backgroundName] : undefined;
|
||||
const value = item?.value || 'transparent';
|
||||
const value = typeof item === 'string' ? item : item?.value || 'transparent';
|
||||
|
||||
const showGrid = data.grid || false;
|
||||
const showGrid = typeof data === 'string' ? false : data.grid || false;
|
||||
const shownBackground = !!item && !disable;
|
||||
|
||||
const backgroundSelector = viewMode === 'docs' ? `#anchor--${id} .docs-story` : '.sb-show-main';
|
||||
|
@ -1,148 +0,0 @@
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { logger } from 'storybook/internal/client-logger';
|
||||
import { IconButton, TooltipLinkList, WithTooltip } from 'storybook/internal/components';
|
||||
|
||||
import { PhotoIcon } from '@storybook/icons';
|
||||
|
||||
import memoize from 'memoizerific';
|
||||
import { useGlobals, useParameter } from 'storybook/manager-api';
|
||||
|
||||
import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants';
|
||||
import type { Background } from '../types';
|
||||
import { ColorIcon } from './ColorIcon';
|
||||
import { getBackgroundColorByName } from './getBackgroundColorByName';
|
||||
|
||||
export interface DeprecatedGlobalState {
|
||||
name: string | undefined;
|
||||
selected: string | undefined;
|
||||
}
|
||||
|
||||
export interface BackgroundsParameter {
|
||||
default?: string | null;
|
||||
disable?: boolean;
|
||||
values: Background[];
|
||||
}
|
||||
|
||||
export interface BackgroundSelectorItem {
|
||||
id: string;
|
||||
title: string;
|
||||
onClick: () => void;
|
||||
value: string;
|
||||
active: boolean;
|
||||
right?: ReactElement;
|
||||
}
|
||||
|
||||
const createBackgroundSelectorItem = memoize(1000)(
|
||||
(
|
||||
id: string | null,
|
||||
name: string,
|
||||
value: string,
|
||||
hasSwatch: boolean | null,
|
||||
change: (arg: { selected: string; name: string }) => void,
|
||||
active: boolean
|
||||
): BackgroundSelectorItem => ({
|
||||
id: id || name,
|
||||
title: name,
|
||||
onClick: () => {
|
||||
change({ selected: value, name });
|
||||
},
|
||||
value,
|
||||
right: hasSwatch ? <ColorIcon background={value} /> : undefined,
|
||||
active,
|
||||
})
|
||||
);
|
||||
|
||||
const getDisplayedItems = memoize(10)((
|
||||
backgrounds: Background[],
|
||||
selectedBackgroundColor: string | null,
|
||||
change: (arg: { selected: string; name: string }) => void
|
||||
) => {
|
||||
const backgroundSelectorItems = backgrounds.map(({ name, value }) =>
|
||||
createBackgroundSelectorItem(null, name, value, true, change, value === selectedBackgroundColor)
|
||||
);
|
||||
|
||||
if (selectedBackgroundColor !== 'transparent') {
|
||||
return [
|
||||
createBackgroundSelectorItem('reset', 'Clear background', 'transparent', null, change, false),
|
||||
...backgroundSelectorItems,
|
||||
];
|
||||
}
|
||||
|
||||
return backgroundSelectorItems;
|
||||
});
|
||||
|
||||
const DEFAULT_BACKGROUNDS_CONFIG: BackgroundsParameter = {
|
||||
default: null,
|
||||
disable: true,
|
||||
values: [],
|
||||
};
|
||||
|
||||
export const BackgroundToolLegacy: FC = memo(function BackgroundSelector() {
|
||||
const backgroundsConfig = useParameter<BackgroundsParameter>(
|
||||
BACKGROUNDS_PARAM_KEY,
|
||||
DEFAULT_BACKGROUNDS_CONFIG
|
||||
);
|
||||
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
|
||||
const [globals, updateGlobals] = useGlobals();
|
||||
|
||||
const globalsBackgroundColor = globals[BACKGROUNDS_PARAM_KEY]?.value;
|
||||
|
||||
const selectedBackgroundColor = useMemo(() => {
|
||||
return getBackgroundColorByName(
|
||||
globalsBackgroundColor,
|
||||
backgroundsConfig.values,
|
||||
backgroundsConfig.default
|
||||
);
|
||||
}, [backgroundsConfig, globalsBackgroundColor]);
|
||||
|
||||
if (Array.isArray(backgroundsConfig)) {
|
||||
logger.warn(
|
||||
'Addon Backgrounds api has changed in Storybook 6.0. Please refer to the migration guide: https://github.com/storybookjs/storybook/blob/next/MIGRATION.md'
|
||||
);
|
||||
}
|
||||
|
||||
const onBackgroundChange = useCallback(
|
||||
(value: string | undefined) => {
|
||||
updateGlobals({ [BACKGROUNDS_PARAM_KEY]: { ...globals[BACKGROUNDS_PARAM_KEY], value } });
|
||||
},
|
||||
[backgroundsConfig, globals, updateGlobals]
|
||||
);
|
||||
|
||||
if (backgroundsConfig.disable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
closeOnOutsideClick
|
||||
tooltip={({ onHide }) => {
|
||||
return (
|
||||
<TooltipLinkList
|
||||
links={getDisplayedItems(
|
||||
backgroundsConfig.values,
|
||||
selectedBackgroundColor,
|
||||
({ selected }: DeprecatedGlobalState) => {
|
||||
if (selectedBackgroundColor !== selected) {
|
||||
onBackgroundChange(selected);
|
||||
}
|
||||
onHide();
|
||||
}
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
onVisibleChange={setIsTooltipVisible}
|
||||
>
|
||||
<IconButton
|
||||
key="background"
|
||||
title="Change the background of the preview"
|
||||
active={selectedBackgroundColor !== 'transparent' || isTooltipVisible}
|
||||
>
|
||||
<PhotoIcon />
|
||||
</IconButton>
|
||||
</WithTooltip>
|
||||
);
|
||||
});
|
@ -1,14 +0,0 @@
|
||||
import { styled } from 'storybook/theming';
|
||||
|
||||
export const ColorIcon = styled.span(
|
||||
({ background }: { background: string }) => ({
|
||||
borderRadius: '1rem',
|
||||
display: 'block',
|
||||
height: '1rem',
|
||||
width: '1rem',
|
||||
background,
|
||||
}),
|
||||
({ theme }) => ({
|
||||
boxShadow: `${theme.appBorderColor} 0 0 0 1px inset`,
|
||||
})
|
||||
);
|
@ -1,39 +0,0 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { IconButton } from 'storybook/internal/components';
|
||||
|
||||
import { GridIcon } from '@storybook/icons';
|
||||
|
||||
import { useGlobals, useParameter } from 'storybook/manager-api';
|
||||
|
||||
import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants';
|
||||
|
||||
export const GridToolLegacy: FC = memo(function GridSelector() {
|
||||
const [globals, updateGlobals] = useGlobals();
|
||||
|
||||
const { grid } = useParameter(BACKGROUNDS_PARAM_KEY, {
|
||||
grid: { disable: false },
|
||||
});
|
||||
|
||||
if (grid?.disable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isActive = globals[BACKGROUNDS_PARAM_KEY]?.grid || false;
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
key="background"
|
||||
active={isActive}
|
||||
title="Apply a grid to the preview"
|
||||
onClick={() =>
|
||||
updateGlobals({
|
||||
[BACKGROUNDS_PARAM_KEY]: { ...globals[BACKGROUNDS_PARAM_KEY], grid: !isActive },
|
||||
})
|
||||
}
|
||||
>
|
||||
<GridIcon />
|
||||
</IconButton>
|
||||
);
|
||||
});
|
@ -1,41 +0,0 @@
|
||||
import { logger } from 'storybook/internal/client-logger';
|
||||
|
||||
import { dedent } from 'ts-dedent';
|
||||
|
||||
import type { Background } from '../types';
|
||||
|
||||
export const getBackgroundColorByName = (
|
||||
currentSelectedValue: string,
|
||||
backgrounds: Background[] = [],
|
||||
defaultName: string | null | undefined
|
||||
): string => {
|
||||
if (currentSelectedValue === 'transparent') {
|
||||
return 'transparent';
|
||||
}
|
||||
|
||||
if (backgrounds.find((background) => background.value === currentSelectedValue)) {
|
||||
return currentSelectedValue;
|
||||
}
|
||||
|
||||
if (currentSelectedValue) {
|
||||
return currentSelectedValue;
|
||||
}
|
||||
|
||||
const defaultBackground = backgrounds.find((background) => background.name === defaultName);
|
||||
if (defaultBackground) {
|
||||
return defaultBackground.value;
|
||||
}
|
||||
|
||||
if (defaultName) {
|
||||
const availableColors = backgrounds.map((background) => background.name).join(', ');
|
||||
logger.warn(
|
||||
dedent`
|
||||
Backgrounds Addon: could not find the default color "${defaultName}".
|
||||
These are the available colors for your story based on your configuration:
|
||||
${availableColors}.
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
return 'transparent';
|
||||
};
|
@ -1,63 +0,0 @@
|
||||
import type { DecoratorFunction } from 'storybook/internal/types';
|
||||
|
||||
import { useEffect, useMemo } from 'storybook/preview-api';
|
||||
|
||||
import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants';
|
||||
import { addBackgroundStyle, clearStyles, isReduceMotionEnabled } from '../utils';
|
||||
import { getBackgroundColorByName } from './getBackgroundColorByName';
|
||||
|
||||
export const withBackground: DecoratorFunction = (StoryFn, context) => {
|
||||
const { globals, parameters } = context;
|
||||
const globalsBackgroundColor = globals[BACKGROUNDS_PARAM_KEY]?.value;
|
||||
const backgroundsConfig = parameters[BACKGROUNDS_PARAM_KEY];
|
||||
|
||||
const selectedBackgroundColor = useMemo(() => {
|
||||
if (backgroundsConfig.disable) {
|
||||
return 'transparent';
|
||||
}
|
||||
|
||||
return getBackgroundColorByName(
|
||||
globalsBackgroundColor,
|
||||
backgroundsConfig.values,
|
||||
backgroundsConfig.default
|
||||
);
|
||||
}, [backgroundsConfig, globalsBackgroundColor]);
|
||||
|
||||
const isActive = useMemo(
|
||||
() => selectedBackgroundColor && selectedBackgroundColor !== 'transparent',
|
||||
[selectedBackgroundColor]
|
||||
);
|
||||
|
||||
const selector =
|
||||
context.viewMode === 'docs' ? `#anchor--${context.id} .docs-story` : '.sb-show-main';
|
||||
|
||||
const backgroundStyles = useMemo(() => {
|
||||
const transitionStyle = 'transition: background-color 0.3s;';
|
||||
return `
|
||||
${selector} {
|
||||
background: ${selectedBackgroundColor} !important;
|
||||
${isReduceMotionEnabled() ? '' : transitionStyle}
|
||||
}
|
||||
`;
|
||||
}, [selectedBackgroundColor, selector]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectorId =
|
||||
context.viewMode === 'docs'
|
||||
? `addon-backgrounds-docs-${context.id}`
|
||||
: `addon-backgrounds-color`;
|
||||
|
||||
if (!isActive) {
|
||||
clearStyles(selectorId);
|
||||
return;
|
||||
}
|
||||
|
||||
addBackgroundStyle(
|
||||
selectorId,
|
||||
backgroundStyles,
|
||||
context.viewMode === 'docs' ? context.id : null
|
||||
);
|
||||
}, [isActive, backgroundStyles, context]);
|
||||
|
||||
return StoryFn();
|
||||
};
|
@ -1,61 +0,0 @@
|
||||
import type { DecoratorFunction } from 'storybook/internal/types';
|
||||
|
||||
import { useEffect, useMemo } from 'storybook/preview-api';
|
||||
|
||||
import { PARAM_KEY as BACKGROUNDS_PARAM_KEY } from '../constants';
|
||||
import { addGridStyle, clearStyles } from '../utils';
|
||||
|
||||
export const withGrid: DecoratorFunction = (StoryFn, context) => {
|
||||
const { globals, parameters } = context;
|
||||
const gridParameters = parameters[BACKGROUNDS_PARAM_KEY].grid;
|
||||
const isActive = globals[BACKGROUNDS_PARAM_KEY]?.grid === true && gridParameters.disable !== true;
|
||||
const { cellAmount, cellSize, opacity } = gridParameters;
|
||||
const isInDocs = context.viewMode === 'docs';
|
||||
|
||||
const isLayoutPadded = parameters.layout === undefined || parameters.layout === 'padded';
|
||||
// 16px offset in the grid to account for padded layout
|
||||
const defaultOffset = isLayoutPadded ? 16 : 0;
|
||||
const offsetX = gridParameters.offsetX ?? (isInDocs ? 20 : defaultOffset);
|
||||
const offsetY = gridParameters.offsetY ?? (isInDocs ? 20 : defaultOffset);
|
||||
|
||||
const gridStyles = useMemo(() => {
|
||||
const selector =
|
||||
context.viewMode === 'docs' ? `#anchor--${context.id} .docs-story` : '.sb-show-main';
|
||||
|
||||
const backgroundSize = [
|
||||
`${cellSize * cellAmount}px ${cellSize * cellAmount}px`,
|
||||
`${cellSize * cellAmount}px ${cellSize * cellAmount}px`,
|
||||
`${cellSize}px ${cellSize}px`,
|
||||
`${cellSize}px ${cellSize}px`,
|
||||
].join(', ');
|
||||
|
||||
return `
|
||||
${selector} {
|
||||
background-size: ${backgroundSize} !important;
|
||||
background-position: ${offsetX}px ${offsetY}px, ${offsetX}px ${offsetY}px, ${offsetX}px ${offsetY}px, ${offsetX}px ${offsetY}px !important;
|
||||
background-blend-mode: difference !important;
|
||||
background-image: linear-gradient(rgba(130, 130, 130, ${opacity}) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(130, 130, 130, ${opacity}) 1px, transparent 1px),
|
||||
linear-gradient(rgba(130, 130, 130, ${opacity / 2}) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(130, 130, 130, ${
|
||||
opacity / 2
|
||||
}) 1px, transparent 1px) !important;
|
||||
}
|
||||
`;
|
||||
}, [cellSize]);
|
||||
|
||||
useEffect(() => {
|
||||
const selectorId =
|
||||
context.viewMode === 'docs'
|
||||
? `addon-backgrounds-grid-docs-${context.id}`
|
||||
: `addon-backgrounds-grid`;
|
||||
if (!isActive) {
|
||||
clearStyles(selectorId);
|
||||
return;
|
||||
}
|
||||
|
||||
addGridStyle(selectorId, gridStyles);
|
||||
}, [isActive, gridStyles, context]);
|
||||
|
||||
return StoryFn();
|
||||
};
|
@ -1,25 +1,15 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { addons, types } from 'storybook/manager-api';
|
||||
|
||||
import { BackgroundTool } from './components/Tool';
|
||||
import { ADDON_ID } from './constants';
|
||||
import { BackgroundToolLegacy } from './legacy/BackgroundSelectorLegacy';
|
||||
import { GridToolLegacy } from './legacy/GridSelectorLegacy';
|
||||
|
||||
addons.register(ADDON_ID, () => {
|
||||
addons.add(ADDON_ID, {
|
||||
title: 'Backgrounds',
|
||||
type: types.TOOL,
|
||||
match: ({ viewMode, tabId }) => !!(viewMode && viewMode.match(/^(story|docs)$/)) && !tabId,
|
||||
render: () =>
|
||||
FEATURES?.backgroundsStoryGlobals ? (
|
||||
<BackgroundTool />
|
||||
) : (
|
||||
<Fragment>
|
||||
<BackgroundToolLegacy />
|
||||
<GridToolLegacy />
|
||||
</Fragment>
|
||||
),
|
||||
render: () => <BackgroundTool />,
|
||||
});
|
||||
});
|
||||
|
@ -1,13 +1,8 @@
|
||||
import { PARAM_KEY as KEY } from './constants';
|
||||
import { withBackgroundAndGrid } from './decorator';
|
||||
import { DEFAULT_BACKGROUNDS } from './defaults';
|
||||
import { withBackground } from './legacy/withBackgroundLegacy';
|
||||
import { withGrid } from './legacy/withGridLegacy';
|
||||
import type { Config, GlobalState } from './types';
|
||||
import type { BackgroundsParameters, GlobalState } from './types';
|
||||
|
||||
export const decorators = globalThis.FEATURES?.backgroundsStoryGlobals
|
||||
? [withBackgroundAndGrid]
|
||||
: [withGrid, withBackground];
|
||||
export const decorators = [withBackgroundAndGrid];
|
||||
|
||||
export const parameters = {
|
||||
[KEY]: {
|
||||
@ -17,17 +12,9 @@ export const parameters = {
|
||||
cellAmount: 5,
|
||||
},
|
||||
disable: false,
|
||||
// TODO: remove in 9.0
|
||||
...(!globalThis.FEATURES?.backgroundsStoryGlobals && {
|
||||
values: Object.values(DEFAULT_BACKGROUNDS),
|
||||
}),
|
||||
} satisfies Partial<Config>,
|
||||
};
|
||||
},
|
||||
} satisfies Partial<BackgroundsParameters>;
|
||||
|
||||
const modern: Record<string, GlobalState> = {
|
||||
export const initialGlobals: Record<string, GlobalState> = {
|
||||
[KEY]: { value: undefined, grid: false },
|
||||
};
|
||||
|
||||
export const initialGlobals = globalThis.FEATURES?.backgroundsStoryGlobals
|
||||
? modern
|
||||
: { [KEY]: null };
|
||||
|
@ -1,3 +1,5 @@
|
||||
import type { PARAM_KEY } from './constants';
|
||||
|
||||
export interface Background {
|
||||
name: string;
|
||||
value: string;
|
||||
@ -13,33 +15,19 @@ export interface GridConfig {
|
||||
offsetY?: number;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
options: BackgroundMap;
|
||||
disable: boolean;
|
||||
grid: GridConfig;
|
||||
}
|
||||
|
||||
export type GlobalState = { value: string | undefined; grid: boolean };
|
||||
export type GlobalStateUpdate = Partial<GlobalState>;
|
||||
|
||||
export interface BackgroundsParameters {
|
||||
/**
|
||||
* Backgrounds configuration
|
||||
*
|
||||
* @see https://storybook.js.org/docs/essentials/backgrounds#parameters
|
||||
*/
|
||||
backgrounds: {
|
||||
/** Default background color */
|
||||
default?: string;
|
||||
|
||||
[PARAM_KEY]: {
|
||||
/** Remove the addon panel and disable the addon's behavior */
|
||||
disable?: boolean;
|
||||
|
||||
/** Configuration for the background grid */
|
||||
grid?: Partial<GridConfig>;
|
||||
grid?: GridConfig;
|
||||
|
||||
/** Available background colors */
|
||||
values?: Array<Background>;
|
||||
options?: BackgroundMap;
|
||||
};
|
||||
}
|
||||
|
||||
@ -49,5 +37,5 @@ export interface BackgroundsGlobals {
|
||||
*
|
||||
* @see https://storybook.js.org/docs/essentials/backgrounds#globals
|
||||
*/
|
||||
backgrounds: GlobalState;
|
||||
[PARAM_KEY]: GlobalState | GlobalState['value'];
|
||||
}
|
||||
|
@ -22,6 +22,12 @@ export const Set = {
|
||||
},
|
||||
};
|
||||
|
||||
export const Shorthand = {
|
||||
globals: {
|
||||
backgrounds: 'red',
|
||||
},
|
||||
};
|
||||
|
||||
export const SetAndCustom = {
|
||||
parameters: {
|
||||
backgrounds: {
|
||||
|
File diff suppressed because one or more lines are too long
@ -11,7 +11,7 @@ import { PARAM_KEY as KEY } from '../constants';
|
||||
import { MINIMAL_VIEWPORTS } from '../defaults';
|
||||
import { responsiveViewport } from '../responsiveViewport';
|
||||
import { registerShortcuts } from '../shortcuts';
|
||||
import type { Config, GlobalStateUpdate, Viewport, ViewportMap } from '../types';
|
||||
import type { GlobalStateUpdate, Viewport, ViewportMap, ViewportParameters } from '../types';
|
||||
import {
|
||||
ActiveViewportLabel,
|
||||
ActiveViewportSize,
|
||||
@ -36,14 +36,14 @@ interface PureProps {
|
||||
type Link = Parameters<typeof TooltipLinkList>['0']['links'][0];
|
||||
|
||||
export const ViewportTool: FC<{ api: API }> = ({ api }) => {
|
||||
const config = useParameter<Config>(KEY);
|
||||
const config = useParameter<ViewportParameters['viewport']>(KEY);
|
||||
const [globals, updateGlobals, storyGlobals] = useGlobals();
|
||||
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
|
||||
|
||||
const { options = MINIMAL_VIEWPORTS, disable } = config || {};
|
||||
const data = globals?.[KEY] || {};
|
||||
const viewportName: string = data.value;
|
||||
const isRotated: boolean = data.isRotated;
|
||||
const viewportName: string = typeof data === 'string' ? data : data.value;
|
||||
const isRotated: boolean = typeof data === 'string' ? false : data.isRotated;
|
||||
|
||||
const item = options[viewportName] || responsiveViewport;
|
||||
const isActive = isTooltipVisible || item !== responsiveViewport;
|
||||
|
@ -1,244 +0,0 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
import React, { Fragment, memo, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { IconButton, TooltipLinkList, WithTooltip } from 'storybook/internal/components';
|
||||
|
||||
import { GrowIcon, TransferIcon } from '@storybook/icons';
|
||||
|
||||
import memoize from 'memoizerific';
|
||||
import { useGlobals, useParameter, useStorybookApi } from 'storybook/manager-api';
|
||||
import { Global, styled } from 'storybook/theming';
|
||||
|
||||
import { PARAM_KEY } from '../constants';
|
||||
import { MINIMAL_VIEWPORTS } from '../defaults';
|
||||
import { registerShortcuts } from '../shortcuts';
|
||||
import type { Styles, ViewportMap, ViewportStyles } from '../types';
|
||||
import type { ViewportAddonParameter } from './ViewportAddonParameter';
|
||||
|
||||
interface ViewportItem {
|
||||
id: string;
|
||||
title: string;
|
||||
styles: Styles;
|
||||
type: 'desktop' | 'mobile' | 'tablet' | 'other';
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
const toList = memoize(50)((items: ViewportMap): ViewportItem[] => [
|
||||
...baseViewports,
|
||||
...Object.entries(items).map(([id, { name, ...rest }]) => ({ ...rest, id, title: name })),
|
||||
]);
|
||||
|
||||
const responsiveViewport: ViewportItem = {
|
||||
id: 'reset',
|
||||
title: 'Reset viewport',
|
||||
styles: null,
|
||||
type: 'other',
|
||||
};
|
||||
|
||||
const baseViewports: ViewportItem[] = [responsiveViewport];
|
||||
|
||||
const toLinks = memoize(50)((
|
||||
list: ViewportItem[],
|
||||
active: LinkBase,
|
||||
updateGlobals,
|
||||
close
|
||||
): Link[] => {
|
||||
return list
|
||||
.filter((i) => i.id !== responsiveViewport.id || active.id !== i.id)
|
||||
.map((i) => {
|
||||
return {
|
||||
...i,
|
||||
onClick: () => {
|
||||
updateGlobals({ viewport: i.id });
|
||||
close();
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
interface LinkBase {
|
||||
id: string;
|
||||
title: string;
|
||||
right?: ReactNode;
|
||||
type: 'desktop' | 'mobile' | 'tablet' | 'other';
|
||||
styles: ViewportStyles | ((s: ViewportStyles) => ViewportStyles) | null;
|
||||
}
|
||||
|
||||
interface Link extends LinkBase {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const flip = ({ width, height, ...styles }: ViewportStyles) => ({
|
||||
...styles,
|
||||
height: width,
|
||||
width: height,
|
||||
});
|
||||
|
||||
const ActiveViewportSize = styled.div({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const ActiveViewportLabel = styled.div(({ theme }) => ({
|
||||
display: 'inline-block',
|
||||
textDecoration: 'none',
|
||||
padding: 10,
|
||||
fontWeight: theme.typography.weight.bold,
|
||||
fontSize: theme.typography.size.s2 - 1,
|
||||
lineHeight: '1',
|
||||
height: 40,
|
||||
border: 'none',
|
||||
borderTop: '3px solid transparent',
|
||||
borderBottom: '3px solid transparent',
|
||||
background: 'transparent',
|
||||
}));
|
||||
|
||||
const IconButtonWithLabel = styled(IconButton)(() => ({
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const IconButtonLabel = styled.div(({ theme }) => ({
|
||||
fontSize: theme.typography.size.s2 - 1,
|
||||
marginLeft: 10,
|
||||
}));
|
||||
|
||||
const getStyles = (
|
||||
prevStyles: ViewportStyles | undefined,
|
||||
styles: Styles,
|
||||
isRotated: boolean
|
||||
): ViewportStyles | undefined => {
|
||||
if (styles === null) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = typeof styles === 'function' ? styles(prevStyles) : styles;
|
||||
return isRotated ? flip(result) : result;
|
||||
};
|
||||
|
||||
export const ViewportToolLegacy: FC = memo(function Tool() {
|
||||
const [globals, updateGlobals] = useGlobals();
|
||||
|
||||
const {
|
||||
viewports = MINIMAL_VIEWPORTS,
|
||||
defaultOrientation,
|
||||
defaultViewport,
|
||||
disable,
|
||||
} = useParameter<ViewportAddonParameter>(PARAM_KEY, {});
|
||||
|
||||
const list = toList(viewports);
|
||||
const api = useStorybookApi();
|
||||
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
|
||||
|
||||
if (defaultViewport && !list.find((i) => i.id === defaultViewport)) {
|
||||
console.warn(
|
||||
`Cannot find "defaultViewport" of "${defaultViewport}" in addon-viewport configs, please check the "viewports" setting in the configuration.`
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
registerShortcuts(api, globals, updateGlobals, Object.keys(viewports));
|
||||
}, [viewports, globals, globals.viewport, updateGlobals, api]);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultRotated = defaultOrientation === 'landscape';
|
||||
|
||||
if (
|
||||
(defaultViewport && globals.viewport !== defaultViewport) ||
|
||||
(defaultOrientation && globals.viewportRotated !== defaultRotated)
|
||||
) {
|
||||
updateGlobals({
|
||||
viewport: defaultViewport,
|
||||
viewportRotated: defaultRotated,
|
||||
});
|
||||
}
|
||||
// NOTE: we don't want to re-run this effect when `globals` changes
|
||||
// due to https://github.com/storybookjs/storybook/issues/26334
|
||||
//
|
||||
// Also, this *will* rerun every time you change story as the parameter is briefly `undefined`.
|
||||
// This behavior is intentional, if a bit of a happy accident in implementation.
|
||||
//
|
||||
// Ultimately this process of "locking in" a parameter value should be
|
||||
// replaced by https://github.com/storybookjs/storybook/discussions/23347
|
||||
// or something similar.
|
||||
//
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [defaultOrientation, defaultViewport, updateGlobals]);
|
||||
|
||||
const item =
|
||||
list.find((i) => i.id === globals.viewport) ||
|
||||
list.find((i) => i.id === defaultViewport) ||
|
||||
list.find((i) => i.default) ||
|
||||
responsiveViewport;
|
||||
|
||||
const ref = useRef<ViewportStyles>();
|
||||
|
||||
const styles = getStyles(ref.current, item.styles, globals.viewportRotated);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current = styles;
|
||||
}, [item]);
|
||||
|
||||
if (disable || Object.entries(viewports).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<WithTooltip
|
||||
placement="top"
|
||||
tooltip={({ onHide }) => (
|
||||
<TooltipLinkList links={toLinks(list, item, updateGlobals, onHide)} />
|
||||
)}
|
||||
closeOnOutsideClick
|
||||
onVisibleChange={setIsTooltipVisible}
|
||||
>
|
||||
<IconButtonWithLabel
|
||||
key="viewport"
|
||||
title="Change the size of the preview"
|
||||
active={isTooltipVisible || !!styles}
|
||||
onDoubleClick={() => {
|
||||
updateGlobals({ viewport: responsiveViewport.id });
|
||||
}}
|
||||
>
|
||||
<GrowIcon />
|
||||
{styles ? (
|
||||
<IconButtonLabel>
|
||||
{globals.viewportRotated ? `${item.title} (L)` : `${item.title} (P)`}
|
||||
</IconButtonLabel>
|
||||
) : null}
|
||||
</IconButtonWithLabel>
|
||||
</WithTooltip>
|
||||
|
||||
{styles ? (
|
||||
<ActiveViewportSize>
|
||||
<Global
|
||||
styles={{
|
||||
[`iframe[data-is-storybook="true"]`]: {
|
||||
...(styles || {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ActiveViewportLabel title="Viewport width">
|
||||
{styles.width.replace('px', '')}
|
||||
</ActiveViewportLabel>
|
||||
<IconButton
|
||||
key="viewport-rotate"
|
||||
title="Rotate viewport"
|
||||
onClick={() => {
|
||||
updateGlobals({ viewportRotated: !globals.viewportRotated });
|
||||
}}
|
||||
>
|
||||
<TransferIcon />
|
||||
</IconButton>
|
||||
<ActiveViewportLabel title="Viewport height">
|
||||
{styles.height.replace('px', '')}
|
||||
</ActiveViewportLabel>
|
||||
</ActiveViewportSize>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
});
|
@ -1,9 +0,0 @@
|
||||
import type { ViewportMap } from '../types';
|
||||
|
||||
// TODO: remove at 9.0
|
||||
export interface ViewportAddonParameter {
|
||||
disable?: boolean;
|
||||
defaultOrientation?: 'portrait' | 'landscape';
|
||||
defaultViewport?: string;
|
||||
viewports?: ViewportMap;
|
||||
}
|
@ -4,14 +4,12 @@ import { addons, types } from 'storybook/manager-api';
|
||||
|
||||
import { ViewportTool } from './components/Tool';
|
||||
import { ADDON_ID } from './constants';
|
||||
import { ViewportToolLegacy } from './legacy/ToolLegacy';
|
||||
|
||||
addons.register(ADDON_ID, (api) => {
|
||||
addons.add(ADDON_ID, {
|
||||
title: 'viewport / media-queries',
|
||||
type: types.TOOL,
|
||||
match: ({ viewMode, tabId }) => viewMode === 'story' && !tabId,
|
||||
render: () =>
|
||||
FEATURES?.viewportStoryGlobals ? <ViewportTool api={api} /> : <ViewportToolLegacy />,
|
||||
render: () => <ViewportTool api={api} />,
|
||||
});
|
||||
});
|
||||
|
@ -1,11 +1,6 @@
|
||||
import { PARAM_KEY as KEY } from './constants';
|
||||
import type { GlobalState } from './types';
|
||||
|
||||
const modern: Record<string, GlobalState> = {
|
||||
export const initialGlobals: Record<string, GlobalState> = {
|
||||
[KEY]: { value: undefined, isRotated: false },
|
||||
};
|
||||
|
||||
// TODO: remove in 9.0
|
||||
const legacy = { viewport: 'reset', viewportRotated: false };
|
||||
|
||||
export const initialGlobals = globalThis.FEATURES?.viewportStoryGlobals ? modern : legacy;
|
||||
|
@ -1,12 +1,4 @@
|
||||
// TODO: remove the function type from styles in 9.0
|
||||
export type Styles = ViewportStyles | ((s: ViewportStyles | undefined) => ViewportStyles) | null;
|
||||
|
||||
export interface Viewport {
|
||||
name: string;
|
||||
styles: Styles;
|
||||
type: 'desktop' | 'mobile' | 'tablet' | 'other';
|
||||
}
|
||||
export interface ModernViewport {
|
||||
name: string;
|
||||
styles: ViewportStyles;
|
||||
type: 'desktop' | 'mobile' | 'tablet' | 'other';
|
||||
@ -19,11 +11,6 @@ export interface ViewportStyles {
|
||||
|
||||
export type ViewportMap = Record<string, Viewport>;
|
||||
|
||||
export interface Config {
|
||||
options: Record<string, ModernViewport>;
|
||||
disable: boolean;
|
||||
}
|
||||
|
||||
export type GlobalState = {
|
||||
/**
|
||||
* When set, the viewport is applied and cannot be changed using the toolbar. Must match the key
|
||||
@ -46,31 +33,19 @@ export interface ViewportParameters {
|
||||
* @see https://storybook.js.org/docs/essentials/viewport#parameters
|
||||
*/
|
||||
viewport: {
|
||||
/**
|
||||
* Specifies the default orientation used when viewing a story. Only available if you haven't
|
||||
* enabled the globals API.
|
||||
*/
|
||||
defaultOrientation?: 'landscape' | 'portrait';
|
||||
|
||||
/**
|
||||
* Specifies the default viewport used when viewing a story. Must match a key in the viewports
|
||||
* (or options) object.
|
||||
*/
|
||||
defaultViewport?: string;
|
||||
|
||||
/**
|
||||
* Remove the addon panel and disable the addon's behavior . If you wish to turn off this addon
|
||||
* for the entire Storybook, you should do so when registering addon-essentials
|
||||
*
|
||||
* @see https://storybook.js.org/docs/essentials/index#disabling-addons
|
||||
*/
|
||||
disabled?: boolean;
|
||||
disable?: boolean;
|
||||
|
||||
/**
|
||||
* Specify the available viewports. The width and height values must include the unit, e.g.
|
||||
* '320px'.
|
||||
*/
|
||||
viewports?: Viewport; // TODO: use ModernViewport in 9.0
|
||||
options: Record<string, Viewport>;
|
||||
};
|
||||
}
|
||||
|
||||
@ -81,6 +56,6 @@ export interface ViewportGlobals {
|
||||
* @see https://storybook.js.org/docs/essentials/viewport#globals
|
||||
*/
|
||||
viewport: {
|
||||
[key: string]: GlobalState;
|
||||
[key: string]: GlobalState | GlobalState['value'];
|
||||
};
|
||||
}
|
||||
|
@ -45,7 +45,13 @@ export const Invalid = {
|
||||
},
|
||||
};
|
||||
|
||||
export const NoRationDefined = {
|
||||
export const Shorthand = {
|
||||
globals: {
|
||||
viewport: first,
|
||||
},
|
||||
};
|
||||
|
||||
export const NoRatioDefined = {
|
||||
globals: {
|
||||
viewport: {
|
||||
value: first,
|
||||
|
@ -1,83 +0,0 @@
|
||||
import { global as globalThis } from '@storybook/global';
|
||||
|
||||
import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
|
||||
|
||||
import { expect } from 'storybook/test';
|
||||
|
||||
// these stories only work with `viewportStoryGlobals` set to false
|
||||
// because the `default` prop is dropped and because, `values` changed to `options` and is now an object
|
||||
|
||||
const first = Object.keys(MINIMAL_VIEWPORTS)[0];
|
||||
|
||||
export default {
|
||||
component: globalThis.Components.Button,
|
||||
args: {
|
||||
label: 'Click Me!',
|
||||
},
|
||||
parameters: {
|
||||
viewport: {
|
||||
viewports: MINIMAL_VIEWPORTS,
|
||||
},
|
||||
chromatic: { disable: true },
|
||||
},
|
||||
};
|
||||
|
||||
export const Basic = {
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
export const Selected = {
|
||||
parameters: {
|
||||
viewport: {
|
||||
defaultViewport: first,
|
||||
},
|
||||
},
|
||||
play: async () => {
|
||||
const viewportStyles = MINIMAL_VIEWPORTS[first].styles;
|
||||
const viewportDimensions = {
|
||||
width: typeof viewportStyles === 'object' && Number.parseInt(viewportStyles!.width, 10),
|
||||
height: typeof viewportStyles === 'object' && Number.parseInt(viewportStyles!.height, 10),
|
||||
};
|
||||
|
||||
const windowDimensions = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
};
|
||||
|
||||
await expect(viewportDimensions).toEqual(windowDimensions);
|
||||
},
|
||||
tags: ['!test'],
|
||||
};
|
||||
|
||||
export const Orientation = {
|
||||
parameters: {
|
||||
viewport: {
|
||||
defaultViewport: first,
|
||||
defaultOrientation: 'landscape',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Custom = {
|
||||
parameters: {
|
||||
viewport: {
|
||||
defaultViewport: 'phone',
|
||||
viewports: {
|
||||
phone: {
|
||||
name: 'Phone Width',
|
||||
styles: {
|
||||
height: '600px',
|
||||
width: '100vh',
|
||||
},
|
||||
type: 'mobile',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled = {
|
||||
parameters: {
|
||||
viewport: { disable: true },
|
||||
},
|
||||
};
|
@ -379,10 +379,6 @@ export interface StorybookConfigRaw {
|
||||
/** Enable asynchronous component rendering in React renderer */
|
||||
experimentalRSC?: boolean;
|
||||
|
||||
/** Use globals & globalTypes for configuring the viewport addon */
|
||||
viewportStoryGlobals?: boolean;
|
||||
/** Use globals & globalTypes for configuring the backgrounds addon */
|
||||
backgroundsStoryGlobals?: boolean;
|
||||
/** Set NODE_ENV to development in built Storybooks for better testability and debuggability */
|
||||
developmentModeForBuild?: boolean;
|
||||
};
|
||||
|
@ -42,25 +42,18 @@ test.describe('addon-viewport', () => {
|
||||
await expect(adjustedDimensions?.width).not.toBe(originalDimensions?.width);
|
||||
});
|
||||
|
||||
test('viewport should be editable when a default viewport is set', async ({ page }) => {
|
||||
test('viewport should be uneditable when a viewport is set via globals', async ({ page }) => {
|
||||
const sbPage = new SbPage(page, expect);
|
||||
|
||||
// Story parameters/selected is set to small mobile
|
||||
await sbPage.navigateToStory('addons/viewport/parameters', 'selected');
|
||||
await sbPage.navigateToStory('addons/viewport/globals', 'selected');
|
||||
|
||||
// Measure the original dimensions of previewRoot
|
||||
const originalDimensions = await sbPage.getCanvasBodyElement().boundingBox();
|
||||
await expect(originalDimensions?.width).toBeDefined();
|
||||
|
||||
// Manually select "large mobile" and give it time to adjust
|
||||
await sbPage.selectToolbar('[title="Change the size of the preview"]', '#list-item-mobile2');
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
const toolbar = page.getByTitle('Change the size of the preview');
|
||||
|
||||
// Measure the adjusted dimensions of previewRoot after clicking the mobile item.
|
||||
const adjustedDimensions = await sbPage.getCanvasBodyElement().boundingBox();
|
||||
await expect(adjustedDimensions?.width).toBeDefined();
|
||||
|
||||
// Compare the two widths
|
||||
await expect(adjustedDimensions?.width).not.toBe(originalDimensions?.width);
|
||||
await expect(toolbar).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
@ -35,18 +35,6 @@ Filter args with a "target" on the type from the render function.
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
## `backgroundsStoryGlobals`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Configures the [Backgrounds addon](../../essentials/backgrounds.mdx) to opt-in to the new story globals API for configuring backgrounds.
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
|
||||
<CodeSnippets path="main-config-features-backgrounds-story-globals.md" />
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
## `legacyDecoratorFileOrder`
|
||||
|
||||
Type: `boolean`
|
||||
@ -59,18 +47,6 @@ Apply decorators from preview.js before decorators from addons or frameworks. [M
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
## `viewportStoryGlobals`
|
||||
|
||||
Type: `boolean`
|
||||
|
||||
Configures the [Viewports addon](../../essentials/viewport.mdx) to opt-in to the new story globals API for configuring viewports.
|
||||
|
||||
{/* prettier-ignore-start */}
|
||||
|
||||
<CodeSnippets path="main-config-features-viewport-story-globals.md" />
|
||||
|
||||
{/* prettier-ignore-end */}
|
||||
|
||||
## `developmentModeForBuild`
|
||||
|
||||
Type: `boolean`
|
||||
|
Loading…
x
Reference in New Issue
Block a user