torch-glare 1.0.1

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +207 -0
  3. package/cli/bin/addComponent.js +278 -0
  4. package/cli/bin/addHooks.js +75 -0
  5. package/cli/bin/addLayout.js +71 -0
  6. package/cli/bin/addProvider.js +71 -0
  7. package/cli/bin/addUtils.js +74 -0
  8. package/cli/bin/cli.js +73 -0
  9. package/cli/bin/init/init.js +15 -0
  10. package/cli/bin/init/tailwindInit.js +174 -0
  11. package/cli/bin/update.js +147 -0
  12. package/lib/components/ActionButton.tsx +63 -0
  13. package/lib/components/ActionsGroup.tsx +34 -0
  14. package/lib/components/AlertDialog.tsx +211 -0
  15. package/lib/components/Badge.tsx +116 -0
  16. package/lib/components/BadgeField.tsx +192 -0
  17. package/lib/components/Button.tsx +277 -0
  18. package/lib/components/Card.tsx +63 -0
  19. package/lib/components/Checkbox.tsx +104 -0
  20. package/lib/components/CountBadge.tsx +54 -0
  21. package/lib/components/DatePicker.tsx +464 -0
  22. package/lib/components/Drawer.tsx +118 -0
  23. package/lib/components/DropdownMenu.tsx +399 -0
  24. package/lib/components/FieldHint.tsx +76 -0
  25. package/lib/components/ImageAttachment.tsx +171 -0
  26. package/lib/components/InnerLabelField.tsx +155 -0
  27. package/lib/components/Input.tsx +179 -0
  28. package/lib/components/InputField.tsx +147 -0
  29. package/lib/components/Label.tsx +107 -0
  30. package/lib/components/LabelField.tsx +75 -0
  31. package/lib/components/LabeledCheckBox.tsx +65 -0
  32. package/lib/components/LabeledRadio.tsx +45 -0
  33. package/lib/components/LinkButton.tsx +90 -0
  34. package/lib/components/LoginButton.tsx +56 -0
  35. package/lib/components/PasswordLevel.tsx +58 -0
  36. package/lib/components/Popover.tsx +274 -0
  37. package/lib/components/ProfileMenu.tsx +90 -0
  38. package/lib/components/Radio.tsx +69 -0
  39. package/lib/components/RadioCard.tsx +70 -0
  40. package/lib/components/RingLoading.tsx +190 -0
  41. package/lib/components/SearchField.tsx +49 -0
  42. package/lib/components/Select.tsx +417 -0
  43. package/lib/components/SlideDatePicker.tsx +120 -0
  44. package/lib/components/SpinLoading.tsx +190 -0
  45. package/lib/components/Switcher.tsx +56 -0
  46. package/lib/components/TabFormItem.tsx +158 -0
  47. package/lib/components/Table.tsx +395 -0
  48. package/lib/components/Textarea.tsx +108 -0
  49. package/lib/components/Tooltip.tsx +111 -0
  50. package/lib/components/TransparentLabel.tsx +72 -0
  51. package/lib/components/TreeDropDown.tsx +69 -0
  52. package/lib/hooks/MobileSlidePicker/components/Picker.tsx +218 -0
  53. package/lib/hooks/MobileSlidePicker/components/PickerColumn.tsx +238 -0
  54. package/lib/hooks/MobileSlidePicker/components/PickerItem.tsx +64 -0
  55. package/lib/hooks/MobileSlidePicker/index.ts +10 -0
  56. package/lib/hooks/useActiveTreeItem.tsx +61 -0
  57. package/lib/hooks/useClickOutside.tsx +20 -0
  58. package/lib/hooks/useResize.tsx +78 -0
  59. package/lib/layouts/CLayout.tsx +326 -0
  60. package/lib/layouts/FieldSection.tsx +64 -0
  61. package/lib/layouts/TreeSubLayout.tsx +187 -0
  62. package/lib/providers/ThemeProvider.tsx +99 -0
  63. package/lib/utils/cn.ts +6 -0
  64. package/lib/utils/convertImageFileToDataUrl.ts +17 -0
  65. package/lib/utils/resize.ts +35 -0
  66. package/lib/utils/types.ts +12 -0
  67. package/package.json +28 -0
  68. package/torch-glare.js +24 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 TORCH corp.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # TORCH Glare Components Library
