storybook-addon-dependency-previews 0.1.0

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.
Files changed (189) hide show
  1. package/README.md +192 -0
  2. package/dist/_virtual/_rolldown/runtime.cjs +29 -0
  3. package/dist/addon/index.cjs +12 -0
  4. package/dist/addon/index.cjs.map +1 -0
  5. package/dist/addon/index.d.cts +5 -0
  6. package/dist/addon/index.d.mts +5 -0
  7. package/dist/addon/index.mjs +11 -0
  8. package/dist/addon/index.mjs.map +1 -0
  9. package/dist/addon/preset.cjs +8 -0
  10. package/dist/addon/preset.cjs.map +1 -0
  11. package/dist/addon/preset.d.cts +5 -0
  12. package/dist/addon/preset.d.mts +5 -0
  13. package/dist/addon/preset.mjs +6 -0
  14. package/dist/addon/preset.mjs.map +1 -0
  15. package/dist/addon/vitePlugin.cjs +26 -0
  16. package/dist/addon/vitePlugin.cjs.map +1 -0
  17. package/dist/addon/vitePlugin.d.cts +9 -0
  18. package/dist/addon/vitePlugin.d.mts +9 -0
  19. package/dist/addon/vitePlugin.mjs +24 -0
  20. package/dist/addon/vitePlugin.mjs.map +1 -0
  21. package/dist/blocks/DependencyPreviews.cjs +102 -0
  22. package/dist/blocks/DependencyPreviews.cjs.map +1 -0
  23. package/dist/blocks/DependencyPreviews.d.cts +7 -0
  24. package/dist/blocks/DependencyPreviews.d.mts +7 -0
  25. package/dist/blocks/DependencyPreviews.mjs +100 -0
  26. package/dist/blocks/DependencyPreviews.mjs.map +1 -0
  27. package/dist/blocks/DependencyPreviews.module.css +14 -0
  28. package/dist/blocks/index.cjs +1 -0
  29. package/dist/blocks/index.d.cts +1 -0
  30. package/dist/blocks/index.d.mts +1 -0
  31. package/dist/blocks/index.mjs +1 -0
  32. package/dist/cli/sb-deps.cjs +321 -0
  33. package/dist/cli/sb-deps.cjs.map +1 -0
  34. package/dist/cli/sb-deps.d.cts +1 -0
  35. package/dist/cli/sb-deps.d.mts +1 -0
  36. package/dist/cli/sb-deps.mjs +319 -0
  37. package/dist/cli/sb-deps.mjs.map +1 -0
  38. package/dist/cli/scripts/depcruise.config.cjs +38 -0
  39. package/dist/cli/scripts/depcruise.config.cjs.map +1 -0
  40. package/dist/cli/scripts/depcruise.config.d.cts +6 -0
  41. package/dist/cli/scripts/depcruise.config.d.mts +7 -0
  42. package/dist/cli/scripts/depcruise.config.mjs +37 -0
  43. package/dist/cli/scripts/depcruise.config.mjs.map +1 -0
  44. package/dist/cli/scripts/postprocess.cjs +106 -0
  45. package/dist/cli/scripts/postprocess.cjs.map +1 -0
  46. package/dist/cli/scripts/postprocess.d.cts +1 -0
  47. package/dist/cli/scripts/postprocess.d.mts +1 -0
  48. package/dist/cli/scripts/postprocess.mjs +106 -0
  49. package/dist/cli/scripts/postprocess.mjs.map +1 -0
  50. package/dist/components/ComponentSourceLink.cjs +42 -0
  51. package/dist/components/ComponentSourceLink.cjs.map +1 -0
  52. package/dist/components/ComponentSourceLink.mjs +40 -0
  53. package/dist/components/ComponentSourceLink.mjs.map +1 -0
  54. package/dist/components/ComponentSourceLink.module.css +5 -0
  55. package/dist/components/CopyButton.cjs +112 -0
  56. package/dist/components/CopyButton.cjs.map +1 -0
  57. package/dist/components/CopyButton.mjs +110 -0
  58. package/dist/components/CopyButton.mjs.map +1 -0
  59. package/dist/components/CopyButton.module.css +76 -0
  60. package/dist/components/Expandable.cjs +51 -0
  61. package/dist/components/Expandable.cjs.map +1 -0
  62. package/dist/components/Expandable.mjs +49 -0
  63. package/dist/components/Expandable.mjs.map +1 -0
  64. package/dist/components/Expandable.module.css +110 -0
  65. package/dist/components/FullParityStory.cjs +47 -0
  66. package/dist/components/FullParityStory.cjs.map +1 -0
  67. package/dist/components/FullParityStory.mjs +44 -0
  68. package/dist/components/FullParityStory.mjs.map +1 -0
  69. package/dist/components/PathCopyMolecule.cjs +28 -0
  70. package/dist/components/PathCopyMolecule.cjs.map +1 -0
  71. package/dist/components/PathCopyMolecule.mjs +26 -0
  72. package/dist/components/PathCopyMolecule.mjs.map +1 -0
  73. package/dist/components/PathCopyMolecule.module.css +14 -0
  74. package/dist/components/PrimaryPreview.cjs +35 -0
  75. package/dist/components/PrimaryPreview.cjs.map +1 -0
  76. package/dist/components/PrimaryPreview.mjs +34 -0
  77. package/dist/components/PrimaryPreview.mjs.map +1 -0
  78. package/dist/components/SbHeading.cjs +18 -0
  79. package/dist/components/SbHeading.cjs.map +1 -0
  80. package/dist/components/SbHeading.mjs +16 -0
  81. package/dist/components/SbHeading.mjs.map +1 -0
  82. package/dist/components/SbHeading.module.css +23 -0
  83. package/dist/components/StoryLink.cjs +45 -0
  84. package/dist/components/StoryLink.cjs.map +1 -0
  85. package/dist/components/StoryLink.mjs +43 -0
  86. package/dist/components/StoryLink.mjs.map +1 -0
  87. package/dist/components/StoryLink.module.css +13 -0
  88. package/dist/components/TooltipTrigger.cjs +100 -0
  89. package/dist/components/TooltipTrigger.cjs.map +1 -0
  90. package/dist/components/TooltipTrigger.mjs +98 -0
  91. package/dist/components/TooltipTrigger.mjs.map +1 -0
  92. package/dist/components/TooltipTrigger.module.css +62 -0
  93. package/dist/components/icons/ArrowToRectangleIcon.cjs +19 -0
  94. package/dist/components/icons/ArrowToRectangleIcon.cjs.map +1 -0
  95. package/dist/components/icons/ArrowToRectangleIcon.mjs +18 -0
  96. package/dist/components/icons/ArrowToRectangleIcon.mjs.map +1 -0
  97. package/dist/components/icons/BuildIcon.cjs +19 -0
  98. package/dist/components/icons/BuildIcon.cjs.map +1 -0
  99. package/dist/components/icons/BuildIcon.mjs +18 -0
  100. package/dist/components/icons/BuildIcon.mjs.map +1 -0
  101. package/dist/components/icons/CopyIcon.cjs +19 -0
  102. package/dist/components/icons/CopyIcon.cjs.map +1 -0
  103. package/dist/components/icons/CopyIcon.mjs +18 -0
  104. package/dist/components/icons/CopyIcon.mjs.map +1 -0
  105. package/dist/components/icons/CubeIcon.cjs +19 -0
  106. package/dist/components/icons/CubeIcon.cjs.map +1 -0
  107. package/dist/components/icons/CubeIcon.mjs +18 -0
  108. package/dist/components/icons/CubeIcon.mjs.map +1 -0
  109. package/dist/components/icons/DoubleChevronDown.cjs +19 -0
  110. package/dist/components/icons/DoubleChevronDown.cjs.map +1 -0
  111. package/dist/components/icons/DoubleChevronDown.mjs +18 -0
  112. package/dist/components/icons/DoubleChevronDown.mjs.map +1 -0
  113. package/dist/components/icons/ExternalLinkIcon.cjs +19 -0
  114. package/dist/components/icons/ExternalLinkIcon.cjs.map +1 -0
  115. package/dist/components/icons/ExternalLinkIcon.mjs +18 -0
  116. package/dist/components/icons/ExternalLinkIcon.mjs.map +1 -0
  117. package/dist/components/icons/EyeOpen.cjs +23 -0
  118. package/dist/components/icons/EyeOpen.cjs.map +1 -0
  119. package/dist/components/icons/EyeOpen.mjs +22 -0
  120. package/dist/components/icons/EyeOpen.mjs.map +1 -0
  121. package/dist/components/icons/LoadingRipples.cjs +79 -0
  122. package/dist/components/icons/LoadingRipples.cjs.map +1 -0
  123. package/dist/components/icons/LoadingRipples.mjs +78 -0
  124. package/dist/components/icons/LoadingRipples.mjs.map +1 -0
  125. package/dist/components/icons/SquaresPlus.cjs +19 -0
  126. package/dist/components/icons/SquaresPlus.cjs.map +1 -0
  127. package/dist/components/icons/SquaresPlus.mjs +18 -0
  128. package/dist/components/icons/SquaresPlus.mjs.map +1 -0
  129. package/dist/components/icons/Svg.cjs +37 -0
  130. package/dist/components/icons/Svg.cjs.map +1 -0
  131. package/dist/components/icons/Svg.mjs +36 -0
  132. package/dist/components/icons/Svg.mjs.map +1 -0
  133. package/dist/components/icons/X.cjs +19 -0
  134. package/dist/components/icons/X.cjs.map +1 -0
  135. package/dist/components/icons/X.mjs +18 -0
  136. package/dist/components/icons/X.mjs.map +1 -0
  137. package/dist/constants.cjs +9 -0
  138. package/dist/constants.cjs.map +1 -0
  139. package/dist/constants.mjs +7 -0
  140. package/dist/constants.mjs.map +1 -0
  141. package/dist/hooks/useDependencyGraph.cjs +63 -0
  142. package/dist/hooks/useDependencyGraph.cjs.map +1 -0
  143. package/dist/hooks/useDependencyGraph.mjs +60 -0
  144. package/dist/hooks/useDependencyGraph.mjs.map +1 -0
  145. package/dist/hooks/useDynamicStory.cjs +83 -0
  146. package/dist/hooks/useDynamicStory.cjs.map +1 -0
  147. package/dist/hooks/useDynamicStory.mjs +82 -0
  148. package/dist/hooks/useDynamicStory.mjs.map +1 -0
  149. package/dist/hooks/useStoryParams.cjs +17 -0
  150. package/dist/hooks/useStoryParams.cjs.map +1 -0
  151. package/dist/hooks/useStoryParams.mjs +16 -0
  152. package/dist/hooks/useStoryParams.mjs.map +1 -0
  153. package/dist/index.cjs +15 -0
  154. package/dist/index.d.cts +6 -0
  155. package/dist/index.d.mts +6 -0
  156. package/dist/index.mjs +9 -0
  157. package/dist/manager.cjs +22 -0
  158. package/dist/manager.cjs.map +1 -0
  159. package/dist/manager.d.cts +1 -0
  160. package/dist/manager.d.mts +1 -0
  161. package/dist/manager.mjs +21 -0
  162. package/dist/manager.mjs.map +1 -0
  163. package/dist/panels/DefaultAutoDocsLayout.cjs +24 -0
  164. package/dist/panels/DefaultAutoDocsLayout.cjs.map +1 -0
  165. package/dist/panels/DefaultAutoDocsLayout.d.cts +10 -0
  166. package/dist/panels/DefaultAutoDocsLayout.d.mts +10 -0
  167. package/dist/panels/DefaultAutoDocsLayout.mjs +22 -0
  168. package/dist/panels/DefaultAutoDocsLayout.mjs.map +1 -0
  169. package/dist/panels/GraphView.cjs +78 -0
  170. package/dist/panels/GraphView.cjs.map +1 -0
  171. package/dist/panels/GraphView.d.cts +7 -0
  172. package/dist/panels/GraphView.d.mts +7 -0
  173. package/dist/panels/GraphView.mjs +77 -0
  174. package/dist/panels/GraphView.mjs.map +1 -0
  175. package/dist/panels/index.cjs +3 -0
  176. package/dist/panels/index.d.cts +3 -0
  177. package/dist/panels/index.d.mts +3 -0
  178. package/dist/panels/index.mjs +3 -0
  179. package/dist/preview.cjs +23 -0
  180. package/dist/preview.cjs.map +1 -0
  181. package/dist/preview.d.cts +9 -0
  182. package/dist/preview.d.mts +10 -0
  183. package/dist/preview.mjs +21 -0
  184. package/dist/preview.mjs.map +1 -0
  185. package/dist/types.cjs +0 -0
  186. package/dist/types.d.cts +104 -0
  187. package/dist/types.d.mts +104 -0
  188. package/dist/types.mjs +0 -0
  189. package/package.json +94 -0
