@starodubenko/fsd-gen 0.1.0

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.
Files changed (81) hide show
  1. package/README.md +61 -0
  2. package/cli.js +114 -0
  3. package/dist/config/defineConfig.d.ts +8 -0
  4. package/dist/config/defineConfig.d.ts.map +1 -0
  5. package/dist/config/defineConfig.js +8 -0
  6. package/dist/config/types.d.ts +98 -0
  7. package/dist/config/types.d.ts.map +1 -0
  8. package/dist/config/types.js +9 -0
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +3 -0
  12. package/dist/lib/barrels/updateBarrels.d.ts +2 -0
  13. package/dist/lib/barrels/updateBarrels.d.ts.map +1 -0
  14. package/dist/lib/barrels/updateBarrels.js +28 -0
  15. package/dist/lib/config/loadConfig.d.ts +21 -0
  16. package/dist/lib/config/loadConfig.d.ts.map +1 -0
  17. package/dist/lib/config/loadConfig.js +61 -0
  18. package/dist/lib/config/validateConfig.d.ts +13 -0
  19. package/dist/lib/config/validateConfig.d.ts.map +1 -0
  20. package/dist/lib/config/validateConfig.js +19 -0
  21. package/dist/lib/constants.d.ts +113 -0
  22. package/dist/lib/constants.d.ts.map +1 -0
  23. package/dist/lib/constants.js +129 -0
  24. package/dist/lib/generators/generate.d.ts +45 -0
  25. package/dist/lib/generators/generate.d.ts.map +1 -0
  26. package/dist/lib/generators/generate.js +104 -0
  27. package/dist/lib/generators/generatePreset.d.ts +6 -0
  28. package/dist/lib/generators/generatePreset.d.ts.map +1 -0
  29. package/dist/lib/generators/generatePreset.js +102 -0
  30. package/dist/lib/helpers/presetHelpers.d.ts +55 -0
  31. package/dist/lib/helpers/presetHelpers.d.ts.map +1 -0
  32. package/dist/lib/helpers/presetHelpers.js +53 -0
  33. package/dist/lib/naming/names.d.ts +10 -0
  34. package/dist/lib/naming/names.d.ts.map +1 -0
  35. package/dist/lib/naming/names.js +15 -0
  36. package/dist/lib/naming/resolvePaths.d.ts +33 -0
  37. package/dist/lib/naming/resolvePaths.d.ts.map +1 -0
  38. package/dist/lib/naming/resolvePaths.js +58 -0
  39. package/dist/lib/preset/actionExecution.d.ts +32 -0
  40. package/dist/lib/preset/actionExecution.d.ts.map +1 -0
  41. package/dist/lib/preset/actionExecution.js +138 -0
  42. package/dist/lib/preset/presetDiscovery.d.ts +40 -0
  43. package/dist/lib/preset/presetDiscovery.d.ts.map +1 -0
  44. package/dist/lib/preset/presetDiscovery.js +189 -0
  45. package/dist/lib/preset/presetLoading.d.ts +22 -0
  46. package/dist/lib/preset/presetLoading.d.ts.map +1 -0
  47. package/dist/lib/preset/presetLoading.js +69 -0
  48. package/dist/lib/routing/injectRoute.d.ts +17 -0
  49. package/dist/lib/routing/injectRoute.d.ts.map +1 -0
  50. package/dist/lib/routing/injectRoute.js +66 -0
  51. package/dist/lib/templates/templateLoader.d.ts +31 -0
  52. package/dist/lib/templates/templateLoader.d.ts.map +1 -0
  53. package/dist/lib/templates/templateLoader.js +122 -0
  54. package/package.json +64 -0
  55. package/templates/entity/model-ui-basic/Component.styles.ts +1 -0
  56. package/templates/entity/model-ui-basic/Component.tsx +18 -0
  57. package/templates/feature/ui-model-basic/Component.styles.ts +1 -0
  58. package/templates/feature/ui-model-basic/Component.tsx +17 -0
  59. package/templates/page/ui-basic/Component.styles.ts +1 -0
  60. package/templates/page/ui-basic/Component.tsx +17 -0
  61. package/templates/preset/table/entity/api/create/Component.tsx +19 -0
  62. package/templates/preset/table/entity/api/delete/Component.tsx +17 -0
  63. package/templates/preset/table/entity/api/get/Component.tsx +34 -0
  64. package/templates/preset/table/entity/api/update/Component.tsx +18 -0
  65. package/templates/preset/table/entity/model.ts +9 -0
  66. package/templates/preset/table/entity/ui/Component.tsx +11 -0
  67. package/templates/preset/table/feature/buttons/create/Component.styles.ts +1 -0
  68. package/templates/preset/table/feature/buttons/create/Component.tsx +30 -0
  69. package/templates/preset/table/feature/buttons/delete/Component.styles.ts +1 -0
  70. package/templates/preset/table/feature/buttons/delete/Component.tsx +30 -0
  71. package/templates/preset/table/feature/buttons/edit/Component.styles.ts +1 -0
  72. package/templates/preset/table/feature/buttons/edit/Component.tsx +30 -0
  73. package/templates/preset/table/feature/buttons.tsx +11 -0
  74. package/templates/preset/table/page/page/Component.tsx +19 -0
  75. package/templates/preset/table/page/page.tsx +19 -0
  76. package/templates/preset/table/widget/table/Component.tsx +50 -0
  77. package/templates/preset/table/widget/table.tsx +50 -0
  78. package/templates/shared/ui-basic/Component.styles.ts +1 -0
  79. package/templates/shared/ui-basic/Component.tsx +18 -0
  80. package/templates/widget/ui-basic/Component.styles.ts +1 -0
  81. package/templates/widget/ui-basic/Component.tsx +18 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Template loading and processing.
