Skip to content
This repository has been archived by the owner on Jul 14, 2022. It is now read-only.

Commit

Permalink
Merge pull request #3 from webstudio-is/24-support-component-story-fo…
Browse files Browse the repository at this point in the history
…rmat-csf

Provide stories with argTypes for primitives
  • Loading branch information
kof authored Jun 1, 2022
2 parents d869ff6 + 97fe83f commit 2290184
Show file tree
Hide file tree
Showing 36 changed files with 31,973 additions and 171 deletions.
12 changes: 12 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.@(js|jsx|ts|tsx)"
],
"addons": [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions"
],
"framework": "@storybook/react"
}
9 changes: 9 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
44 changes: 44 additions & 0 deletions bin/generate-arg-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env node --experimental-loader esbuild-node-loader

import path from "path";
import docgen from "react-docgen-typescript";
import fg from "fast-glob"
import fs from "fs-extra"
import {propsToArgTypes} from "../src/arg-types/utils";

const options = {
shouldExtractLiteralValuesFromEnum: true,
shouldRemoveUndefinedFromOptional: true,
}

const componentsGlobString = process.argv.pop();
const tsConfigPath = path.resolve(process.cwd(), "./tsconfig.json");

if (typeof componentsGlobString === "undefined") {
throw new Error("Please provide glob patterns (space separated) as arguments to match your components");
}

// Search for components
const globs = componentsGlobString.split(" ");
const componentFiles = fg.sync(globs);

console.log(`Resolved tscofig.json at ${tsConfigPath}\n`);
console.log(`Glob patterns used: \n${globs.join('\n')}\n`)
console.log(`Found files to process: \n${componentFiles.join('\n')}\n`)

if (componentFiles.length === 0) {
throw new Error("No component files found");
}

// Create a parser with using your typescript config
const tsConfigParser = docgen.withCustomConfig(tsConfigPath, options);

// For each component file generate argTypes based on the propTypes
componentFiles.forEach(filePath => {
const jsonPath = filePath.replace('.tsx', '.props.json')
const res = tsConfigParser.parse(filePath)
const argTypes = propsToArgTypes(res[0].props)
fs.ensureFileSync(jsonPath)
fs.writeJsonSync(jsonPath, argTypes, {spaces: 2})
console.log(`Done generating argTypes for ${jsonPath}`)
})
24 changes: 20 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,36 @@
"repository": "git@github.com:webstudio-is/webstudio-sdk.git",
"homepage": "https://webstudio.is",
"scripts": {
"build": "yarn build:mdn-data && yarn build:prisma && yarn build:lib",
"build": "yarn build:mdn-data && yarn build:args && yarn build:prisma && yarn build:lib",
"build:mdn-data": "./bin/mdn-data.ts ./src/css",
"build:args": "./bin/generate-arg-types.ts './src/components/*.tsx !./src/**/*.stories.tsx'",
"build:prisma": "prisma format && prisma generate",
"typecheck": "tsc --noEmit",
"build:lib": "rm -fr lib && tsc",
"postinstall": "prisma generate",
"test": "jest",
"checks": "yarn typecheck && yarn test",
"prepublishOnly": "yarn typecheck && yarn build"
"prepublishOnly": "yarn typecheck && yarn build",
"storybook:run": "NODE_OPTIONS=--openssl-legacy-provider start-storybook -p 6006",
"storybook:build": "NODE_OPTIONS=--openssl-legacy-provider build-storybook"
},
"devDependencies": {
"@babel/core": "^7.17.12",
"@remix-run/react": "^1.2.3",
"@remix-run/server-runtime": "^1.2.3",
"@storybook/addon-actions": "^6.5.6",
"@storybook/addon-essentials": "^6.5.6",
"@storybook/addon-interactions": "^6.5.6",
"@storybook/addon-links": "^6.5.6",
"@storybook/builder-webpack4": "^6.5.6",
"@storybook/manager-webpack4": "^6.5.6",
"@storybook/react": "^6.5.6",
"@storybook/testing-library": "^0.0.11",
"@types/css-tree": "^1.0.7",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.21",
"@types/react": "^17.0.40",
"babel-loader": "^8.2.5",
"camelcase": "^6.3.0",
"css-tree": "^2.1.0",
"esbuild": "^0.14.25",
Expand All @@ -32,12 +45,15 @@
"jest": "^27.5.1",
"mdn-data": "2.0.23",
"react": "^17.0.2",
"react-docgen-typescript": "^2.2.2",
"react-dom": "^17.0.2",
"typescript": "^4.6.2"
},
"peerDependencies": {
"@remix-run/react": "^1.2.3",
"@remix-run/server-runtime": "^1.2.3",
"react": "^17.0.2"
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"dependencies": {
"@prisma/client": "^3.10.0",
Expand Down
82 changes: 82 additions & 0 deletions src/arg-types/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type {ArgTypes} from "@storybook/csf"
import {PropItem} from "react-docgen-typescript";

export type FilterPredicate = (prop: PropItem) => boolean

const validAttributes = (prop: PropItem) => {
if (prop.parent) {
// Pass *HTML (both ButtonHTMLAttributes and HTMLAttributes), Aria, and SVG attributes through
const matcher = /.?(HTML|SVG|Aria)Attributes/;
// TODO: Add a test for this
return prop.parent.name.match(matcher);
}
// Always allow component's own props
return true
}

export const propsToArgTypes = (props: Record<string, PropItem>, filter?: FilterPredicate): ArgTypes => {
const filterFn = filter ?? validAttributes
const entries = Object.entries(props);
return entries
.reduce((result, current) => {
const [propName, prop] = current

// Filter out props
if (!filterFn(prop)) {
return result
}

const control = mapControlForType(prop)
result[propName] = {...prop, ...control}
return result
}, {} as ArgTypes);
}

const matchers = {
color: new RegExp('(background|color)', 'i'),
date: /Date$/
}

export const mapControlForType = (propItem: PropItem): any => {
const {type, name} = propItem;
if (!type) {
return undefined;
}

// args that end with background or color e.g. iconColor
if (matchers.color && matchers.color.test(name)) {
const controlType = propItem.type.name;

if (controlType === 'string') {
return { control: { type: 'color' }, defaultValue: propItem.defaultValue?.value };
}
}

// args that end with date e.g. purchaseDate
if (matchers.date && matchers.date.test(name)) {
return { control: { type: 'date' } };
}

switch (type?.name) {
case 'array':
return {control: {type: 'object'}};
case 'boolean':
case 'Booleanish':
return {control: {type: 'boolean'}};
case 'string':
return {control: {type: 'text'}};
case 'number':
return {control: {type: 'number'}};
case 'enum': {
const {value} = type;
// @ts-expect-error Original type has `any` type
const values = value.map(val => val.value)
return {control: {type: values?.length <= 5 ? 'radio' : 'select'}, options: values};
}
case 'function':
case 'symbol':
return null
default:
return {control: {type: 'text'}};
}
};
Loading

0 comments on commit 2290184

Please sign in to comment.