@stevendejong/storybook-stylesheet-toggle 0.0.12 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/manager.js +1 -7
- package/dist/manager.js.map +1 -1
- package/dist/preset.js +2 -0
- package/dist/preset.js.map +1 -0
- package/dist/preview.d.ts +1 -1
- package/dist/preview.js +1 -6
- package/dist/preview.js.map +1 -1
- package/package.json +22 -27
- package/preset.js +1 -0
- package/dist/index.cjs +0 -7
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -3
- package/dist/preset.cjs +0 -8
- package/dist/preset.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
# Storybook Stylesheet Toggle addon
|
|
2
2
|
|
|
3
|
-
The Storybook Stylesheet Toggle addon is a simple and convenient tool for
|
|
3
|
+
The Storybook Stylesheet Toggle addon is a simple and convenient tool for managing stylesheets in your Storybook stories. Switch between configured stylesheets and dynamically add custom ones at runtime via a dropdown menu in the toolbar.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev @stevendejong/storybook-stylesheet-toggle
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Add the addon to your `.storybook/main.ts`:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
export default {
|
|
17
|
+
addons: [
|
|
18
|
+
'@stevendejong/storybook-stylesheet-toggle',
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
```
|
|
7
22
|
|
|
8
23
|
## Usage
|
|
9
24
|
|
|
@@ -33,3 +48,33 @@ export default preview;
|
|
|
33
48
|
|
|
34
49
|
## Using the Toggle
|
|
35
50
|
Once you've configured your story, you'll see a toolbar button in Storybook that allows you to select and apply different stylesheets to your components during development.
|
|
51
|
+
|
|
52
|
+
### Adding Custom Stylesheets
|
|
53
|
+
|
|
54
|
+
In addition to configured stylesheets, you can dynamically add custom stylesheets at runtime:
|
|
55
|
+
|
|
56
|
+
1. Click the paint brush toolbar button
|
|
57
|
+
2. Enter a stylesheet URL in the input field at the top:
|
|
58
|
+
- Relative paths: `./theme.css`, `/assets/theme.css`
|
|
59
|
+
- Absolute URLs: `https://example.com/styles/theme.css`
|
|
60
|
+
3. Press Enter or click "Add"
|
|
61
|
+
|
|
62
|
+
**Features:**
|
|
63
|
+
- ✅ Custom stylesheets persist across browser sessions
|
|
64
|
+
- ✅ Delete custom stylesheets with the trash icon
|
|
65
|
+
- ✅ URL validation (must end with `.css`)
|
|
66
|
+
- ✅ Duplicate detection
|
|
67
|
+
- ✅ Auto-naming from URL (e.g., `theme.css`)
|
|
68
|
+
- ✅ Visual separation from configured stylesheets
|
|
69
|
+
|
|
70
|
+
**Example workflow:**
|
|
71
|
+
1. Open Storybook
|
|
72
|
+
2. Click paint brush icon
|
|
73
|
+
3. Add custom stylesheet: `https://cdn.example.com/dark-theme.css`
|
|
74
|
+
4. Stylesheet appears in "Custom Stylesheets" section
|
|
75
|
+
5. Switch between custom and configured stylesheets
|
|
76
|
+
6. Delete custom stylesheets when no longer needed
|
|
77
|
+
|
|
78
|
+
**localStorage Storage:**
|
|
79
|
+
- Selected stylesheet: `localStorage.getItem('stylesheetToggle')`
|
|
80
|
+
- Custom stylesheets: `localStorage.getItem('stylesheetToggle:custom')`
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["index_default"],"mappings":"AACA,IAAOA,EAAQ","file":"index.js","sourcesContent":["// make it work with --isolatedModules\nexport default {};\n"]}
|
package/dist/manager.js
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
import l from '
|
|
2
|
-
import { addons, types, useParameter, useGlobals } from '@storybook/manager-api';
|
|
3
|
-
import { WithTooltip, TooltipLinkList, IconButton } from '@storybook/components';
|
|
4
|
-
import { PaintBrushIcon } from '@storybook/icons';
|
|
5
|
-
|
|
6
|
-
var s="stylesheetToggle",m=`${s}/tool`,e="stylesheetToggle";var a={default:""};var I=({stylesheets:o})=>{let[f]=useGlobals(),g=[!0,"true"].includes(f[e]);if(o===null)return null;let i={};for(let[t,r]of Object.entries(a))i[t]=r;for(let[t,r]of Object.entries(o))i[t]=r;let n=localStorage.getItem(e);n||localStorage.setItem(e,"default");let d=t=>{localStorage.setItem(e,t),window.location.reload();},c=[];for(let[t,r]of Object.entries(i))c.push({id:t,title:t,onClick:()=>d(t),active:!n&&t==="default"||n===t});return l.createElement(WithTooltip,{placement:"top",trigger:"click",tooltip:l.createElement(TooltipLinkList,{links:c}),closeOnOutsideClick:!0},l.createElement(IconButton,{key:m,active:g,title:"Activate Stylesheet"},l.createElement(PaintBrushIcon,null)))},p=I;addons.register(s,()=>{addons.add(s,{title:"Stylesheet Toggle",type:types.TOOL,render:()=>{let o=useParameter(e,null);return l.createElement(p,{stylesheets:o})}});});
|
|
7
|
-
//# sourceMappingURL=out.js.map
|
|
1
|
+
import l,{useState}from'react';import {addons,types,useParameter,useGlobals}from'storybook/manager-api';import {WithTooltip,IconButton,Form,Button}from'storybook/internal/components';import {PaintBrushIcon,TrashIcon}from'@storybook/icons';import {styled}from'storybook/theming';var m="stylesheetToggle",x=`${m}/tool`,a="stylesheetToggle";var v={default:""};var g=`${a}:custom`;function c(){let t=localStorage.getItem(g);if(!t)return [];try{let e=JSON.parse(t);if(!e||typeof e!="object")throw new Error("Invalid format");return Object.values(e).filter(s=>s&&typeof s.id=="string"&&typeof s.name=="string"&&typeof s.url=="string")}catch{return localStorage.removeItem(g),[]}}function C(t,e){let s=`custom:${Date.now()}`,r=E(t),n=c(),o=N(r,n),i={id:s,name:o,url:t},h=[...n,i].reduce((y,S)=>(y[S.id]=S,y),{});return localStorage.setItem(g,JSON.stringify(h)),i}function I(t){let r=c().filter(o=>o.id!==t).reduce((o,i)=>(o[i.id]=i,o),{});localStorage.setItem(g,JSON.stringify(r)),localStorage.getItem(a)===t&&localStorage.setItem(a,"default");}function b(t){if(!t.endsWith(".css"))return false;try{return t.startsWith("http://")||t.startsWith("https://")?(new URL(t),!0):t.startsWith("./")||t.startsWith("/")||!t.includes("://")}catch{return false}}function k(t,e){return e.some(s=>s.url===t)}function E(t){let e=t.split("/");return e[e.length-1]||"Custom Stylesheet"}function N(t,e){let s=e.map(n=>n.name);if(!s.includes(t))return t;let r=2;for(;s.includes(`${t} (${r})`);)r++;return `${t} (${r})`}var w=({onAdd:t})=>{let[e,s]=useState(""),[r,n]=useState(null),o=()=>{if(!e.trim()){n("URL is required");return}if(!b(e)){n("Invalid stylesheet URL (must end with .css)");return}let u=c();if(k(e,u)){n("This stylesheet already exists");return}let h=C(e);s(""),n(null),t(h.id);},i=u=>{u.key==="Enter"&&(u.preventDefault(),o());};return l.createElement("div",{style:{padding:"8px",borderBottom:"1px solid #ddd"}},l.createElement(Form.Field,{label:"Add Custom Stylesheet"},l.createElement("div",{style:{display:"flex",gap:"4px"}},l.createElement(Form.Input,{value:e,onChange:u=>s(u.target.value),onKeyDown:i,placeholder:"https://example.com/theme.css",style:{flex:1}}),l.createElement(Button,{onClick:o,size:"small"},"Add")),r&&l.createElement("div",{style:{color:"red",fontSize:"12px",marginTop:"4px"}},r)))};var _=styled.div({minWidth:280,maxWidth:400,overflow:"hidden",overflowY:"auto",maxHeight:"400px"}),D=styled.div(({theme:t,active:e})=>({fontSize:t.typography.size.s1,color:e?t.color.secondary:t.color.dark,textDecoration:"none",cursor:"pointer",lineHeight:"18px",padding:"7px 10px",display:"flex",alignItems:"center",justifyContent:"space-between",backgroundColor:e?t.background.hoverable:"transparent","&:hover":{background:t.background.hoverable}})),K=styled.button(({theme:t})=>({background:"none",border:"none",cursor:"pointer",padding:"2px 4px",display:"flex",alignItems:"center",opacity:.6,"&:hover":{opacity:1,color:t.color.negative}})),F=styled.div(({theme:t})=>({height:"1px",background:t.appBorderColor,margin:"8px 0"})),O=styled.div(({theme:t})=>({fontSize:"11px",color:t.color.mediumdark,padding:"4px 10px",fontWeight:"bold",textTransform:"uppercase"})),$=styled.span({overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",maxWidth:"200px"}),A=({configuredStylesheets:t,activeStylesheet:e,onSelect:s})=>{let r=c(),n=(o,i)=>{o.stopPropagation(),confirm("Delete this custom stylesheet?")&&(I(i),window.location.reload());};return l.createElement(_,null,l.createElement(w,{onAdd:s}),r.length>0&&l.createElement(l.Fragment,null,l.createElement(O,null,"Custom Stylesheets"),r.map(o=>l.createElement(D,{key:o.id,active:e===o.id,onClick:()=>s(o.id)},l.createElement($,{title:o.url},o.name),l.createElement(K,{onClick:i=>n(i,o.id),"aria-label":"Delete stylesheet"},l.createElement(TrashIcon,{size:12})))),l.createElement(F,null)),l.createElement(O,null,"Configured Stylesheets"),Object.entries(t).map(([o])=>l.createElement(D,{key:o,active:e===o,onClick:()=>s(o)},o)))};var V=({stylesheets:t})=>{let[e]=useGlobals(),s=[true,"true"].includes(e[a]);if(t===null)return null;let r={...v,...t},n=localStorage.getItem(a)||"default";return l.createElement(WithTooltip,{placement:"top",trigger:"click",tooltip:l.createElement(A,{configuredStylesheets:r,activeStylesheet:n,onSelect:i=>{localStorage.setItem(a,i),window.location.reload();}}),closeOnOutsideClick:true},l.createElement(IconButton,{key:x,active:s,title:"Toggle Stylesheet"},l.createElement(PaintBrushIcon,null)))},P=V;addons.register(m,()=>{addons.add(m,{title:"Stylesheet Toggle",type:types.TOOL,render:()=>{let t=useParameter(a,null);return l.createElement(P,{stylesheets:t})}});});//# sourceMappingURL=manager.js.map
|
|
8
2
|
//# sourceMappingURL=manager.js.map
|
package/dist/manager.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/manager.tsx","../src/constants.ts","../src/Tool.tsx","../src/defaults.ts"],"names":["React","addons","types","useParameter","ADDON_ID","TOOL_ID","PARAM_KEY","useGlobals","IconButton","TooltipLinkList","WithTooltip","PaintBrushIcon","defaultStylesheets","Tool","stylesheets","globals","isActive","mapping","name","url","active","toggleStylesheet","sheet","items","Tool_default"],"mappings":"AAAA,OAAOA,MAAW,QAClB,OAAS,UAAAC,EAAQ,SAAAC,EAAO,gBAAAC,MAAoB,yBCDrC,IAAMC,EAAW,mBACXC,EAAU,GAAGD,CAAQ,QACrBE,EAAY,mBCFzB,OAAON,MAAW,QAClB,OAAS,cAAAO,MAAkB,yBAC3B,OAAS,cAAAC,EAAY,mBAAAC,EAAiB,eAAAC,MAAmB,wBACzD,OAAS,kBAAAC,MAAsB,mBCHxB,IAAMC,EAA8C,CACzD,QAAW,EACb,EDMA,IAAMC,EAAO,CAAC,CAAE,YAAAC,CAAY,IAAiC,CAC3D,GAAM,CAACC,CAAO,EAAIR,EAAW,EACvBS,EAAW,CAAC,GAAM,MAAM,EAAE,SAASD,EAAQT,CAAS,CAAC,EAE3D,GAAIQ,IAAgB,KAClB,OAAO,KAGT,IAAMG,EAAmC,CAAC,EAC1C,OAAW,CAACC,EAAMC,CAAG,IAAK,OAAO,QAAQP,CAAkB,EACzDK,EAAQC,CAAI,EAAIC,EAElB,OAAW,CAACD,EAAMC,CAAG,IAAK,OAAO,QAAQL,CAAW,EAClDG,EAAQC,CAAI,EAAIC,EAGlB,IAAMC,EAAS,aAAa,QAAQd,CAAS,EACxCc,GACH,aAAa,QAAQd,EAAW,SAAS,EAG3C,IAAMe,EAAoBC,GAAkB,CAC1C,aAAa,QAAQhB,EAAWgB,CAAK,EACrC,OAAO,SAAS,OAAO,CACzB,EAEMC,EAAQ,CAAC,EACf,OAAW,CAACL,EAAMC,CAAG,IAAK,OAAO,QAAQF,CAAO,EAC9CM,EAAM,KAAK,CACT,GAAIL,EACJ,MAAOA,EACP,QAAS,IAAMG,EAAiBH,CAAI,EACpC,OAAU,CAACE,GAAUF,IAAS,WAAcE,IAAWF,CACzD,CAAC,EAGH,OACElB,EAAA,cAACU,EAAA,CACC,UAAU,MACV,QAAQ,QACR,QAASV,EAAA,cAACS,EAAA,CAAgB,MAAOc,EAAO,EACxC,oBAAmB,IAEnBvB,EAAA,cAACQ,EAAA,CACC,IAAKH,EACL,OAAQW,EACR,MAAM,uBAENhB,EAAA,cAACW,EAAA,IAAe,CAClB,CACF,CAEJ,EAEOa,EAAQX,EFzDfZ,EAAO,SAASG,EAAU,IAAM,CAC9BH,EAAO,IAAIG,EAAU,CACnB,MAAO,oBACP,KAAMF,EAAM,KACZ,OAAQ,IAAM,CACZ,IAAMY,EAAcX,EAClBG,EACA,IACF,EAEA,OAAON,EAAA,cAACwB,EAAA,CAAK,YAAaV,EAAa,CACzC,CACF,CAAC,CACH,CAAC","sourcesContent":["import React from \"react\";\nimport { addons, types, useParameter } from \"@storybook/manager-api\";\nimport { ADDON_ID, PARAM_KEY } from \"./constants\";\nimport Tool from './Tool'\n\naddons.register(ADDON_ID, () => {\n addons.add(ADDON_ID, {\n title: 'Stylesheet Toggle',\n type: types.TOOL,\n render: () => {\n const stylesheets = useParameter(\n PARAM_KEY,\n null,\n );\n\n return <Tool stylesheets={stylesheets} />\n },\n });\n});\n","export const ADDON_ID = \"stylesheetToggle\";\nexport const TOOL_ID = `${ADDON_ID}/tool`;\nexport const PARAM_KEY = `stylesheetToggle`;\n\n","import React from \"react\";\nimport { useGlobals } from \"@storybook/manager-api\";\nimport { IconButton, TooltipLinkList, WithTooltip } from \"@storybook/components\";\nimport { PaintBrushIcon } from \"@storybook/icons\";\nimport { PARAM_KEY, TOOL_ID } from \"./constants\";\nimport { defaultStylesheets } from \"./defaults\";\n\n\nconst Tool = ({ stylesheets }: { [key: string]: string }) => {\n const [globals] = useGlobals();\n const isActive = [true, \"true\"].includes(globals[PARAM_KEY]);\n\n if (stylesheets === null) {\n return null;\n }\n\n const mapping: {[key: string]: string} = {};\n for (const [name, url] of Object.entries(defaultStylesheets)) {\n mapping[name] = url;\n }\n for (const [name, url] of Object.entries(stylesheets)) {\n mapping[name] = url;\n }\n\n const active = localStorage.getItem(PARAM_KEY);\n if (!active) {\n localStorage.setItem(PARAM_KEY, \"default\");\n }\n\n const toggleStylesheet = (sheet: string) => {\n localStorage.setItem(PARAM_KEY, sheet);\n window.location.reload();\n };\n\n const items = [];\n for (const [name, url] of Object.entries(mapping)) {\n items.push({\n id: name,\n title: name,\n onClick: () => toggleStylesheet(name),\n active: ((!active && name === \"default\") || active === name)\n });\n }\n\n return (\n <WithTooltip\n placement=\"top\"\n trigger=\"click\"\n tooltip={<TooltipLinkList links={items} />}\n closeOnOutsideClick\n >\n <IconButton\n key={TOOL_ID}\n active={isActive}\n title=\"Activate Stylesheet\"\n >\n <PaintBrushIcon />\n </IconButton>\n </WithTooltip>\n );\n};\n\nexport default Tool;\n","export const defaultStylesheets: {[key: string]: string} = {\n \"default\": ''\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/defaults.ts","../src/customStylesheets.ts","../src/CustomStylesheetInput.tsx","../src/CustomTooltipLinkList.tsx","../src/Tool.tsx","../src/manager.tsx"],"names":["ADDON_ID","TOOL_ID","PARAM_KEY","defaultStylesheets","CUSTOM_KEY","getCustomStylesheets","data","parsed","sheet","saveCustomStylesheet","url","name","id","autoName","extractNameFromUrl","customSheets","finalName","ensureUniqueName","newSheet","mapping","acc","deleteCustomStylesheet","isValidStylesheetUrl","isDuplicateUrl","customStylesheets","parts","existing","existingNames","s","counter","CustomStylesheetInput","onAdd","inputValue","setInputValue","useState","error","setError","handleAdd","handleKeyDown","e","React","Form","Button","List","styled","ListItem","theme","active","DeleteButton","Separator","SectionLabel","ItemTitle","CustomTooltipLinkList","configuredStylesheets","activeStylesheet","onSelect","handleDelete","TrashIcon","Tool","stylesheets","globals","useGlobals","isActive","WithTooltip","IconButton","PaintBrushIcon","Tool_default","addons","types","useParameter"],"mappings":"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","file":"manager.js","sourcesContent":["export const ADDON_ID = \"stylesheetToggle\";\nexport const TOOL_ID = `${ADDON_ID}/tool`;\nexport const PARAM_KEY = `stylesheetToggle`;\n\n","export const defaultStylesheets: {[key: string]: string} = {\n \"default\": ''\n}\n","import { PARAM_KEY } from './constants';\n\nexport interface CustomStylesheet {\n id: string;\n name: string;\n url: string;\n}\n\nconst CUSTOM_KEY = `${PARAM_KEY}:custom`;\n\nexport function getCustomStylesheets(): CustomStylesheet[] {\n const data = localStorage.getItem(CUSTOM_KEY);\n if (!data) return [];\n\n try {\n const parsed = JSON.parse(data);\n\n // Validate structure\n if (!parsed || typeof parsed !== 'object') {\n throw new Error('Invalid format');\n }\n\n // Filter out invalid entries\n return Object.values(parsed).filter(\n (sheet: any): sheet is CustomStylesheet =>\n sheet &&\n typeof sheet.id === 'string' &&\n typeof sheet.name === 'string' &&\n typeof sheet.url === 'string'\n );\n } catch {\n // Clear corrupted data\n localStorage.removeItem(CUSTOM_KEY);\n return [];\n }\n}\n\nexport function saveCustomStylesheet(url: string, name?: string): CustomStylesheet {\n const id = `custom:${Date.now()}`;\n const autoName = name || extractNameFromUrl(url);\n const customSheets = getCustomStylesheets();\n\n // Ensure unique name\n const finalName = ensureUniqueName(autoName, customSheets);\n\n const newSheet: CustomStylesheet = { id, name: finalName, url };\n\n const allSheets = [...customSheets, newSheet];\n const mapping = allSheets.reduce((acc, sheet) => {\n acc[sheet.id] = sheet;\n return acc;\n }, {} as Record<string, CustomStylesheet>);\n\n localStorage.setItem(CUSTOM_KEY, JSON.stringify(mapping));\n return newSheet;\n}\n\nexport function deleteCustomStylesheet(id: string): void {\n const customSheets = getCustomStylesheets();\n const updated = customSheets.filter(sheet => sheet.id !== id);\n\n const mapping = updated.reduce((acc, sheet) => {\n acc[sheet.id] = sheet;\n return acc;\n }, {} as Record<string, CustomStylesheet>);\n\n localStorage.setItem(CUSTOM_KEY, JSON.stringify(mapping));\n\n // If deleted stylesheet was active, switch to default\n const active = localStorage.getItem(PARAM_KEY);\n if (active === id) {\n localStorage.setItem(PARAM_KEY, 'default');\n }\n}\n\nexport function isValidStylesheetUrl(url: string): boolean {\n // Must end with .css\n if (!url.endsWith('.css')) return false;\n\n try {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n new URL(url); // Throws if invalid\n return true;\n }\n // Allow relative paths like ./theme.css or /styles/theme.css\n return url.startsWith('./') || url.startsWith('/') || !url.includes('://');\n } catch {\n return false;\n }\n}\n\nexport function isDuplicateUrl(url: string, customStylesheets: CustomStylesheet[]): boolean {\n return customStylesheets.some(sheet => sheet.url === url);\n}\n\nfunction extractNameFromUrl(url: string): string {\n // Extract filename from URL\n const parts = url.split('/');\n const filename = parts[parts.length - 1];\n return filename || 'Custom Stylesheet';\n}\n\nfunction ensureUniqueName(name: string, existing: CustomStylesheet[]): string {\n const existingNames = existing.map(s => s.name);\n if (!existingNames.includes(name)) return name;\n\n let counter = 2;\n while (existingNames.includes(`${name} (${counter})`)) {\n counter++;\n }\n return `${name} (${counter})`;\n}\n","import React, { useState } from 'react';\nimport { Form, Button } from 'storybook/internal/components';\nimport { saveCustomStylesheet, isValidStylesheetUrl, getCustomStylesheets, isDuplicateUrl } from './customStylesheets';\n\ninterface CustomStylesheetInputProps {\n onAdd: (id: string) => void;\n}\n\nexport const CustomStylesheetInput: React.FC<CustomStylesheetInputProps> = ({ onAdd }) => {\n const [inputValue, setInputValue] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const handleAdd = () => {\n // Validate URL\n if (!inputValue.trim()) {\n setError('URL is required');\n return;\n }\n\n if (!isValidStylesheetUrl(inputValue)) {\n setError('Invalid stylesheet URL (must end with .css)');\n return;\n }\n\n // Check duplicates\n const existing = getCustomStylesheets();\n if (isDuplicateUrl(inputValue, existing)) {\n setError('This stylesheet already exists');\n return;\n }\n\n // Save and switch to new stylesheet\n const newSheet = saveCustomStylesheet(inputValue);\n setInputValue('');\n setError(null);\n onAdd(newSheet.id);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleAdd();\n }\n };\n\n return (\n <div style={{ padding: '8px', borderBottom: '1px solid #ddd' }}>\n <Form.Field label=\"Add Custom Stylesheet\">\n <div style={{ display: 'flex', gap: '4px' }}>\n <Form.Input\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder=\"https://example.com/theme.css\"\n style={{ flex: 1 }}\n />\n <Button onClick={handleAdd} size=\"small\">\n Add\n </Button>\n </div>\n {error && (\n <div style={{ color: 'red', fontSize: '12px', marginTop: '4px' }}>\n {error}\n </div>\n )}\n </Form.Field>\n </div>\n );\n};\n","import React from 'react';\nimport { styled } from 'storybook/theming';\nimport { TrashIcon } from '@storybook/icons';\nimport { CustomStylesheetInput } from './CustomStylesheetInput';\nimport { getCustomStylesheets, deleteCustomStylesheet } from './customStylesheets';\n\nconst List = styled.div({\n minWidth: 280,\n maxWidth: 400,\n overflow: 'hidden',\n overflowY: 'auto',\n maxHeight: '400px',\n});\n\nconst ListItem = styled.div<{ active?: boolean }>(({ theme, active }) => ({\n fontSize: theme.typography.size.s1,\n color: active ? theme.color.secondary : theme.color.dark,\n textDecoration: 'none',\n cursor: 'pointer',\n lineHeight: '18px',\n padding: '7px 10px',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n backgroundColor: active ? theme.background.hoverable : 'transparent',\n '&:hover': {\n background: theme.background.hoverable,\n },\n}));\n\nconst DeleteButton = styled.button(({ theme }) => ({\n background: 'none',\n border: 'none',\n cursor: 'pointer',\n padding: '2px 4px',\n display: 'flex',\n alignItems: 'center',\n opacity: 0.6,\n '&:hover': {\n opacity: 1,\n color: theme.color.negative,\n },\n}));\n\nconst Separator = styled.div(({ theme }) => ({\n height: '1px',\n background: theme.appBorderColor,\n margin: '8px 0',\n}));\n\nconst SectionLabel = styled.div(({ theme }) => ({\n fontSize: '11px',\n color: theme.color.mediumdark,\n padding: '4px 10px',\n fontWeight: 'bold',\n textTransform: 'uppercase',\n}));\n\nconst ItemTitle = styled.span({\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n maxWidth: '200px',\n});\n\ninterface CustomTooltipLinkListProps {\n configuredStylesheets: { [key: string]: string };\n activeStylesheet: string;\n onSelect: (id: string) => void;\n}\n\nexport const CustomTooltipLinkList: React.FC<CustomTooltipLinkListProps> = ({\n configuredStylesheets,\n activeStylesheet,\n onSelect,\n}) => {\n const customStylesheets = getCustomStylesheets();\n\n const handleDelete = (e: React.MouseEvent, id: string) => {\n e.stopPropagation();\n if (confirm('Delete this custom stylesheet?')) {\n deleteCustomStylesheet(id);\n window.location.reload();\n }\n };\n\n return (\n <List>\n <CustomStylesheetInput onAdd={onSelect} />\n\n {customStylesheets.length > 0 && (\n <>\n <SectionLabel>Custom Stylesheets</SectionLabel>\n {customStylesheets.map((sheet) => (\n <ListItem\n key={sheet.id}\n active={activeStylesheet === sheet.id}\n onClick={() => onSelect(sheet.id)}\n >\n <ItemTitle title={sheet.url}>{sheet.name}</ItemTitle>\n <DeleteButton onClick={(e) => handleDelete(e, sheet.id)} aria-label=\"Delete stylesheet\">\n <TrashIcon size={12} />\n </DeleteButton>\n </ListItem>\n ))}\n <Separator />\n </>\n )}\n\n <SectionLabel>Configured Stylesheets</SectionLabel>\n {Object.entries(configuredStylesheets).map(([name]) => (\n <ListItem\n key={name}\n active={activeStylesheet === name}\n onClick={() => onSelect(name)}\n >\n {name}\n </ListItem>\n ))}\n </List>\n );\n};\n","import React from \"react\";\nimport { useGlobals } from \"storybook/manager-api\";\nimport { IconButton, WithTooltip } from \"storybook/internal/components\";\nimport { PaintBrushIcon } from \"@storybook/icons\";\nimport { PARAM_KEY, TOOL_ID } from \"./constants\";\nimport { defaultStylesheets } from \"./defaults\";\nimport { CustomTooltipLinkList } from \"./CustomTooltipLinkList\";\n\n\nconst Tool = ({ stylesheets }: { [key: string]: string }) => {\n const [globals] = useGlobals();\n const isActive = [true, \"true\"].includes(globals[PARAM_KEY]);\n\n if (stylesheets === null) {\n return null;\n }\n\n // Merge default and configured stylesheets\n const configuredStylesheets: {[key: string]: string} = {\n ...defaultStylesheets,\n ...stylesheets,\n };\n\n const activeStylesheet = localStorage.getItem(PARAM_KEY) || \"default\";\n\n const handleSelect = (id: string) => {\n localStorage.setItem(PARAM_KEY, id);\n window.location.reload();\n };\n\n return (\n <WithTooltip\n placement=\"top\"\n trigger=\"click\"\n tooltip={\n <CustomTooltipLinkList\n configuredStylesheets={configuredStylesheets}\n activeStylesheet={activeStylesheet}\n onSelect={handleSelect}\n />\n }\n closeOnOutsideClick\n >\n <IconButton\n key={TOOL_ID}\n active={isActive}\n title=\"Toggle Stylesheet\"\n >\n <PaintBrushIcon />\n </IconButton>\n </WithTooltip>\n );\n};\n\nexport default Tool;\n","import React from \"react\";\nimport { addons, types, useParameter } from \"storybook/manager-api\";\nimport { ADDON_ID, PARAM_KEY } from \"./constants\";\nimport Tool from './Tool'\n\naddons.register(ADDON_ID, () => {\n addons.add(ADDON_ID, {\n title: 'Stylesheet Toggle',\n type: types.TOOL,\n render: () => {\n const stylesheets = useParameter(\n PARAM_KEY,\n null,\n );\n\n return <Tool stylesheets={stylesheets} />\n },\n });\n});\n"]}
|
package/dist/preset.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preset.ts"],"names":["viteFinal","config","webpack"],"mappings":"AAKO,IAAMA,CAAAA,CAAY,MAAOC,CAAAA,GAC9B,OAAA,CAAQ,IAAI,0CAA0C,CAAA,CAC/CA,CAAAA,CAAAA,CAGIC,CAAAA,CAAU,MAAOD,CAAAA,GAC5B,OAAA,CAAQ,GAAA,CAAI,6CAA6C,CAAA,CAClDA,CAAAA","file":"preset.js","sourcesContent":["// You can use presets to augment the Storybook configuration\n// You rarely want to do this in addons,\n// so often you want to delete this file and remove the reference to it in package.json#exports and package.json#bunder.nodeEntries\n// Read more about presets at https://storybook.js.org/docs/addons/writing-presets\n\nexport const viteFinal = async (config: any) => {\n console.log(\"This addon is augmenting the Vite config\");\n return config;\n};\n\nexport const webpack = async (config: any) => {\n console.log(\"This addon is augmenting the Webpack config\");\n return config;\n};"]}
|
package/dist/preview.d.ts
CHANGED
package/dist/preview.js
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
var e="stylesheetToggle";var c=(o,r)=>{let[s]=useGlobals(),n=s[e],{theme:t}=r.globals,i=r.parameters[e],l=localStorage.getItem(e);l||localStorage.setItem(e,"default");let y=i[l];return useEffect(()=>{p(y);},[n,t]),o()};function p(o){let r=document.querySelector("#stylesheetToggle"),s=document.querySelector("#storybook-root"),n=document.querySelector("body"),t=document.createElement("link");o&&(t.setAttribute("id","stylesheetToggle"),t.setAttribute("rel","stylesheet"),t.setAttribute("href",o),r?.remove(),n.insertBefore(t,s));}var f={decorators:[c],globals:{[e]:!1}},R=f;
|
|
4
|
-
|
|
5
|
-
export { R as default };
|
|
6
|
-
//# sourceMappingURL=out.js.map
|
|
1
|
+
import {useGlobals,useEffect}from'storybook/preview-api';var o="stylesheetToggle";var c=`${o}:custom`;function a(){let s=localStorage.getItem(c);if(!s)return [];try{let t=JSON.parse(s);if(!t||typeof t!="object")throw new Error("Invalid format");return Object.values(t).filter(e=>e&&typeof e.id=="string"&&typeof e.name=="string"&&typeof e.url=="string")}catch{return localStorage.removeItem(c),[]}}var u=(s,t)=>{let[e]=useGlobals(),i=e[o],{theme:r}=t.globals,m=t.parameters[o],l=localStorage.getItem(o)||"default",n;return l.startsWith("custom:")?n=a().find(f=>f.id===l)?.url:n=m?.[l],useEffect(()=>{n&&S(n);},[i,r,n]),s()};function S(s){let t=document.querySelector("#stylesheetToggle"),e=document.querySelector("#storybook-root"),i=document.querySelector("body"),r=document.createElement("link");s&&(r.setAttribute("id","stylesheetToggle"),r.setAttribute("rel","stylesheet"),r.setAttribute("href",s),t?.remove(),i.insertBefore(r,e));}var d={decorators:[u],initialGlobals:{[o]:false}},P=d;export{P as default};//# sourceMappingURL=preview.js.map
|
|
7
2
|
//# sourceMappingURL=preview.js.map
|
package/dist/preview.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/constants.ts","../src/withGlobals.ts","../src/preview.ts"],"names":["
|
|
1
|
+
{"version":3,"sources":["../src/constants.ts","../src/customStylesheets.ts","../src/withGlobals.ts","../src/preview.ts"],"names":["PARAM_KEY","CUSTOM_KEY","getCustomStylesheets","data","parsed","sheet","withGlobals","StoryFn","context","globals","useGlobals","stylesheetToggle","theme","configuredStylesheets","activeId","stylesheetUrl","s","useEffect","injectStylesheet","stylesheet","previousStylesheet","beforeElement","bodyElement","stylesheetElement","preview","preview_default"],"mappings":"yDAAO,IAEMA,CAAAA,CAAY,mBCMzB,IAAMC,CAAAA,CAAa,CAAA,EAAGD,CAAS,UAExB,SAASE,CAAAA,EAA2C,CACzD,IAAMC,CAAAA,CAAO,YAAA,CAAa,QAAQF,CAAU,CAAA,CAC5C,GAAI,CAACE,CAAAA,CAAM,OAAO,EAAC,CAEnB,GAAI,CACF,IAAMC,CAAAA,CAAS,IAAA,CAAK,MAAMD,CAAI,CAAA,CAG9B,GAAI,CAACC,CAAAA,EAAU,OAAOA,GAAW,QAAA,CAC/B,MAAM,IAAI,KAAA,CAAM,gBAAgB,CAAA,CAIlC,OAAO,MAAA,CAAO,MAAA,CAAOA,CAAM,CAAA,CAAE,MAAA,CAC1BC,CAAAA,EACCA,GACA,OAAOA,CAAAA,CAAM,EAAA,EAAO,QAAA,EACpB,OAAOA,CAAAA,CAAM,MAAS,QAAA,EACtB,OAAOA,CAAAA,CAAM,GAAA,EAAQ,QACzB,CACF,MAAQ,CAEN,OAAA,YAAA,CAAa,UAAA,CAAWJ,CAAU,CAAA,CAC3B,EACT,CACF,CC7BO,IAAMK,CAAAA,CAAc,CACzBC,CAAAA,CACAC,IACG,CACH,GAAM,CAACC,CAAO,CAAA,CAAIC,UAAAA,GACZC,CAAAA,CAAmBF,CAAAA,CAAQT,CAAS,CAAA,CAEpC,CAAE,KAAA,CAAAY,CAAM,CAAA,CAAIJ,CAAAA,CAAQ,OAAA,CACpBK,CAAAA,CAAwBL,CAAAA,CAAQ,UAAA,CAAWR,CAAS,CAAA,CAEpDc,CAAAA,CAAW,YAAA,CAAa,OAAA,CAAQd,CAAS,CAAA,EAAK,UAGhDe,CAAAA,CAEJ,OAAID,CAAAA,CAAS,UAAA,CAAW,SAAS,CAAA,CAI/BC,EAFqBb,CAAAA,EAAqB,CACT,IAAA,CAAKc,CAAAA,EAAKA,CAAAA,CAAE,EAAA,GAAOF,CAAQ,CAAA,EAC/B,GAAA,CAG7BC,CAAAA,CAAgBF,CAAAA,GAAwBC,CAAQ,CAAA,CAGlDG,UAAU,IAAM,CACVF,CAAAA,EACFG,CAAAA,CAAiBH,CAAa,EAElC,EAAG,CAACJ,CAAAA,CAAkBC,CAAAA,CAAOG,CAAa,CAAC,CAAA,CAEpCR,GACT,CAAA,CAEA,SAASW,CAAAA,CAAiBC,CAAAA,CAAoB,CAC5C,IAAMC,CAAAA,CAAqB,QAAA,CAAS,aAAA,CAAc,mBAAmB,CAAA,CAC/DC,CAAAA,CAAgB,SAAS,aAAA,CAAc,iBAAiB,CAAA,CACxDC,CAAAA,CAAc,QAAA,CAAS,aAAA,CAAc,MAAM,CAAA,CAC3CC,CAAAA,CAAoB,QAAA,CAAS,aAAA,CAAc,MAAM,CAAA,CAElDJ,IAILI,CAAAA,CAAkB,YAAA,CAAa,IAAA,CAAM,kBAAkB,CAAA,CACvDA,CAAAA,CAAkB,aAAa,KAAA,CAAO,YAAY,CAAA,CAClDA,CAAAA,CAAkB,YAAA,CAAa,MAAA,CAAQJ,CAAU,CAAA,CAEjDC,CAAAA,EAAoB,MAAA,EAAO,CAC3BE,CAAAA,CAAY,YAAA,CAAaC,EAAmBF,CAAa,CAAA,EAC3D,CCnDA,IAAMG,CAAAA,CAAwC,CAC5C,WAAY,CAAClB,CAAW,CAAA,CACxB,cAAA,CAAgB,CACd,CAACN,CAAS,EAAG,KACf,CACF,CAAA,CAEOyB,CAAAA,CAAQD","file":"preview.js","sourcesContent":["export const ADDON_ID = \"stylesheetToggle\";\nexport const TOOL_ID = `${ADDON_ID}/tool`;\nexport const PARAM_KEY = `stylesheetToggle`;\n\n","import { PARAM_KEY } from './constants';\n\nexport interface CustomStylesheet {\n id: string;\n name: string;\n url: string;\n}\n\nconst CUSTOM_KEY = `${PARAM_KEY}:custom`;\n\nexport function getCustomStylesheets(): CustomStylesheet[] {\n const data = localStorage.getItem(CUSTOM_KEY);\n if (!data) return [];\n\n try {\n const parsed = JSON.parse(data);\n\n // Validate structure\n if (!parsed || typeof parsed !== 'object') {\n throw new Error('Invalid format');\n }\n\n // Filter out invalid entries\n return Object.values(parsed).filter(\n (sheet: any): sheet is CustomStylesheet =>\n sheet &&\n typeof sheet.id === 'string' &&\n typeof sheet.name === 'string' &&\n typeof sheet.url === 'string'\n );\n } catch {\n // Clear corrupted data\n localStorage.removeItem(CUSTOM_KEY);\n return [];\n }\n}\n\nexport function saveCustomStylesheet(url: string, name?: string): CustomStylesheet {\n const id = `custom:${Date.now()}`;\n const autoName = name || extractNameFromUrl(url);\n const customSheets = getCustomStylesheets();\n\n // Ensure unique name\n const finalName = ensureUniqueName(autoName, customSheets);\n\n const newSheet: CustomStylesheet = { id, name: finalName, url };\n\n const allSheets = [...customSheets, newSheet];\n const mapping = allSheets.reduce((acc, sheet) => {\n acc[sheet.id] = sheet;\n return acc;\n }, {} as Record<string, CustomStylesheet>);\n\n localStorage.setItem(CUSTOM_KEY, JSON.stringify(mapping));\n return newSheet;\n}\n\nexport function deleteCustomStylesheet(id: string): void {\n const customSheets = getCustomStylesheets();\n const updated = customSheets.filter(sheet => sheet.id !== id);\n\n const mapping = updated.reduce((acc, sheet) => {\n acc[sheet.id] = sheet;\n return acc;\n }, {} as Record<string, CustomStylesheet>);\n\n localStorage.setItem(CUSTOM_KEY, JSON.stringify(mapping));\n\n // If deleted stylesheet was active, switch to default\n const active = localStorage.getItem(PARAM_KEY);\n if (active === id) {\n localStorage.setItem(PARAM_KEY, 'default');\n }\n}\n\nexport function isValidStylesheetUrl(url: string): boolean {\n // Must end with .css\n if (!url.endsWith('.css')) return false;\n\n try {\n if (url.startsWith('http://') || url.startsWith('https://')) {\n new URL(url); // Throws if invalid\n return true;\n }\n // Allow relative paths like ./theme.css or /styles/theme.css\n return url.startsWith('./') || url.startsWith('/') || !url.includes('://');\n } catch {\n return false;\n }\n}\n\nexport function isDuplicateUrl(url: string, customStylesheets: CustomStylesheet[]): boolean {\n return customStylesheets.some(sheet => sheet.url === url);\n}\n\nfunction extractNameFromUrl(url: string): string {\n // Extract filename from URL\n const parts = url.split('/');\n const filename = parts[parts.length - 1];\n return filename || 'Custom Stylesheet';\n}\n\nfunction ensureUniqueName(name: string, existing: CustomStylesheet[]): string {\n const existingNames = existing.map(s => s.name);\n if (!existingNames.includes(name)) return name;\n\n let counter = 2;\n while (existingNames.includes(`${name} (${counter})`)) {\n counter++;\n }\n return `${name} (${counter})`;\n}\n","import type { PartialStoryFn as StoryFunction, Renderer, StoryContext } from \"storybook/internal/types\";\nimport { useEffect, useGlobals } from \"storybook/preview-api\";\nimport { PARAM_KEY } from \"./constants\";\nimport { getCustomStylesheets } from \"./customStylesheets\";\n\n\nexport const withGlobals = (\n StoryFn: StoryFunction<Renderer>,\n context: StoryContext<Renderer>\n) => {\n const [globals] = useGlobals();\n const stylesheetToggle = globals[PARAM_KEY];\n\n const { theme } = context.globals;\n const configuredStylesheets = context.parameters[PARAM_KEY];\n\n const activeId = localStorage.getItem(PARAM_KEY) || \"default\";\n\n // Resolve URL: check if it's a custom stylesheet or configured stylesheet\n let stylesheetUrl: string | undefined;\n\n if (activeId.startsWith('custom:')) {\n // Look up in custom stylesheets\n const customSheets = getCustomStylesheets();\n const customSheet = customSheets.find(s => s.id === activeId);\n stylesheetUrl = customSheet?.url;\n } else {\n // Look up in configured stylesheets\n stylesheetUrl = configuredStylesheets?.[activeId];\n }\n\n useEffect(() => {\n if (stylesheetUrl) {\n injectStylesheet(stylesheetUrl);\n }\n }, [stylesheetToggle, theme, stylesheetUrl]);\n\n return StoryFn();\n};\n\nfunction injectStylesheet(stylesheet: string) {\n const previousStylesheet = document.querySelector(\"#stylesheetToggle\");\n const beforeElement = document.querySelector(\"#storybook-root\");\n const bodyElement = document.querySelector(\"body\");\n const stylesheetElement = document.createElement(\"link\");\n\n if (!stylesheet) {\n return;\n }\n\n stylesheetElement.setAttribute(\"id\", \"stylesheetToggle\");\n stylesheetElement.setAttribute(\"rel\", \"stylesheet\");\n stylesheetElement.setAttribute(\"href\", stylesheet);\n\n previousStylesheet?.remove();\n bodyElement.insertBefore(stylesheetElement, beforeElement);\n}\n","import type { Renderer, ProjectAnnotations } from \"storybook/internal/types\";\nimport { PARAM_KEY } from \"./constants\";\nimport { withGlobals } from \"./withGlobals\";\n\n\nconst preview: ProjectAnnotations<Renderer> = {\n decorators: [withGlobals],\n initialGlobals: {\n [PARAM_KEY]: false,\n },\n};\n\nexport default preview;\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stevendejong/storybook-stylesheet-toggle",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Toggle between stylesheets in Storybook. Configure stylesheets via parameters or add custom ones dynamically at runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"storybook-addons"
|
|
7
7
|
],
|
|
@@ -15,18 +15,19 @@
|
|
|
15
15
|
"exports": {
|
|
16
16
|
".": {
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
|
-
"
|
|
19
|
-
"import": "./dist/index.js"
|
|
18
|
+
"default": "./dist/index.js"
|
|
20
19
|
},
|
|
21
20
|
"./preview": {
|
|
22
|
-
"types": "./dist/
|
|
23
|
-
"
|
|
24
|
-
"require": "./dist/preview.js"
|
|
21
|
+
"types": "./dist/preview.d.ts",
|
|
22
|
+
"default": "./dist/preview.js"
|
|
25
23
|
},
|
|
26
|
-
"./preset": "./
|
|
24
|
+
"./preset": "./preset.js",
|
|
27
25
|
"./manager": "./dist/manager.js",
|
|
28
26
|
"./package.json": "./package.json"
|
|
29
27
|
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"storybook": "^9.0.0 || ^10.0.0"
|
|
30
|
+
},
|
|
30
31
|
"files": [
|
|
31
32
|
"dist/**/*",
|
|
32
33
|
"README.md",
|
|
@@ -35,24 +36,20 @@
|
|
|
35
36
|
],
|
|
36
37
|
"scripts": {
|
|
37
38
|
"clean": "rimraf ./dist",
|
|
38
|
-
"prebuild": "
|
|
39
|
+
"prebuild": "npm run clean",
|
|
39
40
|
"build": "tsup",
|
|
40
|
-
"build:watch": "
|
|
41
|
+
"build:watch": "npm run build -- --watch",
|
|
41
42
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
42
43
|
"start": "run-p build:watch 'storybook --quiet'",
|
|
43
44
|
"prerelease": "zx scripts/prepublish-checks.js",
|
|
44
|
-
"release": "
|
|
45
|
+
"release": "npm run build && auto shipit",
|
|
45
46
|
"storybook": "storybook dev -p 6006",
|
|
46
47
|
"build-storybook": "storybook build"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
49
|
-
"@storybook/
|
|
50
|
-
"@storybook/
|
|
51
|
-
"@
|
|
52
|
-
"@storybook/addon-mdx-gfm": "^8.0.0",
|
|
53
|
-
"@storybook/react": "^8.0.0",
|
|
54
|
-
"@storybook/react-vite": "^8.0.0",
|
|
55
|
-
"@types/node": "^20.11.26",
|
|
50
|
+
"@storybook/react": "^10.1.11",
|
|
51
|
+
"@storybook/react-vite": "^10.1.11",
|
|
52
|
+
"@types/node": "^22.10.2",
|
|
56
53
|
"@types/react": "^18.2.65",
|
|
57
54
|
"@vitejs/plugin-react": "^4.2.1",
|
|
58
55
|
"auto": "^11.1.1",
|
|
@@ -61,12 +58,12 @@
|
|
|
61
58
|
"npm-run-all": "^4.1.5",
|
|
62
59
|
"prettier": "^3.2.5",
|
|
63
60
|
"prompts": "^2.4.2",
|
|
64
|
-
"react": "^18.
|
|
65
|
-
"react-dom": "^18.
|
|
61
|
+
"react": "^18.3.1",
|
|
62
|
+
"react-dom": "^18.3.1",
|
|
66
63
|
"rimraf": "^5.0.5",
|
|
67
|
-
"storybook": "^
|
|
68
|
-
"tsup": "^8.
|
|
69
|
-
"typescript": "^5.
|
|
64
|
+
"storybook": "^10.1.11",
|
|
65
|
+
"tsup": "^8.3.5",
|
|
66
|
+
"typescript": "^5.7.2",
|
|
70
67
|
"vite": "^5.1.6",
|
|
71
68
|
"zx": "^7.2.3"
|
|
72
69
|
},
|
|
@@ -75,14 +72,12 @@
|
|
|
75
72
|
"registry": "https://registry.npmjs.org"
|
|
76
73
|
},
|
|
77
74
|
"bundler": {
|
|
78
|
-
"exportEntries": [
|
|
79
|
-
"src/index.ts"
|
|
80
|
-
],
|
|
81
75
|
"managerEntries": [
|
|
82
76
|
"src/manager.tsx"
|
|
83
77
|
],
|
|
84
78
|
"previewEntries": [
|
|
85
|
-
"src/preview.ts"
|
|
79
|
+
"src/preview.ts",
|
|
80
|
+
"src/index.ts"
|
|
86
81
|
],
|
|
87
82
|
"nodeEntries": [
|
|
88
83
|
"src/preset.ts"
|
package/preset.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./dist/preset.js";
|
package/dist/index.cjs
DELETED
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["src_default"],"mappings":"AACA,IAAOA,EAAQ,CAAC","sourcesContent":["// make it work with --isolatedModules\nexport default {};\n"]}
|
package/dist/index.d.cts
DELETED
package/dist/preset.cjs
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var e=async n=>(console.log("This addon is augmenting the Vite config"),n),o=async n=>(console.log("This addon is augmenting the Webpack config"),n);
|
|
4
|
-
|
|
5
|
-
exports.viteFinal = e;
|
|
6
|
-
exports.webpack = o;
|
|
7
|
-
//# sourceMappingURL=out.js.map
|
|
8
|
-
//# sourceMappingURL=preset.cjs.map
|
package/dist/preset.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/preset.ts"],"names":["viteFinal","config","webpack"],"mappings":"AAKO,IAAMA,EAAY,MAAOC,IAC9B,QAAQ,IAAI,0CAA0C,EAC/CA,GAGIC,EAAU,MAAOD,IAC5B,QAAQ,IAAI,6CAA6C,EAClDA","sourcesContent":["// You can use presets to augment the Storybook configuration\n// You rarely want to do this in addons,\n// so often you want to delete this file and remove the reference to it in package.json#exports and package.json#bunder.nodeEntries\n// Read more about presets at https://storybook.js.org/docs/addons/writing-presets\n\nexport const viteFinal = async (config: any) => {\n console.log(\"This addon is augmenting the Vite config\");\n return config;\n};\n\nexport const webpack = async (config: any) => {\n console.log(\"This addon is augmenting the Webpack config\");\n return config;\n};"]}
|