Skip to content

Commit 5792f6c

Browse files
chore(storybook): add migrated components list
- adds a MigratedComponentsList() block to ComponentDetails.jsx. That block gets used in the S2 Migration guide MDX file - adds a migrated-component-scanner.js task to scan the components directory and generate a list of migrated components based on their status (status: type: "migrated") - adds a migrated-components.json file to the .storybook/data directory - updates the package.json scripts to include the new migrated-component-scanner.js task so that it builds and captures the migration status when storybook is started
1 parent 4ae5c89 commit 5792f6c

File tree

6 files changed

+302
-5
lines changed

6 files changed

+302
-5
lines changed

.storybook/blocks/ComponentDetails.jsx

+56-1
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import React, { useEffect, useState } from "react";
55

66
import { Body, Code, Heading } from "./Typography.jsx";
77
import { DDefinition, DList, DTerm } from "./Layouts.jsx";
8-
import { fetchToken } from "./utilities.js";
8+
import { fetchToken, getComponentsByStatus } from "./utilities.js";
99

1010
import AdobeSVG from "../assets/images/adobe_logo.svg?raw";
1111
import GitHubSVG from "../assets/images/github_logo.svg?raw";
1212
import NpmSVG from "../assets/images/npm_logo.svg?raw";
1313
import WCSVG from "../assets/images/wc_logo.svg?raw";
1414

