commit 136079ed1a685609da81c1c86f00c6871074a44e Author: Zhongwei Li Date: Sun Nov 30 08:53:03 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..aa7c88f --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,14 @@ +{ + "name": "angular", + "description": "A plugin to support generation of good looking and well designed api components", + "version": "0.0.1", + "author": { + "name": "Reto Ryter" + }, + "agents": [ + "./agents" + ], + "commands": [ + "./commands" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d317a6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# angular + +A plugin to support generation of good looking and well designed api components diff --git a/agents/architecture.md b/agents/architecture.md new file mode 100644 index 0000000..e69de29 diff --git a/commands/create-component.md b/commands/create-component.md new file mode 100644 index 0000000..0aa00d4 --- /dev/null +++ b/commands/create-component.md @@ -0,0 +1,376 @@ +# UI Component Command + +Generate modern Angular standalone components with signals, using Spartan UI components from `libs/ui`. + +## Usage + +``` +/ui-component +``` + +**Arguments:** + +- `component-name` (required): Name for the component in kebab-case (e.g., "user-card", "task-list", "metric-display") +- `reference-description` (required): Description of what the component should do and how it should look + +**Examples:** + +```bash +/ui-component user-card "A card component that displays user info with avatar, name, email, and a follow button" +/ui-component metric-display "Display a metric with a large number, label, trend indicator (up/down arrow), and percentage change" +/ui-component task-list "A list of tasks with checkboxes, due dates, priority badges, and delete buttons" +``` + +## Output Structure + +Generated files are saved to: + +``` +sentinel-frontend/libs/shared/ui-custom/src/lib/components// +├── .component.ts +├── .component.html +└── .component.css +``` + +## Instructions + +When this command is invoked, follow these steps: + +### 1. Gather Context from Angular Docs (Optional) + +If Context7 is available, use it to fetch relevant Angular documentation: + +```bash +npx context7 search angular +# Then use the correct project path: +npx context7 "angular signals input output model" +npx context7 "standalone components" +``` + +If Context7 is not available, proceed with the following Angular patterns: + +- Modern signal APIs: `signal()`, `computed()`, `effect()` +- Input/output patterns: `input()`, `input.required()`, `output()`, `model()` +- Standalone component syntax with `standalone: true` +- Modern control flow: `@if`, `@for`, `@switch`, `@defer` +- Use `inject()` for dependency injection instead of constructor injection + +### 2. Identify Spartan UI Components to Use + +Available Spartan UI components in `libs/ui`: + +- **Layout**: `ui-card-helm`, `ui-separator-helm`, `ui-accordion-helm`, `ui-sheet-helm`, `ui-dialog-helm`, `ui-sidebar-helm` +- **Forms**: `ui-input-helm`, `ui-textarea-helm`, `ui-checkbox-helm`, `ui-radio-group-helm`, `ui-select-helm`, `ui-switch-helm`, `ui-slider-helm`, `ui-form-field-helm` +- **Buttons**: `ui-button-helm`, `ui-button-group-helm`, `ui-toggle-helm`, `ui-toggle-group-helm` +- **Data Display**: `ui-table-helm`, `ui-badge-helm`, `ui-avatar-helm`, `ui-progress-helm`, `ui-skeleton-helm`, `ui-empty-helm` +- **Feedback**: `ui-alert-helm`, `ui-alert-dialog-helm`, `ui-tooltip-helm`, `ui-popover-helm`, `ui-hover-card-helm`, `ui-sonner-helm`, `ui-spinner-helm` +- **Navigation**: `ui-tabs-helm`, `ui-menu-helm`, `ui-breadcrumb-helm`, `ui-pagination-helm` +- **Utilities**: `ui-icon-helm`, `ui-label-helm`, `ui-typography-helm`, `ui-scroll-area-helm`, `ui-resizable-helm`, `ui-kbd-helm` + +Choose appropriate components based on the reference description. + +### 3. Generate Component TypeScript File + +Create a **standalone** component following these patterns: + +**Component Structure:** + +```typescript +import { ChangeDetectionStrategy, Component, input, output, model, signal, computed } from '@angular/core'; +import { HlmButtonDirective } from '@spartan-ng/helm/button'; +import { HlmCardDirective, HlmCardHeaderDirective, HlmCardContentDirective } from '@spartan-ng/helm/card'; +// Import other Spartan UI components as needed + +@Component({ + selector: 'app-', + standalone: true, + imports: [ + // List all Spartan UI components used + HlmButtonDirective, + HlmCardDirective, + HlmCardHeaderDirective, + HlmCardContentDirective, + // Add others as needed + ], + templateUrl: './.component.html', + styleUrls: ['./.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class Component { + // Use input() for read-only inputs + readonly title = input.required(); + readonly subtitle = input(''); + + // Use output() for events + readonly clicked = output(); + + // Use model() for two-way binding + readonly value = model(0); + + // Use signal() for internal state + protected readonly isExpanded = signal(false); + + // Use computed() for derived state + protected readonly displayValue = computed(() => { + return `${this.title()}: ${this.value()}`; + }); + + // Methods + protected handleClick() { + this.clicked.emit(); + } + + protected toggle() { + this.isExpanded.update(v => !v); + } +} +``` + +**Key Patterns:** + +- Use `ChangeDetectionStrategy.OnPush` for better performance +- Use `input()` for props, `input.required()` for required props +- Use `output()` for events +- Use `model()` for two-way bound properties +- Use `signal()` for internal reactive state +- Use `computed()` for derived values +- Use `readonly` for public API properties +- Use `protected` for template-only methods and properties +- Import Spartan UI directives/components, not modules + +### 4. Generate Component HTML Template + +Use modern Angular template syntax: + +**Control Flow:** + +```html + +@if (isExpanded()) { +
Expanded content
+} + + +@for (item of items(); track item.id) { +
{{ item.name }}
+} + + +@switch (status()) { @case ('loading') { + +} @case ('success') { +
Success!
+} @default { +
Unknown status
+} } + + +@defer (on viewport) { + +} @placeholder { + +} +``` + +**Spartan UI Components:** +Use Spartan UI components with their directive syntax: + +```html + + +
+
+

{{ title() }}

+

{{ subtitle() }}

+
+
+ +
+
+ + + +Badge + +
+ Avatar +
+``` + +### 5. Generate Component SCSS File + +Create minimal SCSS for custom styling: + +- Only add SCSS that can't be achieved with Spartan UI or Tailwind +- Use SCSS custom properties for theming when possible +- Keep styles scoped to the component +- Add comments explaining custom styles + +```scss +/* Custom styles that extend Spartan UI */ +:host { + display: block; +} + +.custom-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; +} +``` + +### 6. Create the Component Files + +Use the Write tool to create all three files: + +1. `.component.ts` +2. `.component.html` +3. `.component.css` + + + +Save them to: `sentinel-frontend/libs/shared/ui-custom/src/lib/components//` + +### 7. Generate Usage Example + +Provide a clear usage example showing: + +- How to import the component +- Example template usage with all inputs/outputs +- Common use cases + +Example: + +```typescript +// In another component +import { Component } from '@shared/ui-custom'; + +@Component({ + imports: [Component], + template: ` + + [title]="myTitle" + [subtitle]="mySubtitle" + [(value)]="currentValue" + (clicked)="handleClick()" + /> + ` +}) +export class MyComponent { + protected myTitle = 'Example'; + protected mySubtitle = 'Subtitle'; + protected currentValue = signal(42); + + protected handleClick() { + console.log('Component clicked!'); + } +} +``` + +## Important Notes + +- **Always use modern Angular syntax**: signals, standalone components, new control flow +- **No NgModules**: Components should be standalone +- **Use inject() instead of constructor DI**: More modern and flexible +- **Spartan UI over custom HTML**: Use Spartan UI components as building blocks +- **OnPush change detection**: Always use for better performance +- **Type safety**: Always provide proper TypeScript types +- **Accessibility**: Ensure proper ARIA labels, semantic HTML, keyboard navigation +- **Naming convention**: Components should be kebab-case, class names should be PascalCase +- **Template syntax**: Use `@if/@for/@switch` not `*ngIf/*ngFor/*ngSwitch` +- **Signal calls**: Remember to call signals with `()`: `mySignal()` not `mySignal` + +## Common Patterns + +### Presentational Component (Dumb Component) + +```typescript +@Component({ + selector: "app-user-card", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class UserCardComponent { + // Inputs only + readonly user = input.required(); + readonly compact = input(false); + + // Outputs only + readonly userClicked = output(); + + // No internal state or business logic + protected handleClick() { + this.userClicked.emit(this.user()); + } +} +``` + +### Form Control Component + +```typescript +@Component({ + selector: "app-rating-input", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RatingInputComponent { + // Two-way binding with model() + readonly rating = model.required(); + readonly max = input(5); + readonly disabled = input(false); + + protected setRating(value: number) { + if (!this.disabled()) { + this.rating.set(value); + } + } +} +``` + +### Stateful Component + +```typescript +@Component({ + selector: "app-expandable-section", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ExpandableSectionComponent { + // External inputs + readonly title = input.required(); + readonly defaultExpanded = input(false); + + // Internal state + protected readonly isExpanded = signal(false); + + constructor() { + // Initialize from input + effect(() => { + this.isExpanded.set(this.defaultExpanded()); + }); + } + + protected toggle() { + this.isExpanded.update((v) => !v); + } +} +``` + +## Validation + +After generating the component: + +1. Check that all imports are correct +2. Verify signal syntax (remember the `()` calls) +3. Ensure proper TypeScript types +4. Confirm Spartan UI components are used correctly +5. Validate template syntax uses modern control flow +6. Check that component is standalone +7. Verify change detection strategy is OnPush + +## Summary Output + +Provide to the user: + +- Paths to all generated files +- Brief description of the component +- List of Spartan UI components used +- Usage example +- Any notes about customization or extension diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..967a214 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,49 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:rryter/claude-plugins:angular", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "3ebd96907c49162ad6b7985eff7cd00ff59e40fe", + "treeHash": "17eeb4ed8deae4655eecc58db9692d26e96f5ffd4f9618d89eec41f62a413d9c", + "generatedAt": "2025-11-28T10:28:03.447032Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "angular", + "description": "A plugin to support generation of good looking and well designed api components", + "version": "0.0.1" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "945d86b3fc0f9f4f105c0f722cd832ee7b8d56dcf429a05a5105c28cc059885d" + }, + { + "path": "agents/architecture.md", + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "fe5338ec9cbe7513d3fa8a896429bd2a9a19fdc584e04542483ba7071755e12d" + }, + { + "path": "commands/create-component.md", + "sha256": "a5ae0741cba19d0b2cace7512541909fed14a01ed5ed90cad4d94ced9d58841b" + } + ], + "dirSha256": "17eeb4ed8deae4655eecc58db9692d26e96f5ffd4f9618d89eec41f62a413d9c" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file