├── .nvmrc ├── manager.js ├── .husky └── pre-commit ├── preview.js ├── .releaserc.json ├── .lintstagedrc.json ├── src ├── index.ts ├── constants.ts ├── stories │ ├── card.css │ ├── Card.tsx │ └── Card.stories.ts ├── manager.ts ├── types.ts ├── preview.ts ├── components │ └── PaddingIcon.tsx ├── helpers.ts ├── withGlobals.ts └── Tool.tsx ├── demo.gif ├── .gitignore ├── netlify.toml ├── vite.config.ts ├── .prettierrc.json ├── .storybook ├── preview.ts ├── manager.ts ├── main.ts └── local-preset.cjs ├── icon.svg ├── tsconfig.json ├── LICENSE ├── .github └── workflows │ └── main.yml ├── tsup.config.ts ├── README.md └── package.json /.nvmrc: -------------------------------------------------------------------------------- 1 | 20 2 | -------------------------------------------------------------------------------- /manager.js: -------------------------------------------------------------------------------- 1 | import './dist/manager' 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx --no lint-staged 2 | -------------------------------------------------------------------------------- /preview.js: -------------------------------------------------------------------------------- 1 | export * from './dist/preview' 2 | -------------------------------------------------------------------------------- /.releaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "conventionalcommits" 3 | } 4 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*": "prettier --ignore-unknown --write" 3 | } 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // make it work with --isolatedModules 2 | export default {} 3 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rbardini/storybook-addon-paddings/HEAD/demo.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | storybook-static/ 4 | .DS_Store 5 | .env 6 | .vscode 7 | build-storybook.log 8 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm i && npm run build && npm run build-storybook" 3 | publish = "storybook-static" 4 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const ADDON_ID = 'storybook/paddings' 2 | export const PARAM_KEY = 'paddings' 3 | export const DEFAULT_PADDING = '0' 4 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import { defineConfig } from 'vite' 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }) 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "plugins": ["prettier-plugin-packagejson"], 4 | "semi": false, 5 | "singleQuote": true, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from '@storybook/react-vite' 2 | 3 | const preview: Preview = { 4 | parameters: { 5 | layout: 'fullscreen', 6 | }, 7 | tags: ['autodocs'], 8 | } 9 | 10 | export default preview 11 | -------------------------------------------------------------------------------- /src/stories/card.css: -------------------------------------------------------------------------------- 1 | .card { 2 | background-color: #fff; 3 | border-radius: 1em; 4 | box-shadow: 0 2em 4em rgba(0, 0, 0, 0.2); 5 | display: inline-block; 6 | font-family: sans-serif; 7 | line-height: 1.5; 8 | padding: 4em; 9 | } 10 | -------------------------------------------------------------------------------- /.storybook/manager.ts: -------------------------------------------------------------------------------- 1 | import { addons } from 'storybook/manager-api' 2 | import { create } from 'storybook/theming/create' 3 | 4 | addons.setConfig({ 5 | theme: create({ 6 | base: 'light', 7 | brandTitle: 'Storybook Paddings Addon', 8 | }), 9 | }) 10 | -------------------------------------------------------------------------------- /src/stories/Card.tsx: -------------------------------------------------------------------------------- 1 | import React, { type ReactNode } from 'react' 2 | import './card.css' 3 | 4 | interface CardProps { 5 | children?: ReactNode 6 | } 7 | 8 | export const Card = ({ children }: CardProps) => ( 9 |
{children}
10 | ) 11 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import { defineMain } from '@storybook/react-vite/node' 2 | 3 | const config = defineMain({ 4 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], 5 | addons: ['@storybook/addon-docs', './local-preset.cjs'], 6 | framework: '@storybook/react-vite', 7 | }) 8 | 9 | export default config 10 | -------------------------------------------------------------------------------- /.storybook/local-preset.cjs: -------------------------------------------------------------------------------- 1 | function previewAnnotations(entry = []) { 2 | return [...entry, require.resolve('../dist/preview.js')] 3 | } 4 | 5 | function managerEntries(entry = []) { 6 | return [...entry, require.resolve('../dist/manager.js')] 7 | } 8 | 9 | module.exports = { 10 | managerEntries, 11 | previewAnnotations, 12 | } 13 | -------------------------------------------------------------------------------- /src/manager.ts: -------------------------------------------------------------------------------- 1 | import { addons, types } from 'storybook/manager-api' 2 | 3 | import { ADDON_ID } from './constants' 4 | import { Tool } from './Tool' 5 | 6 | addons.register(ADDON_ID, () => { 7 | addons.add(ADDON_ID, { 8 | title: 'Paddings', 9 | type: types.TOOL, 10 | match: ({ viewMode }) => !!viewMode?.match(/^(story|docs)$/), 11 | render: Tool, 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { type ComponentProps } from 'react' 2 | import { TooltipLinkList } from 'storybook/internal/components' 3 | 4 | export type Padding = { name: string; value: string } 5 | 6 | export type PaddingsParameter = { 7 | default?: string 8 | disable?: boolean 9 | values: Padding[] 10 | } 11 | 12 | export type PaddingWithDefault = Padding & { default?: boolean } 13 | 14 | export type Item = Extract< 15 | ComponentProps['links'][number], 16 | Array 17 | >[number] 18 | 19 | export type GlobalState = { 20 | name?: string 21 | selected?: string 22 | } 23 | -------------------------------------------------------------------------------- /src/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Renderer, ProjectAnnotations } from 'storybook/internal/types' 2 | 3 | import { PARAM_KEY } from './constants' 4 | import { withGlobals } from './withGlobals' 5 | 6 | const preview: ProjectAnnotations = { 7 | decorators: [withGlobals], 8 | initialGlobals: { 9 | [PARAM_KEY]: false, 10 | }, 11 | parameters: { 12 | [PARAM_KEY]: { 13 | values: [ 14 | { name: 'Small', value: '16px' }, 15 | { name: 'Medium', value: '32px' }, 16 | { name: 'Large', value: '64px' }, 17 | ], 18 | }, 19 | }, 20 | } 21 | 22 | export default preview 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "baseUrl": ".", 5 | "esModuleInterop": true, 6 | "isolatedModules": true, 7 | "jsx": "react", 8 | "lib": ["es2023", "dom", "dom.iterable"], 9 | "module": "preserve", 10 | "moduleDetection": "force", 11 | "moduleResolution": "bundler", 12 | "noImplicitAny": true, 13 | "noImplicitOverride": true, 14 | "noUncheckedIndexedAccess": true, 15 | "resolveJsonModule": true, 16 | "rootDir": "./src", 17 | "skipLibCheck": true, 18 | "strict": true, 19 | "target": "es2023", // Node 20 according to https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping#node-20 20 | "verbatimModuleSyntax": true 21 | }, 22 | "include": ["src/**/*"] 23 | } 24 | -------------------------------------------------------------------------------- /src/components/PaddingIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, memo } from 'react' 2 | 3 | interface IconProps extends React.SVGAttributes { 4 | children?: never 5 | color?: string 6 | size?: number 7 | } 8 | 9 | export const PaddingIcon = forwardRef( 10 | ({ color = 'currentColor', size = 14, ...props }, ref) => ( 11 | 20 | 26 | 27 | ), 28 | ) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rafael Bardini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | test-build-release: 12 | name: Test, build and release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v5 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v5 20 | with: 21 | node-version: 24 22 | 23 | - name: Install 24 | run: npm ci 25 | 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Format-check 30 | run: npm run format-check 31 | 32 | - name: Type-check 33 | run: npm run type-check 34 | 35 | - name: Package-check 36 | run: npm run package-check 37 | 38 | - name: Release 39 | uses: cycjimmy/semantic-release-action@v5 40 | with: 41 | semantic_version: 25 42 | extra_plugins: | 43 | conventional-changelog-conventionalcommits 44 | if: github.ref == 'refs/heads/main' 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 48 | -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_PADDING } from './constants' 2 | import type { PaddingWithDefault, PaddingsParameter, Padding } from './types' 3 | 4 | export const isEnabled = (values: PaddingWithDefault[]): boolean => 5 | values.length > 0 6 | 7 | export const isReducedMotionPreferred = (): boolean => 8 | window.matchMedia('(prefers-reduced-motion: reduce)').matches 9 | 10 | export const normalizeValues = ( 11 | parameters: PaddingsParameter | null, 12 | ): PaddingWithDefault[] => { 13 | if (!parameters || parameters.disable || !parameters.values?.length) { 14 | return [] 15 | } 16 | 17 | return parameters.values.map((item: Padding) => { 18 | const { name, value } = item 19 | const isDefault = parameters.default === name 20 | 21 | return { name, value, default: isDefault } 22 | }) 23 | } 24 | 25 | export const getSelectedPadding = ( 26 | values: PaddingWithDefault[], 27 | currentValue: string, 28 | ): string => { 29 | if (currentValue === DEFAULT_PADDING) { 30 | return currentValue 31 | } 32 | 33 | if (values.find(({ value }) => value === currentValue)) { 34 | return currentValue 35 | } 36 | 37 | return values.find(option => option.default)?.value ?? DEFAULT_PADDING 38 | } 39 | -------------------------------------------------------------------------------- /src/stories/Card.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react-vite' 2 | 3 | import { Card } from './Card' 4 | 5 | type Story = StoryObj 6 | 7 | const meta: Meta = { 8 | title: 'Example', 9 | component: Card, 10 | } 11 | 12 | export default meta 13 | 14 | export const PresetOptions: Story = { 15 | args: { 16 | children: 'This story uses preset padding options. (Small, Medium & Large)', 17 | }, 18 | } 19 | 20 | export const CustomOptions: Story = { 21 | args: { 22 | children: 'This story uses custom padding options. (xs, sm, md, lg & xl)', 23 | }, 24 | parameters: { 25 | paddings: { 26 | values: [ 27 | { name: 'xs', value: '8px' }, 28 | { name: 'sm', value: '16px' }, 29 | { name: 'md', value: '24px' }, 30 | { name: 'lg', value: '32px' }, 31 | { name: 'xl', value: '48px' }, 32 | ], 33 | }, 34 | }, 35 | } 36 | 37 | export const DefaultOption: Story = { 38 | args: { 39 | children: 'This story sets a default padding option. (Medium)', 40 | }, 41 | parameters: { 42 | paddings: { default: 'Medium' }, 43 | }, 44 | } 45 | 46 | export const Disabled: Story = { 47 | args: { 48 | children: 'This story disables paddings.', 49 | }, 50 | parameters: { 51 | paddings: { disable: true }, 52 | }, 53 | } 54 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs/promises' 2 | import { globalPackages as globalManagerPackages } from 'storybook/internal/manager/globals' 3 | import { globalPackages as globalPreviewPackages } from 'storybook/internal/preview/globals' 4 | import { defineConfig, type Options } from 'tsup' 5 | 6 | const NODE_TARGET: Options['target'] = 'node20' 7 | 8 | type BundlerConfig = { 9 | bundler?: { 10 | exportEntries?: string[] 11 | managerEntries?: string[] 12 | previewEntries?: string[] 13 | } 14 | } 15 | 16 | export default defineConfig(async options => { 17 | const packageJson = (await readFile('./package.json', 'utf8').then( 18 | JSON.parse, 19 | )) as BundlerConfig 20 | 21 | const { 22 | bundler: { 23 | exportEntries = [], 24 | managerEntries = [], 25 | previewEntries = [], 26 | } = {}, 27 | } = packageJson 28 | 29 | const commonConfig: Options = { 30 | splitting: false, 31 | minify: !options.watch, 32 | treeshake: true, 33 | sourcemap: true, 34 | clean: true, 35 | } 36 | 37 | const configs: Options[] = [] 38 | 39 | if (exportEntries.length) { 40 | configs.push({ 41 | ...commonConfig, 42 | entry: exportEntries, 43 | dts: { resolve: true }, 44 | format: ['esm', 'cjs'], 45 | target: NODE_TARGET, 46 | platform: 'neutral', 47 | external: [...globalManagerPackages, ...globalPreviewPackages], 48 | }) 49 | } 50 | 51 | if (managerEntries.length) { 52 | configs.push({ 53 | ...commonConfig, 54 | entry: managerEntries, 55 | format: ['esm'], 56 | platform: 'browser', 57 | external: globalManagerPackages, 58 | }) 59 | } 60 | 61 | if (previewEntries.length) { 62 | configs.push({ 63 | ...commonConfig, 64 | entry: previewEntries, 65 | dts: { resolve: true }, 66 | format: ['esm', 'cjs'], 67 | platform: 'browser', 68 | external: globalPreviewPackages, 69 | }) 70 | } 71 | 72 | return configs 73 | }) 74 | -------------------------------------------------------------------------------- /src/withGlobals.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Renderer, 3 | PartialStoryFn as StoryFunction, 4 | StoryContext, 5 | } from 'storybook/internal/types' 6 | import { useEffect, useMemo } from 'storybook/preview-api' 7 | 8 | import { DEFAULT_PADDING, PARAM_KEY } from './constants' 9 | import { 10 | getSelectedPadding, 11 | isReducedMotionPreferred, 12 | normalizeValues, 13 | } from './helpers' 14 | 15 | const setStyle = (selector: string, css: string) => { 16 | const existingStyle = document.getElementById(selector) 17 | 18 | if (existingStyle) { 19 | if (existingStyle.innerHTML !== css) { 20 | existingStyle.innerHTML = css 21 | } 22 | } else { 23 | const style = document.createElement('style') 24 | style.setAttribute('id', selector) 25 | style.innerHTML = css 26 | 27 | document.head.appendChild(style) 28 | } 29 | } 30 | 31 | export const withGlobals = ( 32 | StoryFn: StoryFunction, 33 | context: StoryContext, 34 | ) => { 35 | const { id, globals, parameters, viewMode } = context 36 | const globalsSelectedPadding = globals[PARAM_KEY]?.value 37 | const paddingsConfig = parameters[PARAM_KEY] 38 | const isInDocs = viewMode === 'docs' 39 | const selector = isInDocs 40 | ? `#anchor--${context.id} .sb-story` 41 | : '#storybook-root' 42 | 43 | const selectedPadding = useMemo( 44 | () => 45 | getSelectedPadding( 46 | normalizeValues(paddingsConfig), 47 | globalsSelectedPadding, 48 | ) ?? DEFAULT_PADDING, 49 | [paddingsConfig, globalsSelectedPadding], 50 | ) 51 | 52 | const paddingStyles = useMemo( 53 | () => ` 54 | ${selector} { 55 | margin: 0; 56 | padding: ${selectedPadding} !important; 57 | ${isReducedMotionPreferred() ? '' : 'transition: padding .3s;'} 58 | } 59 | 60 | ${selector} .innerZoomElementWrapper > div { 61 | border-width: 0 !important; 62 | } 63 | `, 64 | [selector, selectedPadding], 65 | ) 66 | 67 | useEffect(() => { 68 | const selectorId = isInDocs ? `addon-paddings-docs-${id}` : `addon-paddings` 69 | 70 | setStyle(selectorId, paddingStyles) 71 | }, [id, isInDocs, paddingStyles]) 72 | 73 | return StoryFn() 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Storybook Paddings Addon 2 | 3 | [![npm package version](https://img.shields.io/npm/v/storybook-addon-paddings)](https://www.npmjs.com/package/storybook-addon-paddings) 4 | [![Build status](https://img.shields.io/github/actions/workflow/status/rbardini/storybook-addon-paddings/main.yml)](https://github.com/rbardini/storybook-addon-paddings/actions) 5 | [![Dependencies status](https://img.shields.io/librariesio/release/npm/storybook-addon-paddings)](https://libraries.io/npm/storybook-addon-paddings) 6 | 7 | 🔲 A [Storybook](https://storybook.js.org) addon to add different paddings to your preview. Useful for checking how components behave when surrounded with white space. 8 | 9 | ![Demo](demo.gif) 10 | 11 | [View demo →](https://storybook-addon-paddings.rbrd.in) 12 | 13 | ## Installation 14 | 15 | ```sh 16 | npm install --save-dev storybook-addon-paddings 17 | ``` 18 | 19 | ```js 20 | // .storybook/main.js 21 | export default { 22 | addons: ['storybook-addon-paddings'], 23 | } 24 | 25 | // .storybook/preview.js 26 | export default { 27 | parameters: { 28 | layout: 'fullscreen', // remove default Storybook padding 29 | }, 30 | } 31 | ``` 32 | 33 | ## Configuration 34 | 35 | The paddings toolbar comes with small, medium and large options by default, but you can configure your own set of paddings via the `paddings` [parameter](https://storybook.js.org/docs/react/writing-stories/parameters). 36 | 37 | To configure for all stories, set the `paddings` parameter in [`.storybook/preview.js`](https://storybook.js.org/docs/react/configure/overview#configure-story-rendering): 38 | 39 | ```js 40 | export const parameters = { 41 | paddings: { 42 | values: [ 43 | { name: 'Small', value: '16px' }, 44 | { name: 'Medium', value: '32px' }, 45 | { name: 'Large', value: '64px' }, 46 | ], 47 | default: 'Medium', 48 | }, 49 | } 50 | ``` 51 | 52 | You can also configure on per-story or per-component basis using [parameter inheritance](https://storybook.js.org/docs/react/writing-stories/parameters#component-parameters): 53 | 54 | ```js 55 | // Button.stories.js 56 | 57 | // Set padding options for all Button stories 58 | export default { 59 | title: 'Button', 60 | component: Button, 61 | parameters: { 62 | paddings: { 63 | values: [ 64 | { name: 'Small', value: '16px' }, 65 | { name: 'Medium', value: '32px' }, 66 | { name: 'Large', value: '64px' }, 67 | ], 68 | default: 'Large', 69 | }, 70 | }, 71 | } 72 | 73 | // Disable addon in Button/Large story only 74 | export const Large { 75 | parameters: { 76 | paddings: { disable: true }, 77 | }, 78 | } 79 | ``` 80 | 81 | See other [story examples](./src/stories/Card.stories.ts). 82 | -------------------------------------------------------------------------------- /src/Tool.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useCallback, useMemo } from 'react' 2 | import { 3 | IconButton, 4 | WithTooltip, 5 | TooltipLinkList, 6 | } from 'storybook/internal/components' 7 | import { useParameter, useGlobals } from 'storybook/manager-api' 8 | 9 | import { PaddingIcon } from './components/PaddingIcon' 10 | import { DEFAULT_PADDING, PARAM_KEY } from './constants' 11 | import { getSelectedPadding, normalizeValues, isEnabled } from './helpers' 12 | import type { PaddingWithDefault, Item, GlobalState } from './types' 13 | 14 | const createItem = ( 15 | id: string | undefined, 16 | name: string, 17 | value: string, 18 | hasValue: boolean, 19 | active: boolean, 20 | change: (arg: { selected: string; name: string }) => void, 21 | ): Item => ({ 22 | id: id || name, 23 | title: name, 24 | onClick: () => change({ selected: value, name }), 25 | active, 26 | right: hasValue ? value : undefined, 27 | }) 28 | 29 | const getDisplayedItems = ( 30 | list: PaddingWithDefault[], 31 | selected: string, 32 | change: (arg: GlobalState) => void, 33 | ) => 34 | list.reduce( 35 | (acc, { name, value }) => ( 36 | acc.push( 37 | createItem(undefined, name, value, true, value === selected, change), 38 | ), 39 | acc 40 | ), 41 | selected === DEFAULT_PADDING 42 | ? [] 43 | : [ 44 | createItem( 45 | 'reset', 46 | 'Clear paddings', 47 | DEFAULT_PADDING, 48 | false, 49 | false, 50 | change, 51 | ), 52 | ], 53 | ) 54 | 55 | export const Tool = memo(() => { 56 | const [globals, updateGlobals] = useGlobals() 57 | const options = useParameter(PARAM_KEY, null) 58 | const values = normalizeValues(options) 59 | 60 | const selectedPadding = useMemo( 61 | () => getSelectedPadding(values, globals[PARAM_KEY]?.value), 62 | [globals, values], 63 | ) 64 | 65 | const onPaddingChange = useCallback( 66 | (value?: string) => 67 | updateGlobals({ [PARAM_KEY]: { ...globals[PARAM_KEY], value } }), 68 | [globals, updateGlobals], 69 | ) 70 | 71 | return isEnabled(values) ? ( 72 | ( 76 | { 78 | onPaddingChange(selected) 79 | onHide() 80 | })} 81 | /> 82 | )} 83 | closeOnOutsideClick 84 | > 85 | 90 | 91 | 92 | 93 | ) : null 94 | }) 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "storybook-addon-paddings", 3 | "version": "0.0.0-development", 4 | "description": "Add paddings to view components under different white space settings", 5 | "keywords": [ 6 | "storybook-addons", 7 | "style", 8 | "padding", 9 | "margin", 10 | "spacing", 11 | "whitespace" 12 | ], 13 | "homepage": "https://github.com/rbardini/storybook-addon-paddings", 14 | "bugs": { 15 | "url": "https://github.com/rbardini/storybook-addon-paddings/issues" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/rbardini/storybook-addon-paddings.git" 20 | }, 21 | "license": "MIT", 22 | "author": { 23 | "name": "Rafael Bardini", 24 | "email": "rafael@rbardini.com", 25 | "url": "https://rbardini.com/" 26 | }, 27 | "type": "module", 28 | "exports": { 29 | ".": { 30 | "types": "./dist/index.d.ts", 31 | "import": "./dist/index.js", 32 | "require": "./dist/index.cjs" 33 | }, 34 | "./preview": { 35 | "types": "./dist/index.d.ts", 36 | "import": "./dist/preview.js", 37 | "require": "./dist/preview.cjs" 38 | }, 39 | "./manager": "./dist/manager.js", 40 | "./package.json": "./package.json" 41 | }, 42 | "types": "dist/index.d.ts", 43 | "files": [ 44 | "dist/**/*", 45 | "*.js", 46 | "*.d.ts" 47 | ], 48 | "scripts": { 49 | "prebuild": "npm run clean", 50 | "build": "tsup", 51 | "build-storybook": "storybook build", 52 | "build:watch": "npm run build -- --watch", 53 | "clean": "rimraf ./dist", 54 | "format": "prettier --ignore-path .gitignore --write .", 55 | "format-check": "prettier --ignore-path .gitignore --check .", 56 | "package-check": "package-check", 57 | "prepare": "husky", 58 | "start": "concurrently \"npm run build:watch\" \"npm run storybook -- --quiet\"", 59 | "storybook": "storybook dev -p 6006", 60 | "type-check": "tsc --noEmit" 61 | }, 62 | "devDependencies": { 63 | "@skypack/package-check": "^0.2.0", 64 | "@storybook/addon-docs": "^9.0.0", 65 | "@storybook/react-vite": "^9.0.0", 66 | "@types/node": "^24.0.0", 67 | "@types/react": "^18.0.0", 68 | "@vitejs/plugin-react": "^5.0.0", 69 | "concurrently": "^9.0.0", 70 | "husky": "^9.0.0", 71 | "lint-staged": "^16.0.0", 72 | "prettier": "^3.1.0", 73 | "prettier-plugin-packagejson": "^2.4.0", 74 | "react": "^18.0.0", 75 | "react-dom": "^18.0.0", 76 | "rimraf": "^6.0.0", 77 | "storybook": "^9.0.0", 78 | "tsup": "^8.0.0", 79 | "typescript": "^5.3.0", 80 | "vite": "^7.0.0" 81 | }, 82 | "peerDependencies": { 83 | "storybook": "^9.0.0" 84 | }, 85 | "bundler": { 86 | "exportEntries": [ 87 | "src/index.ts" 88 | ], 89 | "managerEntries": [ 90 | "src/manager.ts" 91 | ], 92 | "previewEntries": [ 93 | "src/preview.ts" 94 | ] 95 | }, 96 | "storybook": { 97 | "displayName": "Paddings", 98 | "supportedFrameworks": [ 99 | "react", 100 | "vue", 101 | "angular", 102 | "web-components", 103 | "ember", 104 | "html", 105 | "svelte", 106 | "preact", 107 | "react-native" 108 | ], 109 | "icon": "https://raw.githubusercontent.com/rbardini/storybook-addon-paddings/HEAD/icon.svg" 110 | } 111 | } 112 | --------------------------------------------------------------------------------