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.
- package/README.md +192 -0
- package/dist/_virtual/_rolldown/runtime.cjs +29 -0
- package/dist/addon/index.cjs +12 -0
- package/dist/addon/index.cjs.map +1 -0
- package/dist/addon/index.d.cts +5 -0
- package/dist/addon/index.d.mts +5 -0
- package/dist/addon/index.mjs +11 -0
- package/dist/addon/index.mjs.map +1 -0
- package/dist/addon/preset.cjs +8 -0
- package/dist/addon/preset.cjs.map +1 -0
- package/dist/addon/preset.d.cts +5 -0
- package/dist/addon/preset.d.mts +5 -0
- package/dist/addon/preset.mjs +6 -0
- package/dist/addon/preset.mjs.map +1 -0
- package/dist/addon/vitePlugin.cjs +26 -0
- package/dist/addon/vitePlugin.cjs.map +1 -0
- package/dist/addon/vitePlugin.d.cts +9 -0
- package/dist/addon/vitePlugin.d.mts +9 -0
- package/dist/addon/vitePlugin.mjs +24 -0
- package/dist/addon/vitePlugin.mjs.map +1 -0
- package/dist/blocks/DependencyPreviews.cjs +102 -0
- package/dist/blocks/DependencyPreviews.cjs.map +1 -0
- package/dist/blocks/DependencyPreviews.d.cts +7 -0
- package/dist/blocks/DependencyPreviews.d.mts +7 -0
- package/dist/blocks/DependencyPreviews.mjs +100 -0
- package/dist/blocks/DependencyPreviews.mjs.map +1 -0
- package/dist/blocks/DependencyPreviews.module.css +14 -0
- package/dist/blocks/index.cjs +1 -0
- package/dist/blocks/index.d.cts +1 -0
- package/dist/blocks/index.d.mts +1 -0
- package/dist/blocks/index.mjs +1 -0
- package/dist/cli/sb-deps.cjs +321 -0
- package/dist/cli/sb-deps.cjs.map +1 -0
- package/dist/cli/sb-deps.d.cts +1 -0
- package/dist/cli/sb-deps.d.mts +1 -0
- package/dist/cli/sb-deps.mjs +319 -0
- package/dist/cli/sb-deps.mjs.map +1 -0
- package/dist/cli/scripts/depcruise.config.cjs +38 -0
- package/dist/cli/scripts/depcruise.config.cjs.map +1 -0
- package/dist/cli/scripts/depcruise.config.d.cts +6 -0
- package/dist/cli/scripts/depcruise.config.d.mts +7 -0
- package/dist/cli/scripts/depcruise.config.mjs +37 -0
- package/dist/cli/scripts/depcruise.config.mjs.map +1 -0
- package/dist/cli/scripts/postprocess.cjs +106 -0
- package/dist/cli/scripts/postprocess.cjs.map +1 -0
- package/dist/cli/scripts/postprocess.d.cts +1 -0
- package/dist/cli/scripts/postprocess.d.mts +1 -0
- package/dist/cli/scripts/postprocess.mjs +106 -0
- package/dist/cli/scripts/postprocess.mjs.map +1 -0
- package/dist/components/ComponentSourceLink.cjs +42 -0
- package/dist/components/ComponentSourceLink.cjs.map +1 -0
- package/dist/components/ComponentSourceLink.mjs +40 -0
- package/dist/components/ComponentSourceLink.mjs.map +1 -0
- package/dist/components/ComponentSourceLink.module.css +5 -0
- package/dist/components/CopyButton.cjs +112 -0
- package/dist/components/CopyButton.cjs.map +1 -0
- package/dist/components/CopyButton.mjs +110 -0
- package/dist/components/CopyButton.mjs.map +1 -0
- package/dist/components/CopyButton.module.css +76 -0
- package/dist/components/Expandable.cjs +51 -0
- package/dist/components/Expandable.cjs.map +1 -0
- package/dist/components/Expandable.mjs +49 -0
- package/dist/components/Expandable.mjs.map +1 -0
- package/dist/components/Expandable.module.css +110 -0
- package/dist/components/FullParityStory.cjs +47 -0
- package/dist/components/FullParityStory.cjs.map +1 -0
- package/dist/components/FullParityStory.mjs +44 -0
- package/dist/components/FullParityStory.mjs.map +1 -0
- package/dist/components/PathCopyMolecule.cjs +28 -0
- package/dist/components/PathCopyMolecule.cjs.map +1 -0
- package/dist/components/PathCopyMolecule.mjs +26 -0
- package/dist/components/PathCopyMolecule.mjs.map +1 -0
- package/dist/components/PathCopyMolecule.module.css +14 -0
- package/dist/components/PrimaryPreview.cjs +35 -0
- package/dist/components/PrimaryPreview.cjs.map +1 -0
- package/dist/components/PrimaryPreview.mjs +34 -0
- package/dist/components/PrimaryPreview.mjs.map +1 -0
- package/dist/components/SbHeading.cjs +18 -0
- package/dist/components/SbHeading.cjs.map +1 -0
- package/dist/components/SbHeading.mjs +16 -0
- package/dist/components/SbHeading.mjs.map +1 -0
- package/dist/components/SbHeading.module.css +23 -0
- package/dist/components/StoryLink.cjs +45 -0
- package/dist/components/StoryLink.cjs.map +1 -0
- package/dist/components/StoryLink.mjs +43 -0
- package/dist/components/StoryLink.mjs.map +1 -0
- package/dist/components/StoryLink.module.css +13 -0
- package/dist/components/TooltipTrigger.cjs +100 -0
- package/dist/components/TooltipTrigger.cjs.map +1 -0
- package/dist/components/TooltipTrigger.mjs +98 -0
- package/dist/components/TooltipTrigger.mjs.map +1 -0
- package/dist/components/TooltipTrigger.module.css +62 -0
- package/dist/components/icons/ArrowToRectangleIcon.cjs +19 -0
- package/dist/components/icons/ArrowToRectangleIcon.cjs.map +1 -0
- package/dist/components/icons/ArrowToRectangleIcon.mjs +18 -0
- package/dist/components/icons/ArrowToRectangleIcon.mjs.map +1 -0
- package/dist/components/icons/BuildIcon.cjs +19 -0
- package/dist/components/icons/BuildIcon.cjs.map +1 -0
- package/dist/components/icons/BuildIcon.mjs +18 -0
- package/dist/components/icons/BuildIcon.mjs.map +1 -0
- package/dist/components/icons/CopyIcon.cjs +19 -0
- package/dist/components/icons/CopyIcon.cjs.map +1 -0
- package/dist/components/icons/CopyIcon.mjs +18 -0
- package/dist/components/icons/CopyIcon.mjs.map +1 -0
- package/dist/components/icons/CubeIcon.cjs +19 -0
- package/dist/components/icons/CubeIcon.cjs.map +1 -0
- package/dist/components/icons/CubeIcon.mjs +18 -0
- package/dist/components/icons/CubeIcon.mjs.map +1 -0
- package/dist/components/icons/DoubleChevronDown.cjs +19 -0
- package/dist/components/icons/DoubleChevronDown.cjs.map +1 -0
- package/dist/components/icons/DoubleChevronDown.mjs +18 -0
- package/dist/components/icons/DoubleChevronDown.mjs.map +1 -0
- package/dist/components/icons/ExternalLinkIcon.cjs +19 -0
- package/dist/components/icons/ExternalLinkIcon.cjs.map +1 -0
- package/dist/components/icons/ExternalLinkIcon.mjs +18 -0
- package/dist/components/icons/ExternalLinkIcon.mjs.map +1 -0
- package/dist/components/icons/EyeOpen.cjs +23 -0
- package/dist/components/icons/EyeOpen.cjs.map +1 -0
- package/dist/components/icons/EyeOpen.mjs +22 -0
- package/dist/components/icons/EyeOpen.mjs.map +1 -0
- package/dist/components/icons/LoadingRipples.cjs +79 -0
- package/dist/components/icons/LoadingRipples.cjs.map +1 -0
- package/dist/components/icons/LoadingRipples.mjs +78 -0
- package/dist/components/icons/LoadingRipples.mjs.map +1 -0
- package/dist/components/icons/SquaresPlus.cjs +19 -0
- package/dist/components/icons/SquaresPlus.cjs.map +1 -0
- package/dist/components/icons/SquaresPlus.mjs +18 -0
- package/dist/components/icons/SquaresPlus.mjs.map +1 -0
- package/dist/components/icons/Svg.cjs +37 -0
- package/dist/components/icons/Svg.cjs.map +1 -0
- package/dist/components/icons/Svg.mjs +36 -0
- package/dist/components/icons/Svg.mjs.map +1 -0
- package/dist/components/icons/X.cjs +19 -0
- package/dist/components/icons/X.cjs.map +1 -0
- package/dist/components/icons/X.mjs +18 -0
- package/dist/components/icons/X.mjs.map +1 -0
- package/dist/constants.cjs +9 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.mjs +7 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/hooks/useDependencyGraph.cjs +63 -0
- package/dist/hooks/useDependencyGraph.cjs.map +1 -0
- package/dist/hooks/useDependencyGraph.mjs +60 -0
- package/dist/hooks/useDependencyGraph.mjs.map +1 -0
- package/dist/hooks/useDynamicStory.cjs +83 -0
- package/dist/hooks/useDynamicStory.cjs.map +1 -0
- package/dist/hooks/useDynamicStory.mjs +82 -0
- package/dist/hooks/useDynamicStory.mjs.map +1 -0
- package/dist/hooks/useStoryParams.cjs +17 -0
- package/dist/hooks/useStoryParams.cjs.map +1 -0
- package/dist/hooks/useStoryParams.mjs +16 -0
- package/dist/hooks/useStoryParams.mjs.map +1 -0
- package/dist/index.cjs +15 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.mjs +9 -0
- package/dist/manager.cjs +22 -0
- package/dist/manager.cjs.map +1 -0
- package/dist/manager.d.cts +1 -0
- package/dist/manager.d.mts +1 -0
- package/dist/manager.mjs +21 -0
- package/dist/manager.mjs.map +1 -0
- package/dist/panels/DefaultAutoDocsLayout.cjs +24 -0
- package/dist/panels/DefaultAutoDocsLayout.cjs.map +1 -0
- package/dist/panels/DefaultAutoDocsLayout.d.cts +10 -0
- package/dist/panels/DefaultAutoDocsLayout.d.mts +10 -0
- package/dist/panels/DefaultAutoDocsLayout.mjs +22 -0
- package/dist/panels/DefaultAutoDocsLayout.mjs.map +1 -0
- package/dist/panels/GraphView.cjs +78 -0
- package/dist/panels/GraphView.cjs.map +1 -0
- package/dist/panels/GraphView.d.cts +7 -0
- package/dist/panels/GraphView.d.mts +7 -0
- package/dist/panels/GraphView.mjs +77 -0
- package/dist/panels/GraphView.mjs.map +1 -0
- package/dist/panels/index.cjs +3 -0
- package/dist/panels/index.d.cts +3 -0
- package/dist/panels/index.d.mts +3 -0
- package/dist/panels/index.mjs +3 -0
- package/dist/preview.cjs +23 -0
- package/dist/preview.cjs.map +1 -0
- package/dist/preview.d.cts +9 -0
- package/dist/preview.d.mts +10 -0
- package/dist/preview.mjs +21 -0
- package/dist/preview.mjs.map +1 -0
- package/dist/types.cjs +0 -0
- package/dist/types.d.cts +104 -0
- package/dist/types.d.mts +104 -0
- package/dist/types.mjs +0 -0
- 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,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
|