2
+
3
+ Welcome to the **TORCH Glare Components Library**! This library provides a collection of reusable React components to help you build user interfaces efficiently. Additionally, a CLI tool (**torch-glare CLI**) is available to streamline component management.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Installation](#installation)
8
+ 2. [Usage](#usage)
9
+ 3. [CLI Commands](#cli-commands)
10
+ 4. [Theming](#theming)
11
+ 5. [Contributing](#contributing)
12
+ 6. [License](#license)
13
+
14
+ ## Installation
15
+
16
+
17
+ ## 1. Initialize Your Project
18
+
19
+ To install the TORCH Glare Components Library, run the following command:
20
+
21
+ ```sh
22
+ npx torch-glare@latest init
23
+ ```
24
+
25
+ This command will:
26
+ - Create or modify the `tailwind.config.js` file to support Tailwind CSS for Tailwind versions less then 4.
27
+ - Generate a `torch.json` file where you can customize the installation path for components.
28
+
29
+ ### Tailwind CSS Requirement
30
+ Ensure that Tailwind CSS is installed in your project before running the initialization command.
31
+
32
+ ## 2. Add Essential Plugins for Tailwind CSS
33
+
34
+ If you're using Tailwind CSS version 4 or above, add the following plugins to your global CSS file:
35
+
36
+ ```css
37
+ @import "tailwindcss";
38
+ /* Essential plugins */
39
+ @plugin 'glare-typography';
40
+ @plugin 'glare-themes';
41
+ @plugin 'glare-torch-mode';
42
+ @plugin 'tailwind-scrollbar-hide';
43
+ @plugin 'tailwindcss-animate';
44
+ ```
45
+
46
+
47
+ ### 2. Add Remix Icon Library
48
+ Include the following in `index.html` or nextjs `layout.tsx` or meta data for icon support:
49
+
50
+ ```html
51
+ <html>
52
+ <head>
53
+ <link
54
+ href="https://cdn.jsdelivr.net/npm/remixicon@4.5.0/fonts/remixicon.css"
55
+ rel="stylesheet"
56
+ />
57
+ </head>
58
+ <body></body>
59
+ </html>
60
+
61
+ ```
62
+
63
+ ## 3. Configure Installation Path
64
+
65
+ Adjust the `glare.json` file to specify where you want to install components:
66
+
67
+ ```json
68
+ {
69
+ "path": "./src" // The directory where components will be installed
70
+ }
71
+ ```
72
+
73
+ ### 4. Add Components
74
+ To add a specific component, run:
75
+
76
+ ```sh
77
+ npx torch-glare@latest add [component-name]
78
+ ```
79
+
80
+ Or, to add components interactively:
81
+
82
+ ```sh
83
+ npx torch-glare@latest add
84
+ ```
85
+
86
+ ## Usage
87
+
88
+ Once installed, import and use the components as needed:
89
+
90
+ ```tsx
91
+ import React from "react";
92
+ import { Button } from "./components";
93
+
94
+ const App = () => {
95
+ return (
96
+ <div>
97
+ <Button >Hello.</Button>
98
+ </div>
99
+ );
100
+ };
101
+
102
+ export default App;
103
+ ```
104
+
105
+ ## CLI Commands
106
+
107
+ ### Initialize Configuration
108
+ ```sh
109
+ npx torch-glare@latest init
110
+ ```
111
+ - Creates a `torch.json` configuration file.
112
+ - Create or modify `tailwind.config.ts` file for tailwind support.
113
+
114
+ ### Add Components
115
+ ```sh
116
+ npx torch-glare@latest add [component]
117
+ ```
118
+ Adds a specific component or runs an interactive prompt if no name is provided.
119
+
120
+ ### Add Hooks
121
+ ```sh
122
+ npx torch-glare@latest hook [hook]
123
+ ```
124
+ Adds a specific hook or runs an interactive prompt if no name is provided.
125
+
126
+ ### Add Utilities
127
+ ```sh
128
+ npx torch-glare@latest util [util]
129
+ ```
130
+ Adds a specific utility or runs an interactive prompt if no name is provided.
131
+
132
+ ### Providers
133
+ ```sh
134
+ npx torch-glare@latest provider [provider]
135
+ ```
136
+ Adds a specific provider or runs an interactive prompt if no name is provided.
137
+
138
+ ### Update Installed Resources
139
+
140
+ ```sh
141
+ npx torch-glare@latest update
142
+ ```
143
+ Updates all installed components, hooks, utilities, and providers.
144
+
145
+
146
+ ## Theming
147
+
148
+ The TORCH Glare Components Library supports both light and dark themes. You can set a fixed theme for your components using the `theme` attribute.
149
+
150
+ ### Setting a Fixed Theme
151
+
152
+ To apply a fixed theme (dark or light) to a component, add the `theme `attribute with the desired theme value:
153
+
154
+ ```tsx
155
+ import React from "react";
156
+ import { Button } from "./components";
157
+
158
+ const App = () => {
159
+ return (
160
+ <div>
161
+ <Button theme="dark">Dark Theme Button</Button>
162
+ <Button theme="light">Light Theme Button</Button>
163
+ </div>
164
+ );
165
+ };
166
+
167
+ export default App;
168
+ ```
169
+
170
+ ### Global Theme
171
+
172
+ To apply a theme globally, wrap your application with the `ThemeProvider` and set the optional `defaultTheme` props:
173
+
174
+
175
+ ```tsx
176
+ import { ThemeProvider } from "./components";
177
+
178
+ const App = () => {
179
+ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
180
+ <ThemeProvider defaultTheme="light" defaultThemeMode="TORCH">
181
+ <App />
182
+ </ThemeProvider>
183
+ );
184
+ };
185
+
186
+ export default App;
187
+ ```
188
+
189
+ ## Contributing
190
+
191
+ We welcome contributions! Follow these steps:
192
+
193
+ 1. Fork the repository.
194
+ 2. Create a new branch.
195
+ 3. Implement your changes.
196
+ 4. Commit with a clear message.
197
+ 5. Push your changes and open a pull request.
198
+
199
+ ### Contribution Guidelines
200
+ - Follow existing code style.
201
+ - Update documentation if necessary.
202
+
203
+ ## License
204
+
205
+ This project is licensed under the **MIT License**.
206
+
207
+
@@ -0,0 +1,278 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import inquirer from "inquirer";
4
+ import { getConfig } from "./cli.js";
5
+ import { execSync } from "child_process";
6
+ import { fileURLToPath } from "url";
7
+ import { addUtil } from "./addUtils.js";
8
+ import { addHook } from "./addHooks.js";
9
+
10
+ const __filename = fileURLToPath(import.meta.url);
11
+
12
+ // Get the current file and directory paths
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ // Define the path to the templates directory
16
+ const templatesDir = path.resolve(__dirname, "../../lib/components");
17
+
18
+
19
+ /**
20
+ * Main function to add a component and its dependencies.
21
+ * @param {string} component - The name of the component to add.
22
+ */
23
+ export async function addComponent(component) {
24
+ const config = getConfig();
25
+ const availableComponents = getAvailableComponents(templatesDir);
26
+
27
+ // If no component is provided, prompt the user to select one
28
+ if (!component) {
29
+ component = await promptComponentSelection(availableComponents);
30
+ }
31
+
32
+ // Validate if the component exists in the templates directory
33
+ if (!availableComponents.includes(component)) {
34
+ console.error(`❌ Component "${component}" not found.`);
35
+ return;
36
+ }
37
+
38
+ const { source, targetDir } = getComponentPaths(component, config, templatesDir, "components");
39
+ const target = path.join(targetDir, component);
40
+
41
+ // replace the component
42
+ fs.rmSync(target, { recursive: true, force: true });
43
+ console.log(`🔄 Replacing "${component}"...`);
44
+
45
+ // Ensure the target directory exists
46
+ ensureDirectoryExists(targetDir);
47
+
48
+ // Copy the component (directory or file) and install dependencies
49
+ copyComponent(source, target, addComponent);
50
+
51
+ console.log(`✅ ${component} has been added to ${config.path}!`);
52
+ }
53
+
54
+ /**
55
+ * Get a list of available components from the templates directory.
56
+ * @param {string} templatesDir - Path to the templates directory.
57
+ * @returns {string[]} - Array of component names.
58
+ */
59
+ function getAvailableComponents(templatesDir) {
60
+ return fs.readdirSync(templatesDir).map((file) => path.basename(file));
61
+ }
62
+
63
+ /**
64
+ * Prompt the user to select a component from a list.
65
+ * @param {string[]} availableComponents - Array of available components.
66
+ * @returns {string} - The selected component.
67
+ */
68
+ async function promptComponentSelection(availableComponents) {
69
+ const { selectedComponent } = await inquirer.prompt([
70
+ {
71
+ type: "list",
72
+ name: "selectedComponent",
73
+ message: "Which component would you like to add?",
74
+ choices: availableComponents,
75
+ },
76
+ ]);
77
+ return selectedComponent;
78
+ }
79
+
80
+ /**
81
+ * Get the source and target paths for the component.
82
+ * @param {string} component - The name of the component.
83
+ * @param {object} config - Configuration object.
84
+ * @returns {object} - Object containing source and target directory paths.
85
+ */
86
+ export function getComponentPaths(component, config, templatesDir, saveFolderName) {
87
+ const source = path.join(templatesDir, `${component}`);
88
+ const normalizedPath = config.path.replace("@/", "");
89
+ const targetDir = path.join(process.cwd(), normalizedPath, saveFolderName);
90
+ return { source, targetDir };
91
+ }
92
+
93
+ /**
94
+ * Ensure the target directory exists.
95
+ * @param {string} targetDir - The target directory path.
96
+ */
97
+ export function ensureDirectoryExists(targetDir) {
98
+ if (!fs.existsSync(targetDir)) {
99
+ fs.mkdirSync(targetDir, { recursive: true });
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Copy a component (directory or file) and install its dependencies.
105
+ * @param {string} source - The source path of the component.
106
+ * @param {string} target - The target path of the component.
107
+ */
108
+ export function copyComponent(source, target, addFunction) {
109
+ if (fs.lstatSync(source).isDirectory()) {
110
+ copyDirectorySync(source, target, addFunction);
111
+ } else {
112
+ fs.copyFileSync(source, target);
113
+ installDependencies(source, addFunction); // Pass addFunction here
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Detect the package manager used in the project.
119
+ * @returns {string} - The detected package manager (pnpm, yarn, or npm).
120
+ */
121
+ export function detectPackageManager() {
122
+ if (fs.existsSync(path.join(process.cwd(), "pnpm-lock.yaml"))) return "pnpm";
123
+ if (fs.existsSync(path.join(process.cwd(), "yarn.lock"))) return "yarn";
124
+ if (fs.existsSync(path.join(process.cwd(), "package-lock.json"))) return "npm";
125
+ return "npm"; // Default to npm if no lock file is found
126
+ }
127
+
128
+ /**
129
+ * Get the installed dependencies from the project's package.json.
130
+ * @returns {Set<string>} - Set of installed dependencies.
131
+ */
132
+ export function getCurrentInstalledDependencies() {
133
+ const packageJsonPath = path.join(process.cwd(), "package.json");
134
+ if (!fs.existsSync(packageJsonPath)) {
135
+ console.error("❌ No package.json found. Run `npm init` or `yarn init` first.");
136
+ process.exit(1);
137
+ }
138
+
139
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
140
+
141
+ const depsNames = new Set([
142
+ ...Object.keys(packageJson.dependencies || {}),
143
+ ...Object.keys(packageJson.devDependencies || {}),
144
+ ]);
145
+
146
+ const depsNamesAndVersions = {
147
+ ...(packageJson.dependencies || {}),
148
+ ...(packageJson.devDependencies || {}),
149
+ };
150
+ return { depsNames, depsNamesAndVersions }
151
+ }
152
+
153
+ /**
154
+ * Extract dependencies from a component file.
155
+ * @param {string} componentPath - Path to the component file.
156
+ * @param {Set<string>} installedDependencies - Set of installed dependencies.
157
+ * @param {function} addFunction - Function to make add operation
158
+ * @returns {Set<string>} - Set of dependencies to install.
159
+ */
160
+ function getDependenciesToInstall(componentPath, installedDependencies, addFunction) {
161
+ const componentContent = fs.readFileSync(componentPath, "utf-8");
162
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
163
+ const dependenciesToInstall = new Set();
164
+
165
+ let match;
166
+ while ((match = importRegex.exec(componentContent)) !== null) {
167
+ const moduleName = match[1];
168
+
169
+ if (!moduleName.startsWith(".") && !installedDependencies.has(moduleName)) {
170
+ dependenciesToInstall.add(moduleName);
171
+ }
172
+
173
+ // install required utils.
174
+ else if (
175
+ moduleName.startsWith("../utils") &&
176
+ !installedDependencies.has(moduleName)
177
+ ) {
178
+ addUtil(moduleName.slice(9) + ".ts"); // Use addFunction here
179
+ }
180
+
181
+ // install required hooks.
182
+ else if (
183
+ moduleName.startsWith("../hooks") &&
184
+ !installedDependencies.has(moduleName)
185
+ ) {
186
+ addHook(moduleName.slice(9)); // Use addFunction here
187
+ }
188
+ // install required components
189
+ else if (
190
+ moduleName.startsWith("./") ||
191
+ !moduleName.startsWith("../components") &&
192
+ !installedDependencies.has(moduleName)
193
+ ) {
194
+ addComponent(moduleName.slice(2) + ".tsx"); // Use addFunction here
195
+ }
196
+ // install required for layouts components
197
+ else if (
198
+ moduleName.startsWith("../components") &&
199
+ !installedDependencies.has(moduleName)
200
+ ) {
201
+ addComponent(moduleName.slice(14) + ".tsx"); // Use addFunction here
202
+ }
203
+ }
204
+
205
+ return dependenciesToInstall;
206
+ }
207
+ /**
208
+ * Install dependencies for a component.
209
+ * @param {string} componentPath - Path to the component file.
210
+ */
211
+ export function installDependencies(componentPath, addFunction) {
212
+ const { depsNames } = getCurrentInstalledDependencies();
213
+ const dependenciesToInstall = getDependenciesToInstall(
214
+ componentPath,
215
+ depsNames,
216
+ addFunction // Pass addFunction here
217
+ );
218
+
219
+ if (dependenciesToInstall.size > 0) {
220
+ const packageManager = detectPackageManager();
221
+ const installCommand = getInstallCommand(packageManager, dependenciesToInstall);
222
+
223
+ console.log(
224
+ `📦 Installing missing dependencies using ${packageManager}:`,
225
+ [...dependenciesToInstall].join(", ")
226
+ );
227
+
228
+ try {
229
+ execSync(installCommand, { stdio: "inherit" });
230
+ console.log("✅ Dependencies installed successfully.");
231
+ } catch (error) {
232
+ console.error("❌ Error installing dependencies:", error.message);
233
+ }
234
+ } else {
235
+ console.log("✅ All dependencies are already installed.");
236
+ }
237
+ }
238
+ /**
239
+ * Generate the installation command based on the package manager.
240
+ * @param {string} packageManager - The package manager (pnpm, yarn, or npm).
241
+ * @param {Set<string>} dependencies - Set of dependencies to install.
242
+ * @returns {string} - The installation command.
243
+ */
244
+ function getInstallCommand(packageManager, dependencies) {
245
+ const deps = [...dependencies].join(" ");
246
+ switch (packageManager) {
247
+ case "pnpm":
248
+ return `pnpm add ${deps}`;
249
+ case "yarn":
250
+ return `yarn add ${deps}`;
251
+ default:
252
+ return `npm install ${deps}`;
253
+ }
254
+ }
255
+
256
+ /**
257
+ * Copy a directory and its contents recursively.
258
+ * @param {string} source - Source directory path.
259
+ * @param {string} target - Target directory path.
260
+ */
261
+ export function copyDirectorySync(source, target, addFunction) {
262
+ if (!fs.existsSync(target)) {
263
+ fs.mkdirSync(target, { recursive: true });
264
+ }
265
+
266
+ const items = fs.readdirSync(source, { withFileTypes: true });
267
+ for (const item of items) {
268
+ const sourcePath = path.join(source, item.name);
269
+ const targetPath = path.join(target, item.name);
270
+
271
+ if (item.isDirectory()) {
272
+ copyDirectorySync(sourcePath, targetPath, addFunction);
273
+ } else {
274
+ fs.copyFileSync(sourcePath, targetPath);
275
+ installDependencies(sourcePath, addFunction); // Pass addFunction here
276
+ }
277
+ }
278
+ }
@@ -0,0 +1,75 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getConfig } from "./cli.js";
4
+ import { fileURLToPath } from "url";
5
+ import { ensureDirectoryExists, getComponentPaths, copyComponent } from "./addComponent.js";
6
+ import inquirer from "inquirer";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ // Define the path to the hooks templates directory
12
+ const hooksTemplatesDir = path.resolve(__dirname, "../../lib/hooks");
13
+
14
+
15
+ /**
16
+ * Main function to add a hook and its dependencies.
17
+ * @param {string} hook - The name of the hook to add.
18
+ */
19
+ export async function addHook(hook) {
20
+ const config = getConfig();
21
+ const availableHooks = getAvailableHooks(hooksTemplatesDir);
22
+
23
+ // If no hook is provided, prompt the user to select one
24
+ if (!hook) {
25
+ hook = await promptHookSelection(availableHooks);
26
+ }
27
+
28
+ // Validate if the hook exists in the hooks templates directory
29
+ if (!availableHooks.includes(hook)) {
30
+ // console.error(`❌ Hook "${hook}" not found.`);
31
+ return;
32
+ }
33
+
34
+ // get the path and create the create the target directory
35
+ const { source, targetDir } = getComponentPaths(hook, config, hooksTemplatesDir, "hooks");
36
+ const target = path.join(targetDir, hook);
37
+ fs.rmSync(target, { recursive: true, force: true });
38
+
39
+ // Ensure the target directory exists
40
+ ensureDirectoryExists(targetDir);
41
+
42
+ // Copy the hook (file) and install dependencies
43
+ copyComponent(source, target, addHook);
44
+
45
+ console.log(`✅ ${hook} has been added to ${config.path}!`);
46
+ }
47
+
48
+ /**
49
+ * Get a list of available hooks from the hooks templates directory.
50
+ * @param {string} hooksTemplatesDir - Path to the hooks templates directory.
51
+ * @returns {string[]} - Array of hook names.
52
+ */
53
+ function getAvailableHooks(hooksTemplatesDir) {
54
+ return fs.readdirSync(hooksTemplatesDir).map((file) => path.basename(file));
55
+ }
56
+
57
+
58
+ /**
59
+ * Prompt the user to select a hook from a list.
60
+ * @param {string[]} availableHooks - Array of available hooks.
61
+ * @returns {string} - The selected hook.
62
+ */
63
+ async function promptHookSelection(availableHooks) {
64
+ const { selectedHook } = await inquirer.prompt([
65
+ {
66
+ type: "list",
67
+ name: "selectedHook",
68
+ message: "Which hook would you like to add?",
69
+ choices: availableHooks,
70
+ },
71
+ ]);
72
+ return selectedHook;
73
+ }
74
+
75
+
@@ -0,0 +1,71 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { getConfig } from "./cli.js";
4
+ import { fileURLToPath } from "url";
5
+ import { ensureDirectoryExists, getComponentPaths, copyComponent } from "./addComponent.js";
6
+ import inquirer from "inquirer";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ // Define the path to the layouts templates directory
12
+ const layoutsTemplatesDir = path.resolve(__dirname, "../../lib/layouts");
13
+
14
+ /**
15
+ * Main function to add a layout and its dependencies.
16
+ * @param {string} layout - The name of the layout to add.
17
+ */
18
+ export async function addLayout(layout) {
19
+ const config = getConfig();
20
+ const availableLayouts = getAvailableLayouts(layoutsTemplatesDir);
21
+
22
+ // If no layout is provided, prompt the user to select one
23
+ if (!layout) {
24
+ layout = await promptLayoutSelection(availableLayouts);
25
+ }
26
+
27
+ // Validate if the layout exists in the layouts templates directory
28
+ if (!availableLayouts.includes(layout)) {
29
+ // console.error(`❌ Layout "${layout}" not found.`);
30
+ return;
31
+ }
32
+
33
+ // get the path and create the create the target directory
34
+ const { source, targetDir } = getComponentPaths(layout, config, layoutsTemplatesDir, "layouts");
35
+ const target = path.join(targetDir, layout);
36
+ fs.rmSync(target, { recursive: true, force: true });
37
+
38
+ // Ensure the target directory exists
39
+ ensureDirectoryExists(targetDir);
40
+
41
+ // Copy the layout (file) and install dependencies
42
+ copyComponent(source, target, addLayout);
43
+
44
+ console.log(`✅ ${layout} has been added to ${config.path}!`);
45
+ }
46
+
47
+ /**
48
+ * Get a list of available layouts from the layouts templates directory.
49
+ * @param {string} layoutsTemplatesDir - Path to the layouts templates directory.
50
+ * @returns {string[]} - Array of layout names.
51
+ */
52
+ function getAvailableLayouts(layoutsTemplatesDir) {
53
+ return fs.readdirSync(layoutsTemplatesDir).map((file) => path.basename(file));
54
+ }
55
+
56
+ /**
57
+ * Prompt the user to select a layout from a list.
58
+ * @param {string[]} availableLayouts - Array of available layouts.
59
+ * @returns {string} - The selected layout.
60
+ */
61
+ async function promptLayoutSelection(availableLayouts) {
62
+ const { selectedLayout } = await inquirer.prompt([
63
+ {
64
+ type: "list",
65
+ name: "selectedLayout",
66
+ message: "Which layout would you like to add?",
67
+ choices: availableLayouts,
68
+ },
69
+ ]);
70
+ return selectedLayout;
71
+ }