3
+ *
4
+ * Responsible for finding, reading, and processing template files. Handles variable
5
+ * substitution in template content and resolves template paths based on layers and types.
6
+ */
7
+ import { readFile, readdir, stat } from 'fs/promises';
8
+ import { join, dirname } from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { TEMPLATE_FILES, PRESET_DIRS } from '../constants.js';
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ /**
14
+ * Resolve the list of directories to search for templates
15
+ * Custom directory is checked first, then internal templates
16
+ */
17
+ export function resolveTemplateDirs(customTemplatesDir) {
18
+ const internalTemplatesDir = join(__dirname, '../../../templates');
19
+ if (customTemplatesDir) {
20
+ return [customTemplatesDir, internalTemplatesDir];
21
+ }
22
+ return [internalTemplatesDir];
23
+ }
24
+ /**
25
+ * Find the template directory in the search directories
26
+ * @returns The path to the template directory or null if not found
27
+ */
28
+ export async function findTemplateDir(layer, type, searchDirs) {
29
+ for (const templatesDir of searchDirs) {
30
+ const templateDir = layer
31
+ ? join(templatesDir, layer, type)
32
+ : join(templatesDir, type);
33
+ try {
34
+ await stat(templateDir);
35
+ return templateDir;
36
+ }
37
+ catch {
38
+ // Not found in this dir, continue
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ /**
44
+ * Read the component template file
45
+ */
46
+ export async function readComponentTemplate(templateDir) {
47
+ const componentPath = join(templateDir, TEMPLATE_FILES.COMPONENT);
48
+ return await readFile(componentPath, 'utf-8');
49
+ }
50
+ /**
51
+ * Read the styles template file (optional)
52
+ * @returns The styles content or empty string if not found
53
+ */
54
+ export async function readStylesTemplate(templateDir) {
55
+ const stylesPath = join(templateDir, TEMPLATE_FILES.STYLES);
56
+ try {
57
+ return await readFile(stylesPath, 'utf-8');
58
+ }
59
+ catch {
60
+ return '';
61
+ }
62
+ }
63
+ /**
64
+ * Load a template for a given layer and type
65
+ * Orchestrates finding and reading template files
66
+ */
67
+ export async function loadTemplate(layer, type = 'ui', customTemplatesDir) {
68
+ const searchDirs = resolveTemplateDirs(customTemplatesDir);
69
+ const templateDir = await findTemplateDir(layer, type, searchDirs);
70
+ if (!templateDir) {
71
+ console.warn(`Template not found: ${layer}/${type} in paths: ${searchDirs.join(', ')}`);
72
+ throw new Error(`Template not found: ${layer}/${type}`);
73
+ }
74
+ const component = await readComponentTemplate(templateDir);
75
+ const styles = await readStylesTemplate(templateDir);
76
+ console.log(`Loaded template from: ${templateDir}`);
77
+ return { component, styles };
78
+ }
79
+ export function processTemplate(content, variables) {
80
+ return content.replace(/\{\s*\{\s*(\w+)\s*\}\s*\}/g, (_, key) => String(variables[key] ?? ''));
81
+ }
82
+ export async function listPresets(customTemplatesDir) {
83
+ const internalTemplatesDir = join(__dirname, '../../../templates');
84
+ const presets = new Set();
85
+ const dirsToCheck = [internalTemplatesDir];
86
+ if (customTemplatesDir) {
87
+ dirsToCheck.unshift(customTemplatesDir);
88
+ }
89
+ for (const dir of dirsToCheck) {
90
+ const presetDir = join(dir, PRESET_DIRS.ROOT);
91
+ try {
92
+ const entries = await readdir(presetDir, { withFileTypes: true });
93
+ for (const entry of entries) {
94
+ if (entry.isDirectory()) {
95
+ presets.add(entry.name);
96
+ }
97
+ }
98
+ }
99
+ catch {
100
+ // Ignore if directory doesn't exist
101
+ }
102
+ }
103
+ return Array.from(presets);
104
+ }
105
+ export async function resolvePresetDir(presetName, customTemplatesDir) {
106
+ const internalTemplatesDir = join(__dirname, '../../../templates');
107
+ const dirsToCheck = [internalTemplatesDir];
108
+ if (customTemplatesDir) {
109
+ dirsToCheck.unshift(customTemplatesDir);
110
+ }
111
+ for (const dir of dirsToCheck) {
112
+ const presetDir = join(dir, PRESET_DIRS.ROOT, presetName);
113
+ try {
114
+ await stat(presetDir);
115
+ return presetDir;
116
+ }
117
+ catch {
118
+ // Check next
119
+ }
120
+ }
121
+ return null;
122
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@starodubenko/fsd-gen",
3
+ "version": "0.1.0",
4
+ "description": "A powerful CLI tool for scaffolding Feature-Sliced Design (FSD) components, slices, and layers.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "fsd-gen": "cli.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "cli.js",
14
+ "templates",
15
+ "README.md",
16
+ "package.json"
17
+ ],
18
+ "author": "Rodion Starodubenko",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/rodionstarodubenko/fsd-generator.git"
23
+ },
24
+ "keywords": [
25
+ "fsd",
26
+ "feature-sliced-design",
27
+ "generator",
28
+ "cli",
29
+ "react",
30
+ "scaffold"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "start": "node cli.js",
37
+ "build": "tsc -p tsconfig.build.json",
38
+ "lint": "eslint .",
39
+ "test": "yarn build && vitest run",
40
+ "test:ui": "yarn build && vitest --ui",
41
+ "test:coverage": "yarn build && vitest run --coverage",
42
+ "prepublishOnly": "yarn build && yarn test"
43
+ },
44
+ "dependencies": {
45
+ "chalk": "^5.4.1",
46
+ "commander": "^13.0.1",
47
+ "enquirer": "^2.4.1",
48
+ "inquirer": "^12.3.0",
49
+ "jiti": "^2.6.1",
50
+ "zod": "^3.24.1"
51
+ },
52
+ "devDependencies": {
53
+ "@eslint/js": "^9.39.1",
54
+ "@types/node": "^25.0.6",
55
+ "@vitest/coverage-v8": "4.0.16",
56
+ "@vitest/ui": "^4.0.16",
57
+ "eslint": "^9.39.1",
58
+ "tsx": "^4.19.2",
59
+ "typescript": "~5.9.3",
60
+ "typescript-eslint": "^8.46.4",
61
+ "vite-tsconfig-paths": "^6.0.4",
62
+ "vitest": "^4.0.16"
63
+ }
64
+ }
@@ -0,0 +1 @@
1
+ // Styles for {{componentName}}
@@ -0,0 +1,18 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ export const {{componentName}}Root = styled.div`
4
+ padding: 1rem;
5
+ border: 1px solid #ccc;
6
+ `;
7
+
8
+ export interface {{componentName}}Props {
9
+ className?: string;
10
+ }
11
+
12
+ export const {{componentName}} = ({ className }: {{componentName}}Props) => {
13
+ return (
14
+ <{{componentName}}Root className={className}>
15
+ {{componentName}} Entity
16
+ </{{componentName}}Root>
17
+ );
18
+ };
@@ -0,0 +1 @@
1
+ // Styles for Feature {{componentName}}
@@ -0,0 +1,17 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ export const {{componentName}}Root = styled.div`
4
+ padding: 1rem;
5
+ `;
6
+
7
+ export interface {{componentName}}Props {
8
+ className?: string;
9
+ }
10
+
11
+ export const {{componentName}} = ({ className }: {{componentName}}Props) => {
12
+ return (
13
+ <{{componentName}}Root className={className}>
14
+ Feature: {{componentName}}
15
+ </{{componentName}}Root>
16
+ );
17
+ };
@@ -0,0 +1 @@
1
+ // Styles for Page {{componentName}}
@@ -0,0 +1,17 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ export const {{componentName}}Root = styled.div`
4
+ min-height: 100vh;
5
+ `;
6
+
7
+ export interface {{componentName}}Props {
8
+ className?: string;
9
+ }
10
+
11
+ export const {{componentName}} = ({ className }: {{componentName}}Props) => {
12
+ return (
13
+ <{{componentName}}Root className={className}>
14
+ <h1>Page: {{componentName}}</h1>
15
+ </{{componentName}}Root>
16
+ );
17
+ };
@@ -0,0 +1,19 @@
1
+ import { useState } from 'react';
2
+ import type { {{componentName}} } from '../model/types';
3
+
4
+ // Mock API delay
5
+ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
6
+
7
+ export function useCreate{{componentName}}() {
8
+ const [isLoading, setIsLoading] = useState(false);
9
+
10
+ const mutate = async (newItem: Omit<{{componentName}}, 'id'>) => {
11
+ setIsLoading(true);
12
+ await delay(500);
13
+ console.log('Created:', newItem);
14
+ setIsLoading(false);
15
+ return { ...newItem, id: Math.random().toString() } as {{componentName}};
16
+ };
17
+
18
+ return { mutate, isLoading };
19
+ }
@@ -0,0 +1,17 @@
1
+ import { useState } from 'react';
2
+
3
+ // Mock API delay
4
+ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
5
+
6
+ export function useDelete{{componentName}}() {
7
+ const [isLoading, setIsLoading] = useState(false);
8
+
9
+ const mutate = async (id: string) => {
10
+ setIsLoading(true);
11
+ await delay(500);
12
+ console.log('Deleted:', id);
13
+ setIsLoading(false);
14
+ };
15
+
16
+ return { mutate, isLoading };
17
+ }
@@ -0,0 +1,34 @@
1
+ import { useState, useEffect } from 'react';
2
+ import type { {{componentName}} } from '../model/types';
3
+ import { mock{{componentName}}Data } from '../model/types';
4
+
5
+ // Mock API delay
6
+ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
7
+
8
+ export function useGet{{componentName}}s() {
9
+ const [data, setData] = useState<{{componentName}}[]>([]);
10
+ const [isLoading, setIsLoading] = useState(false);
11
+ const [error, setError] = useState<Error | null>(null);
12
+
13
+ useEffect(() => {
14
+ let mounted = true;
15
+
16
+ async function fetchData() {
17
+ setIsLoading(true);
18
+ try {
19
+ await delay(500);
20
+ if (mounted) setData(mock{{componentName}}Data);
21
+ } catch (e) {
22
+ if (mounted) setError(e as Error);
23
+ } finally {
24
+ if (mounted) setIsLoading(false);
25
+ }
26
+ }
27
+
28
+ fetchData();
29
+
30
+ return () => { mounted = false; };
31
+ }, []);
32
+
33
+ return { data, isLoading, error };
34
+ }
@@ -0,0 +1,18 @@
1
+ import { useState } from 'react';
2
+ import type { {{componentName}} } from '../model/types';
3
+
4
+ // Mock API delay
5
+ const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
6
+
7
+ export function useUpdate{{componentName}}() {
8
+ const [isLoading, setIsLoading] = useState(false);
9
+
10
+ const mutate = async (id: string, updates: Partial<{{componentName}}>) => {
11
+ setIsLoading(true);
12
+ await delay(500);
13
+ console.log('Updated:', id, updates);
14
+ setIsLoading(false);
15
+ };
16
+
17
+ return { mutate, isLoading };
18
+ }
@@ -0,0 +1,9 @@
1
+ export interface { { componentName } } {
2
+ id: string;
3
+ name: string;
4
+ }
5
+
6
+ export const mock{{ componentName }}Data: { { componentName } } [] = [
7
+ { id: '1', name: 'Test {{componentName}} 1' },
8
+ { id: '2', name: 'Test {{componentName}} 2' },
9
+ ];
@@ -0,0 +1,11 @@
1
+ import styled from '@emotion/styled';
2
+ import type { {{componentName}} as {{componentName}}Type } from '../model/types';
3
+
4
+ const Root = styled.div`
5
+ padding: 10px;
6
+ border: 1px solid #ccc;
7
+ `;
8
+
9
+ export const {{componentName}} = (props: { data: {{componentName}}Type }) => {
10
+ return <Root>{props.data.name}</Root>;
11
+ };
@@ -0,0 +1,30 @@
1
+ import styled from '@emotion/styled';
2
+ import { useCreate{{componentName}} } from '{{apiImportPath}}';
3
+
4
+ const Button = styled.button`
5
+ padding: 0.5rem 1rem;
6
+ margin: 0 0.5rem;
7
+ border-radius: 4px;
8
+ border: none;
9
+ background-color: #007bff;
10
+ color: white;
11
+ cursor: pointer;
12
+
13
+ &:disabled {
14
+ background-color: #ccc;
15
+ cursor: not-allowed;
16
+ }
17
+
18
+ &:hover:not(:disabled) {
19
+ background-color: #0056b3;
20
+ }
21
+ `;
22
+
23
+ export const Create{{componentName}}Button = () => {
24
+ const { mutate, isLoading } = useCreate{{componentName}}();
25
+ return (
26
+ <Button onClick={() => mutate({ /* mock data */ } as any)} disabled={isLoading}>
27
+ {isLoading ? 'Creating...' : 'Create {{componentName}}'}
28
+ </Button>
29
+ );
30
+ };
@@ -0,0 +1,30 @@
1
+ import styled from '@emotion/styled';
2
+ import { useDelete{{componentName}} } from '{{apiImportPath}}';
3
+
4
+ const Button = styled.button`
5
+ padding: 0.5rem 1rem;
6
+ margin: 0 0.5rem;
7
+ border-radius: 4px;
8
+ border: none;
9
+ background-color: #dc3545;
10
+ color: white;
11
+ cursor: pointer;
12
+
13
+ &:disabled {
14
+ background-color: #ccc;
15
+ cursor: not-allowed;
16
+ }
17
+
18
+ &:hover:not(:disabled) {
19
+ background-color: #c82333;
20
+ }
21
+ `;
22
+
23
+ export const Delete{{componentName}}Button = ({ id }: { id: string }) => {
24
+ const { mutate, isLoading } = useDelete{{componentName}}();
25
+ return (
26
+ <Button onClick={() => mutate(id)} disabled={isLoading}>
27
+ {isLoading ? 'Deleting...' : 'Delete'}
28
+ </Button>
29
+ );
30
+ };
@@ -0,0 +1,30 @@
1
+ import styled from '@emotion/styled';
2
+ import { useUpdate{{componentName}} } from '{{apiImportPath}}';
3
+
4
+ const Button = styled.button`
5
+ padding: 0.5rem 1rem;
6
+ margin: 0 0.5rem;
7
+ border-radius: 4px;
8
+ border: none;
9
+ background-color: #007bff;
10
+ color: white;
11
+ cursor: pointer;
12
+
13
+ &:disabled {
14
+ background-color: #ccc;
15
+ cursor: not-allowed;
16
+ }
17
+
18
+ &:hover:not(:disabled) {
19
+ background-color: #0056b3;
20
+ }
21
+ `;
22
+
23
+ export const Edit{{componentName}}Button = ({ id }: { id: string }) => {
24
+ const { mutate, isLoading } = useUpdate{{componentName}}();
25
+ return (
26
+ <Button onClick={() => mutate(id, {})} disabled={isLoading}>
27
+ {isLoading ? 'Saving...' : 'Edit'}
28
+ </Button>
29
+ );
30
+ };
@@ -0,0 +1,11 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ const Button = styled.button`
4
+ padding: 8px 16px;
5
+ margin: 0 4px;
6
+ cursor: pointer;
7
+ `;
8
+
9
+ export const Create{{componentName}}Button = () => <Button>Create</Button>;
10
+ export const Edit{{componentName}}Button = () => <Button>Edit</Button>;
11
+ export const Delete{{componentName}}Button = () => <Button>Delete</Button>;
@@ -0,0 +1,19 @@
1
+ import styled from '@emotion/styled';
2
+ import { {{componentName}}Table } from '{{widgetImportPath}}/ui/{{componentName}}Table';
3
+
4
+ const PageWrapper = styled.div`
5
+ padding: 2rem;
6
+ `;
7
+
8
+ const Title = styled.h1`
9
+ margin-bottom: 2rem;
10
+ `;
11
+
12
+ export const {{componentName}}Page = () => {
13
+ return (
14
+ <PageWrapper>
15
+ <Title>{{componentName}} Management</Title>
16
+ <{{componentName}}Table />
17
+ </PageWrapper>
18
+ );
19
+ };
@@ -0,0 +1,19 @@
1
+ import styled from '@emotion/styled';
2
+ import { {{componentName}}Table } from '../../widgets/{{componentName}}Table/ui/{{componentName}}Table';
3
+
4
+ const PageWrapper = styled.div`
5
+ padding: 2rem;
6
+ `;
7
+
8
+ const Title = styled.h1`
9
+ margin-bottom: 2rem;
10
+ `;
11
+
12
+ export const {{componentName}}Page = () => {
13
+ return (
14
+ <PageWrapper>
15
+ <Title>{{componentName}} Management</Title>
16
+ <{{componentName}}Table />
17
+ </PageWrapper>
18
+ );
19
+ };
@@ -0,0 +1,50 @@
1
+ import styled from '@emotion/styled';
2
+ import { mock{{componentName}}Data } from '{{entityImportPath}}/model/types';
3
+ import { Create{{componentName}}Button, Edit{{componentName}}Button, Delete{{componentName}}Button } from '{{featureImportPath}}/ui';
4
+
5
+ const TableWrapper = styled.div`
6
+ border: 1px solid #eee;
7
+ padding: 1rem;
8
+ `;
9
+
10
+ const Table = styled.table`
11
+ width: 100%;
12
+ border-collapse: collapse;
13
+
14
+ th, td {
15
+ border: 1px solid #ddd;
16
+ padding: 8px;
17
+ text-align: left;
18
+ }
19
+ `;
20
+
21
+ export const {{componentName}}Table = () => {
22
+ return (
23
+ <TableWrapper>
24
+ <div style={{ marginBottom: '1rem' }}>
25
+ <Create{{componentName}}Button />
26
+ </div>
27
+ <Table>
28
+ <thead>
29
+ <tr>
30
+ <th>ID</th>
31
+ <th>Name</th>
32
+ <th>Actions</th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ {mock{{componentName}}Data.map((item) => (
37
+ <tr key={item.id}>
38
+ <td>{item.id}</td>
39
+ <td>{item.name}</td>
40
+ <td>
41
+ <Edit{{componentName}}Button id={item.id} />
42
+ <Delete{{componentName}}Button id={item.id} />
43
+ </td>
44
+ </tr>
45
+ ))}
46
+ </tbody>
47
+ </Table>
48
+ </TableWrapper>
49
+ );
50
+ };
@@ -0,0 +1,50 @@
1
+ import styled from '@emotion/styled';
2
+ import { {{componentName}}, mock{{componentName}}Data } from '../../../entities/{{componentName}}/model';
3
+ import { Create{{componentName}}Button, Edit{{componentName}}Button, Delete{{componentName}}Button } from '../../../features/Manage{{componentName}}/ui/Manage{{componentName}}';
4
+
5
+ const TableWrapper = styled.div`
6
+ border: 1px solid #eee;
7
+ padding: 1rem;
8
+ `;
9
+
10
+ const Table = styled.table`
11
+ width: 100%;
12
+ border-collapse: collapse;
13
+
14
+ th, td {
15
+ border: 1px solid #ddd;
16
+ padding: 8px;
17
+ text-align: left;
18
+ }
19
+ `;
20
+
21
+ export const {{componentName}}Table = () => {
22
+ return (
23
+ <TableWrapper>
24
+ <div style={{ marginBottom: '1rem' }}>
25
+ <Create{{componentName}}Button />
26
+ </div>
27
+ <Table>
28
+ <thead>
29
+ <tr>
30
+ <th>ID</th>
31
+ <th>Name</th>
32
+ <th>Actions</th>
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ {mock{{componentName}}Data.map((item) => (
37
+ <tr key={item.id}>
38
+ <td>{item.id}</td>
39
+ <td>{item.name}</td>
40
+ <td>
41
+ <Edit{{componentName}}Button />
42
+ <Delete{{componentName}}Button />
43
+ </td>
44
+ </tr>
45
+ ))}
46
+ </tbody>
47
+ </Table>
48
+ </TableWrapper>
49
+ );
50
+ };
@@ -0,0 +1 @@
1
+ // Styles for {{componentName}}
@@ -0,0 +1,18 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ export const {{componentName}}Root = styled.div`
4
+ display: flex;
5
+ `;
6
+
7
+ export interface {{componentName}}Props {
8
+ className?: string;
9
+ children?: React.ReactNode;
10
+ }
11
+
12
+ export const {{componentName}} = ({ className, children }: {{componentName}}Props) => {
13
+ return (
14
+ <{{componentName}}Root className={className}>
15
+ {children}
16
+ </{{componentName}}Root>
17
+ );
18
+ };
@@ -0,0 +1 @@
1
+ // Styles for Widget {{componentName}}
@@ -0,0 +1,18 @@
1
+ import styled from '@emotion/styled';
2
+
3
+ export const {{componentName}}Root = styled.div`
4
+ display: grid;
5
+ gap: 1rem;
6
+ `;
7
+
8
+ export interface {{componentName}}Props {
9
+ className?: string;
10
+ }
11
+
12
+ export const {{componentName}} = ({ className }: {{componentName}}Props) => {
13
+ return (
14
+ <{{componentName}}Root className={className}>
15
+ Widget: {{componentName}}
16
+ </{{componentName}}Root>
17
+ );
18
+ };