15+
// Import the pre-generated migrated components data
16+
import migratedComponentsData from '../data/migrated-components.json';
17+
1518
export const DSet = ({ term, children }) => {
1619
return (
1720
<>
@@ -443,4 +446,56 @@ export const TaggedReleases = () => {
443446
);
444447
};
445448

449+
/**
450+
* Displays a list of all components that have been migrated to Spectrum 2.
451+
*
452+
* Usage of this doc block within MDX template(s):
453+
* <MigratedComponentsList />
454+
*/
455+
export const MigratedComponentsList = () => {
456+
const [components, setComponents] = useState([]);
457+
const [isLoading, setIsLoading] = useState(true);
458+
459+
useEffect(() => {
460+
// Try to load components server-side first (Node.js environment)
461+
try {
462+
// Use the pre-generated data from our JSON file
463+
if (migratedComponentsData && migratedComponentsData.components) {
464+
setComponents(migratedComponentsData.components);
465+
setIsLoading(false);
466+
return;
467+
}
468+
469+
// Dynamic loading as fallback
470+
const migrated = getComponentsByStatus({ statusType: 'migrated' });
471+
472+
if (migrated && migrated.length > 0) {
473+
setComponents(migrated);
474+
}
475+
} catch (error) {
476+
console.warn('Failed to get migrated components:', error);
477+
}
478+
479+
setIsLoading(false);
480+
}, []);
481+
482+
return (
483+
<ResetWrapper>
484+
{!isLoading ? (
485+
<>
486+
<ul className="sb-unstyled" style={{ columnCount: 3, columnGap: '1rem', listStyle: 'none', padding: 0, marginTop: '1rem' }}>
487+
{components.map((component, idx) => (
488+
<li key={`${component}-${idx}`} style={{ marginBottom: '0.5rem' }}>
489+
{component.charAt(0).toUpperCase() + component.slice(1)}
490+
</li>
491+
))}
492+
</ul>
493+
</>
494+
) : (
495+
<Body>Loading migrated components...</Body>
496+
)}
497+
</ResetWrapper>
498+
);
499+
};
500+
446501
export default ComponentDetails;

.storybook/blocks/utilities.js

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,48 @@
11
import spectrum from "@spectrum-css/tokens/dist/json/tokens.json";
2-
32
import { useTheme } from "@storybook/theming";
43

4+
// Import fs and path conditionally to avoid browser issues
5+
let isNode = false;
6+
let componentUtils = null;
7+
8+
// Check if we're running in Node.js environment
9+
try {
10+
isNode = typeof process !== 'undefined' &&
11+
typeof process.versions !== 'undefined' &&
12+
typeof process.versions.node !== 'undefined';
13+
14+
if (isNode) {
15+
// Import the component utility functions from migrated-component-scanner.js
16+
try {
17+
componentUtils = require('../../tasks/migrated-component-scanner');
18+
} catch (err) {
19+
console.warn('Failed to import component utilities:', err);
20+
}
21+
}
22+
} catch (e) {
23+
// We're in a browser environment
24+
console.log('Running in browser environment, file system functions will be limited');
25+
}
26+
27+
/**
28+
* Gets all component directories that have a specific status
29+
* Only works in Node.js environment
30+
* @param {Object} options Options for filtering components
31+
* @param {string} options.statusType Status type to filter by (e.g., 'migrated')
32+
* @returns {string[]} Array of matching component directory names
33+
*/
34+
export function getComponentsByStatus(options = {}) {
35+
// Check if we're in a Node.js environment
36+
if (!isNode || !componentUtils) {
37+
console.warn('getComponentsByStatus can only be used in a Node.js environment');
38+
return [];
39+
}
40+
41+
const { statusType } = options;
42+
43+
return componentUtils.getComponentsByStatus(statusType);
44+
}
45+
546
/**
647
* A nestable function to search for a token value in the spectrum token data
748
* - If the key doesn't exist, it will log a warning
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"total": 85,
3+
"migrated": 49,
4+
"components": [
5+
"actionbar",
6+
"actionbutton",
7+
"actiongroup",
8+
"alertbanner",
9+
"avatar",
10+
"breadcrumb",
11+
"button",
12+
"buttongroup",
13+
"checkbox",
14+
"closebutton",
15+
"coachmark",
16+
"colorarea",
17+
"colorhandle",
18+
"colorloupe",
19+
"colorslider",
20+
"colorwheel",
21+
"dial",
22+
"dialog",
23+
"divider",
24+
"dropzone",
25+
"fieldgroup",
26+
"fieldlabel",
27+
"form",
28+
"helptext",
29+
"icon",
30+
"illustratedmessage",
31+
"infieldbutton",
32+
"infieldprogresscircle",
33+
"inlinealert",
34+
"link",
35+
"opacitycheckerboard",
36+
"pagination",
37+
"picker",
38+
"popover",
39+
"progressbar",
40+
"progresscircle",
41+
"radio",
42+
"rating",
43+
"search",
44+
"statuslight",
45+
"stepper",
46+
"swatch",
47+
"swatchgroup",
48+
"switch",
49+
"tag",
50+
"textfield",
51+
"thumbnail",
52+
"toast",
53+
"tooltip"
54+
],
55+
"generatedAt": "2025-05-15T21:09:40.681Z"
56+
}

.storybook/guides/s2_migration.mdx

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Meta, Title } from "@storybook/blocks";
22
import { ThemeContainer, Heading } from "../blocks";
3+
import { MigratedComponentsList } from "../blocks/ComponentDetails.jsx";
34

45
import GrayMigrationGuide from "../assets/images/gray_migration-guide.png";
56

@@ -13,6 +14,12 @@ import GrayMigrationGuide from "../assets/images/gray_migration-guide.png";
1314
- Search within: Use a search field with a separate control to filter the search instead.
1415
- Split button: Use a button group to show any additional actions related to the most critical action. Reference [Spectrum documentation](https://spectrum.corp.adobe.com/page/button-group/#Use-a-button-group-to-show-additional-actions) for more information.
1516

17+
## Migrated components
18+
19+
This is a list of all components that have been fully migrated to Spectrum 2.
20+
21+
<MigratedComponentsList />
22+
1623
## Grays
1724

1825
<img

package.json

+5-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"scripts": {
1616
"build": "yarn builder tag:component,ui-icons",
1717
"build:docs": "yarn build:preview --output-dir ../dist/",
18-
"build:preview": "yarn report && nx build storybook",
18+
"build:preview": "yarn report && yarn component:data && nx build storybook",
1919
"builder": "nx run-many --target build report --projects",
2020
"bundle": "nx build bundle",
2121
"changeset": "changeset",
@@ -31,7 +31,9 @@
3131
"component:build": "node --no-warnings ./tasks/component-builder.js",
3232
"component:compare": "node --no-warnings ./tasks/component-compare.js",
3333
"component:report": "node --no-warnings ./tasks/component-reporter.js",
34-
"dev": "cross-env NODE_ENV=development nx run storybook:build:docs",
34+
"component:data": "node --no-warnings ./tasks/migrated-component-scanner.js --output=.storybook/data/migrated-components.json",
35+
"component:migrated": "node --no-warnings ./tasks/migrated-component-scanner.js",
36+
"dev": "cross-env NODE_ENV=development yarn component:data && nx run storybook:build:docs",
3537
"docs:report": "yarn report",
3638
"format": "yarn formatter tag:component",
3739
"formatter": "nx run-many --target format --projects",
@@ -47,7 +49,7 @@
4749
"release": "yarn ci && changeset publish",
4850
"report": "yarn reporter tag:component",
4951
"reporter": "nx run-many --target report --projects",
50-
"start": "cross-env NODE_ENV=development nx start storybook",
52+
"start": "cross-env NODE_ENV=development yarn component:data && nx start storybook",
5153
"test": "cross-env NODE_ENV=development nx test storybook",
5254
"test:plugins": "cross-env NODE_ENV=production nx run-many --target test --projects tag:plugin",
5355
"tester": "cross-env NODE_ENV=development nx run storybook:test:scope",

tasks/migrated-component-scanner.js

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#!/usr/bin/env node
2+
3+
/*!
4+
* Copyright 2025 Adobe. All rights reserved.
5+
*
6+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License. You may obtain a copy
8+
* of the License at <http://www.apache.org/licenses/LICENSE-2.0>
9+
*
10+
* Unless required by applicable law or agreed to in writing, software distributed under
11+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
12+
* OF ANY KIND, either express or implied. See the License for the specific language
13+
* governing permissions and limitations under the License.
14+
*/
15+
16+
/* eslint-disable no-console */
17+
18+
/**
19+
* This script scans the components directory and generates a list of components
20+
* with a migrated status. The output can be saved to a JSON file or printed to console.
21+
*
22+
* Usage:
23+
* node tasks/migrated-component-scanner.js [--output=path/to/output.json]
24+
*/
25+
26+
const fs = require("fs");
27+
const path = require("path");
28+
29+
/**
30+
* Gets all component directory names from the components folder
31+
* @returns {string[]} Array of component directory names
32+
*/
33+
function getAllComponentDirectories() {
34+
try {
35+
// Get the absolute path to the components directory
36+
const componentsDir = path.resolve(process.cwd(), "components");
37+
38+
// Read all directories in the components folder
39+
const directories = fs.readdirSync(componentsDir, { withFileTypes: true })
40+
.filter(dirent => dirent.isDirectory())
41+
.map(dirent => dirent.name)
42+
.sort();
43+
44+
return directories;
45+
} catch (error) {
46+
console.error("Error getting component directories:", error);
47+
return [];
48+
}
49+
}
50+
51+
/**
52+
* Gets all component directories that have a specific status
53+
* @param {string} statusType Status type to filter by (e.g., 'migrated')
54+
* @returns {string[]} Array of matching component directory names
55+
*/
56+
function getComponentsByStatus(statusType) {
57+
try {
58+
const componentsDir = path.resolve(process.cwd(), "components");
59+
const directories = getAllComponentDirectories();
60+
61+
if (!statusType) return directories;
62+
63+
// Filter directories that have status type in their stories
64+
const matchingComponents = directories.filter(dir => {
65+
const storiesDir = path.join(componentsDir, dir, "stories");
66+
67+
// Check if stories directory exists
68+
if (!fs.existsSync(storiesDir)) return false;
69+
70+
// Get all story files
71+
const storyFiles = fs.readdirSync(storiesDir)
72+
.filter(file => file.endsWith(".stories.js"));
73+
74+
// Check each story file for status type
75+
return storyFiles.some(file => {
76+
const storyContent = fs.readFileSync(path.join(storiesDir, file), "utf8");
77+
return storyContent.includes(`type: "${statusType}"`);
78+
});
79+
});
80+
81+
return matchingComponents;
82+
} catch (error) {
83+
console.error(`Error getting components with status ${statusType}:`, error);
84+
return [];
85+
}
86+
}
87+
88+
/**
89+
* Generates a list of migrated components
90+
* @returns {Object} Information about migrated components
91+
*/
92+
function generateMigratedComponentsReport() {
93+
const allComponents = getAllComponentDirectories();
94+
const migratedComponents = getComponentsByStatus("migrated");
95+
96+
return {
97+
total: allComponents.length,
98+
migrated: migratedComponents.length,
99+
components: migratedComponents,
100+
generatedAt: new Date().toISOString()
101+
};
102+
}
103+
104+
// Export the functions for use in other modules
105+
module.exports = {
106+
getAllComponentDirectories,
107+
getComponentsByStatus,
108+
generateMigratedComponentsReport
109+
};
110+
111+
// Main execution - only runs when script is executed directly in the terminal
112+
if (require.main === module) {
113+
(async () => {
114+
const args = process.argv.slice(2);
115+
const outputArg = args.find(arg => arg.startsWith("--output="));
116+
const outputPath = outputArg ? outputArg.split("=")[1] : null;
117+
118+
console.log("Scanning for migrated components...");
119+
const report = generateMigratedComponentsReport();
120+
121+
if (outputPath) {
122+
const outputDir = path.dirname(outputPath);
123+
if (!fs.existsSync(outputDir)) {
124+
fs.mkdirSync(outputDir, { recursive: true });
125+
}
126+
127+
fs.writeFileSync(outputPath, JSON.stringify(report, null, 2));
128+
console.log(`Report saved to ${outputPath}`);
129+
console.log(`Found ${report.migrated} migrated components out of ${report.total} total components.`);
130+
} else {
131+
console.log("Migrated Components:");
132+
console.log(report.components.join(", "));
133+
console.log(`\nTotal: ${report.migrated} out of ${report.total} components are migrated.`);
134+
}
135+
})();
136+
}

0 commit comments

Comments
 (0)