react-scanner-ui 0.0.8 → 0.0.10
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/.husky/commit-msg +3 -0
- package/.husky/pre-commit +1 -0
- package/.prettierignore +34 -0
- package/.prettierrc +15 -0
- package/dist/commands/start.d.ts +1 -1
- package/dist/commands/start.js +18 -18
- package/dist/commands/start.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +29 -32
- package/dist/server/index.js.map +1 -1
- package/dist/utils/config.js +1 -1
- package/dist/utils/dependencies.d.ts.map +1 -1
- package/dist/utils/dependencies.js +3 -3
- package/dist/utils/dependencies.js.map +1 -1
- package/dist/utils/index.d.ts +5 -5
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -3
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/port.js +1 -1
- package/dist/utils/scannerConfig.d.ts +6 -10
- package/dist/utils/scannerConfig.d.ts.map +1 -1
- package/dist/utils/scannerConfig.js +57 -50
- package/dist/utils/scannerConfig.js.map +1 -1
- package/docs/.vitepress/config.ts +44 -0
- package/docs/README.md +23 -0
- package/docs/api/index.md +11 -0
- package/docs/guide/index.md +14 -0
- package/docs/guide/installation.md +19 -0
- package/docs/guide/usage.md +14 -0
- package/docs/index.md +23 -0
- package/eslint.config.js +90 -0
- package/package.json +33 -4
- package/ui/components/App.tsx +11 -11
- package/ui/components/ComponentTable.css +88 -0
- package/ui/components/ComponentTable.tsx +85 -33
- package/ui/index.css +6 -2
- package/ui/index.html +9 -9
- package/ui/main.tsx +6 -6
- package/ui/vite.config.ts +6 -6
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineConfig } from 'vitepress';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
title: 'React Scanner UI',
|
|
5
|
+
description: 'A React component scanner and visualization tool',
|
|
6
|
+
base: '/react-scanner-ui/',
|
|
7
|
+
|
|
8
|
+
themeConfig: {
|
|
9
|
+
nav: [
|
|
10
|
+
{ text: 'Home', link: '/' },
|
|
11
|
+
{ text: 'Guide', link: '/guide/' },
|
|
12
|
+
{ text: 'API', link: '/api/' },
|
|
13
|
+
],
|
|
14
|
+
|
|
15
|
+
sidebar: [
|
|
16
|
+
{
|
|
17
|
+
text: 'Guide',
|
|
18
|
+
items: [
|
|
19
|
+
{ text: 'Getting Started', link: '/guide/' },
|
|
20
|
+
{ text: 'Installation', link: '/guide/installation' },
|
|
21
|
+
{ text: 'Usage', link: '/guide/usage' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
text: 'API Reference',
|
|
26
|
+
items: [{ text: 'API Overview', link: '/api/' }],
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
socialLinks: [
|
|
31
|
+
{
|
|
32
|
+
icon: 'github',
|
|
33
|
+
link: 'https://github.com/vimalmunjani/react-scanner-ui',
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
|
|
37
|
+
footer: {
|
|
38
|
+
message: 'Released under the MIT License.',
|
|
39
|
+
copyright: 'Copyright © 2024 Vimal Munjani',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
head: [['link', { rel: 'icon', href: '/react-scanner-ui/favicon.ico' }]],
|
|
44
|
+
});
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
This directory contains the VitePress documentation for React Scanner UI.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
To run the documentation locally:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn dev:docs
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Building
|
|
14
|
+
|
|
15
|
+
To build the documentation:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn build:docs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Deployment
|
|
22
|
+
|
|
23
|
+
The documentation is automatically deployed to GitHub Pages when changes are pushed to the main branch via GitHub Actions.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
React Scanner UI is a tool for scanning and visualizing React components in your project.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
1. Install the package globally or use npx
|
|
8
|
+
2. Run the scanner in your React project
|
|
9
|
+
3. View the interactive UI
|
|
10
|
+
|
|
11
|
+
## Next Steps
|
|
12
|
+
|
|
13
|
+
- [Installation Guide](./installation)
|
|
14
|
+
- [Usage Instructions](./usage)
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
layout: home
|
|
3
|
+
|
|
4
|
+
hero:
|
|
5
|
+
name: 'React Scanner UI'
|
|
6
|
+
text: 'Component Scanner & Visualization Tool'
|
|
7
|
+
tagline: 'Scan, analyze, and visualize your React components with ease'
|
|
8
|
+
actions:
|
|
9
|
+
- theme: brand
|
|
10
|
+
text: Get Started
|
|
11
|
+
link: /guide/
|
|
12
|
+
- theme: alt
|
|
13
|
+
text: View on GitHub
|
|
14
|
+
link: https://github.com/vimalmunjani/react-scanner-ui
|
|
15
|
+
|
|
16
|
+
features:
|
|
17
|
+
- title: Component Scanning
|
|
18
|
+
details: Automatically scan and analyze React components in your project
|
|
19
|
+
- title: Visual Interface
|
|
20
|
+
details: Interactive UI for exploring component structure and relationships
|
|
21
|
+
- title: Easy Integration
|
|
22
|
+
details: Simple CLI tool that works with any React project
|
|
23
|
+
---
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import typescript from '@typescript-eslint/eslint-plugin';
|
|
3
|
+
import typescriptParser from '@typescript-eslint/parser';
|
|
4
|
+
import react from 'eslint-plugin-react';
|
|
5
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
6
|
+
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
7
|
+
import prettier from 'eslint-plugin-prettier';
|
|
8
|
+
import prettierConfig from 'eslint-config-prettier';
|
|
9
|
+
|
|
10
|
+
export default [
|
|
11
|
+
js.configs.recommended,
|
|
12
|
+
{
|
|
13
|
+
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
14
|
+
languageOptions: {
|
|
15
|
+
ecmaVersion: 'latest',
|
|
16
|
+
sourceType: 'module',
|
|
17
|
+
parser: typescriptParser,
|
|
18
|
+
parserOptions: {
|
|
19
|
+
ecmaFeatures: {
|
|
20
|
+
jsx: true,
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
globals: {
|
|
24
|
+
console: 'readonly',
|
|
25
|
+
process: 'readonly',
|
|
26
|
+
Buffer: 'readonly',
|
|
27
|
+
__dirname: 'readonly',
|
|
28
|
+
__filename: 'readonly',
|
|
29
|
+
module: 'readonly',
|
|
30
|
+
require: 'readonly',
|
|
31
|
+
exports: 'readonly',
|
|
32
|
+
global: 'readonly',
|
|
33
|
+
window: 'readonly',
|
|
34
|
+
document: 'readonly',
|
|
35
|
+
navigator: 'readonly',
|
|
36
|
+
setTimeout: 'readonly',
|
|
37
|
+
clearTimeout: 'readonly',
|
|
38
|
+
setInterval: 'readonly',
|
|
39
|
+
clearInterval: 'readonly',
|
|
40
|
+
NodeJS: 'readonly',
|
|
41
|
+
fetch: 'readonly',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
plugins: {
|
|
45
|
+
'@typescript-eslint': typescript,
|
|
46
|
+
react: react,
|
|
47
|
+
'react-hooks': reactHooks,
|
|
48
|
+
'react-refresh': reactRefresh,
|
|
49
|
+
prettier: prettier,
|
|
50
|
+
},
|
|
51
|
+
rules: {
|
|
52
|
+
...typescript.configs.recommended.rules,
|
|
53
|
+
...react.configs.recommended.rules,
|
|
54
|
+
...reactHooks.configs.recommended.rules,
|
|
55
|
+
...prettierConfig.rules,
|
|
56
|
+
'prettier/prettier': 'error',
|
|
57
|
+
'react/react-in-jsx-scope': 'off',
|
|
58
|
+
'react/prop-types': 'off',
|
|
59
|
+
'@typescript-eslint/no-unused-vars': [
|
|
60
|
+
'error',
|
|
61
|
+
{ argsIgnorePattern: '^_' },
|
|
62
|
+
],
|
|
63
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
64
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
65
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
66
|
+
'react-hooks/rules-of-hooks': 'error',
|
|
67
|
+
'react-hooks/exhaustive-deps': 'warn',
|
|
68
|
+
'react-refresh/only-export-components': [
|
|
69
|
+
'warn',
|
|
70
|
+
{ allowConstantExport: true },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
settings: {
|
|
74
|
+
react: {
|
|
75
|
+
version: 'detect',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
ignores: [
|
|
81
|
+
'dist/',
|
|
82
|
+
'node_modules/',
|
|
83
|
+
'build/',
|
|
84
|
+
'*.min.js',
|
|
85
|
+
'*.min.css',
|
|
86
|
+
'docs/.vitepress/dist/',
|
|
87
|
+
'docs/.vitepress/cache/',
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-scanner-ui",
|
|
3
|
-
"
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.10",
|
|
4
5
|
"main": "dist/index.js",
|
|
5
6
|
"bin": {
|
|
6
7
|
"react-scanner-ui": "dist/index.js"
|
|
@@ -17,17 +18,24 @@
|
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsc",
|
|
19
20
|
"build:ui": "vite build --config ui/vite.config.ts",
|
|
21
|
+
"build:docs": "vitepress build docs",
|
|
20
22
|
"start": "node dist/index.js",
|
|
21
23
|
"dev": "ts-node src/index.ts",
|
|
22
24
|
"dev:ui": "vite --config ui/vite.config.ts",
|
|
25
|
+
"dev:docs": "vitepress dev docs",
|
|
26
|
+
"preview:docs": "vitepress preview docs",
|
|
23
27
|
"prepublishOnly": "yarn build",
|
|
24
|
-
"publish:npm": "yarn build && npm publish"
|
|
28
|
+
"publish:npm": "yarn build && npm publish",
|
|
29
|
+
"prepare": "husky",
|
|
30
|
+
"lint": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
|
|
31
|
+
"lint:check": "eslint . --ext .ts,.tsx,.js,.jsx",
|
|
32
|
+
"format": "prettier --write .",
|
|
33
|
+
"format:check": "prettier --check ."
|
|
25
34
|
},
|
|
26
35
|
"dependencies": {
|
|
27
36
|
"@vitejs/plugin-react": "^4.3.4",
|
|
28
37
|
"commander": "^14.0.2",
|
|
29
38
|
"detect-port": "^1.6.1",
|
|
30
|
-
"polka": "^0.5.2",
|
|
31
39
|
"react": "^18.3.1",
|
|
32
40
|
"react-dom": "^18.3.1",
|
|
33
41
|
"vite": "^5.4.19"
|
|
@@ -37,7 +45,28 @@
|
|
|
37
45
|
"@types/polka": "^0.5.7",
|
|
38
46
|
"@types/react": "^18.3.18",
|
|
39
47
|
"@types/react-dom": "^18.3.5",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
49
|
+
"@typescript-eslint/parser": "^8.52.0",
|
|
50
|
+
"eslint": "^9.39.2",
|
|
51
|
+
"eslint-config-prettier": "^10.1.8",
|
|
52
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
53
|
+
"eslint-plugin-react": "^7.37.5",
|
|
54
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
55
|
+
"eslint-plugin-react-refresh": "^0.4.26",
|
|
56
|
+
"husky": "^9.1.7",
|
|
57
|
+
"lint-staged": "^16.2.7",
|
|
58
|
+
"prettier": "^3.7.4",
|
|
40
59
|
"ts-node": "^10.9.2",
|
|
41
|
-
"typescript": "^5.9.3"
|
|
60
|
+
"typescript": "^5.9.3",
|
|
61
|
+
"vitepress": "^1.6.4"
|
|
62
|
+
},
|
|
63
|
+
"lint-staged": {
|
|
64
|
+
"*.{ts,tsx,js,jsx}": [
|
|
65
|
+
"eslint --fix",
|
|
66
|
+
"prettier --write"
|
|
67
|
+
],
|
|
68
|
+
"*.{json,md,css,scss,yaml,yml}": [
|
|
69
|
+
"prettier --write"
|
|
70
|
+
]
|
|
42
71
|
}
|
|
43
72
|
}
|
package/ui/components/App.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useState, useEffect } from
|
|
2
|
-
import { ComponentTable } from
|
|
3
|
-
import
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { ComponentTable } from './ComponentTable';
|
|
3
|
+
import './App.css';
|
|
4
4
|
|
|
5
5
|
export interface ScanData {
|
|
6
6
|
[componentName: string]: {
|
|
@@ -20,8 +20,8 @@ function App() {
|
|
|
20
20
|
const [loading, setLoading] = useState(true);
|
|
21
21
|
|
|
22
22
|
useEffect(() => {
|
|
23
|
-
fetch(
|
|
24
|
-
.then(
|
|
23
|
+
fetch('/api/scan-data')
|
|
24
|
+
.then(res => res.json())
|
|
25
25
|
.then((result: ApiResponse) => {
|
|
26
26
|
if (result.error) {
|
|
27
27
|
setError(result.error);
|
|
@@ -30,20 +30,20 @@ function App() {
|
|
|
30
30
|
}
|
|
31
31
|
setLoading(false);
|
|
32
32
|
})
|
|
33
|
-
.catch(
|
|
34
|
-
setError(
|
|
33
|
+
.catch(err => {
|
|
34
|
+
setError('Failed to fetch scan data: ' + err.message);
|
|
35
35
|
setLoading(false);
|
|
36
36
|
});
|
|
37
37
|
}, []);
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
|
-
<div className=
|
|
40
|
+
<div className='container'>
|
|
41
41
|
<h1>React Scanner UI</h1>
|
|
42
|
-
{loading && <div className=
|
|
43
|
-
{error && <div className=
|
|
42
|
+
{loading && <div className='loading'>Loading scan data...</div>}
|
|
43
|
+
{error && <div className='error'>{error}</div>}
|
|
44
44
|
{!loading && !error && data && <ComponentTable data={data} />}
|
|
45
45
|
{!loading && !error && !data && (
|
|
46
|
-
<div className=
|
|
46
|
+
<div className='error'>No component data found.</div>
|
|
47
47
|
)}
|
|
48
48
|
</div>
|
|
49
49
|
);
|
|
@@ -59,3 +59,91 @@
|
|
|
59
59
|
.component-table .total-row:hover td {
|
|
60
60
|
background-color: var(--color-bg-total);
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/* Expandable rows */
|
|
64
|
+
.clickable-row {
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: background-color 0.15s ease;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.clickable-row.has-props:hover td {
|
|
70
|
+
background-color: var(--color-bg-hover);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.clickable-row.expanded td {
|
|
74
|
+
background-color: var(--color-bg-hover);
|
|
75
|
+
border-bottom: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.expand-icon {
|
|
79
|
+
display: inline-block;
|
|
80
|
+
width: 16px;
|
|
81
|
+
margin-right: 8px;
|
|
82
|
+
font-size: 0.7em;
|
|
83
|
+
color: var(--color-text-muted);
|
|
84
|
+
transition: transform 0.2s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.expand-icon.expanded {
|
|
88
|
+
transform: rotate(90deg);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Props row */
|
|
92
|
+
.props-row td {
|
|
93
|
+
padding: 8px 16px 16px 16px;
|
|
94
|
+
background-color: var(--color-bg-hover);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.props-container {
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: flex-start;
|
|
100
|
+
gap: 12px;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.props-label {
|
|
104
|
+
font-size: 0.85em;
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
color: var(--color-text-muted);
|
|
107
|
+
padding-top: 4px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.props-chips {
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-wrap: wrap;
|
|
113
|
+
gap: 8px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.prop-chip {
|
|
117
|
+
display: inline-flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
gap: 6px;
|
|
120
|
+
padding: 4px 10px;
|
|
121
|
+
background-color: var(--color-bg-card);
|
|
122
|
+
border: 1px solid var(--color-border);
|
|
123
|
+
border-radius: 16px;
|
|
124
|
+
font-size: 0.85em;
|
|
125
|
+
color: var(--color-text);
|
|
126
|
+
transition:
|
|
127
|
+
background-color 0.15s ease,
|
|
128
|
+
border-color 0.15s ease;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.prop-chip:hover {
|
|
132
|
+
background-color: var(--color-bg-header);
|
|
133
|
+
border-color: var(--color-border-strong);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.prop-count {
|
|
137
|
+
display: inline-flex;
|
|
138
|
+
align-items: center;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
min-width: 20px;
|
|
141
|
+
height: 20px;
|
|
142
|
+
padding: 0 6px;
|
|
143
|
+
background-color: var(--color-primary, #4a90d9);
|
|
144
|
+
color: white;
|
|
145
|
+
border-radius: 10px;
|
|
146
|
+
font-size: 0.8em;
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
font-family: var(--font-mono);
|
|
149
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { useState } from
|
|
2
|
-
import type { ScanData } from
|
|
3
|
-
import
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type { ScanData } from './App';
|
|
3
|
+
import './ComponentTable.css';
|
|
4
4
|
|
|
5
5
|
interface ComponentTableProps {
|
|
6
6
|
data: ScanData;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
type SortKey =
|
|
10
|
-
type SortDirection =
|
|
9
|
+
type SortKey = 'name' | 'count';
|
|
10
|
+
type SortDirection = 'asc' | 'desc';
|
|
11
11
|
|
|
12
12
|
interface SortConfig {
|
|
13
13
|
key: SortKey;
|
|
@@ -16,70 +16,122 @@ interface SortConfig {
|
|
|
16
16
|
|
|
17
17
|
export function ComponentTable({ data }: ComponentTableProps) {
|
|
18
18
|
const [sortConfig, setSortConfig] = useState<SortConfig>({
|
|
19
|
-
key:
|
|
20
|
-
direction:
|
|
19
|
+
key: 'count',
|
|
20
|
+
direction: 'desc',
|
|
21
21
|
});
|
|
22
|
+
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
|
22
23
|
|
|
23
24
|
const handleSort = (key: SortKey) => {
|
|
24
|
-
setSortConfig(
|
|
25
|
+
setSortConfig(prev => ({
|
|
25
26
|
key,
|
|
26
|
-
direction: prev.key === key && prev.direction ===
|
|
27
|
+
direction: prev.key === key && prev.direction === 'asc' ? 'desc' : 'asc',
|
|
27
28
|
}));
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
const getSortIndicator = (key: SortKey) => {
|
|
31
|
-
if (sortConfig.key !== key) return
|
|
32
|
-
return sortConfig.direction ===
|
|
32
|
+
if (sortConfig.key !== key) return '';
|
|
33
|
+
return sortConfig.direction === 'asc' ? '▲' : '▼';
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const toggleRow = (name: string) => {
|
|
37
|
+
setExpandedRows(prev => {
|
|
38
|
+
const next = new Set(prev);
|
|
39
|
+
if (next.has(name)) {
|
|
40
|
+
next.delete(name);
|
|
41
|
+
} else {
|
|
42
|
+
next.add(name);
|
|
43
|
+
}
|
|
44
|
+
return next;
|
|
45
|
+
});
|
|
33
46
|
};
|
|
34
47
|
|
|
35
48
|
// Transform data into array and sort
|
|
36
49
|
const components = Object.entries(data).map(([name, info]) => ({
|
|
37
50
|
name,
|
|
38
|
-
count: info.instances
|
|
51
|
+
count: info.instances,
|
|
52
|
+
props: info.props || {},
|
|
39
53
|
}));
|
|
40
54
|
|
|
41
55
|
components.sort((a, b) => {
|
|
42
|
-
const aValue = sortConfig.key ===
|
|
43
|
-
const bValue = sortConfig.key ===
|
|
56
|
+
const aValue = sortConfig.key === 'name' ? a.name.toLowerCase() : a.count;
|
|
57
|
+
const bValue = sortConfig.key === 'name' ? b.name.toLowerCase() : b.count;
|
|
44
58
|
|
|
45
|
-
if (aValue < bValue) return sortConfig.direction ===
|
|
46
|
-
if (aValue > bValue) return sortConfig.direction ===
|
|
59
|
+
if (aValue < bValue) return sortConfig.direction === 'asc' ? -1 : 1;
|
|
60
|
+
if (aValue > bValue) return sortConfig.direction === 'asc' ? 1 : -1;
|
|
47
61
|
return 0;
|
|
48
62
|
});
|
|
49
63
|
|
|
50
64
|
const totalCount = components.reduce((sum, c) => sum + c.count, 0);
|
|
51
65
|
|
|
52
66
|
if (components.length === 0) {
|
|
53
|
-
return <div className=
|
|
67
|
+
return <div className='empty-state'>No components found in scan data.</div>;
|
|
54
68
|
}
|
|
55
69
|
|
|
56
70
|
return (
|
|
57
|
-
<div className=
|
|
58
|
-
<table className=
|
|
71
|
+
<div className='table-container'>
|
|
72
|
+
<table className='component-table'>
|
|
59
73
|
<thead>
|
|
60
74
|
<tr>
|
|
61
|
-
<th onClick={() => handleSort(
|
|
75
|
+
<th onClick={() => handleSort('name')}>
|
|
62
76
|
Component
|
|
63
|
-
<span className=
|
|
77
|
+
<span className='sort-indicator'>{getSortIndicator('name')}</span>
|
|
64
78
|
</th>
|
|
65
|
-
<th onClick={() => handleSort(
|
|
66
|
-
Count
|
|
67
|
-
<span className=
|
|
68
|
-
{getSortIndicator(
|
|
79
|
+
<th onClick={() => handleSort('count')}>
|
|
80
|
+
Usage Count
|
|
81
|
+
<span className='sort-indicator'>
|
|
82
|
+
{getSortIndicator('count')}
|
|
69
83
|
</span>
|
|
70
84
|
</th>
|
|
71
85
|
</tr>
|
|
72
86
|
</thead>
|
|
73
87
|
<tbody>
|
|
74
|
-
{components.map(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
{components.map(component => {
|
|
89
|
+
const isExpanded = expandedRows.has(component.name);
|
|
90
|
+
const hasProps = Object.keys(component.props).length > 0;
|
|
91
|
+
const sortedProps = Object.entries(component.props).sort(
|
|
92
|
+
([, a], [, b]) => b - a
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<>
|
|
97
|
+
<tr
|
|
98
|
+
key={component.name}
|
|
99
|
+
onClick={() => toggleRow(component.name)}
|
|
100
|
+
className={`clickable-row ${isExpanded ? 'expanded' : ''} ${hasProps ? 'has-props' : ''}`}
|
|
101
|
+
>
|
|
102
|
+
<td className='component-name'>
|
|
103
|
+
<span
|
|
104
|
+
className={`expand-icon ${isExpanded ? 'expanded' : ''}`}
|
|
105
|
+
>
|
|
106
|
+
{hasProps ? '▶' : ''}
|
|
107
|
+
</span>
|
|
108
|
+
{component.name}
|
|
109
|
+
</td>
|
|
110
|
+
<td className='count'>{component.count}</td>
|
|
111
|
+
</tr>
|
|
112
|
+
{isExpanded && hasProps && (
|
|
113
|
+
<tr key={`${component.name}-props`} className='props-row'>
|
|
114
|
+
<td colSpan={2}>
|
|
115
|
+
<div className='props-container'>
|
|
116
|
+
<span className='props-label'>Props:</span>
|
|
117
|
+
<div className='props-chips'>
|
|
118
|
+
{sortedProps.map(([propName, propCount]) => (
|
|
119
|
+
<span key={propName} className='prop-chip'>
|
|
120
|
+
{propName}
|
|
121
|
+
<span className='prop-count'>{propCount}</span>
|
|
122
|
+
</span>
|
|
123
|
+
))}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</td>
|
|
127
|
+
</tr>
|
|
128
|
+
)}
|
|
129
|
+
</>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
<tr className='total-row'>
|
|
81
133
|
<td>Total</td>
|
|
82
|
-
<td className=
|
|
134
|
+
<td className='count'>{totalCount}</td>
|
|
83
135
|
</tr>
|
|
84
136
|
</tbody>
|
|
85
137
|
</table>
|
package/ui/index.css
CHANGED
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
--radius-sm: 4px;
|
|
24
24
|
--radius-md: 8px;
|
|
25
25
|
--font-mono: 'SF Mono', Monaco, 'Courier New', monospace;
|
|
26
|
-
--font-sans:
|
|
26
|
+
--font-sans:
|
|
27
|
+
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
|
28
|
+
sans-serif;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
body {
|
|
@@ -54,6 +56,8 @@ button {
|
|
|
54
56
|
cursor: pointer;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
input,
|
|
59
|
+
input,
|
|
60
|
+
select,
|
|
61
|
+
textarea {
|
|
58
62
|
font-family: inherit;
|
|
59
63
|
}
|
package/ui/index.html
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>React Scanner UI</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
12
|
</html>
|