@@ -0,0 +1,106 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { basename, dirname, extname, posix, resolve } from "node:path";
3
+ import { toId } from "@storybook/csf";
4
+
5
+ //#region cli/scripts/postprocess.ts
6
+ const [, , inPathArg, outPathArg] = process.argv;
7
+ const inPath = resolve(inPathArg || ".storybook/dependency-previews.raw.json");
8
+ const outPath = resolve(outPathArg || ".storybook/dependency-previews.json");
9
+ if (!existsSync(dirname(outPath))) mkdirSync(dirname(outPath), { recursive: true });
10
+ const raw = JSON.parse(readFileSync(inPath, "utf8"));
11
+ const norm = (p) => posix.normalize(p.replaceAll("\\", "/"));
12
+ const isComponent = (p) => {
13
+ return /src\/(components|ui|lib)\//.test(p) && !/\.(css|scss|sass|less)$/.test(p);
14
+ };
15
+ /** Simple keyed-push to avoid duplicates */
16
+ function pushUnique(list, item) {
17
+ if (!list.some((x) => x.componentPath === item.componentPath)) {
18
+ list.push(item);
19
+ }
20
+ }
21
+ const graph = {};
22
+ for (const m of raw.modules || []) {
23
+ const from = norm(m.source);
24
+ if (!isComponent(from)) continue;
25
+ const deps = (m.dependencies || []).map((d) => d.resolved && norm(d.resolved)).filter(Boolean).filter(isComponent);
26
+ const topLevelFromStory = getStoryId(from);
27
+ if (!graph[from]) graph[from] = {
28
+ componentPath: from,
29
+ ...topLevelFromStory && {
30
+ storyId: topLevelFromStory?.id,
31
+ storyTitle: topLevelFromStory?.title,
32
+ storyTitlePath: topLevelFromStory?.titlePath,
33
+ storyFilePath: topLevelFromStory?.filePath
34
+ },
35
+ builtWith: [],
36
+ usedIn: []
37
+ };
38
+ for (const to of deps) {
39
+ if (from === to) continue;
40
+ const toStory = getStoryId(to);
41
+ const builtWithEntry = {
42
+ componentPath: to,
43
+ ...toStory && {
44
+ storyId: toStory.id,
45
+ storyTitle: toStory.title,
46
+ storyTitlePath: toStory.titlePath,
47
+ storyFilePath: toStory.filePath
48
+ }
49
+ };
50
+ if (!graph[to]) graph[to] = {
51
+ ...builtWithEntry,
52
+ builtWith: [],
53
+ usedIn: []
54
+ };
55
+ pushUnique(graph[from].builtWith, builtWithEntry);
56
+ const fromStory = getStoryId(from);
57
+ const usedInEntry = {
58
+ componentPath: from,
59
+ ...fromStory && {
60
+ storyId: fromStory.id,
61
+ storyTitle: fromStory.title,
62
+ storyTitlePath: fromStory.titlePath,
63
+ storyFilePath: fromStory.filePath
64
+ }
65
+ };
66
+ pushUnique(graph[to].usedIn, usedInEntry);
67
+ }
68
+ }
69
+ const byPath = (a, b) => (a.componentPath || "").localeCompare(b.componentPath || "");
70
+ for (const k of Object.keys(graph)) {
71
+ graph[k].builtWith.sort(byPath);
72
+ graph[k].usedIn.sort(byPath);
73
+ }
74
+ writeFileSync(outPath, JSON.stringify(graph, null, 2));
75
+ function getStoryId(componentPath) {
76
+ const rawFileData = getRawStoryFileData(componentPath);
77
+ if (!rawFileData.storyFileData) return null;
78
+ const match = rawFileData.storyFileData.match(/title:\s*['"`]([^'"`]+)['"`]/);
79
+ if (!match) return null;
80
+ const titlePath = match[1];
81
+ const title = basename(titlePath).trim();
82
+ return {
83
+ title,
84
+ titlePath,
85
+ id: toId(titlePath, "docs"),
86
+ filePath: rawFileData.storyFilePath
87
+ };
88
+ }
89
+ function getRawStoryFileData(componentPath) {
90
+ const base = componentPath.replace(/\.\w+$/, "");
91
+ const storiesPath = `${base}.stories${extname(componentPath)}`;
92
+ const storyPath = `${base}.story${extname(componentPath)}`;
93
+ const storiesData = getRawFileData(storiesPath);
94
+ const storyData = getRawFileData(storyPath);
95
+ return {
96
+ storyFileData: storiesData || storyData || null,
97
+ storyFilePath: storiesData && storiesPath || storyData && storyPath || null
98
+ };
99
+ }
100
+ function getRawFileData(path) {
101
+ return existsSync(path) && readFileSync(path, "utf8");
102
+ }
103
+
104
+ //#endregion
105
+ export { };
106
+ //# sourceMappingURL=postprocess.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postprocess.mjs","names":[],"sources":["../../../cli/scripts/postprocess.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, posix, dirname, extname, basename } from 'node:path'\nimport { toId } from '@storybook/csf'\nimport type { Deps, Graph, StoryInfo } from '../../src/types.js'\n\nconst [, , inPathArg, outPathArg] = process.argv\nconst inPath = resolve(inPathArg || '.storybook/dependency-previews.raw.json')\nconst outPath = resolve(outPathArg || '.storybook/dependency-previews.json')\n\nif (!existsSync(dirname(outPath)))\n\tmkdirSync(dirname(outPath), { recursive: true })\n\nconst raw = JSON.parse(readFileSync(inPath, 'utf8'))\nconst norm = (p: string) => posix.normalize(p.replaceAll('\\\\', '/'))\nconst isComponent = (p: string) => {\n\treturn (\n\t\t/src\\/(components|ui|lib)\\//.test(p) &&\n\t\t// Ignore css files\n\t\t!/\\.(css|scss|sass|less)$/.test(p)\n\t)\n}\n\n/** Simple keyed-push to avoid duplicates */\nfunction pushUnique(list: Array<StoryInfo>, item: StoryInfo) {\n\tif (!list.some((x) => x.componentPath === item.componentPath)) {\n\t\tlist.push(item)\n\t}\n}\n\nconst graph: Graph = {}\n\nfor (const m of raw.modules || []) {\n\tconst from = norm(m.source)\n\tif (!isComponent(from)) continue\n\n\tconst deps = (m.dependencies || [])\n\t\t.map((d: any) => d.resolved && norm(d.resolved))\n\t\t.filter(Boolean)\n\t\t.filter(isComponent)\n\n\tconst topLevelFromStory = getStoryId(from)\n\n\tif (!graph[from])\n\t\tgraph[from] = {\n\t\t\tcomponentPath: from,\n\t\t\t...(topLevelFromStory && {\n\t\t\t\tstoryId: topLevelFromStory?.id,\n\t\t\t\tstoryTitle: topLevelFromStory?.title,\n\t\t\t\tstoryTitlePath: topLevelFromStory?.titlePath,\n\t\t\t\tstoryFilePath: topLevelFromStory?.filePath,\n\t\t\t}),\n\t\t\tbuiltWith: [],\n\t\t\tusedIn: [],\n\t\t} satisfies Deps\n\n\tfor (const to of deps) {\n\t\tif (from === to) continue // 🔒 skip self-edges\n\n\t\t// ---- builtWith: from → to\n\t\tconst toStory = getStoryId(to)\n\t\tconst builtWithEntry: StoryInfo = {\n\t\t\tcomponentPath: to, // ✅ the dependency, not \"from\"\n\t\t\t...(toStory && {\n\t\t\t\tstoryId: toStory.id,\n\t\t\t\tstoryTitle: toStory.title,\n\t\t\t\tstoryTitlePath: toStory.titlePath,\n\t\t\t\tstoryFilePath: toStory.filePath,\n\t\t\t}),\n\t\t}\n\n\t\tif (!graph[to])\n\t\t\tgraph[to] = {\n\t\t\t\t...builtWithEntry,\n\t\t\t\tbuiltWith: [],\n\t\t\t\tusedIn: [],\n\t\t\t}\n\n\t\tpushUnique(graph[from].builtWith, builtWithEntry)\n\n\t\t// ---- usedIn: to ← from\n\t\tconst fromStory = getStoryId(from)\n\t\tconst usedInEntry: StoryInfo = {\n\t\t\tcomponentPath: from,\n\t\t\t...(fromStory && {\n\t\t\t\tstoryId: fromStory.id,\n\t\t\t\tstoryTitle: fromStory.title,\n\t\t\t\tstoryTitlePath: fromStory.titlePath,\n\t\t\t\tstoryFilePath: fromStory.filePath,\n\t\t\t}),\n\t\t}\n\t\tpushUnique(graph[to].usedIn, usedInEntry)\n\t}\n}\n\n// Stable sort by path for nicer diffs/UI\nconst byPath = (a: StoryInfo, b: StoryInfo) =>\n\t(a.componentPath || '').localeCompare(b.componentPath || '')\n\nfor (const k of Object.keys(graph)) {\n\tgraph[k].builtWith.sort(byPath)\n\tgraph[k].usedIn.sort(byPath)\n}\n\nwriteFileSync(outPath, JSON.stringify(graph, null, 2))\n\nfunction getStoryId(componentPath: string) {\n\tconst rawFileData = getRawStoryFileData(componentPath)\n\tif (!rawFileData.storyFileData) return null\n\n\tconst match = rawFileData.storyFileData.match(\n\t\t/title:\\s*['\"`]([^'\"`]+)['\"`]/,\n\t)\n\tif (!match) return null\n\n\tconst titlePath = match[1]\n\tconst title = basename(titlePath).trim()\n\treturn {\n\t\ttitle: title,\n\t\ttitlePath: titlePath,\n\t\tid: toId(titlePath, 'docs'),\n\t\tfilePath: rawFileData.storyFilePath,\n\t}\n}\n\nfunction getRawStoryFileData(componentPath: string) {\n\tconst base = componentPath.replace(/\\.\\w+$/, '')\n\tconst storiesPath = `${base}.stories${extname(componentPath)}`\n\tconst storyPath = `${base}.story${extname(componentPath)}`\n\n\tconst storiesData = getRawFileData(storiesPath)\n\tconst storyData = getRawFileData(storyPath)\n\n\treturn {\n\t\tstoryFileData: storiesData || storyData || null,\n\t\tstoryFilePath:\n\t\t\t(storiesData && storiesPath) || (storyData && storyPath) || null,\n\t}\n}\n\nfunction getRawFileData(path: string) {\n\treturn existsSync(path) && readFileSync(path, 'utf8')\n}\n"],"mappings":";;;;;AAKA,MAAM,KAAK,WAAW,cAAc,QAAQ;AAC5C,MAAM,SAAS,QAAQ,aAAa,0CAA0C;AAC9E,MAAM,UAAU,QAAQ,cAAc,sCAAsC;AAE5E,IAAI,CAAC,WAAW,QAAQ,QAAQ,CAAC,CAChC,WAAU,QAAQ,QAAQ,EAAE,EAAE,WAAW,MAAM,CAAC;AAEjD,MAAM,MAAM,KAAK,MAAM,aAAa,QAAQ,OAAO,CAAC;AACpD,MAAM,QAAQ,MAAc,MAAM,UAAU,EAAE,WAAW,MAAM,IAAI,CAAC;AACpE,MAAM,eAAe,MAAc;AAClC,QACC,6BAA6B,KAAK,EAAE,IAEpC,CAAC,0BAA0B,KAAK,EAAE;;;AAKpC,SAAS,WAAW,MAAwB,MAAiB;AAC5D,KAAI,CAAC,KAAK,MAAM,MAAM,EAAE,kBAAkB,KAAK,cAAc,EAAE;AAC9D,OAAK,KAAK,KAAK;;;AAIjB,MAAM,QAAe,EAAE;AAEvB,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,EAAE;CAClC,MAAM,OAAO,KAAK,EAAE,OAAO;AAC3B,KAAI,CAAC,YAAY,KAAK,CAAE;CAExB,MAAM,QAAQ,EAAE,gBAAgB,EAAE,EAChC,KAAK,MAAW,EAAE,YAAY,KAAK,EAAE,SAAS,CAAC,CAC/C,OAAO,QAAQ,CACf,OAAO,YAAY;CAErB,MAAM,oBAAoB,WAAW,KAAK;AAE1C,KAAI,CAAC,MAAM,MACV,OAAM,QAAQ;EACb,eAAe;EACf,GAAI,qBAAqB;GACxB,SAAS,mBAAmB;GAC5B,YAAY,mBAAmB;GAC/B,gBAAgB,mBAAmB;GACnC,eAAe,mBAAmB;GAClC;EACD,WAAW,EAAE;EACb,QAAQ,EAAE;EACV;AAEF,MAAK,MAAM,MAAM,MAAM;AACtB,MAAI,SAAS,GAAI;EAGjB,MAAM,UAAU,WAAW,GAAG;EAC9B,MAAM,iBAA4B;GACjC,eAAe;GACf,GAAI,WAAW;IACd,SAAS,QAAQ;IACjB,YAAY,QAAQ;IACpB,gBAAgB,QAAQ;IACxB,eAAe,QAAQ;IACvB;GACD;AAED,MAAI,CAAC,MAAM,IACV,OAAM,MAAM;GACX,GAAG;GACH,WAAW,EAAE;GACb,QAAQ,EAAE;GACV;AAEF,aAAW,MAAM,MAAM,WAAW,eAAe;EAGjD,MAAM,YAAY,WAAW,KAAK;EAClC,MAAM,cAAyB;GAC9B,eAAe;GACf,GAAI,aAAa;IAChB,SAAS,UAAU;IACnB,YAAY,UAAU;IACtB,gBAAgB,UAAU;IAC1B,eAAe,UAAU;IACzB;GACD;AACD,aAAW,MAAM,IAAI,QAAQ,YAAY;;;AAK3C,MAAM,UAAU,GAAc,OAC5B,EAAE,iBAAiB,IAAI,cAAc,EAAE,iBAAiB,GAAG;AAE7D,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM,EAAE;AACnC,OAAM,GAAG,UAAU,KAAK,OAAO;AAC/B,OAAM,GAAG,OAAO,KAAK,OAAO;;AAG7B,cAAc,SAAS,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC;AAEtD,SAAS,WAAW,eAAuB;CAC1C,MAAM,cAAc,oBAAoB,cAAc;AACtD,KAAI,CAAC,YAAY,cAAe,QAAO;CAEvC,MAAM,QAAQ,YAAY,cAAc,MACvC,+BACA;AACD,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,YAAY,MAAM;CACxB,MAAM,QAAQ,SAAS,UAAU,CAAC,MAAM;AACxC,QAAO;EACC;EACI;EACX,IAAI,KAAK,WAAW,OAAO;EAC3B,UAAU,YAAY;EACtB;;AAGF,SAAS,oBAAoB,eAAuB;CACnD,MAAM,OAAO,cAAc,QAAQ,UAAU,GAAG;CAChD,MAAM,cAAc,GAAG,KAAK,UAAU,QAAQ,cAAc;CAC5D,MAAM,YAAY,GAAG,KAAK,QAAQ,QAAQ,cAAc;CAExD,MAAM,cAAc,eAAe,YAAY;CAC/C,MAAM,YAAY,eAAe,UAAU;AAE3C,QAAO;EACN,eAAe,eAAe,aAAa;EAC3C,eACE,eAAe,eAAiB,aAAa,aAAc;EAC7D;;AAGF,SAAS,eAAe,MAAc;AACrC,QAAO,WAAW,KAAK,IAAI,aAAa,MAAM,OAAO"}
@@ -0,0 +1,42 @@
1
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_useStoryParams = require('../hooks/useStoryParams.cjs');
3
+ const require_TooltipTrigger = require('./TooltipTrigger.cjs');
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let src_components_ComponentSourceLink_module_css = require("./ComponentSourceLink.module.css");
6
+ src_components_ComponentSourceLink_module_css = require_runtime.__toESM(src_components_ComponentSourceLink_module_css);
7
+
8
+ //#region src/components/ComponentSourceLink.tsx
9
+ function ComponentSourceLink({ storyInfo, ariaDescribedBy }) {
10
+ const storyParameters = require_useStoryParams.useStoryParams();
11
+ const isLocalHost = window?.location.hostname === "localhost" || window?.location.hostname === "127.0.0.1";
12
+ const vsCodeRootUrl = getVsCodeRootUrl(storyParameters.dependencyPreviews.projectRootPath);
13
+ const gitRootUrl = storyParameters.dependencyPreviews.sourceRootUrl;
14
+ const sourceRootUrl = isLocalHost ? vsCodeRootUrl : gitRootUrl;
15
+ const relativePath = storyInfo.componentPath.replace(/\\/g, "/").replace(/^\.?\/+/, "");
16
+ if (!sourceRootUrl) {
17
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: relativePath });
18
+ }
19
+ const href = (sourceRootUrl.endsWith("/") ? sourceRootUrl : sourceRootUrl + "/") + encodeURI(relativePath);
20
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_TooltipTrigger.TooltipTrigger, {
21
+ TriggerElem: "a",
22
+ className: src_components_ComponentSourceLink_module_css.default.ComponentSourceLink,
23
+ href,
24
+ newWindow: true,
25
+ "aria-describedby": ariaDescribedBy,
26
+ tooltipText: `View source (opens in ${isLocalHost ? "VS Code" : "new tab"})`,
27
+ dangerouslySetInnerHTML: { __html: relativePath.replaceAll("/", "/<wbr/>") }
28
+ });
29
+ }
30
+ function getVsCodeRootUrl(projectRootPath) {
31
+ let newProjectRootPath = projectRootPath;
32
+ newProjectRootPath = projectRootPath.replace(/^\/@fs\//, "/");
33
+ if (newProjectRootPath.match(/^\/[A-Za-z]:\//)) {
34
+ newProjectRootPath = newProjectRootPath.slice(1);
35
+ }
36
+ newProjectRootPath = newProjectRootPath.replace(/\\/g, "/");
37
+ return `vscode://file/${newProjectRootPath}`;
38
+ }
39
+
40
+ //#endregion
41
+ exports.ComponentSourceLink = ComponentSourceLink;
42
+ //# sourceMappingURL=ComponentSourceLink.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComponentSourceLink.cjs","names":["useStoryParams","TooltipTrigger","s"],"sources":["../../src/components/ComponentSourceLink.tsx"],"sourcesContent":["import type { StoryInfo } from '../types'\n\nimport s from './ComponentSourceLink.module.css'\nimport { TooltipTrigger } from './TooltipTrigger'\nimport { useStoryParams } from '../hooks/useStoryParams'\n\ninterface Props {\n\tstoryInfo: StoryInfo\n\tariaDescribedBy?: string\n}\nexport function ComponentSourceLink({ storyInfo, ariaDescribedBy }: Props) {\n\tconst storyParameters = useStoryParams()\n\n\tconst isLocalHost =\n\t\twindow?.location.hostname === 'localhost' ||\n\t\twindow?.location.hostname === '127.0.0.1'\n\n\tconst vsCodeRootUrl = getVsCodeRootUrl(\n\t\tstoryParameters.dependencyPreviews.projectRootPath,\n\t)\n\tconst gitRootUrl = storyParameters.dependencyPreviews.sourceRootUrl\n\n\tconst sourceRootUrl = isLocalHost ? vsCodeRootUrl : gitRootUrl\n\n\t// Normalize: force POSIX slashes and strip leading ./ or /\n\tconst relativePath = storyInfo.componentPath\n\t\t.replace(/\\\\/g, '/')\n\t\t.replace(/^\\.?\\/+/, '')\n\n\tif (!sourceRootUrl) {\n\t\treturn <span>{relativePath}</span>\n\t}\n\tconst href =\n\t\t(sourceRootUrl.endsWith('/') ? sourceRootUrl : sourceRootUrl + '/') +\n\t\tencodeURI(relativePath)\n\n\treturn (\n\t\t<TooltipTrigger\n\t\t\tTriggerElem=\"a\"\n\t\t\tclassName={s.ComponentSourceLink}\n\t\t\thref={href}\n\t\t\tnewWindow\n\t\t\taria-describedby={ariaDescribedBy}\n\t\t\ttooltipText={`View source (opens in ${\n\t\t\t\tisLocalHost ? 'VS Code' : 'new tab'\n\t\t\t})`}\n\t\t\tdangerouslySetInnerHTML={{\n\t\t\t\t// allow line breaks on slashes\n\t\t\t\t__html: relativePath.replaceAll('/', '/<wbr/>'),\n\t\t\t}}\n\t\t/>\n\t)\n}\n\nfunction getVsCodeRootUrl(projectRootPath: string) {\n\tlet newProjectRootPath = projectRootPath\n\n\t// Remove Vite's /@fs/ prefix if present\n\tnewProjectRootPath = projectRootPath.replace(/^\\/@fs\\//, '/')\n\n\t// Fix Windows path: remove leading slash from /C:/Users/... -> C:/Users/...\n\tif (newProjectRootPath.match(/^\\/[A-Za-z]:\\//)) {\n\t\tnewProjectRootPath = newProjectRootPath.slice(1)\n\t}\n\n\t// Ensure forward slashes for VS Code (works on all platforms)\n\tnewProjectRootPath = newProjectRootPath.replace(/\\\\/g, '/')\n\n\treturn `vscode://file/${newProjectRootPath}`\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,oBAAoB,EAAE,WAAW,mBAA0B;CAC1E,MAAM,kBAAkBA,uCAAgB;CAExC,MAAM,cACL,QAAQ,SAAS,aAAa,eAC9B,QAAQ,SAAS,aAAa;CAE/B,MAAM,gBAAgB,iBACrB,gBAAgB,mBAAmB,gBACnC;CACD,MAAM,aAAa,gBAAgB,mBAAmB;CAEtD,MAAM,gBAAgB,cAAc,gBAAgB;CAGpD,MAAM,eAAe,UAAU,cAC7B,QAAQ,OAAO,IAAI,CACnB,QAAQ,WAAW,GAAG;AAExB,KAAI,CAAC,eAAe;AACnB,SAAO,2CAAC,oBAAM,eAAoB;;CAEnC,MAAM,QACJ,cAAc,SAAS,IAAI,GAAG,gBAAgB,gBAAgB,OAC/D,UAAU,aAAa;AAExB,QACC,2CAACC;EACA,aAAY;EACZ,WAAWC,sDAAE;EACP;EACN;EACA,oBAAkB;EAClB,aAAa,yBACZ,cAAc,YAAY,UAC1B;EACD,yBAAyB,EAExB,QAAQ,aAAa,WAAW,KAAK,UAAU,EAC/C;GACA;;AAIJ,SAAS,iBAAiB,iBAAyB;CAClD,IAAI,qBAAqB;AAGzB,sBAAqB,gBAAgB,QAAQ,YAAY,IAAI;AAG7D,KAAI,mBAAmB,MAAM,iBAAiB,EAAE;AAC/C,uBAAqB,mBAAmB,MAAM,EAAE;;AAIjD,sBAAqB,mBAAmB,QAAQ,OAAO,IAAI;AAE3D,QAAO,iBAAiB"}
@@ -0,0 +1,40 @@
1
+ import { useStoryParams } from "../hooks/useStoryParams.mjs";
2
+ import { TooltipTrigger } from "./TooltipTrigger.mjs";
3
+ import { jsx } from "react/jsx-runtime";
4
+ import s from "./ComponentSourceLink.module.css";
5
+
6
+ //#region src/components/ComponentSourceLink.tsx
7
+ function ComponentSourceLink({ storyInfo, ariaDescribedBy }) {
8
+ const storyParameters = useStoryParams();
9
+ const isLocalHost = window?.location.hostname === "localhost" || window?.location.hostname === "127.0.0.1";
10
+ const vsCodeRootUrl = getVsCodeRootUrl(storyParameters.dependencyPreviews.projectRootPath);
11
+ const gitRootUrl = storyParameters.dependencyPreviews.sourceRootUrl;
12
+ const sourceRootUrl = isLocalHost ? vsCodeRootUrl : gitRootUrl;
13
+ const relativePath = storyInfo.componentPath.replace(/\\/g, "/").replace(/^\.?\/+/, "");
14
+ if (!sourceRootUrl) {
15
+ return /* @__PURE__ */ jsx("span", { children: relativePath });
16
+ }
17
+ const href = (sourceRootUrl.endsWith("/") ? sourceRootUrl : sourceRootUrl + "/") + encodeURI(relativePath);
18
+ return /* @__PURE__ */ jsx(TooltipTrigger, {
19
+ TriggerElem: "a",
20
+ className: s.ComponentSourceLink,
21
+ href,
22
+ newWindow: true,
23
+ "aria-describedby": ariaDescribedBy,
24
+ tooltipText: `View source (opens in ${isLocalHost ? "VS Code" : "new tab"})`,
25
+ dangerouslySetInnerHTML: { __html: relativePath.replaceAll("/", "/<wbr/>") }
26
+ });
27
+ }
28
+ function getVsCodeRootUrl(projectRootPath) {
29
+ let newProjectRootPath = projectRootPath;
30
+ newProjectRootPath = projectRootPath.replace(/^\/@fs\//, "/");
31
+ if (newProjectRootPath.match(/^\/[A-Za-z]:\//)) {
32
+ newProjectRootPath = newProjectRootPath.slice(1);
33
+ }
34
+ newProjectRootPath = newProjectRootPath.replace(/\\/g, "/");
35
+ return `vscode://file/${newProjectRootPath}`;
36
+ }
37
+
38
+ //#endregion
39
+ export { ComponentSourceLink };
40
+ //# sourceMappingURL=ComponentSourceLink.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComponentSourceLink.mjs","names":[],"sources":["../../src/components/ComponentSourceLink.tsx"],"sourcesContent":["import type { StoryInfo } from '../types'\n\nimport s from './ComponentSourceLink.module.css'\nimport { TooltipTrigger } from './TooltipTrigger'\nimport { useStoryParams } from '../hooks/useStoryParams'\n\ninterface Props {\n\tstoryInfo: StoryInfo\n\tariaDescribedBy?: string\n}\nexport function ComponentSourceLink({ storyInfo, ariaDescribedBy }: Props) {\n\tconst storyParameters = useStoryParams()\n\n\tconst isLocalHost =\n\t\twindow?.location.hostname === 'localhost' ||\n\t\twindow?.location.hostname === '127.0.0.1'\n\n\tconst vsCodeRootUrl = getVsCodeRootUrl(\n\t\tstoryParameters.dependencyPreviews.projectRootPath,\n\t)\n\tconst gitRootUrl = storyParameters.dependencyPreviews.sourceRootUrl\n\n\tconst sourceRootUrl = isLocalHost ? vsCodeRootUrl : gitRootUrl\n\n\t// Normalize: force POSIX slashes and strip leading ./ or /\n\tconst relativePath = storyInfo.componentPath\n\t\t.replace(/\\\\/g, '/')\n\t\t.replace(/^\\.?\\/+/, '')\n\n\tif (!sourceRootUrl) {\n\t\treturn <span>{relativePath}</span>\n\t}\n\tconst href =\n\t\t(sourceRootUrl.endsWith('/') ? sourceRootUrl : sourceRootUrl + '/') +\n\t\tencodeURI(relativePath)\n\n\treturn (\n\t\t<TooltipTrigger\n\t\t\tTriggerElem=\"a\"\n\t\t\tclassName={s.ComponentSourceLink}\n\t\t\thref={href}\n\t\t\tnewWindow\n\t\t\taria-describedby={ariaDescribedBy}\n\t\t\ttooltipText={`View source (opens in ${\n\t\t\t\tisLocalHost ? 'VS Code' : 'new tab'\n\t\t\t})`}\n\t\t\tdangerouslySetInnerHTML={{\n\t\t\t\t// allow line breaks on slashes\n\t\t\t\t__html: relativePath.replaceAll('/', '/<wbr/>'),\n\t\t\t}}\n\t\t/>\n\t)\n}\n\nfunction getVsCodeRootUrl(projectRootPath: string) {\n\tlet newProjectRootPath = projectRootPath\n\n\t// Remove Vite's /@fs/ prefix if present\n\tnewProjectRootPath = projectRootPath.replace(/^\\/@fs\\//, '/')\n\n\t// Fix Windows path: remove leading slash from /C:/Users/... -> C:/Users/...\n\tif (newProjectRootPath.match(/^\\/[A-Za-z]:\\//)) {\n\t\tnewProjectRootPath = newProjectRootPath.slice(1)\n\t}\n\n\t// Ensure forward slashes for VS Code (works on all platforms)\n\tnewProjectRootPath = newProjectRootPath.replace(/\\\\/g, '/')\n\n\treturn `vscode://file/${newProjectRootPath}`\n}\n"],"mappings":";;;;;;AAUA,SAAgB,oBAAoB,EAAE,WAAW,mBAA0B;CAC1E,MAAM,kBAAkB,gBAAgB;CAExC,MAAM,cACL,QAAQ,SAAS,aAAa,eAC9B,QAAQ,SAAS,aAAa;CAE/B,MAAM,gBAAgB,iBACrB,gBAAgB,mBAAmB,gBACnC;CACD,MAAM,aAAa,gBAAgB,mBAAmB;CAEtD,MAAM,gBAAgB,cAAc,gBAAgB;CAGpD,MAAM,eAAe,UAAU,cAC7B,QAAQ,OAAO,IAAI,CACnB,QAAQ,WAAW,GAAG;AAExB,KAAI,CAAC,eAAe;AACnB,SAAO,oBAAC,oBAAM,eAAoB;;CAEnC,MAAM,QACJ,cAAc,SAAS,IAAI,GAAG,gBAAgB,gBAAgB,OAC/D,UAAU,aAAa;AAExB,QACC,oBAAC;EACA,aAAY;EACZ,WAAW,EAAE;EACP;EACN;EACA,oBAAkB;EAClB,aAAa,yBACZ,cAAc,YAAY,UAC1B;EACD,yBAAyB,EAExB,QAAQ,aAAa,WAAW,KAAK,UAAU,EAC/C;GACA;;AAIJ,SAAS,iBAAiB,iBAAyB;CAClD,IAAI,qBAAqB;AAGzB,sBAAqB,gBAAgB,QAAQ,YAAY,IAAI;AAG7D,KAAI,mBAAmB,MAAM,iBAAiB,EAAE;AAC/C,uBAAqB,mBAAmB,MAAM,EAAE;;AAIjD,sBAAqB,mBAAmB,QAAQ,OAAO,IAAI;AAE3D,QAAO,iBAAiB"}
@@ -0,0 +1,5 @@
1
+ .ComponentSourceLink {
2
+ &:hover {
3
+ text-decoration: underline;
4
+ }
5
+ }
@@ -0,0 +1,112 @@
1
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_CopyIcon = require('./icons/CopyIcon.cjs');
3
+ let react = require("react");
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ let src_components_CopyButton_module_css = require("./CopyButton.module.css");
6
+ src_components_CopyButton_module_css = require_runtime.__toESM(src_components_CopyButton_module_css);
7
+
8
+ //#region src/components/CopyButton.tsx
9
+ function CopyButton({ label, copyContent, copyMessage = "Copied to clipboard", errorMessage = "Copy failed" }) {
10
+ const [status, setStatus] = (0, react.useState)("idle");
11
+ const [isResultOpen, setResultOpen] = (0, react.useState)(false);
12
+ const [isForcedShut, setIsForcedShut] = (0, react.useState)(false);
13
+ const [isHovered, setIsHovered] = (0, react.useState)(false);
14
+ const [isFocused, setIsFocused] = (0, react.useState)(false);
15
+ const tooltipId = (0, react.useId)();
16
+ const srId = (0, react.useId)();
17
+ const resultMessageId = (0, react.useId)();
18
+ const btnRef = (0, react.useRef)(null);
19
+ (0, react.useEffect)(() => {
20
+ function handler(e) {
21
+ if (e.key === "Escape" && (isHovered || isFocused)) {
22
+ e.stopPropagation();
23
+ reset();
24
+ setIsForcedShut(true);
25
+ }
26
+ }
27
+ document.addEventListener("keyup", handler);
28
+ return () => document.removeEventListener("keyup", handler);
29
+ }, [isHovered, isFocused]);
30
+ async function onCopy() {
31
+ try {
32
+ await navigator.clipboard.writeText(copyContent);
33
+ setStatus("copied");
34
+ setResultOpen(true);
35
+ } catch {
36
+ setStatus("error");
37
+ setResultOpen(true);
38
+ }
39
+ }
40
+ function reset() {
41
+ setStatus("idle");
42
+ setResultOpen(false);
43
+ }
44
+ function onBlur() {
45
+ reset();
46
+ setIsForcedShut(false);
47
+ setIsFocused(false);
48
+ }
49
+ function onFocus() {
50
+ setIsFocused(true);
51
+ }
52
+ function onMouseEnter() {
53
+ setIsForcedShut(false);
54
+ setIsHovered(true);
55
+ }
56
+ function onMouseLeave() {
57
+ setIsHovered(false);
58
+ reset();
59
+ if (isFocused) {
60
+ setIsForcedShut(true);
61
+ }
62
+ }
63
+ function onKeyDown(e) {
64
+ if (e.key === "Enter" || e.key === " ") {
65
+ e.preventDefault();
66
+ onCopy();
67
+ }
68
+ }
69
+ const copyResultMessage = {
70
+ error: errorMessage,
71
+ copied: copyMessage,
72
+ idle: ""
73
+ }[status];
74
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
75
+ className: src_components_CopyButton_module_css.default.wrapper,
76
+ children: [
77
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
78
+ ref: btnRef,
79
+ type: "button",
80
+ className: src_components_CopyButton_module_css.default.CopyButton,
81
+ onClick: onCopy,
82
+ onFocus,
83
+ onBlur,
84
+ onMouseEnter,
85
+ onMouseLeave,
86
+ onKeyDown,
87
+ "aria-label": label,
88
+ "aria-describedby": isResultOpen ? resultMessageId : undefined,
89
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_CopyIcon.CopyIcon, { className: src_components_CopyButton_module_css.default.icon })
90
+ }),
91
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
92
+ id: tooltipId,
93
+ className: src_components_CopyButton_module_css.default.tooltip,
94
+ "aria-hidden": "true",
95
+ "data-force-closed": isForcedShut,
96
+ children: copyResultMessage || label
97
+ }),
98
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
99
+ id: srId,
100
+ role: "status",
101
+ "aria-live": "polite",
102
+ "aria-atomic": "true",
103
+ className: src_components_CopyButton_module_css.default.srOnly,
104
+ children: copyResultMessage
105
+ })
106
+ ]
107
+ });
108
+ }
109
+
110
+ //#endregion
111
+ exports.CopyButton = CopyButton;
112
+ //# sourceMappingURL=CopyButton.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopyButton.cjs","names":["s","CopyIcon"],"sources":["../../src/components/CopyButton.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from 'react'\nimport { CopyIcon } from './icons/CopyIcon'\nimport s from './CopyButton.module.css'\n\ntype Status = 'idle' | 'copied' | 'error'\n\ninterface Props {\n\tlabel: string\n\tcopyContent: string\n\tcopyMessage?: string\n\terrorMessage?: string\n}\n\nexport function CopyButton({\n\tlabel,\n\tcopyContent,\n\tcopyMessage = 'Copied to clipboard',\n\terrorMessage = 'Copy failed',\n}: Props) {\n\tconst [status, setStatus] = useState<Status>('idle')\n\tconst [isResultOpen, setResultOpen] = useState(false)\n\tconst [isForcedShut, setIsForcedShut] = useState(false)\n\tconst [isHovered, setIsHovered] = useState(false)\n\tconst [isFocused, setIsFocused] = useState(false)\n\tconst tooltipId = useId()\n\tconst srId = useId()\n\tconst resultMessageId = useId()\n\tconst btnRef = useRef<HTMLButtonElement | null>(null)\n\n\tuseEffect(() => {\n\t\tfunction handler(e: KeyboardEvent) {\n\t\t\tif (e.key === 'Escape' && (isHovered || isFocused)) {\n\t\t\t\te.stopPropagation()\n\t\t\t\treset()\n\t\t\t\tsetIsForcedShut(true)\n\t\t\t}\n\t\t}\n\t\tdocument.addEventListener('keyup', handler)\n\t\treturn () => document.removeEventListener('keyup', handler)\n\t}, [isHovered, isFocused])\n\n\tasync function onCopy() {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(copyContent)\n\t\t\tsetStatus('copied')\n\t\t\tsetResultOpen(true)\n\t\t} catch {\n\t\t\tsetStatus('error')\n\t\t\tsetResultOpen(true)\n\t\t}\n\t}\n\tfunction reset() {\n\t\tsetStatus('idle')\n\t\tsetResultOpen(false)\n\t}\n\n\tfunction onBlur() {\n\t\treset()\n\t\tsetIsForcedShut(false)\n\t\tsetIsFocused(false)\n\t}\n\tfunction onFocus() {\n\t\tsetIsFocused(true)\n\t}\n\tfunction onMouseEnter() {\n\t\tsetIsForcedShut(false)\n\t\tsetIsHovered(true)\n\t}\n\tfunction onMouseLeave() {\n\t\tsetIsHovered(false)\n\t\treset()\n\t\tif (isFocused) {\n\t\t\tsetIsForcedShut(true)\n\t\t}\n\t}\n\tfunction onKeyDown(e: React.KeyboardEvent) {\n\t\tif (e.key === 'Enter' || e.key === ' ') {\n\t\t\te.preventDefault()\n\t\t\tonCopy()\n\t\t}\n\t}\n\n\tconst copyResultMessage = {\n\t\terror: errorMessage,\n\t\tcopied: copyMessage,\n\t\tidle: '',\n\t}[status]\n\n\treturn (\n\t\t<span className={s.wrapper}>\n\t\t\t<button\n\t\t\t\tref={btnRef}\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName={s.CopyButton}\n\t\t\t\tonClick={onCopy}\n\t\t\t\tonFocus={onFocus}\n\t\t\t\tonBlur={onBlur}\n\t\t\t\tonMouseEnter={onMouseEnter}\n\t\t\t\tonMouseLeave={onMouseLeave}\n\t\t\t\tonKeyDown={onKeyDown}\n\t\t\t\taria-label={label}\n\t\t\t\taria-describedby={isResultOpen ? resultMessageId : undefined}\n\t\t\t>\n\t\t\t\t<CopyIcon className={s.icon} />\n\t\t\t</button>\n\n\t\t\t<span\n\t\t\t\tid={tooltipId}\n\t\t\t\tclassName={s.tooltip}\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tdata-force-closed={isForcedShut}\n\t\t\t>\n\t\t\t\t{copyResultMessage || label}\n\t\t\t</span>\n\n\t\t\t<span\n\t\t\t\tid={srId}\n\t\t\t\trole=\"status\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t\tclassName={s.srOnly}\n\t\t\t>\n\t\t\t\t{copyResultMessage}\n\t\t\t</span>\n\t\t</span>\n\t)\n}\n"],"mappings":";;;;;;;;AAaA,SAAgB,WAAW,EAC1B,OACA,aACA,cAAc,uBACd,eAAe,iBACN;CACT,MAAM,CAAC,QAAQ,iCAA8B,OAAO;CACpD,MAAM,CAAC,cAAc,qCAA0B,MAAM;CACrD,MAAM,CAAC,cAAc,uCAA4B,MAAM;CACvD,MAAM,CAAC,WAAW,oCAAyB,MAAM;CACjD,MAAM,CAAC,WAAW,oCAAyB,MAAM;CACjD,MAAM,8BAAmB;CACzB,MAAM,yBAAc;CACpB,MAAM,oCAAyB;CAC/B,MAAM,2BAA0C,KAAK;AAErD,4BAAgB;EACf,SAAS,QAAQ,GAAkB;AAClC,OAAI,EAAE,QAAQ,aAAa,aAAa,YAAY;AACnD,MAAE,iBAAiB;AACnB,WAAO;AACP,oBAAgB,KAAK;;;AAGvB,WAAS,iBAAiB,SAAS,QAAQ;AAC3C,eAAa,SAAS,oBAAoB,SAAS,QAAQ;IACzD,CAAC,WAAW,UAAU,CAAC;CAE1B,eAAe,SAAS;AACvB,MAAI;AACH,SAAM,UAAU,UAAU,UAAU,YAAY;AAChD,aAAU,SAAS;AACnB,iBAAc,KAAK;UACZ;AACP,aAAU,QAAQ;AAClB,iBAAc,KAAK;;;CAGrB,SAAS,QAAQ;AAChB,YAAU,OAAO;AACjB,gBAAc,MAAM;;CAGrB,SAAS,SAAS;AACjB,SAAO;AACP,kBAAgB,MAAM;AACtB,eAAa,MAAM;;CAEpB,SAAS,UAAU;AAClB,eAAa,KAAK;;CAEnB,SAAS,eAAe;AACvB,kBAAgB,MAAM;AACtB,eAAa,KAAK;;CAEnB,SAAS,eAAe;AACvB,eAAa,MAAM;AACnB,SAAO;AACP,MAAI,WAAW;AACd,mBAAgB,KAAK;;;CAGvB,SAAS,UAAU,GAAwB;AAC1C,MAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACvC,KAAE,gBAAgB;AAClB,WAAQ;;;CAIV,MAAM,oBAAoB;EACzB,OAAO;EACP,QAAQ;EACR,MAAM;EACN,CAAC;AAEF,QACC,4CAAC;EAAK,WAAWA,6CAAE;;GAClB,2CAAC;IACA,KAAK;IACL,MAAK;IACL,WAAWA,6CAAE;IACb,SAAS;IACA;IACD;IACM;IACA;IACH;IACX,cAAY;IACZ,oBAAkB,eAAe,kBAAkB;cAEnD,2CAACC,6BAAS,WAAWD,6CAAE,OAAQ;KACvB;GAET,2CAAC;IACA,IAAI;IACJ,WAAWA,6CAAE;IACb,eAAY;IACZ,qBAAmB;cAElB,qBAAqB;KAChB;GAEP,2CAAC;IACA,IAAI;IACJ,MAAK;IACL,aAAU;IACV,eAAY;IACZ,WAAWA,6CAAE;cAEZ;KACK;;GACD"}
@@ -0,0 +1,110 @@
1
+ import { CopyIcon } from "./icons/CopyIcon.mjs";
2
+ import { useEffect, useId, useRef, useState } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import s from "./CopyButton.module.css";
5
+
6
+ //#region src/components/CopyButton.tsx
7
+ function CopyButton({ label, copyContent, copyMessage = "Copied to clipboard", errorMessage = "Copy failed" }) {
8
+ const [status, setStatus] = useState("idle");
9
+ const [isResultOpen, setResultOpen] = useState(false);
10
+ const [isForcedShut, setIsForcedShut] = useState(false);
11
+ const [isHovered, setIsHovered] = useState(false);
12
+ const [isFocused, setIsFocused] = useState(false);
13
+ const tooltipId = useId();
14
+ const srId = useId();
15
+ const resultMessageId = useId();
16
+ const btnRef = useRef(null);
17
+ useEffect(() => {
18
+ function handler(e) {
19
+ if (e.key === "Escape" && (isHovered || isFocused)) {
20
+ e.stopPropagation();
21
+ reset();
22
+ setIsForcedShut(true);
23
+ }
24
+ }
25
+ document.addEventListener("keyup", handler);
26
+ return () => document.removeEventListener("keyup", handler);
27
+ }, [isHovered, isFocused]);
28
+ async function onCopy() {
29
+ try {
30
+ await navigator.clipboard.writeText(copyContent);
31
+ setStatus("copied");
32
+ setResultOpen(true);
33
+ } catch {
34
+ setStatus("error");
35
+ setResultOpen(true);
36
+ }
37
+ }
38
+ function reset() {
39
+ setStatus("idle");
40
+ setResultOpen(false);
41
+ }
42
+ function onBlur() {
43
+ reset();
44
+ setIsForcedShut(false);
45
+ setIsFocused(false);
46
+ }
47
+ function onFocus() {
48
+ setIsFocused(true);
49
+ }
50
+ function onMouseEnter() {
51
+ setIsForcedShut(false);
52
+ setIsHovered(true);
53
+ }
54
+ function onMouseLeave() {
55
+ setIsHovered(false);
56
+ reset();
57
+ if (isFocused) {
58
+ setIsForcedShut(true);
59
+ }
60
+ }
61
+ function onKeyDown(e) {
62
+ if (e.key === "Enter" || e.key === " ") {
63
+ e.preventDefault();
64
+ onCopy();
65
+ }
66
+ }
67
+ const copyResultMessage = {
68
+ error: errorMessage,
69
+ copied: copyMessage,
70
+ idle: ""
71
+ }[status];
72
+ return /* @__PURE__ */ jsxs("span", {
73
+ className: s.wrapper,
74
+ children: [
75
+ /* @__PURE__ */ jsx("button", {
76
+ ref: btnRef,
77
+ type: "button",
78
+ className: s.CopyButton,
79
+ onClick: onCopy,
80
+ onFocus,
81
+ onBlur,
82
+ onMouseEnter,
83
+ onMouseLeave,
84
+ onKeyDown,
85
+ "aria-label": label,
86
+ "aria-describedby": isResultOpen ? resultMessageId : undefined,
87
+ children: /* @__PURE__ */ jsx(CopyIcon, { className: s.icon })
88
+ }),
89
+ /* @__PURE__ */ jsx("span", {
90
+ id: tooltipId,
91
+ className: s.tooltip,
92
+ "aria-hidden": "true",
93
+ "data-force-closed": isForcedShut,
94
+ children: copyResultMessage || label
95
+ }),
96
+ /* @__PURE__ */ jsx("span", {
97
+ id: srId,
98
+ role: "status",
99
+ "aria-live": "polite",
100
+ "aria-atomic": "true",
101
+ className: s.srOnly,
102
+ children: copyResultMessage
103
+ })
104
+ ]
105
+ });
106
+ }
107
+
108
+ //#endregion
109
+ export { CopyButton };
110
+ //# sourceMappingURL=CopyButton.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CopyButton.mjs","names":[],"sources":["../../src/components/CopyButton.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState } from 'react'\nimport { CopyIcon } from './icons/CopyIcon'\nimport s from './CopyButton.module.css'\n\ntype Status = 'idle' | 'copied' | 'error'\n\ninterface Props {\n\tlabel: string\n\tcopyContent: string\n\tcopyMessage?: string\n\terrorMessage?: string\n}\n\nexport function CopyButton({\n\tlabel,\n\tcopyContent,\n\tcopyMessage = 'Copied to clipboard',\n\terrorMessage = 'Copy failed',\n}: Props) {\n\tconst [status, setStatus] = useState<Status>('idle')\n\tconst [isResultOpen, setResultOpen] = useState(false)\n\tconst [isForcedShut, setIsForcedShut] = useState(false)\n\tconst [isHovered, setIsHovered] = useState(false)\n\tconst [isFocused, setIsFocused] = useState(false)\n\tconst tooltipId = useId()\n\tconst srId = useId()\n\tconst resultMessageId = useId()\n\tconst btnRef = useRef<HTMLButtonElement | null>(null)\n\n\tuseEffect(() => {\n\t\tfunction handler(e: KeyboardEvent) {\n\t\t\tif (e.key === 'Escape' && (isHovered || isFocused)) {\n\t\t\t\te.stopPropagation()\n\t\t\t\treset()\n\t\t\t\tsetIsForcedShut(true)\n\t\t\t}\n\t\t}\n\t\tdocument.addEventListener('keyup', handler)\n\t\treturn () => document.removeEventListener('keyup', handler)\n\t}, [isHovered, isFocused])\n\n\tasync function onCopy() {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(copyContent)\n\t\t\tsetStatus('copied')\n\t\t\tsetResultOpen(true)\n\t\t} catch {\n\t\t\tsetStatus('error')\n\t\t\tsetResultOpen(true)\n\t\t}\n\t}\n\tfunction reset() {\n\t\tsetStatus('idle')\n\t\tsetResultOpen(false)\n\t}\n\n\tfunction onBlur() {\n\t\treset()\n\t\tsetIsForcedShut(false)\n\t\tsetIsFocused(false)\n\t}\n\tfunction onFocus() {\n\t\tsetIsFocused(true)\n\t}\n\tfunction onMouseEnter() {\n\t\tsetIsForcedShut(false)\n\t\tsetIsHovered(true)\n\t}\n\tfunction onMouseLeave() {\n\t\tsetIsHovered(false)\n\t\treset()\n\t\tif (isFocused) {\n\t\t\tsetIsForcedShut(true)\n\t\t}\n\t}\n\tfunction onKeyDown(e: React.KeyboardEvent) {\n\t\tif (e.key === 'Enter' || e.key === ' ') {\n\t\t\te.preventDefault()\n\t\t\tonCopy()\n\t\t}\n\t}\n\n\tconst copyResultMessage = {\n\t\terror: errorMessage,\n\t\tcopied: copyMessage,\n\t\tidle: '',\n\t}[status]\n\n\treturn (\n\t\t<span className={s.wrapper}>\n\t\t\t<button\n\t\t\t\tref={btnRef}\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName={s.CopyButton}\n\t\t\t\tonClick={onCopy}\n\t\t\t\tonFocus={onFocus}\n\t\t\t\tonBlur={onBlur}\n\t\t\t\tonMouseEnter={onMouseEnter}\n\t\t\t\tonMouseLeave={onMouseLeave}\n\t\t\t\tonKeyDown={onKeyDown}\n\t\t\t\taria-label={label}\n\t\t\t\taria-describedby={isResultOpen ? resultMessageId : undefined}\n\t\t\t>\n\t\t\t\t<CopyIcon className={s.icon} />\n\t\t\t</button>\n\n\t\t\t<span\n\t\t\t\tid={tooltipId}\n\t\t\t\tclassName={s.tooltip}\n\t\t\t\taria-hidden=\"true\"\n\t\t\t\tdata-force-closed={isForcedShut}\n\t\t\t>\n\t\t\t\t{copyResultMessage || label}\n\t\t\t</span>\n\n\t\t\t<span\n\t\t\t\tid={srId}\n\t\t\t\trole=\"status\"\n\t\t\t\taria-live=\"polite\"\n\t\t\t\taria-atomic=\"true\"\n\t\t\t\tclassName={s.srOnly}\n\t\t\t>\n\t\t\t\t{copyResultMessage}\n\t\t\t</span>\n\t\t</span>\n\t)\n}\n"],"mappings":";;;;;;AAaA,SAAgB,WAAW,EAC1B,OACA,aACA,cAAc,uBACd,eAAe,iBACN;CACT,MAAM,CAAC,QAAQ,aAAa,SAAiB,OAAO;CACpD,MAAM,CAAC,cAAc,iBAAiB,SAAS,MAAM;CACrD,MAAM,CAAC,cAAc,mBAAmB,SAAS,MAAM;CACvD,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,CAAC,WAAW,gBAAgB,SAAS,MAAM;CACjD,MAAM,YAAY,OAAO;CACzB,MAAM,OAAO,OAAO;CACpB,MAAM,kBAAkB,OAAO;CAC/B,MAAM,SAAS,OAAiC,KAAK;AAErD,iBAAgB;EACf,SAAS,QAAQ,GAAkB;AAClC,OAAI,EAAE,QAAQ,aAAa,aAAa,YAAY;AACnD,MAAE,iBAAiB;AACnB,WAAO;AACP,oBAAgB,KAAK;;;AAGvB,WAAS,iBAAiB,SAAS,QAAQ;AAC3C,eAAa,SAAS,oBAAoB,SAAS,QAAQ;IACzD,CAAC,WAAW,UAAU,CAAC;CAE1B,eAAe,SAAS;AACvB,MAAI;AACH,SAAM,UAAU,UAAU,UAAU,YAAY;AAChD,aAAU,SAAS;AACnB,iBAAc,KAAK;UACZ;AACP,aAAU,QAAQ;AAClB,iBAAc,KAAK;;;CAGrB,SAAS,QAAQ;AAChB,YAAU,OAAO;AACjB,gBAAc,MAAM;;CAGrB,SAAS,SAAS;AACjB,SAAO;AACP,kBAAgB,MAAM;AACtB,eAAa,MAAM;;CAEpB,SAAS,UAAU;AAClB,eAAa,KAAK;;CAEnB,SAAS,eAAe;AACvB,kBAAgB,MAAM;AACtB,eAAa,KAAK;;CAEnB,SAAS,eAAe;AACvB,eAAa,MAAM;AACnB,SAAO;AACP,MAAI,WAAW;AACd,mBAAgB,KAAK;;;CAGvB,SAAS,UAAU,GAAwB;AAC1C,MAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACvC,KAAE,gBAAgB;AAClB,WAAQ;;;CAIV,MAAM,oBAAoB;EACzB,OAAO;EACP,QAAQ;EACR,MAAM;EACN,CAAC;AAEF,QACC,qBAAC;EAAK,WAAW,EAAE;;GAClB,oBAAC;IACA,KAAK;IACL,MAAK;IACL,WAAW,EAAE;IACb,SAAS;IACA;IACD;IACM;IACA;IACH;IACX,cAAY;IACZ,oBAAkB,eAAe,kBAAkB;cAEnD,oBAAC,YAAS,WAAW,EAAE,OAAQ;KACvB;GAET,oBAAC;IACA,IAAI;IACJ,WAAW,EAAE;IACb,eAAY;IACZ,qBAAmB;cAElB,qBAAqB;KAChB;GAEP,oBAAC;IACA,IAAI;IACJ,MAAK;IACL,aAAU;IACV,eAAY;IACZ,WAAW,EAAE;cAEZ;KACK;;GACD"}
@@ -0,0 +1,76 @@
1
+ .wrapper {
2
+ position: relative;
3
+ display: inline-flex;
4
+ align-items: center;
5
+ }
6
+
7
+ /* 44x44 target (WCAG) while keeping visual size small */
8
+ .CopyButton {
9
+ --btn-size: 1.3em;
10
+ inline-size: var(--btn-size);
11
+ block-size: var(--btn-size);
12
+ display: inline-grid;
13
+ place-items: center;
14
+ padding: 0.2em;
15
+ color: var(--sb-color-text-secondary, #5c6870);
16
+ background: var(--sb-bg2, #f8f9fa);
17
+ border: 1px solid currentColor;
18
+ border-radius: 4px;
19
+ cursor: pointer;
20
+ }
21
+
22
+ .CopyButton:focus-visible {
23
+ outline: 2px solid var(--sb-color-primary, #1ea7fd);
24
+ outline-offset: 2px;
25
+ }
26
+
27
+ /* Prefer minimum 3:1 contrast for the icon */
28
+ .icon {
29
+ inline-size: 100%;
30
+ block-size: 100%;
31
+ }
32
+
33
+ /* Tooltip element (instead of ::before so SRs can reference it) */
34
+ .tooltip {
35
+ position: absolute;
36
+ inset-inline-start: calc(100% + 0.5em);
37
+ inset-block-start: 50%;
38
+ transform: translateY(-50%);
39
+ background: var(--sb-bg1, #fff);
40
+ color: var(--sb-color-text-secondary, #5c6870);
41
+ border: 1px solid currentColor;
42
+ border-radius: 4px;
43
+ padding: 0.25em 0.5em;
44
+ white-space: nowrap;
45
+ z-index: 10;
46
+ font-size: 0.8em;
47
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
48
+ transition: opacity 0.3s ease, visibility 0.3s ease;
49
+ opacity: 0;
50
+ visibility: hidden;
51
+ pointer-events: none;
52
+ }
53
+
54
+ .CopyButton {
55
+ &:hover,
56
+ &:focus-visible {
57
+ & + .tooltip:not([data-force-closed='true']) {
58
+ opacity: 1;
59
+ visibility: visible;
60
+ pointer-events: auto;
61
+ }
62
+ }
63
+ }
64
+
65
+ /* Screen-reader only utility */
66
+ .srOnly {
67
+ position: absolute !important;
68
+ inline-size: 1px;
69
+ block-size: 1px;
70
+ padding: 0;
71
+ margin: -1px;
72
+ overflow: hidden;
73
+ clip: rect(0 0 0 0);
74
+ white-space: nowrap;
75
+ border: 0;
76
+ }
@@ -0,0 +1,51 @@
1
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
+ const require_DoubleChevronDown = require('./icons/DoubleChevronDown.cjs');
3
+ const require_X = require('./icons/X.cjs');
4
+ let react = require("react");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ let src_components_Expandable_module_css = require("./Expandable.module.css");
7
+ src_components_Expandable_module_css = require_runtime.__toESM(src_components_Expandable_module_css);
8
+
9
+ //#region src/components/Expandable.tsx
10
+ function Expandable({ onToggle, Header, children, Icon }) {
11
+ const [isOpen, setIsOpen] = (0, react.useState)(false);
12
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
13
+ className: src_components_Expandable_module_css.default.container,
14
+ onToggle: (e) => {
15
+ e.stopPropagation();
16
+ const isTogglingToOpen = e.currentTarget.open;
17
+ const isAnimationSupported = CSS.supports("interpolate-size", "allow-keywords") && CSS.supports("transition-behavior", "allow-discrete");
18
+ onToggle?.(isTogglingToOpen);
19
+ if (isAnimationSupported) {
20
+ if (isTogglingToOpen) {
21
+ setIsOpen(isTogglingToOpen);
22
+ } else {
23
+ setTimeout(() => {
24
+ setIsOpen(isTogglingToOpen);
25
+ }, .3 * 1e3);
26
+ }
27
+ } else {
28
+ setIsOpen(isTogglingToOpen);
29
+ }
30
+ },
31
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("summary", {
32
+ className: src_components_Expandable_module_css.default.header,
33
+ children: [
34
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { className: `${src_components_Expandable_module_css.default.icon}` }),
35
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
36
+ className: src_components_Expandable_module_css.default.headContent,
37
+ children: Header
38
+ }),
39
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_DoubleChevronDown.DoubleChevronDown, { className: `${src_components_Expandable_module_css.default.icon} ${src_components_Expandable_module_css.default.OpenIcon}` }),
40
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_X.X, { className: `${src_components_Expandable_module_css.default.icon} ${src_components_Expandable_module_css.default.CloseIcon}` })
41
+ ]
42
+ }), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
43
+ className: src_components_Expandable_module_css.default.body,
44
+ children
45
+ })]
46
+ });
47
+ }
48
+
49
+ //#endregion
50
+ exports.Expandable = Expandable;
51
+ //# sourceMappingURL=Expandable.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Expandable.cjs","names":["s","DoubleChevronDown","X"],"sources":["../../src/components/Expandable.tsx"],"sourcesContent":["import { useState, type ReactNode } from 'react'\nimport s from './Expandable.module.css'\nimport { DoubleChevronDown } from './icons/DoubleChevronDown'\nimport { X } from './icons/X'\nimport type { IconComponent } from './icons/iconTypes'\n\ninterface PropsForExpandable {\n\tHeader: ReactNode\n\tchildren: ReactNode\n\tIcon: IconComponent\n\tonToggle?: (toggleState: boolean) => void\n}\nexport function Expandable({\n\tonToggle,\n\tHeader,\n\tchildren,\n\tIcon,\n}: PropsForExpandable) {\n\tconst [isOpen, setIsOpen] = useState(false)\n\n\treturn (\n\t\t<details\n\t\t\tclassName={s.container}\n\t\t\tonToggle={(e) => {\n\t\t\t\te.stopPropagation()\n\t\t\t\tconst isTogglingToOpen = e.currentTarget.open\n\t\t\t\tconst isAnimationSupported =\n\t\t\t\t\tCSS.supports('interpolate-size', 'allow-keywords') &&\n\t\t\t\t\tCSS.supports('transition-behavior', 'allow-discrete')\n\t\t\t\tonToggle?.(isTogglingToOpen)\n\n\t\t\t\t// since we unmount on close we need to delay the unmount when the accordion is closing\n\t\t\t\tif (isAnimationSupported) {\n\t\t\t\t\tif (isTogglingToOpen) {\n\t\t\t\t\t\tsetIsOpen(isTogglingToOpen)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tsetIsOpen(isTogglingToOpen)\n\t\t\t\t\t\t}, 0.3 * 1000)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsetIsOpen(isTogglingToOpen)\n\t\t\t\t}\n\t\t\t}}\n\t\t>\n\t\t\t<summary className={s.header}>\n\t\t\t\t{/* Need to handle show/hide of icons with CSS since isOpen has a delay on close */}\n\t\t\t\t<Icon className={`${s.icon}`} />\n\t\t\t\t<div className={s.headContent}>{Header}</div>\n\t\t\t\t<DoubleChevronDown className={`${s.icon} ${s.OpenIcon}`} />\n\t\t\t\t<X className={`${s.icon} ${s.CloseIcon}`} />\n\t\t\t</summary>\n\t\t\t{isOpen && <div className={s.body}>{children}</div>}\n\t\t</details>\n\t)\n}\n"],"mappings":";;;;;;;;;AAYA,SAAgB,WAAW,EAC1B,UACA,QACA,UACA,QACsB;CACtB,MAAM,CAAC,QAAQ,iCAAsB,MAAM;AAE3C,QACC,4CAAC;EACA,WAAWA,6CAAE;EACb,WAAW,MAAM;AAChB,KAAE,iBAAiB;GACnB,MAAM,mBAAmB,EAAE,cAAc;GACzC,MAAM,uBACL,IAAI,SAAS,oBAAoB,iBAAiB,IAClD,IAAI,SAAS,uBAAuB,iBAAiB;AACtD,cAAW,iBAAiB;AAG5B,OAAI,sBAAsB;AACzB,QAAI,kBAAkB;AACrB,eAAU,iBAAiB;WACrB;AACN,sBAAiB;AAChB,gBAAU,iBAAiB;QACzB,KAAM,IAAK;;UAET;AACN,cAAU,iBAAiB;;;aAI7B,4CAAC;GAAQ,WAAWA,6CAAE;;IAErB,2CAAC,QAAK,WAAW,GAAGA,6CAAE,SAAU;IAChC,2CAAC;KAAI,WAAWA,6CAAE;eAAc;MAAa;IAC7C,2CAACC,+CAAkB,WAAW,GAAGD,6CAAE,KAAK,GAAGA,6CAAE,aAAc;IAC3D,2CAACE,eAAE,WAAW,GAAGF,6CAAE,KAAK,GAAGA,6CAAE,cAAe;;IACnC,EACT,UAAU,2CAAC;GAAI,WAAWA,6CAAE;GAAO;IAAe;GAC1C"}
@@ -0,0 +1,49 @@
1
+ import { DoubleChevronDown } from "./icons/DoubleChevronDown.mjs";
2
+ import { X } from "./icons/X.mjs";
3
+ import { useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import s from "./Expandable.module.css";
6
+
7
+ //#region src/components/Expandable.tsx
8
+ function Expandable({ onToggle, Header, children, Icon }) {
9
+ const [isOpen, setIsOpen] = useState(false);
10
+ return /* @__PURE__ */ jsxs("details", {
11
+ className: s.container,
12
+ onToggle: (e) => {
13
+ e.stopPropagation();
14
+ const isTogglingToOpen = e.currentTarget.open;
15
+ const isAnimationSupported = CSS.supports("interpolate-size", "allow-keywords") && CSS.supports("transition-behavior", "allow-discrete");
16
+ onToggle?.(isTogglingToOpen);
17
+ if (isAnimationSupported) {
18
+ if (isTogglingToOpen) {
19
+ setIsOpen(isTogglingToOpen);
20
+ } else {
21
+ setTimeout(() => {
22
+ setIsOpen(isTogglingToOpen);
23
+ }, .3 * 1e3);
24
+ }
25
+ } else {
26
+ setIsOpen(isTogglingToOpen);
27
+ }
28
+ },
29
+ children: [/* @__PURE__ */ jsxs("summary", {
30
+ className: s.header,
31
+ children: [
32
+ /* @__PURE__ */ jsx(Icon, { className: `${s.icon}` }),
33
+ /* @__PURE__ */ jsx("div", {
34
+ className: s.headContent,
35
+ children: Header
36
+ }),
37
+ /* @__PURE__ */ jsx(DoubleChevronDown, { className: `${s.icon} ${s.OpenIcon}` }),
38
+ /* @__PURE__ */ jsx(X, { className: `${s.icon} ${s.CloseIcon}` })
39
+ ]
40
+ }), isOpen && /* @__PURE__ */ jsx("div", {
41
+ className: s.body,
42
+ children
43
+ })]
44
+ });
45
+ }
46
+
47
+ //#endregion
48
+ export { Expandable };
49
+ //# sourceMappingURL=Expandable.mjs.map