react-scanner-ui 0.0.9 → 0.0.11
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.js +19 -19
- package/dist/server/index.js.map +1 -1
- package/dist/utils/config.js +4 -4
- 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/port.js +1 -1
- package/dist/utils/scannerConfig.d.ts +3 -3
- package/dist/utils/scannerConfig.d.ts.map +1 -1
- package/dist/utils/scannerConfig.js +49 -17
- 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 -3
- package/ui/components/App.tsx +11 -11
- package/ui/components/ComponentTable.css +78 -78
- package/ui/components/ComponentTable.tsx +39 -39
- 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
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.11",
|
|
4
5
|
"main": "dist/index.js",
|
|
5
6
|
"bin": {
|
|
6
7
|
"react-scanner-ui": "dist/index.js"
|
|
@@ -17,11 +18,19 @@
|
|
|
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",
|
|
@@ -36,7 +45,28 @@
|
|
|
36
45
|
"@types/polka": "^0.5.7",
|
|
37
46
|
"@types/react": "^18.3.18",
|
|
38
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",
|
|
39
59
|
"ts-node": "^10.9.2",
|
|
40
|
-
"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
|
+
]
|
|
41
71
|
}
|
|
42
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
|
);
|
|
@@ -1,149 +1,149 @@
|
|
|
1
1
|
.component-table {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
width: 100%;
|
|
3
|
+
border-collapse: collapse;
|
|
4
|
+
background-color: var(--color-bg-card);
|
|
5
|
+
box-shadow: var(--shadow-sm);
|
|
6
|
+
border-radius: var(--radius-md);
|
|
7
|
+
overflow: hidden;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
.component-table th,
|
|
11
11
|
.component-table td {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
padding: 12px 16px;
|
|
13
|
+
text-align: left;
|
|
14
|
+
border-bottom: 1px solid var(--color-border);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.component-table th {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
background-color: var(--color-bg-header);
|
|
19
|
+
font-weight: 600;
|
|
20
|
+
color: var(--color-text);
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
user-select: none;
|
|
23
|
+
transition: background-color 0.15s ease;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
.component-table th:hover {
|
|
27
|
-
|
|
27
|
+
background-color: #e9ecef;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
.component-table th .sort-indicator {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
margin-left: 5px;
|
|
32
|
+
color: var(--color-text-muted);
|
|
33
|
+
font-size: 0.75em;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
.component-table tbody tr:last-child td {
|
|
37
|
-
|
|
37
|
+
border-bottom: none;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
.component-table tbody tr:hover td {
|
|
41
|
-
|
|
41
|
+
background-color: var(--color-bg-hover);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
.component-table .count {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
font-family: var(--font-mono);
|
|
46
|
+
font-weight: 500;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
.component-table .total-row {
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
background-color: var(--color-bg-total);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
.component-table .total-row td {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
border-top: 2px solid var(--color-border-strong);
|
|
56
|
+
border-bottom: none;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
.component-table .total-row:hover td {
|
|
60
|
-
|
|
60
|
+
background-color: var(--color-bg-total);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/* Expandable rows */
|
|
64
64
|
.clickable-row {
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
transition: background-color 0.15s ease;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
.clickable-row.has-props:hover td {
|
|
70
|
-
|
|
70
|
+
background-color: var(--color-bg-hover);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
.clickable-row.expanded td {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
background-color: var(--color-bg-hover);
|
|
75
|
+
border-bottom: none;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
.expand-icon {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
85
|
}
|
|
86
86
|
|
|
87
87
|
.expand-icon.expanded {
|
|
88
|
-
|
|
88
|
+
transform: rotate(90deg);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/* Props row */
|
|
92
92
|
.props-row td {
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
padding: 8px 16px 16px 16px;
|
|
94
|
+
background-color: var(--color-bg-hover);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
.props-container {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: flex-start;
|
|
100
|
+
gap: 12px;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
.props-label {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
font-size: 0.85em;
|
|
105
|
+
font-weight: 600;
|
|
106
|
+
color: var(--color-text-muted);
|
|
107
|
+
padding-top: 4px;
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
.props-chips {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-wrap: wrap;
|
|
113
|
+
gap: 8px;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
.prop-chip {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
129
|
}
|
|
130
130
|
|
|
131
131
|
.prop-chip:hover {
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
background-color: var(--color-bg-header);
|
|
133
|
+
border-color: var(--color-border-strong);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
.prop-count {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
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,25 +16,25 @@ 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
22
|
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
|
|
23
23
|
|
|
24
24
|
const handleSort = (key: SortKey) => {
|
|
25
|
-
setSortConfig(
|
|
25
|
+
setSortConfig(prev => ({
|
|
26
26
|
key,
|
|
27
|
-
direction: prev.key === key && prev.direction ===
|
|
27
|
+
direction: prev.key === key && prev.direction === 'asc' ? 'desc' : 'asc',
|
|
28
28
|
}));
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const getSortIndicator = (key: SortKey) => {
|
|
32
|
-
if (sortConfig.key !== key) return
|
|
33
|
-
return sortConfig.direction ===
|
|
32
|
+
if (sortConfig.key !== key) return '';
|
|
33
|
+
return sortConfig.direction === 'asc' ? '▲' : '▼';
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const toggleRow = (name: string) => {
|
|
37
|
-
setExpandedRows(
|
|
37
|
+
setExpandedRows(prev => {
|
|
38
38
|
const next = new Set(prev);
|
|
39
39
|
if (next.has(name)) {
|
|
40
40
|
next.delete(name);
|
|
@@ -53,43 +53,43 @@ export function ComponentTable({ data }: ComponentTableProps) {
|
|
|
53
53
|
}));
|
|
54
54
|
|
|
55
55
|
components.sort((a, b) => {
|
|
56
|
-
const aValue = sortConfig.key ===
|
|
57
|
-
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;
|
|
58
58
|
|
|
59
|
-
if (aValue < bValue) return sortConfig.direction ===
|
|
60
|
-
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;
|
|
61
61
|
return 0;
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
const totalCount = components.reduce((sum, c) => sum + c.count, 0);
|
|
65
65
|
|
|
66
66
|
if (components.length === 0) {
|
|
67
|
-
return <div className=
|
|
67
|
+
return <div className='empty-state'>No components found in scan data.</div>;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
return (
|
|
71
|
-
<div className=
|
|
72
|
-
<table className=
|
|
71
|
+
<div className='table-container'>
|
|
72
|
+
<table className='component-table'>
|
|
73
73
|
<thead>
|
|
74
74
|
<tr>
|
|
75
|
-
<th onClick={() => handleSort(
|
|
75
|
+
<th onClick={() => handleSort('name')}>
|
|
76
76
|
Component
|
|
77
|
-
<span className=
|
|
77
|
+
<span className='sort-indicator'>{getSortIndicator('name')}</span>
|
|
78
78
|
</th>
|
|
79
|
-
<th onClick={() => handleSort(
|
|
79
|
+
<th onClick={() => handleSort('count')}>
|
|
80
80
|
Usage Count
|
|
81
|
-
<span className=
|
|
82
|
-
{getSortIndicator(
|
|
81
|
+
<span className='sort-indicator'>
|
|
82
|
+
{getSortIndicator('count')}
|
|
83
83
|
</span>
|
|
84
84
|
</th>
|
|
85
85
|
</tr>
|
|
86
86
|
</thead>
|
|
87
87
|
<tbody>
|
|
88
|
-
{components.map(
|
|
88
|
+
{components.map(component => {
|
|
89
89
|
const isExpanded = expandedRows.has(component.name);
|
|
90
90
|
const hasProps = Object.keys(component.props).length > 0;
|
|
91
91
|
const sortedProps = Object.entries(component.props).sort(
|
|
92
|
-
([, a], [, b]) => b - a
|
|
92
|
+
([, a], [, b]) => b - a
|
|
93
93
|
);
|
|
94
94
|
|
|
95
95
|
return (
|
|
@@ -97,28 +97,28 @@ export function ComponentTable({ data }: ComponentTableProps) {
|
|
|
97
97
|
<tr
|
|
98
98
|
key={component.name}
|
|
99
99
|
onClick={() => toggleRow(component.name)}
|
|
100
|
-
className={`clickable-row ${isExpanded ?
|
|
100
|
+
className={`clickable-row ${isExpanded ? 'expanded' : ''} ${hasProps ? 'has-props' : ''}`}
|
|
101
101
|
>
|
|
102
|
-
<td className=
|
|
102
|
+
<td className='component-name'>
|
|
103
103
|
<span
|
|
104
|
-
className={`expand-icon ${isExpanded ?
|
|
104
|
+
className={`expand-icon ${isExpanded ? 'expanded' : ''}`}
|
|
105
105
|
>
|
|
106
|
-
{hasProps ?
|
|
106
|
+
{hasProps ? '▶' : ''}
|
|
107
107
|
</span>
|
|
108
108
|
{component.name}
|
|
109
109
|
</td>
|
|
110
|
-
<td className=
|
|
110
|
+
<td className='count'>{component.count}</td>
|
|
111
111
|
</tr>
|
|
112
112
|
{isExpanded && hasProps && (
|
|
113
|
-
<tr key={`${component.name}-props`} className=
|
|
113
|
+
<tr key={`${component.name}-props`} className='props-row'>
|
|
114
114
|
<td colSpan={2}>
|
|
115
|
-
<div className=
|
|
116
|
-
<span className=
|
|
117
|
-
<div className=
|
|
115
|
+
<div className='props-container'>
|
|
116
|
+
<span className='props-label'>Props:</span>
|
|
117
|
+
<div className='props-chips'>
|
|
118
118
|
{sortedProps.map(([propName, propCount]) => (
|
|
119
|
-
<span key={propName} className=
|
|
119
|
+
<span key={propName} className='prop-chip'>
|
|
120
120
|
{propName}
|
|
121
|
-
<span className=
|
|
121
|
+
<span className='prop-count'>{propCount}</span>
|
|
122
122
|
</span>
|
|
123
123
|
))}
|
|
124
124
|
</div>
|
|
@@ -129,9 +129,9 @@ export function ComponentTable({ data }: ComponentTableProps) {
|
|
|
129
129
|
</>
|
|
130
130
|
);
|
|
131
131
|
})}
|
|
132
|
-
<tr className=
|
|
132
|
+
<tr className='total-row'>
|
|
133
133
|
<td>Total</td>
|
|
134
|
-
<td className=
|
|
134
|
+
<td className='count'>{totalCount}</td>
|
|
135
135
|
</tr>
|
|
136
136
|
</tbody>
|
|
137
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>
|