diff --git a/addons/essentials/package.json b/addons/essentials/package.json index 1b1c6668700e..13f834be5c8d 100644 --- a/addons/essentials/package.json +++ b/addons/essentials/package.json @@ -43,7 +43,7 @@ "@storybook/addon-backgrounds": "6.4.0-alpha.13", "@storybook/addon-controls": "6.4.0-alpha.13", "@storybook/addon-docs": "6.4.0-alpha.13", - "@storybook/addon-measure": "^2.0.0", + "@storybook/addon-measure": "6.4.0-alpha.13", "@storybook/addon-outline": "6.4.0-alpha.13", "@storybook/addon-toolbars": "6.4.0-alpha.13", "@storybook/addon-viewport": "6.4.0-alpha.13", diff --git a/addons/measure/README.md b/addons/measure/README.md new file mode 100644 index 000000000000..99638a461da3 --- /dev/null +++ b/addons/measure/README.md @@ -0,0 +1,33 @@ +# Storybook Addon Measure + +Storybook addon for inspecting layouts and visualizing the box model. + +1. Press the m key to enable the addon: + +2. Hover over a DOM node + +3. Storybook will display the dimensions of the selected element—margin, padding, border, width and height—in pixels. + +![](https://user-images.githubusercontent.com/42671/119589961-dff9b380-bda1-11eb-9550-7ae28bc70bf4.gif) + +## Usage + +This addon requires Storybook 6.3 or later. Measure is part of [essentials](https://storybook.js.org/docs/react/essentials/introduction) and so is installed in all new Storybooks by default. If you need to add it to your Storybook, you can run: + +```sh +npm i -D @storybook/addon-measure +``` + +Add `"@storybook/addon-measure"` to the addons array in your `.storybook/main.js`: + +```js +module.exports = { + addons: ['@storybook/addon-measure'], +}; +``` + +### Inspiration + +- [Inspx](https://github.com/raunofreiberg/inspx) by Rauno Freiberg +- [Aaron Westbrook's script](https://gist.github.com/awestbro/e668c12662ad354f02a413205b65fce7) +- [Visbug](https://visbug.web.app/) from the Chrome team diff --git a/addons/measure/package.json b/addons/measure/package.json new file mode 100644 index 000000000000..8b2c403ad7c2 --- /dev/null +++ b/addons/measure/package.json @@ -0,0 +1,82 @@ +{ + "name": "@storybook/addon-measure", + "version": "6.4.0-alpha.13", + "description": "Inspect layouts by visualizing the box model", + "keywords": [ + "storybook-addons", + "essentials", + "style", + "CSS", + "design" + ], + "homepage": "https://github.com/storybookjs/storybook/tree/main/addons/measure", + "bugs": { + "url": "https://github.com/storybookjs/storybook/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/storybookjs/storybook.git", + "directory": "addons/measure" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "license": "MIT", + "author": "winkerVSbecks", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/ts3.9/index.d.ts", + "typesVersions": { + "<3.8": { + "*": [ + "dist/ts3.4/*" + ] + } + }, + "files": [ + "dist/**/*", + "README.md", + "*.js", + "*.d.ts" + ], + "scripts": { + "prepare": "node ../../scripts/prepare.js" + }, + "dependencies": { + "@storybook/addons": "6.4.0-alpha.13", + "@storybook/api": "6.4.0-alpha.13", + "@storybook/client-logger": "6.4.0-alpha.13", + "@storybook/components": "6.4.0-alpha.13", + "@storybook/core-events": "6.4.0-alpha.13", + "core-js": "^3.8.2", + "global": "^4.4.0" + }, + "devDependencies": { + "@types/webpack-env": "^1.16.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "70d04492b677c52c518b1b9591e382ba57484042", + "sbmodern": "dist/modern/index.js", + "storybook": { + "displayName": "Measure", + "unsupportedFrameworks": [ + "react-native" + ], + "icon": "https://user-images.githubusercontent.com/42671/119589951-dbcd9600-bda1-11eb-9227-078f3cfc1e74.png" + } +} diff --git a/addons/measure/preset.js b/addons/measure/preset.js new file mode 100644 index 000000000000..459bbb650ccd --- /dev/null +++ b/addons/measure/preset.js @@ -0,0 +1,12 @@ +function config(entry = []) { + return [...entry, require.resolve('./dist/esm/preset/addDecorator')]; +} + +function managerEntries(entry = [], options) { + return [...entry, require.resolve('./dist/esm/register')]; +} + +module.exports = { + managerEntries, + config, +}; diff --git a/addons/measure/register.js b/addons/measure/register.js new file mode 100644 index 000000000000..f209c0eb3703 --- /dev/null +++ b/addons/measure/register.js @@ -0,0 +1 @@ +require('./dist/esm/register'); diff --git a/addons/measure/src/Tool.tsx b/addons/measure/src/Tool.tsx new file mode 100644 index 000000000000..86edabf47e46 --- /dev/null +++ b/addons/measure/src/Tool.tsx @@ -0,0 +1,39 @@ +import React, { useCallback, useEffect } from 'react'; +import { useGlobals, useStorybookApi } from '@storybook/api'; +import { Icons, IconButton } from '@storybook/components'; +import { TOOL_ID, ADDON_ID } from './constants'; + +export const Tool = () => { + const [globals, updateGlobals] = useGlobals(); + const { measureEnabled } = globals; + const api = useStorybookApi(); + + const toggleMeasure = useCallback( + () => + updateGlobals({ + measureEnabled: !measureEnabled, + }), + [updateGlobals, measureEnabled] + ); + + useEffect(() => { + api.setAddonShortcut(ADDON_ID, { + label: 'Toggle Measure [M]', + defaultShortcut: ['M'], + actionName: 'measure', + showInMenu: false, + action: toggleMeasure, + }); + }, [toggleMeasure, api]); + + return ( + + + + ); +}; diff --git a/addons/measure/src/box-model/canvas.ts b/addons/measure/src/box-model/canvas.ts new file mode 100644 index 000000000000..08387ec05a64 --- /dev/null +++ b/addons/measure/src/box-model/canvas.ts @@ -0,0 +1,97 @@ +/* eslint-disable no-param-reassign */ +import global from 'global'; + +interface Size { + width: number; + height: number; +} + +interface CanvasState { + canvas?: HTMLCanvasElement; + context?: CanvasRenderingContext2D; + width?: number; + height?: number; +} + +function getDocumentWidthAndHeight() { + const container = global.document.documentElement; + + const height = Math.max(container.scrollHeight, container.offsetHeight); + const width = Math.max(container.scrollWidth, container.offsetWidth); + return { width, height }; +} + +function createCanvas(): CanvasState { + const canvas = global.document.createElement('canvas'); + canvas.id = 'storybook-addon-measure'; + const context = canvas.getContext('2d'); + // Set canvas width & height + const { width, height } = getDocumentWidthAndHeight(); + setCanvasWidthAndHeight(canvas, context, { width, height }); + // Position canvas + canvas.style.position = 'absolute'; + canvas.style.left = '0'; + canvas.style.top = '0'; + canvas.style.zIndex = '100000'; + // Disable any user interactions + canvas.style.pointerEvents = 'none'; + global.document.body.appendChild(canvas); + + return { canvas, context, width, height }; +} + +function setCanvasWidthAndHeight( + canvas: HTMLCanvasElement, + context: CanvasRenderingContext2D, + { width, height }: Size +) { + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + + // Scale + const scale = global.window.devicePixelRatio; + canvas.width = Math.floor(width * scale); + canvas.height = Math.floor(height * scale); + + // Normalize coordinate system to use css pixels. + context.scale(scale, scale); +} + +let state: CanvasState = {}; + +export function init() { + if (!state.canvas) { + state = createCanvas(); + } +} + +export function clear() { + if (state.context) { + state.context.clearRect(0, 0, state.width, state.height); + } +} + +export function draw(callback: (context: CanvasRenderingContext2D) => void) { + clear(); + callback(state.context); +} + +export function rescale() { + // First reset so that the canvas size doesn't impact the container size + setCanvasWidthAndHeight(state.canvas, state.context, { width: 0, height: 0 }); + + const { width, height } = getDocumentWidthAndHeight(); + setCanvasWidthAndHeight(state.canvas, state.context, { width, height }); + + // update state + state.width = width; + state.height = height; +} + +export function destroy() { + if (state.canvas) { + clear(); + state.canvas.parentNode.removeChild(state.canvas); + state = {}; + } +} diff --git a/addons/measure/src/box-model/labels.ts b/addons/measure/src/box-model/labels.ts new file mode 100644 index 000000000000..80bbf440c92e --- /dev/null +++ b/addons/measure/src/box-model/labels.ts @@ -0,0 +1,308 @@ +/* eslint-disable operator-assignment */ +/* eslint-disable no-param-reassign */ +type LabelType = 'margin' | 'padding' | 'border' | 'content'; +type LabelPosition = 'top' | 'right' | 'bottom' | 'left' | 'center'; +type Direction = 'top' | 'right' | 'bottom' | 'left'; + +export interface Label { + type: LabelType; + text: number | string; + position: LabelPosition; +} + +export type LabelStack = Label[]; + +interface RectSize { + w: number; + h: number; +} + +interface Coordinate { + x: number; + y: number; +} + +interface Rect extends RectSize, Coordinate {} + +interface RoundedRect extends Rect { + r: number; +} + +const colors = { + margin: '#f6b26b', + border: '#ffe599', + padding: '#93c47d', + content: '#6fa8dc', + text: '#232020', +}; + +const labelPadding = 6; + +function roundedRect(context: CanvasRenderingContext2D, { x, y, w, h, r }: RoundedRect) { + x = x - w / 2; + y = y - h / 2; + + if (w < 2 * r) r = w / 2; + if (h < 2 * r) r = h / 2; + + context.beginPath(); + context.moveTo(x + r, y); + context.arcTo(x + w, y, x + w, y + h, r); + context.arcTo(x + w, y + h, x, y + h, r); + context.arcTo(x, y + h, x, y, r); + context.arcTo(x, y, x + w, y, r); + context.closePath(); +} + +function positionCoordinate( + position: LabelPosition, + { padding, border, width, height, top, left }: ElementMeasurements +): Coordinate { + const contentWidth = width - border.left - border.right - padding.left - padding.right; + const contentHeight = height - padding.top - padding.bottom - border.top - border.bottom; + + let x = left + border.left + padding.left; + let y = top + border.top + padding.top; + + if (position === 'top') { + x += contentWidth / 2; + } else if (position === 'right') { + x += contentWidth; + y += contentHeight / 2; + } else if (position === 'bottom') { + x += contentWidth / 2; + y += contentHeight; + } else if (position === 'left') { + y += contentHeight / 2; + } else if (position === 'center') { + x += contentWidth / 2; + y += contentHeight / 2; + } + + return { x, y }; +} + +/** + * Offset the label based on how many layers appear before it + * For example: + * margin labels will shift further outwards if there are + * padding labels + */ +function offset( + type: LabelType, + position: LabelPosition, + { margin, border, padding }: ElementMeasurements, + labelPaddingSize: number, + external: boolean +) { + let shift = (dir: Direction) => 0; + let offsetX = 0; + let offsetY = 0; + + // If external labels then push them to the edge of the band + // else keep them centred + const locationMultiplier = external ? 1 : 0.5; + // Account for padding within the label + const labelPaddingShift = external ? labelPaddingSize * 2 : 0; + + if (type === 'padding') { + shift = (dir: Direction) => padding[dir] * locationMultiplier + labelPaddingShift; + } else if (type === 'border') { + shift = (dir: Direction) => padding[dir] + border[dir] * locationMultiplier + labelPaddingShift; + } else if (type === 'margin') { + shift = (dir: Direction) => + padding[dir] + border[dir] + margin[dir] * locationMultiplier + labelPaddingShift; + } + + if (position === 'top') { + offsetY = -shift('top'); + } else if (position === 'right') { + offsetX = shift('right'); + } else if (position === 'bottom') { + offsetY = shift('bottom'); + } else if (position === 'left') { + offsetX = -shift('left'); + } + + return { offsetX, offsetY }; +} + +function collide(a: Rect, b: Rect) { + return ( + Math.abs(a.x - b.x) < Math.abs(a.w + b.w) / 2 && Math.abs(a.y - b.y) < Math.abs(a.h + b.h) / 2 + ); +} + +function overlapAdjustment(position: LabelPosition, currentRect: Rect, prevRect: Rect) { + if (position === 'top') { + currentRect.y = prevRect.y - prevRect.h - labelPadding; + } else if (position === 'right') { + currentRect.x = prevRect.x + prevRect.w / 2 + labelPadding + currentRect.w / 2; + } else if (position === 'bottom') { + currentRect.y = prevRect.y + prevRect.h + labelPadding; + } else if (position === 'left') { + currentRect.x = prevRect.x - prevRect.w / 2 - labelPadding - currentRect.w / 2; + } + + return { x: currentRect.x, y: currentRect.y }; +} + +function textWithRect( + context: CanvasRenderingContext2D, + type: LabelType, + { x, y, w, h }: Rect, + text: number | string +) { + roundedRect(context, { x, y, w, h, r: 3 }); + context.fillStyle = `${colors[type]}dd`; + context.fill(); + context.strokeStyle = colors[type]; + context.stroke(); + + context.fillStyle = colors.text; + context.fillText(text as string, x, y); + + roundedRect(context, { x, y, w, h, r: 3 }); + context.fillStyle = `${colors[type]}dd`; + context.fill(); + context.strokeStyle = colors[type]; + context.stroke(); + + context.fillStyle = colors.text; + context.fillText(text as string, x, y); + + return { x, y, w, h }; +} + +function configureText(context: CanvasRenderingContext2D, text: number | string): RectSize { + context.font = '600 12px monospace'; + context.textBaseline = 'middle'; + context.textAlign = 'center'; + + const metrics = context.measureText(text as string); + const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; + + const w = metrics.width + labelPadding * 2; + const h = actualHeight + labelPadding * 2; + + return { w, h }; +} + +function drawLabel( + context: CanvasRenderingContext2D, + measurements: ElementMeasurements, + { type, position = 'center', text }: Label, + prevRect: Rect, + external = false +) { + let { x, y } = positionCoordinate(position, measurements); + const { offsetX, offsetY } = offset(type, position, measurements, labelPadding + 1, external); + + // Shift coordinate to center within + // the band of measurement + x += offsetX; + y += offsetY; + + const { w, h } = configureText(context, text); + + // Adjust for overlap + if (prevRect && collide({ x, y, w, h }, prevRect)) { + const adjusted = overlapAdjustment(position, { x, y, w, h }, prevRect); + x = adjusted.x; + y = adjusted.y; + } + + return textWithRect(context, type, { x, y, w, h }, text); +} + +function floatingOffset(alignment: FloatingAlignment, { w, h }: RectSize) { + const deltaW = w * 0.5 + labelPadding; + const deltaH = h * 0.5 + labelPadding; + + return { + offsetX: (alignment.x === 'left' ? -1 : 1) * deltaW, + offsetY: (alignment.y === 'top' ? -1 : 1) * deltaH, + }; +} + +export function drawFloatingLabel( + context: CanvasRenderingContext2D, + measurements: ElementMeasurements, + { type, text }: Label +) { + const { floatingAlignment, extremities } = measurements; + + let x = extremities[floatingAlignment.x]; + let y = extremities[floatingAlignment.y]; + + const { w, h } = configureText(context, text); + + const { offsetX, offsetY } = floatingOffset(floatingAlignment, { + w, + h, + }); + + x += offsetX; + y += offsetY; + + return textWithRect(context, type, { x, y, w, h }, text); +} + +function drawStack( + context: CanvasRenderingContext2D, + measurements: ElementMeasurements, + stack: LabelStack, + external: boolean +) { + const rects: Rect[] = []; + + stack.forEach((l, idx) => { + // Move the centred label to floating in external mode + const rect = + external && l.position === 'center' + ? drawFloatingLabel(context, measurements, l) + : drawLabel(context, measurements, l, rects[idx - 1], external); + rects[idx] = rect; + }); +} + +interface GroupedLabelStacks { + top?: LabelStack; + right?: LabelStack; + bottom?: LabelStack; + left?: LabelStack; + center?: LabelStack; +} + +export function labelStacks( + context: CanvasRenderingContext2D, + measurements: ElementMeasurements, + labels: LabelStack, + externalLabels: boolean +) { + const stacks = labels.reduce((acc, l) => { + if (!Object.prototype.hasOwnProperty.call(acc, l.position)) { + acc[l.position] = []; + } + + acc[l.position].push(l); + + return acc; + }, {}); + + if (stacks.top) { + drawStack(context, measurements, stacks.top, externalLabels); + } + if (stacks.right) { + drawStack(context, measurements, stacks.right, externalLabels); + } + if (stacks.bottom) { + drawStack(context, measurements, stacks.bottom, externalLabels); + } + if (stacks.left) { + drawStack(context, measurements, stacks.left, externalLabels); + } + if (stacks.center) { + drawStack(context, measurements, stacks.center, externalLabels); + } +} diff --git a/addons/measure/src/box-model/visualizer.ts b/addons/measure/src/box-model/visualizer.ts new file mode 100644 index 000000000000..97ac7791bb73 --- /dev/null +++ b/addons/measure/src/box-model/visualizer.ts @@ -0,0 +1,308 @@ +/* eslint-disable operator-assignment */ +/** + * Based on https://gist.github.com/awestbro/e668c12662ad354f02a413205b65fce7 + */ +import global from 'global'; +import { draw } from './canvas'; +import { labelStacks, Label, LabelStack } from './labels'; + +const colors = { + margin: '#f6b26ba8', + border: '#ffe599a8', + padding: '#93c47d8c', + content: '#6fa8dca8', +}; + +const SMALL_NODE_SIZE = 30; + +function pxToNumber(px: string): number { + return parseInt(px.replace('px', ''), 10); +} + +function round(value: number): number | string { + return Number.isInteger(value) ? value : value.toFixed(2); +} + +function filterZeroValues(labels: LabelStack): LabelStack { + return labels.filter((l) => l.text !== 0 && l.text !== '0'); +} + +function floatingAlignment(extremities: Extremities): FloatingAlignment { + const windowExtremities = { + top: global.window.scrollY, + bottom: global.window.scrollY + global.window.innerHeight, + left: global.window.scrollX, + right: global.window.scrollX + global.window.innerWidth, + }; + + const distances = { + top: Math.abs(windowExtremities.top - extremities.top), + bottom: Math.abs(windowExtremities.bottom - extremities.bottom), + left: Math.abs(windowExtremities.left - extremities.left), + right: Math.abs(windowExtremities.right - extremities.right), + }; + + return { + x: distances.left > distances.right ? 'left' : 'right', + y: distances.top > distances.bottom ? 'top' : 'bottom', + }; +} + +function measureElement(element: HTMLElement): ElementMeasurements { + const style = global.getComputedStyle(element); + // eslint-disable-next-line prefer-const + let { top, left, right, bottom, width, height } = element.getBoundingClientRect(); + + const { + marginTop, + marginBottom, + marginLeft, + marginRight, + paddingTop, + paddingBottom, + paddingLeft, + paddingRight, + borderBottomWidth, + borderTopWidth, + borderLeftWidth, + borderRightWidth, + } = style; + + top = top + global.window.scrollY; + left = left + global.window.scrollX; + bottom = bottom + global.window.scrollY; + right = right + global.window.scrollX; + + const margin = { + top: pxToNumber(marginTop), + bottom: pxToNumber(marginBottom), + left: pxToNumber(marginLeft), + right: pxToNumber(marginRight), + }; + + const padding = { + top: pxToNumber(paddingTop), + bottom: pxToNumber(paddingBottom), + left: pxToNumber(paddingLeft), + right: pxToNumber(paddingRight), + }; + + const border = { + top: pxToNumber(borderTopWidth), + bottom: pxToNumber(borderBottomWidth), + left: pxToNumber(borderLeftWidth), + right: pxToNumber(borderRightWidth), + }; + + const extremities = { + top: top - margin.top, + bottom: bottom + margin.bottom, + left: left - margin.left, + right: right + margin.right, + }; + + return { + margin, + padding, + border, + top, + left, + bottom, + right, + width, + height, + extremities, + floatingAlignment: floatingAlignment(extremities), + }; +} + +function drawMargin( + context: CanvasRenderingContext2D, + { margin, width, height, top, left, bottom, right }: Dimensions +): LabelStack { + // Draw Margin + const marginHeight = height + margin.bottom + margin.top; + + context.fillStyle = colors.margin; + // Top margin rect + context.fillRect(left, top - margin.top, width, margin.top); + // Right margin rect + context.fillRect(right, top - margin.top, margin.right, marginHeight); + // Bottom margin rect + context.fillRect(left, bottom, width, margin.bottom); + // Left margin rect + context.fillRect(left - margin.left, top - margin.top, margin.left, marginHeight); + + const marginLabels: LabelStack = [ + { + type: 'margin', + text: round(margin.top), + position: 'top', + }, + { + type: 'margin', + text: round(margin.right), + position: 'right', + }, + { + type: 'margin', + text: round(margin.bottom), + position: 'bottom', + }, + { + type: 'margin', + text: round(margin.left), + position: 'left', + }, + ]; + + return filterZeroValues(marginLabels); +} + +function drawPadding( + context: CanvasRenderingContext2D, + { padding, border, width, height, top, left, bottom, right }: Dimensions +): LabelStack { + const paddingWidth = width - border.left - border.right; + const paddingHeight = height - padding.top - padding.bottom - border.top - border.bottom; + + context.fillStyle = colors.padding; + // Top padding rect + context.fillRect(left + border.left, top + border.top, paddingWidth, padding.top); + // Right padding rect + context.fillRect( + right - padding.right - border.right, + top + padding.top + border.top, + padding.right, + paddingHeight + ); + // Bottom padding rect + context.fillRect( + left + border.left, + bottom - padding.bottom - border.bottom, + paddingWidth, + padding.bottom + ); + // Left padding rect + context.fillRect(left + border.left, top + padding.top + border.top, padding.left, paddingHeight); + + const paddingLabels: LabelStack = [ + { + type: 'padding', + text: padding.top, + position: 'top', + }, + { + type: 'padding', + text: padding.right, + position: 'right', + }, + { + type: 'padding', + text: padding.bottom, + position: 'bottom', + }, + { + type: 'padding', + text: padding.left, + position: 'left', + }, + ]; + + return filterZeroValues(paddingLabels); +} + +function drawBorder( + context: CanvasRenderingContext2D, + { border, width, height, top, left, bottom, right }: Dimensions +): Label[] { + const borderHeight = height - border.top - border.bottom; + + context.fillStyle = colors.border; + // Top border rect + context.fillRect(left, top, width, border.top); + // Bottom border rect + context.fillRect(left, bottom - border.bottom, width, border.bottom); + // Left border rect + context.fillRect(left, top + border.top, border.left, borderHeight); + // Right border rect + context.fillRect(right - border.right, top + border.top, border.right, borderHeight); + + const borderLabels: LabelStack = [ + { + type: 'border', + text: border.top, + position: 'top', + }, + { + type: 'border', + text: border.right, + position: 'right', + }, + { + type: 'border', + text: border.bottom, + position: 'bottom', + }, + { + type: 'border', + text: border.left, + position: 'left', + }, + ]; + + return filterZeroValues(borderLabels); +} + +function drawContent( + context: CanvasRenderingContext2D, + { padding, border, width, height, top, left }: Dimensions +): LabelStack { + const contentWidth = width - border.left - border.right - padding.left - padding.right; + const contentHeight = height - padding.top - padding.bottom - border.top - border.bottom; + + context.fillStyle = colors.content; + // content rect + context.fillRect( + left + border.left + padding.left, + top + border.top + padding.top, + contentWidth, + contentHeight + ); + + // Dimension label + return [ + { + type: 'content', + position: 'center', + text: `${round(contentWidth)} x ${round(contentHeight)}`, + }, + ]; +} + +function drawBoxModel(element: HTMLElement) { + return (context: CanvasRenderingContext2D) => { + if (element && context) { + const measurements = measureElement(element); + + const marginLabels = drawMargin(context, measurements); + const paddingLabels = drawPadding(context, measurements); + const borderLabels = drawBorder(context, measurements); + const contentLabels = drawContent(context, measurements); + + const externalLabels = + measurements.width <= SMALL_NODE_SIZE * 3 || measurements.height <= SMALL_NODE_SIZE; + + labelStacks( + context, + measurements, + [...contentLabels, ...paddingLabels, ...borderLabels, ...marginLabels], + externalLabels + ); + } + }; +} + +export function drawSelectedElement(element: HTMLElement) { + draw(drawBoxModel(element)); +} diff --git a/addons/measure/src/constants.ts b/addons/measure/src/constants.ts new file mode 100644 index 000000000000..3375eac3e939 --- /dev/null +++ b/addons/measure/src/constants.ts @@ -0,0 +1,9 @@ +export const ADDON_ID = 'storybook/measure-addon'; +export const TOOL_ID = `${ADDON_ID}/tool`; +export const PARAM_KEY = 'measureEnabled'; + +export const EVENTS = { + RESULT: `${ADDON_ID}/result`, + REQUEST: `${ADDON_ID}/request`, + CLEAR: `${ADDON_ID}/clear`, +}; diff --git a/addons/measure/src/index.ts b/addons/measure/src/index.ts new file mode 100644 index 000000000000..644402abb41c --- /dev/null +++ b/addons/measure/src/index.ts @@ -0,0 +1,6 @@ +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} + +// make it work with --isolatedModules +export default {}; diff --git a/addons/measure/src/preset/addDecorator.tsx b/addons/measure/src/preset/addDecorator.tsx new file mode 100644 index 000000000000..8aabbd42a2da --- /dev/null +++ b/addons/measure/src/preset/addDecorator.tsx @@ -0,0 +1,8 @@ +import { withMeasure } from '../withMeasure'; +import { PARAM_KEY } from '../constants'; + +export const decorators = [withMeasure]; + +export const globals = { + [PARAM_KEY]: false, +}; diff --git a/addons/measure/src/register.tsx b/addons/measure/src/register.tsx new file mode 100644 index 000000000000..162e7ce43ecd --- /dev/null +++ b/addons/measure/src/register.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { addons, types } from '@storybook/addons'; + +import { ADDON_ID, TOOL_ID } from './constants'; +import { Tool } from './Tool'; + +addons.register(ADDON_ID, () => { + addons.add(TOOL_ID, { + type: types.TOOL, + title: 'Measure', + match: ({ viewMode }) => viewMode === 'story', + render: () => , + }); +}); diff --git a/addons/measure/src/typings.d.ts b/addons/measure/src/typings.d.ts new file mode 100644 index 000000000000..4a8ad58bf897 --- /dev/null +++ b/addons/measure/src/typings.d.ts @@ -0,0 +1,51 @@ +declare module 'global'; + +interface Margin { + top: number; + bottom: number; + left: number; + right: number; +} + +interface Padding { + top: number; + bottom: number; + left: number; + right: number; +} + +interface Border { + top: number; + bottom: number; + left: number; + right: number; +} + +interface Dimensions { + margin: Margin; + padding: Padding; + border: Border; + width: number; + height: number; + top: number; + left: number; + bottom: number; + right: number; +} + +interface Extremities { + top: number; + bottom: number; + left: number; + right: number; +} + +interface FloatingAlignment { + x: 'left' | 'right'; + y: 'top' | 'bottom'; +} + +interface ElementMeasurements extends Dimensions { + extremities: Extremities; + floatingAlignment: FloatingAlignment; +} diff --git a/addons/measure/src/util.ts b/addons/measure/src/util.ts new file mode 100644 index 000000000000..afb07d91299d --- /dev/null +++ b/addons/measure/src/util.ts @@ -0,0 +1,28 @@ +import global from 'global'; + +export const deepElementFromPoint = (x: number, y: number) => { + const element = global.document.elementFromPoint(x, y); + + const crawlShadows = (node: Element): Element => { + if (node && node.shadowRoot) { + const nestedElement = node.shadowRoot.elementFromPoint(x, y); + + // Nested node is same as the root one + if (node.isEqualNode(nestedElement)) { + return node; + } + // The nested node has shadow DOM too so continue crawling + if (nestedElement.shadowRoot) { + return crawlShadows(nestedElement); + } + // No more shadow DOM + return nestedElement; + } + + return node; + }; + + const shadowElement = crawlShadows(element); + + return shadowElement || element; +}; diff --git a/addons/measure/src/withMeasure.ts b/addons/measure/src/withMeasure.ts new file mode 100644 index 000000000000..64711e69a5e4 --- /dev/null +++ b/addons/measure/src/withMeasure.ts @@ -0,0 +1,63 @@ +/* eslint-env browser */ +import { StoryFn as StoryFunction, StoryContext, useEffect } from '@storybook/addons'; +import { drawSelectedElement } from './box-model/visualizer'; +import { init, rescale, destroy } from './box-model/canvas'; +import { deepElementFromPoint } from './util'; + +let nodeAtPointerRef; +const pointer = { x: 0, y: 0 }; + +function findAndDrawElement(x: number, y: number) { + nodeAtPointerRef = deepElementFromPoint(x, y); + drawSelectedElement(nodeAtPointerRef); +} + +export const withMeasure = (StoryFn: StoryFunction, context: StoryContext) => { + const { measureEnabled } = context.globals; + + useEffect(() => { + const onMouseMove = (event: MouseEvent) => { + window.requestAnimationFrame(() => { + event.stopPropagation(); + pointer.x = event.clientX; + pointer.y = event.clientY; + }); + }; + + document.addEventListener('mousemove', onMouseMove); + + return () => { + document.removeEventListener('mousemove', onMouseMove); + }; + }, []); + + useEffect(() => { + const onMouseOver = (event: MouseEvent) => { + window.requestAnimationFrame(() => { + event.stopPropagation(); + findAndDrawElement(event.clientX, event.clientY); + }); + }; + + const onResize = () => { + window.requestAnimationFrame(() => { + rescale(); + }); + }; + + if (measureEnabled) { + document.addEventListener('mouseover', onMouseOver); + init(); + window.addEventListener('resize', onResize); + // Draw the element below the pointer when first enabled + findAndDrawElement(pointer.x, pointer.y); + } + + return () => { + window.removeEventListener('resize', onResize); + destroy(); + }; + }, [measureEnabled]); + + return StoryFn(); +}; diff --git a/addons/measure/tsconfig.json b/addons/measure/tsconfig.json new file mode 100644 index 000000000000..d1ee4fc75941 --- /dev/null +++ b/addons/measure/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "types": ["webpack-env"] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/**/*.test.*", + "src/**/tests/**/*", + "src/**/__tests__/**/*", + "src/**/*.stories.*", + "src/**/*.mockdata.*", + "src/**/__testfixtures__/**" + ] +} diff --git a/addons/outline/src/index.ts b/addons/outline/src/index.ts new file mode 100644 index 000000000000..644402abb41c --- /dev/null +++ b/addons/outline/src/index.ts @@ -0,0 +1,6 @@ +if (module && module.hot && module.hot.decline) { + module.hot.decline(); +} + +// make it work with --isolatedModules +export default {}; diff --git a/examples/angular-cli/src/stories/__snapshots__/welcome-storybook.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/welcome-storybook.stories.storyshot index cce66c723f4c..9e2b2d1a9a20 100644 --- a/examples/angular-cli/src/stories/__snapshots__/welcome-storybook.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/welcome-storybook.stories.storyshot @@ -3,47 +3,47 @@ exports[`Storyshots Welcome/ To Storybook To Storybook 1`] = `

