@salas-ds/cli 0.2.2 → 0.2.3

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/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import chalk2 from 'chalk';
4
- import fs from 'fs-extra';
4
+ import fs2 from 'fs-extra';
5
5
  import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
7
  import { glob } from 'glob';
@@ -229,7 +229,7 @@ export const throttle = <T extends (...args: any[]) => any>(
229
229
  var CONFIG_FILE = "salas-ds.json";
230
230
  async function injectStyleImport(projectRoot, componentsPath) {
231
231
  const angularJsonPath = path.join(projectRoot, "angular.json");
232
- const angularJson = await fs.readJSON(angularJsonPath);
232
+ const angularJson = await fs2.readJSON(angularJsonPath);
233
233
  const projectName = Object.keys(angularJson.projects)[0];
234
234
  if (!projectName) return null;
235
235
  const project = angularJson.projects[projectName];
@@ -241,20 +241,20 @@ async function injectStyleImport(projectRoot, componentsPath) {
241
241
  if (!globalStyleEntry) return null;
242
242
  const globalStyleRelative = typeof globalStyleEntry === "string" ? globalStyleEntry : globalStyleEntry.input;
243
243
  const globalStyleAbsolute = path.join(projectRoot, globalStyleRelative);
244
- if (!await fs.pathExists(globalStyleAbsolute)) return null;
244
+ if (!await fs2.pathExists(globalStyleAbsolute)) return null;
245
245
  const styleDir = path.dirname(globalStyleAbsolute);
246
246
  const salasStylesAbsolute = path.join(componentsPath, "styles.css");
247
247
  let importPath = path.relative(styleDir, salasStylesAbsolute).replace(/\\/g, "/");
248
248
  if (!importPath.startsWith(".")) {
249
249
  importPath = "./" + importPath;
250
250
  }
251
- const content = await fs.readFile(globalStyleAbsolute, "utf-8");
251
+ const content = await fs2.readFile(globalStyleAbsolute, "utf-8");
252
252
  const importLine = `@import '${importPath}';`;
253
253
  if (content.includes(importPath)) {
254
254
  return null;
255
255
  }
256
256
  const newContent = importLine + "\n" + content;
257
- await fs.writeFile(globalStyleAbsolute, newContent);
257
+ await fs2.writeFile(globalStyleAbsolute, newContent);
258
258
  return globalStyleRelative;
259
259
  }
260
260
  async function initCommand(options) {
@@ -263,13 +263,13 @@ async function initCommand(options) {
263
263
  const componentsPath = path.join(projectRoot, options.path);
264
264
  try {
265
265
  const angularJsonPath = path.join(projectRoot, "angular.json");
266
- const isAngular = await fs.pathExists(angularJsonPath);
266
+ const isAngular = await fs2.pathExists(angularJsonPath);
267
267
  if (!isAngular) {
268
268
  console.error(chalk2.red("\u274C Projeto Angular n\xE3o detectado (angular.json n\xE3o encontrado)."));
269
269
  console.log(chalk2.gray("Execute este comando na raiz de um projeto Angular."));
270
270
  process.exit(1);
271
271
  }
272
- await fs.ensureDir(componentsPath);
272
+ await fs2.ensureDir(componentsPath);
273
273
  const config = {
274
274
  $schema: "https://salas-ds.dev/schema.json",
275
275
  framework: options.framework,
@@ -277,11 +277,11 @@ async function initCommand(options) {
277
277
  utils: `${options.path}/utils.ts`,
278
278
  styles: `${options.path}/styles.css`
279
279
  };
280
- await fs.writeJSON(path.join(projectRoot, CONFIG_FILE), config, { spaces: 2 });
280
+ await fs2.writeJSON(path.join(projectRoot, CONFIG_FILE), config, { spaces: 2 });
281
281
  const cssPath = path.join(componentsPath, "styles.css");
282
- await fs.writeFile(cssPath, THEME_CSS);
282
+ await fs2.writeFile(cssPath, THEME_CSS);
283
283
  const utilsPath = path.join(componentsPath, "utils.ts");
284
- await fs.writeFile(utilsPath, UTILS_TS);
284
+ await fs2.writeFile(utilsPath, UTILS_TS);
285
285
  const injectedFile = await injectStyleImport(projectRoot, componentsPath);
286
286
  console.log(chalk2.green("\u2705 Salas Design System inicializado com sucesso!\n"));
287
287
  console.log(chalk2.gray("Arquivos criados:"));
@@ -312,6 +312,103 @@ async function initCommand(options) {
312
312
  process.exit(1);
313
313
  }
314
314
  }
315
+ var SIDEBAR_LAYOUT_CSS = `
316
+ /* Salas Sidebar - layout (adicionado pelo CLI) */
317
+ .salas-sidebar .salas-sidebar-content {
318
+ min-height: 120px;
319
+ }
320
+ `;
321
+ async function getGlobalStylesPath(projectRoot) {
322
+ const angularJsonPath = path.join(projectRoot, "angular.json");
323
+ if (!await fs2.pathExists(angularJsonPath)) return null;
324
+ const angularJson = await fs2.readJSON(angularJsonPath);
325
+ const projectName = Object.keys(angularJson.projects)[0];
326
+ if (!projectName) return null;
327
+ const project = angularJson.projects[projectName];
328
+ const stylesArray = project?.architect?.build?.options?.styles ?? [];
329
+ const globalStyleEntry = stylesArray.find((s) => {
330
+ const file = typeof s === "string" ? s : s.input;
331
+ return /\.(css|scss|sass|less)$/.test(file);
332
+ });
333
+ if (!globalStyleEntry) return null;
334
+ const globalStyleRelative = typeof globalStyleEntry === "string" ? globalStyleEntry : globalStyleEntry.input;
335
+ const globalStyleAbsolute = path.join(projectRoot, globalStyleRelative);
336
+ if (!await fs2.pathExists(globalStyleAbsolute)) return null;
337
+ return globalStyleAbsolute;
338
+ }
339
+ async function patchIndexHtmlForSidebar(projectRoot) {
340
+ const indexPath = path.join(projectRoot, "src", "index.html");
341
+ if (!await fs2.pathExists(indexPath)) return false;
342
+ let content = await fs2.readFile(indexPath, "utf-8");
343
+ if (content.includes("data-theme=")) return false;
344
+ const htmlOpenTag = content.match(/<html(?:\s[^>]*)?>/);
345
+ if (!htmlOpenTag) return false;
346
+ const current = htmlOpenTag[0];
347
+ let next = current;
348
+ if (!next.includes("lang=")) {
349
+ next = next.replace("<html", '<html lang="en"');
350
+ }
351
+ next = next.replace(/>$/, ' data-theme="light">');
352
+ if (next === current) return false;
353
+ content = content.replace(current, next);
354
+ await fs2.writeFile(indexPath, content);
355
+ return true;
356
+ }
357
+ async function appendSidebarLayoutStyles(projectRoot) {
358
+ const stylePath = await getGlobalStylesPath(projectRoot);
359
+ if (!stylePath) return false;
360
+ const content = await fs2.readFile(stylePath, "utf-8");
361
+ if (content.includes("Salas Sidebar - layout") || content.includes(".salas-sidebar-content")) {
362
+ return false;
363
+ }
364
+ await fs2.appendFile(stylePath, SIDEBAR_LAYOUT_CSS);
365
+ return true;
366
+ }
367
+ var LUCIDE_SIDEBAR_IMPORT = "import { LucideAngularModule, Home, Settings, PanelLeft, PanelRight } from 'lucide-angular';";
368
+ var LUCIDE_SIDEBAR_PROVIDERS = "...LucideAngularModule.pick({ Home, Settings, PanelLeft, PanelRight }),";
369
+ async function patchAppConfigForLucide(projectRoot) {
370
+ const configPath = path.join(projectRoot, "src", "app", "app.config.ts");
371
+ if (!await fs2.pathExists(configPath)) return false;
372
+ let content = await fs2.readFile(configPath, "utf-8");
373
+ if (content.includes("PanelLeft") || content.includes("LucideAngularModule.pick")) {
374
+ return false;
375
+ }
376
+ let modified = false;
377
+ if (!content.includes("from 'lucide-angular'")) {
378
+ const firstImportMatch = content.match(/^(\s*import\s+.+;\s*\n)/m);
379
+ if (firstImportMatch) {
380
+ content = content.replace(
381
+ /^(\s*import\s+.+;\s*\n)/m,
382
+ `$1${LUCIDE_SIDEBAR_IMPORT}
383
+ `
384
+ );
385
+ modified = true;
386
+ }
387
+ }
388
+ if (!content.includes(LUCIDE_SIDEBAR_PROVIDERS)) {
389
+ const providersMatch = content.match(/providers:\s*\[/);
390
+ if (providersMatch) {
391
+ content = content.replace(
392
+ /(providers:\s*\[)(\s*\n?)/,
393
+ `$1
394
+ ${LUCIDE_SIDEBAR_PROVIDERS}$2`
395
+ );
396
+ modified = true;
397
+ }
398
+ }
399
+ if (modified) {
400
+ await fs2.writeFile(configPath, content);
401
+ }
402
+ return modified;
403
+ }
404
+ async function runSidebarScaffold(projectRoot) {
405
+ const indexPatched = await patchIndexHtmlForSidebar(projectRoot);
406
+ const stylesPatched = await appendSidebarLayoutStyles(projectRoot);
407
+ const appConfigPatched = await patchAppConfigForLucide(projectRoot);
408
+ return { indexPatched, stylesPatched, appConfigPatched };
409
+ }
410
+
411
+ // src/commands/add.ts
315
412
  var __filename$1 = fileURLToPath(import.meta.url);
316
413
  var __dirname$1 = path.dirname(__filename$1);
317
414
  var COMPONENTS = [
@@ -338,7 +435,7 @@ var COMPONENTS = [
338
435
  "toast",
339
436
  "tooltip"
340
437
  ];
341
- var LUCIDE_COMPONENTS = ["autocomplete", "datepicker", "input", "select", "textarea"];
438
+ var LUCIDE_COMPONENTS = ["autocomplete", "datepicker", "input", "select", "sidebar", "textarea"];
342
439
  var CONFIG_FILE2 = "salas-ds.json";
343
440
  function toPascalCase(str) {
344
441
  return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
@@ -346,24 +443,24 @@ function toPascalCase(str) {
346
443
  async function readConfig() {
347
444
  const projectRoot = process.cwd();
348
445
  const configPath = path.join(projectRoot, CONFIG_FILE2);
349
- if (!await fs.pathExists(configPath)) {
446
+ if (!await fs2.pathExists(configPath)) {
350
447
  return null;
351
448
  }
352
- return fs.readJSON(configPath);
449
+ return fs2.readJSON(configPath);
353
450
  }
354
451
  async function addSingleComponent(componentName, componentsDir, options) {
355
452
  const projectRoot = process.cwd();
356
453
  const componentPath = path.join(projectRoot, componentsDir, componentName);
357
- if (await fs.pathExists(componentPath)) {
454
+ if (await fs2.pathExists(componentPath)) {
358
455
  if (!options.overwrite) {
359
456
  console.log(chalk2.yellow(` \u26A0 ${componentName} j\xE1 existe. Use --overwrite para substituir.`));
360
457
  return false;
361
458
  }
362
- await fs.remove(componentPath);
459
+ await fs2.remove(componentPath);
363
460
  }
364
- await fs.ensureDir(componentPath);
461
+ await fs2.ensureDir(componentPath);
365
462
  const templatePath = path.join(__dirname$1, "..", "templates", "angular", componentName);
366
- if (!await fs.pathExists(templatePath)) {
463
+ if (!await fs2.pathExists(templatePath)) {
367
464
  console.error(chalk2.red(` \u274C Template n\xE3o encontrado para: ${componentName}`));
368
465
  return false;
369
466
  }
@@ -375,8 +472,8 @@ async function addSingleComponent(componentName, componentsDir, options) {
375
472
  for (const file of templateFiles) {
376
473
  const srcPath = path.join(templatePath, file);
377
474
  const destPath = path.join(componentPath, file);
378
- await fs.ensureDir(path.dirname(destPath));
379
- await fs.copyFile(srcPath, destPath);
475
+ await fs2.ensureDir(path.dirname(destPath));
476
+ await fs2.copyFile(srcPath, destPath);
380
477
  }
381
478
  return true;
382
479
  }
@@ -428,11 +525,27 @@ Exemplos:`));
428
525
  console.log(chalk2.yellow("\nNenhum componente adicionado."));
429
526
  return;
430
527
  }
431
- process.cwd();
528
+ const projectRoot = process.cwd();
432
529
  console.log(chalk2.green(`
433
530
  \u2705 ${added.length} componente(s) adicionado(s) com sucesso!`));
434
531
  console.log(chalk2.gray(` Localiza\xE7\xE3o: ${componentsDir}/
435
532
  `));
533
+ if (added.includes("sidebar")) {
534
+ const { indexPatched, stylesPatched, appConfigPatched } = await runSidebarScaffold(projectRoot);
535
+ if (indexPatched || stylesPatched || appConfigPatched) {
536
+ console.log(chalk2.blue("\u{1F4D0} Sidebar: configura\xE7\xE3o aplicada no projeto:"));
537
+ if (indexPatched) console.log(chalk2.gray(' \u2022 src/index.html: data-theme="light" e lang="en" no <html>'));
538
+ if (stylesPatched) console.log(chalk2.gray(" \u2022 Estilos globais: min-height da \xE1rea do menu (.salas-sidebar-content)"));
539
+ if (appConfigPatched) console.log(chalk2.gray(" \u2022 app.config.ts: \xEDcones Lucide (Home, Settings, PanelLeft, PanelRight) registrados"));
540
+ console.log("");
541
+ }
542
+ if (added.includes("sidebar") && needsLucide.has("sidebar")) {
543
+ console.log(chalk2.yellow(" Instale lucide-angular para o trigger e \xEDcones do menu: npm install lucide-angular"));
544
+ console.log("");
545
+ }
546
+ console.log(chalk2.blue("\u{1F4A1} Sidebar: use no template com salas-sidebar-provider, salas-sidebar, salas-sidebar-header, salas-sidebar-content e salas-sidebar-inset."));
547
+ console.log(chalk2.gray(" \xCDcones do trigger (PanelLeft/PanelRight) e do menu (Home, Settings) usam lucide-angular.\n"));
548
+ }
436
549
  if (needsLucide.size > 0) {
437
550
  console.log(chalk2.yellow("\u26A0 Os seguintes componentes precisam do lucide-angular:"));
438
551
  console.log(chalk2.gray(` ${[...needsLucide].join(", ")}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salas-ds/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "CLI para adicionar componentes do Salas Design System ao projeto (estilo shadcn)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,35 @@
1
1
  import {
2
+
3
+ export type DialogSize = 'default' | 'sm';
4
+
5
+ export interface DialogProps {
6
+ /**
7
+ * Controlled open state. If provided, the dialog becomes controlled and
8
+ * will not manage its own internal state.
9
+ */
10
+ open?: boolean;
11
+ /**
12
+ * Width of the dialog panel. Accepts any valid CSS width value (e.g. '480px', '32rem', '50%').
13
+ */
14
+ width?: string;
15
+ /**
16
+ * Height of the dialog panel. Accepts any valid CSS height value.
17
+ */
18
+ height?: string;
19
+ /**
20
+ * Named size preset. When provided, defines sensible defaults for width and padding.
21
+ */
22
+ size?: DialogSize;
23
+ /**
24
+ * If true, closes the dialog when the user clicks on the overlay.
25
+ */
26
+ closeOnOverlayClick?: boolean;
27
+ /**
28
+ * If true, hides the X close button in the top-right corner.
29
+ */
30
+ hideCloseButton?: boolean;
31
+ }
32
+
2
33
  Component,
3
34
  Input,
4
35
  Output,
@@ -7,8 +38,6 @@ import {
7
38
  HostListener,
8
39
  } from '@angular/core';
9
40
 
10
- export type DialogSize = 'default' | 'sm';
11
-
12
41
  @Component({
13
42
  selector: 'salas-dialog',
14
43
  standalone: true,
@@ -14,7 +14,7 @@ import { Component } from '@angular/core';
14
14
  }
15
15
  .salas-sidebar-content {
16
16
  flex: 1;
17
- min-height: 0;
17
+ min-height: 120px;
18
18
  overflow-y: auto;
19
19
  overflow-x: hidden;
20
20
  padding: 0.5rem;
@@ -1,22 +1,17 @@
1
1
  import { Component, Optional } from '@angular/core';
2
+ import { LucideAngularModule } from 'lucide-angular';
2
3
  import { SidebarService } from './sidebar.service';
3
4
 
4
5
  @Component({
5
6
  selector: 'salas-sidebar-trigger',
6
7
  standalone: true,
7
- imports: [],
8
+ imports: [LucideAngularModule],
8
9
  template: `
9
10
  <button type="button" class="salas-sidebar-trigger" [class.salas-sidebar-trigger--right]="isRight" (click)="onClick()" aria-label="Toggle sidebar">
10
11
  @if (isRight) {
11
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
12
- <rect width="18" height="18" x="3" y="3" rx="2"/>
13
- <path d="M15 3v18"/>
14
- </svg>
12
+ <lucide-icon name="panel-right" [size]="20" />
15
13
  } @else {
16
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
17
- <rect width="18" height="18" x="3" y="3" rx="2"/>
18
- <path d="M9 3v18"/>
19
- </svg>
14
+ <lucide-icon name="panel-left" [size]="20" />
20
15
  }
21
16
  </button>
22
17
  `,
@@ -65,6 +65,7 @@ export interface SidebarProviderProps {
65
65
  width: var(--salas-sidebar-width);
66
66
  }
67
67
 
68
+ /* Layout dos filhos: header e content participam do flex */
68
69
  .salas-sidebar > salas-sidebar-header {
69
70
  flex-shrink: 0;
70
71
  display: block;
@@ -84,15 +85,57 @@ export interface SidebarProviderProps {
84
85
  width: 100%;
85
86
  padding: 0.5rem;
86
87
  }
87
- .salas-sidebar salas-sidebar-menu {
88
+ .salas-sidebar salas-sidebar-menu,
89
+ .salas-sidebar .salas-sidebar-menu {
88
90
  display: flex;
89
91
  flex-direction: column;
90
92
  gap: 0.125rem;
91
93
  }
92
- .salas-sidebar salas-sidebar-menu-item {
94
+ .salas-sidebar salas-sidebar-menu-item,
95
+ .salas-sidebar .salas-sidebar-menu-item {
93
96
  display: block;
94
97
  }
95
98
 
99
+ /* Plain HTML menu (ul/li/a with classes) */
100
+ .salas-sidebar .salas-sidebar-menu-button,
101
+ .salas-sidebar salas-sidebar-menu-button {
102
+ display: flex;
103
+ align-items: center;
104
+ gap: 0.75rem;
105
+ width: 100%;
106
+ padding: 0.5rem 0.75rem;
107
+ border-radius: 0.375rem;
108
+ font-size: 0.875rem;
109
+ color: var(--salas-text);
110
+ text-decoration: none;
111
+ transition: background-color 0.2s, color 0.2s, padding 0.2s, gap 0.2s;
112
+ cursor: pointer;
113
+ border: none;
114
+ background: none;
115
+ font-family: inherit;
116
+ overflow: hidden;
117
+ box-sizing: border-box;
118
+ }
119
+ .salas-sidebar .salas-sidebar-menu-button:hover,
120
+ .salas-sidebar salas-sidebar-menu-button:hover {
121
+ background-color: var(--salas-gray-100);
122
+ color: var(--salas-text);
123
+ }
124
+ .salas-sidebar .salas-sidebar-menu-button--active,
125
+ .salas-sidebar .salas-sidebar-menu-button[aria-current="page"],
126
+ .salas-sidebar salas-sidebar-menu-button.salas-sidebar-menu-button--active {
127
+ background-color: var(--salas-gray-100);
128
+ color: var(--salas-text);
129
+ font-weight: 500;
130
+ }
131
+ .salas-sidebar .salas-sidebar-menu-icon,
132
+ .salas-sidebar .salas-sidebar-menu-button .salas-sidebar-menu-icon {
133
+ flex-shrink: 0;
134
+ display: inline-flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ }
138
+
96
139
  .salas-sidebar[data-side="left"] {
97
140
  left: 0;
98
141
  }