Skip to content

chore(storybook): add migrated components list #3745

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: spectrum-two
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion .storybook/blocks/ComponentDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import React, { useEffect, useState } from "react";

import { Body, Code, Heading } from "./Typography.jsx";
import { DDefinition, DList, DTerm } from "./Layouts.jsx";
import { fetchToken } from "./utilities.js";
import { fetchToken, getComponentsByStatus } from "./utilities.js";

import AdobeSVG from "../assets/images/adobe_logo.svg?raw";
import GitHubSVG from "../assets/images/github_logo.svg?raw";
import NpmSVG from "../assets/images/npm_logo.svg?raw";
import WCSVG from "../assets/images/wc_logo.svg?raw";

// Import the pre-generated migrated components data
import migratedComponentsData from '../data/migrated-components.json';

export const DSet = ({ term, children }) => {
return (
<>
Expand Down Expand Up @@ -443,4 +446,56 @@ export const TaggedReleases = () => {
);
};

/**
* Displays a list of all components that have been migrated to Spectrum 2.
*
* Usage of this doc block within MDX template(s):
* <MigratedComponentsList />
*/
export const MigratedComponentsList = () => {
const [components, setComponents] = useState([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
// Try to load components server-side first (Node.js environment)
try {
// Use the pre-generated data from our JSON file
if (migratedComponentsData && migratedComponentsData.components) {
setComponents(migratedComponentsData.components);
setIsLoading(false);
return;
}

// Dynamic loading as fallback
const migrated = getComponentsByStatus({ statusType: 'migrated' });

if (migrated && migrated.length > 0) {
setComponents(migrated);
}
} catch (error) {
console.warn('Failed to get migrated components:', error);
}

setIsLoading(false);
}, []);

return (
<ResetWrapper>
{!isLoading ? (
<>
<ul className="sb-unstyled" style={{ columnCount: 3, columnGap: '1rem', listStyle: 'none', padding: 0, marginTop: '1rem' }}>
{components.map((component, idx) => (
<li key={`${component}-${idx}`} style={{ marginBottom: '0.5rem' }}>
{component.charAt(0).toUpperCase() + component.slice(1)}
</li>
))}
</ul>
</>
) : (
<Body>Loading migrated components...</Body>
)}
</ResetWrapper>
);
};

export default ComponentDetails;
43 changes: 42 additions & 1 deletion .storybook/blocks/utilities.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,48 @@
import spectrum from "@spectrum-css/tokens/dist/json/tokens.json";

import { useTheme } from "@storybook/theming";

// Import fs and path conditionally to avoid browser issues
let isNode = false;
let componentUtils = null;

// Check if we're running in Node.js environment
try {
isNode = typeof process !== 'undefined' &&
typeof process.versions !== 'undefined' &&
typeof process.versions.node !== 'undefined';

if (isNode) {
// Import the component utility functions from migrated-component-scanner.js
try {
componentUtils = require('../../tasks/migrated-component-scanner');
} catch (err) {
console.warn('Failed to import component utilities:', err);
}
}
} catch (e) {
// We're in a browser environment
console.log('Running in browser environment, file system functions will be limited');
}

/**
* Gets all component directories that have a specific status
* Only works in Node.js environment
* @param {Object} options Options for filtering components
* @param {string} options.statusType Status type to filter by (e.g., 'migrated')
* @returns {string[]} Array of matching component directory names
*/
export function getComponentsByStatus(options = {}) {
// Check if we're in a Node.js environment
if (!isNode || !componentUtils) {
console.warn('getComponentsByStatus can only be used in a Node.js environment');
return [];
}

const { statusType } = options;

return componentUtils.getComponentsByStatus(statusType);
}

/**
* A nestable function to search for a token value in the spectrum token data
* - If the key doesn't exist, it will log a warning
Expand Down
56 changes: 56 additions & 0 deletions .storybook/data/migrated-components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"total": 85,
"migrated": 49,
"components": [
"actionbar",
"actionbutton",
"actiongroup",
"alertbanner",
"avatar",
"breadcrumb",
"button",
"buttongroup",
"checkbox",
"closebutton",
"coachmark",
"colorarea",
"colorhandle",
"colorloupe",
"colorslider",
"colorwheel",
"dial",
"dialog",
"divider",
"dropzone",
"fieldgroup",
"fieldlabel",
"form",
"helptext",
"icon",
"illustratedmessage",
"infieldbutton",
"infieldprogresscircle",
"inlinealert",
"link",
"opacitycheckerboard",
"pagination",
"picker",
"popover",
"progressbar",
"progresscircle",
"radio",
"rating",
"search",
"statuslight",
"stepper",
"swatch",
"swatchgroup",
"switch",
"tag",
"textfield",
"thumbnail",
"toast",
"tooltip"
],
"generatedAt": "2025-05-15T21:09:40.681Z"
}
7 changes: 7 additions & 0 deletions .storybook/guides/s2_migration.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Meta, Title } from "@storybook/blocks";
import { ThemeContainer, Heading } from "../blocks";
import { MigratedComponentsList } from "../blocks/ComponentDetails.jsx";

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

Expand All @@ -13,6 +14,12 @@ import GrayMigrationGuide from "../assets/images/gray_migration-guide.png";
- Search within: Use a search field with a separate control to filter the search instead.
- 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.

## Migrated components

This is a list of all components that have been fully migrated to Spectrum 2.

<MigratedComponentsList />

## Grays

<img
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"scripts": {
"build": "yarn builder tag:component,ui-icons",
"build:docs": "yarn build:preview --output-dir ../dist/",
"build:preview": "yarn report && nx build storybook",
"build:preview": "yarn report && yarn component:data && nx build storybook",
"builder": "nx run-many --target build report --projects",
"bundle": "nx build bundle",
"changeset": "changeset",
Expand All @@ -31,7 +31,9 @@
"component:build": "node --no-warnings ./tasks/component-builder.js",
"component:compare": "node --no-warnings ./tasks/component-compare.js",
"component:report": "node --no-warnings ./tasks/component-reporter.js",
"dev": "cross-env NODE_ENV=development nx run storybook:build:docs",
"component:data": "node --no-warnings ./tasks/migrated-component-scanner.js --output=.storybook/data/migrated-components.json",
"component:migrated": "node --no-warnings ./tasks/migrated-component-scanner.js",
"dev": "cross-env NODE_ENV=development yarn component:data && nx run storybook:build:docs",
"docs:report": "yarn report",
"format": "yarn formatter tag:component",
"formatter": "nx run-many --target format --projects",
Expand All @@ -47,7 +49,7 @@
"release": "yarn ci && changeset publish",
"report": "yarn reporter tag:component",
"reporter": "nx run-many --target report --projects",
"start": "cross-env NODE_ENV=development nx start storybook",
"start": "cross-env NODE_ENV=development yarn component:data && nx start storybook",
"test": "cross-env NODE_ENV=development nx test storybook",
"test:plugins": "cross-env NODE_ENV=production nx run-many --target test --projects tag:plugin",
"tester": "cross-env NODE_ENV=development nx run storybook:test:scope",
Expand Down
136 changes: 136 additions & 0 deletions tasks/migrated-component-scanner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env node

/*!
* Copyright 2025 Adobe. All rights reserved.
*
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at <http://www.apache.org/licenses/LICENSE-2.0>
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-disable no-console */

/**
* This script scans the components directory and generates a list of components
* with a migrated status. The output can be saved to a JSON file or printed to console.
*
* Usage:
* node tasks/migrated-component-scanner.js [--output=path/to/output.json]
*/

const fs = require("fs");
const path = require("path");

/**
* Gets all component directory names from the components folder
* @returns {string[]} Array of component directory names
*/
function getAllComponentDirectories() {
try {
// Get the absolute path to the components directory
const componentsDir = path.resolve(process.cwd(), "components");

// Read all directories in the components folder
const directories = fs.readdirSync(componentsDir, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name)
.sort();

return directories;
} catch (error) {
console.error("Error getting component directories:", error);
return [];
}
}

/**
* Gets all component directories that have a specific status
* @param {string} statusType Status type to filter by (e.g., 'migrated')
* @returns {string[]} Array of matching component directory names
*/
function getComponentsByStatus(statusType) {
try {
const componentsDir = path.resolve(process.cwd(), "components");
const directories = getAllComponentDirectories();

if (!statusType) return directories;

// Filter directories that have status type in their stories
const matchingComponents = directories.filter(dir => {
const storiesDir = path.join(componentsDir, dir, "stories");

// Check if stories directory exists
if (!fs.existsSync(storiesDir)) return false;

// Get all story files
const storyFiles = fs.readdirSync(storiesDir)
.filter(file => file.endsWith(".stories.js"));

// Check each story file for status type
return storyFiles.some(file => {
const storyContent = fs.readFileSync(path.join(storiesDir, file), "utf8");
return storyContent.includes(`type: "${statusType}"`);
});
});

return matchingComponents;
} catch (error) {
console.error(`Error getting components with status ${statusType}:`, error);
return [];
}
}

/**
* Generates a list of migrated components
* @returns {Object} Information about migrated components
*/
function generateMigratedComponentsReport() {
const allComponents = getAllComponentDirectories();
const migratedComponents = getComponentsByStatus("migrated");

return {
total: allComponents.length,
migrated: migratedComponents.length,
components: migratedComponents,
generatedAt: new Date().toISOString()
};
}

// Export the functions for use in other modules
module.exports = {
getAllComponentDirectories,
getComponentsByStatus,
generateMigratedComponentsReport
};

// Main execution - only runs when script is executed directly in the terminal
if (require.main === module) {
(async () => {
const args = process.argv.slice(2);
const outputArg = args.find(arg => arg.startsWith("--output="));
const outputPath = outputArg ? outputArg.split("=")[1] : null;

console.log("Scanning for migrated components...");
const report = generateMigratedComponentsReport();

if (outputPath) {
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

fs.writeFileSync(outputPath, JSON.stringify(report, null, 2));
console.log(`Report saved to ${outputPath}`);
console.log(`Found ${report.migrated} migrated components out of ${report.total} total components.`);
} else {
console.log("Migrated Components:");
console.log(report.components.join(", "));
console.log(`\nTotal: ${report.migrated} out of ${report.total} components are migrated.`);
}
})();
}
Loading