Welcome to storybook

This is a UI component dev environment for your app.

We've added some basic stories inside the src/stories directory.
A story is a single state of one or more UI components. You can have as many stories as you want.
(Basically a story is like a visual test case.)

See these sample @@ -51,7 +51,7 @@ exports[`Storyshots Welcome/ To Storybook To Storybook 1`] = ` for a component called Button @@ -59,26 +59,26 @@ exports[`Storyshots Welcome/ To Storybook To Storybook 1`] = ` .

Just like that, you can add your own components as stories.
You can also edit those components and see changes right away.
(Try editing the Button stories located at src/stories/index.js @@ -86,15 +86,15 @@ exports[`Storyshots Welcome/ To Storybook To Storybook 1`] = ` .)

Usually we create stories with smaller UI components in the app.
Have a look at the

NOTE:
Have a look at the .storybook/webpack.config.js diff --git a/examples/angular-cli/src/stories/core/styles/__snapshots__/story-styles.stories.storyshot b/examples/angular-cli/src/stories/core/styles/__snapshots__/story-styles.stories.storyshot index 9e96c5f03b1b..7307da48a797 100644 --- a/examples/angular-cli/src/stories/core/styles/__snapshots__/story-styles.stories.storyshot +++ b/examples/angular-cli/src/stories/core/styles/__snapshots__/story-styles.stories.storyshot @@ -3,7 +3,7 @@ exports[`Storyshots Core / Story host styles With Args 1`] = ` @@ -19,7 +19,7 @@ exports[`Storyshots Core / Story host styles With Args 1`] = ` exports[`Storyshots Core / Story host styles With story template 1`] = ` diff --git a/examples/official-storybook/components/addon-measure/ShadowRoot.js b/examples/official-storybook/components/addon-measure/ShadowRoot.js new file mode 100644 index 000000000000..665257cf6d6d --- /dev/null +++ b/examples/official-storybook/components/addon-measure/ShadowRoot.js @@ -0,0 +1,65 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { drawSelectedElement } from '@storybook/addon-measure/dist/cjs/box-model/visualizer'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { init, destroy } from '@storybook/addon-measure/dist/cjs/box-model/canvas'; + +export const ShadowRoot = ({ label = 'Hello from shadow DOM', drawMode = 'ROOT' }) => { + const ref = React.useRef(); + + React.useEffect(() => { + if (!ref.current.attachShadow) return; + + ref.current.attachShadow({ mode: 'open' }); + + ref.current.shadowRoot.innerHTML = ` + + + `; + + init(); + drawSelectedElement(drawMode === 'ROOT' ? ref.current : ref.current.shadowRoot.children[1]); + + // eslint-disable-next-line consistent-return + return () => { + destroy(); + }; + }, []); + + return

; +}; + +ShadowRoot.propTypes = { + label: PropTypes.string, + drawMode: PropTypes.oneOf(['ROOT', 'NESTED']), +}; + +ShadowRoot.defaultProps = { + label: 'Hello from shadow DOM', + drawMode: 'ROOT', +}; diff --git a/examples/official-storybook/components/addon-measure/Visualization.js b/examples/official-storybook/components/addon-measure/Visualization.js new file mode 100644 index 000000000000..bcd10d6a3d1b --- /dev/null +++ b/examples/official-storybook/components/addon-measure/Visualization.js @@ -0,0 +1,36 @@ +import React, { useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { drawSelectedElement } from '@storybook/addon-measure/dist/cjs/box-model/visualizer'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { init, destroy } from '@storybook/addon-measure/dist/cjs/box-model/canvas'; + +export const Visualization = ({ render }) => { + const element = useRef(null); + + useEffect(() => { + if (element.current) { + init(); + drawSelectedElement(element.current); + } + + return () => { + destroy(); + }; + }, [element]); + + return ( +
+ {render(element)} +
+ ); +}; + +Visualization.propTypes = { + render: PropTypes.func.isRequired, +}; diff --git a/examples/official-storybook/stories/addon-measure/BoxModel.stories.js b/examples/official-storybook/stories/addon-measure/BoxModel.stories.js new file mode 100644 index 000000000000..17876cde74b4 --- /dev/null +++ b/examples/official-storybook/stories/addon-measure/BoxModel.stories.js @@ -0,0 +1,118 @@ +import React from 'react'; +import { Visualization } from '../../components/addon-measure/Visualization'; + +export default { + title: 'Addons/Measure/BoxModel', + parameters: { + layout: 'fullscreen', + }, +}; + +const Template = (args) => ; + +export const MarginUniform = Template.bind({}); +MarginUniform.args = { + render: (ref) => ( +
+ ), +}; + +export const MarginAsymmetric = Template.bind({}); +MarginAsymmetric.args = { + render: (ref) => ( +
+ ), +}; + +export const PaddingUniform = Template.bind({}); +PaddingUniform.args = { + render: (ref) => ( +
+ ), +}; + +export const PaddingAsymmetric = Template.bind({}); +PaddingAsymmetric.args = { + render: (ref) => ( +
+ ), +}; + +export const BorderUniform = Template.bind({}); +BorderUniform.args = { + render: (ref) => ( +
+ ), +}; + +export const BorderAsymmetric = Template.bind({}); +BorderAsymmetric.args = { + render: (ref) => ( +
+ ), +}; + +export const DecimalSizing = Template.bind({}); +DecimalSizing.args = { + render: (ref) => ( +
+ ), +}; diff --git a/examples/official-storybook/stories/addon-measure/Grid.stories.js b/examples/official-storybook/stories/addon-measure/Grid.stories.js new file mode 100644 index 000000000000..f9849389bf6d --- /dev/null +++ b/examples/official-storybook/stories/addon-measure/Grid.stories.js @@ -0,0 +1,46 @@ +import React from 'react'; + +export default { + title: 'Addons/Measure/Grid', +}; + +const MeasureButton = () => ( + + + +); + +export const Basic = () => ( +
+
+ {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map((id) => ( +
+ {id} +
+ ))} +
+

+ Click the measure button in the toolbar to enable the addon +

+
+); diff --git a/examples/official-storybook/stories/addon-measure/ShadowRoot.stories.js b/examples/official-storybook/stories/addon-measure/ShadowRoot.stories.js new file mode 100644 index 000000000000..eec397d9d8d7 --- /dev/null +++ b/examples/official-storybook/stories/addon-measure/ShadowRoot.stories.js @@ -0,0 +1,26 @@ +import React from 'react'; + +import { ShadowRoot } from '../../components/addon-measure/ShadowRoot'; + +export default { + title: 'Addons/Measure/ShadowRoot', + component: ShadowRoot, +}; + +const Template = (args) => ( +
+ +
+); + +export const Root = Template.bind({}); + +export const Nested = Template.bind({}); +Nested.args = { + drawMode: 'NESTED', +}; diff --git a/examples/official-storybook/stories/addon-measure/SmallNode.stories.js b/examples/official-storybook/stories/addon-measure/SmallNode.stories.js new file mode 100644 index 000000000000..6a38904f5d21 --- /dev/null +++ b/examples/official-storybook/stories/addon-measure/SmallNode.stories.js @@ -0,0 +1,73 @@ +import React from 'react'; +import { Visualization } from '../../components/addon-measure/Visualization'; + +export default { + title: 'Addons/Measure/SmallNode', + parameters: { + layout: 'fullscreen', + }, +}; + +const Template = (args) => ; + +export const Everything30px = Template.bind({}); +Everything30px.args = { + render: (ref) => ( +
+ ), +}; + +export const Short = Template.bind({}); +Short.args = { + render: (ref) => ( +
+ ), +}; + +export const Narrow = Template.bind({}); +Narrow.args = { + render: (ref) => ( +
+ ), +}; + +export const Tiny = Template.bind({}); +Tiny.args = { + render: (ref) => ( +
+ ), +}; diff --git a/examples/official-storybook/stories/addon-measure/StackingLabels.stories.js b/examples/official-storybook/stories/addon-measure/StackingLabels.stories.js new file mode 100644 index 000000000000..cd8197237dec --- /dev/null +++ b/examples/official-storybook/stories/addon-measure/StackingLabels.stories.js @@ -0,0 +1,67 @@ +import React from 'react'; +import { Visualization } from '../../components/addon-measure/Visualization'; + +export default { + title: 'Addons/Measure/StackingLabels', + parameters: { + layout: 'fullscreen', + }, +}; + +const Template = (args) => ; + +export const EverythingUniform = Template.bind({}); +EverythingUniform.args = { + render: (ref) => ( +
+ ), +}; + +export const Asymmetric = Template.bind({}); +Asymmetric.args = { + render: (ref) => ( +
+ ), +}; + +export const MoreAsymmetric = Template.bind({}); +MoreAsymmetric.args = { + render: (ref) => ( +
+ ), +}; diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-dev b/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-dev index 3acb73c6b296..e655ab5e5c96 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-dev +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-dev @@ -10,7 +10,7 @@ Object { "ROOT/addons/actions/dist/esm/register.js", "ROOT/addons/backgrounds/dist/esm/register.js", "ROOT/addons/toolbars/dist/esm/register.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/manager.js", + "ROOT/addons/measure/dist/esm/register.js", "ROOT/addons/outline/dist/esm/register.js", ], "keys": Array [ diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-prod b/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-prod index 05d044eeacde..3960607da817 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-prod +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_manager-prod @@ -10,7 +10,7 @@ Object { "ROOT/addons/actions/dist/esm/register.js", "ROOT/addons/backgrounds/dist/esm/register.js", "ROOT/addons/toolbars/dist/esm/register.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/manager.js", + "ROOT/addons/measure/dist/esm/register.js", "ROOT/addons/outline/dist/esm/register.js", ], "keys": Array [ diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev index 8ed1c87f02a9..be400084b96b 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-dev @@ -12,7 +12,7 @@ Object { "ROOT/addons/actions/dist/esm/preset/addArgs.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addParameter.js-generated-other-entry.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/preview.js-generated-other-entry.js", + "ROOT/addons/measure/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/outline/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/examples/cra-ts-essentials/.storybook/preview.js-generated-config-entry.js", "ROOT/examples/cra-ts-essentials/.storybook/generated-stories-entry.js", diff --git a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod index 43c8930903af..c561f897abfa 100644 --- a/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod +++ b/lib/core-server/src/__snapshots__/cra-ts-essentials_preview-prod @@ -12,7 +12,7 @@ Object { "ROOT/addons/actions/dist/esm/preset/addArgs.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addParameter.js-generated-other-entry.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/preview.js-generated-other-entry.js", + "ROOT/addons/measure/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/outline/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/examples/cra-ts-essentials/.storybook/preview.js-generated-config-entry.js", "ROOT/examples/cra-ts-essentials/.storybook/generated-stories-entry.js", diff --git a/lib/core-server/src/__snapshots__/vue-3-cli_manager-dev b/lib/core-server/src/__snapshots__/vue-3-cli_manager-dev index d2c84b977969..be6c5b62a0d3 100644 --- a/lib/core-server/src/__snapshots__/vue-3-cli_manager-dev +++ b/lib/core-server/src/__snapshots__/vue-3-cli_manager-dev @@ -12,7 +12,7 @@ Object { "ROOT/addons/backgrounds/dist/esm/register.js", "ROOT/addons/viewport/dist/esm/register.js", "ROOT/addons/toolbars/dist/esm/register.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/manager.js", + "ROOT/addons/measure/dist/esm/register.js", "ROOT/addons/outline/dist/esm/register.js", ], "keys": Array [ diff --git a/lib/core-server/src/__snapshots__/vue-3-cli_manager-prod b/lib/core-server/src/__snapshots__/vue-3-cli_manager-prod index b557d6f74789..b86aeecbfb37 100644 --- a/lib/core-server/src/__snapshots__/vue-3-cli_manager-prod +++ b/lib/core-server/src/__snapshots__/vue-3-cli_manager-prod @@ -12,7 +12,7 @@ Object { "ROOT/addons/backgrounds/dist/esm/register.js", "ROOT/addons/viewport/dist/esm/register.js", "ROOT/addons/toolbars/dist/esm/register.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/manager.js", + "ROOT/addons/measure/dist/esm/register.js", "ROOT/addons/outline/dist/esm/register.js", ], "keys": Array [ diff --git a/lib/core-server/src/__snapshots__/vue-3-cli_preview-dev b/lib/core-server/src/__snapshots__/vue-3-cli_preview-dev index 221bee3241bd..69659043a13f 100644 --- a/lib/core-server/src/__snapshots__/vue-3-cli_preview-dev +++ b/lib/core-server/src/__snapshots__/vue-3-cli_preview-dev @@ -13,7 +13,7 @@ Object { "ROOT/addons/actions/dist/esm/preset/addArgs.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addParameter.js-generated-other-entry.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/preview.js-generated-other-entry.js", + "ROOT/addons/measure/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/outline/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/examples/vue-3-cli/.storybook/preview.ts-generated-config-entry.js", "ROOT/examples/vue-3-cli/.storybook/generated-stories-entry.js", diff --git a/lib/core-server/src/__snapshots__/vue-3-cli_preview-prod b/lib/core-server/src/__snapshots__/vue-3-cli_preview-prod index 98ffbef4f8e2..e49eaefc4796 100644 --- a/lib/core-server/src/__snapshots__/vue-3-cli_preview-prod +++ b/lib/core-server/src/__snapshots__/vue-3-cli_preview-prod @@ -13,7 +13,7 @@ Object { "ROOT/addons/actions/dist/esm/preset/addArgs.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/backgrounds/dist/esm/preset/addParameter.js-generated-other-entry.js", - "NODE_MODULES/@storybook/addon-measure/dist/esm/preset/preview.js-generated-other-entry.js", + "ROOT/addons/measure/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/addons/outline/dist/esm/preset/addDecorator.js-generated-other-entry.js", "ROOT/examples/vue-3-cli/.storybook/preview.ts-generated-config-entry.js", "ROOT/examples/vue-3-cli/.storybook/generated-stories-entry.js", diff --git a/nx.json b/nx.json index 67811749944a..e3dd0270bdff 100644 --- a/nx.json +++ b/nx.json @@ -43,6 +43,9 @@ "@storybook/addon-links": { "implicitDependencies": [] }, + "@storybook/addon-measure": { + "implicitDependencies": [] + }, "@storybook/addon-outline": { "implicitDependencies": [] }, diff --git a/package.json b/package.json index 0cc091d3f6d3..82ee6419212b 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "@storybook/addon-essentials": "workspace:*", "@storybook/addon-jest": "workspace:*", "@storybook/addon-links": "workspace:*", + "@storybook/addon-measure": "workspace:*", "@storybook/addon-outline": "workspace:*", "@storybook/addon-storyshots": "workspace:*", "@storybook/addon-storyshots-puppeteer": "workspace:*", diff --git a/workspace.json b/workspace.json index a637596318e5..d342fa8b56c0 100644 --- a/workspace.json +++ b/workspace.json @@ -33,6 +33,10 @@ "root": "addons/links", "type": "library" }, + "@storybook/addon-measure": { + "root": "addons/measure", + "type": "library" + }, "@storybook/addon-outline": { "root": "addons/outline", "type": "library" diff --git a/yarn.lock b/yarn.lock index ed2a2c3ab087..f9f292024d4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5777,7 +5777,7 @@ __metadata: "@storybook/addon-backgrounds": 6.4.0-alpha.13 "@storybook/addon-controls": 6.4.0-alpha.13 "@storybook/addon-docs": 6.4.0-alpha.13 - "@storybook/addon-measure": ^2.0.0 + "@storybook/addon-measure": 6.4.0-alpha.13 "@storybook/addon-outline": 6.4.0-alpha.13 "@storybook/addon-toolbars": 6.4.0-alpha.13 "@storybook/addon-viewport": 6.4.0-alpha.13 @@ -5869,15 +5869,19 @@ __metadata: languageName: unknown linkType: soft -"@storybook/addon-measure@npm:^2.0.0": - version: 2.0.0 - resolution: "@storybook/addon-measure@npm:2.0.0" +"@storybook/addon-measure@6.4.0-alpha.13, @storybook/addon-measure@workspace:*, @storybook/addon-measure@workspace:addons/measure": + version: 0.0.0-use.local + resolution: "@storybook/addon-measure@workspace:addons/measure" + dependencies: + "@storybook/addons": 6.4.0-alpha.13 + "@storybook/api": 6.4.0-alpha.13 + "@storybook/client-logger": 6.4.0-alpha.13 + "@storybook/components": 6.4.0-alpha.13 + "@storybook/core-events": 6.4.0-alpha.13 + "@types/webpack-env": ^1.16.0 + core-js: ^3.8.2 + global: ^4.4.0 peerDependencies: - "@storybook/addons": ^6.3.0 - "@storybook/api": ^6.3.0 - "@storybook/components": ^6.3.0 - "@storybook/core-events": ^6.3.0 - "@storybook/theming": ^6.3.0 react: ^16.8.0 || ^17.0.0 react-dom: ^16.8.0 || ^17.0.0 peerDependenciesMeta: @@ -5885,9 +5889,8 @@ __metadata: optional: true react-dom: optional: true - checksum: fa701e19ac0806590a24fdc4de75a00e06cee26cc0d909fe03743fee5f92abddafa3756a01def312622ff2ba1dd1fe92c6de5046c4d49363dfdff8120cf231de - languageName: node - linkType: hard + languageName: unknown + linkType: soft "@storybook/addon-outline@6.4.0-alpha.13, @storybook/addon-outline@workspace:*, @storybook/addon-outline@workspace:addons/outline": version: 0.0.0-use.local @@ -7271,6 +7274,7 @@ __metadata: "@storybook/addon-essentials": "workspace:*" "@storybook/addon-jest": "workspace:*" "@storybook/addon-links": "workspace:*" + "@storybook/addon-measure": "workspace:*" "@storybook/addon-outline": "workspace:*" "@storybook/addon-storyshots": "workspace:*" "@storybook/addon-storyshots-puppeteer": "workspace:*"