Skip to content

Commit e3fea3e

Browse files
committed
feat: enhance todo component with improved input handling and skeleton loading UI
- updated input field layout for better user experience - added button for creating todos with loading state management - refined skeleton loading component for consistent styling and structure - included import ordering guidelines in documentation
1 parent d03331f commit e3fea3e

3 files changed

Lines changed: 103 additions & 37 deletions

File tree

.github/copilot-instructions.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,49 @@ Follow modern Angular syntax and best practices like
99
* use signals to consume data in template exclusively, this means there won't be any `async` pipes in the template
1010
* use Angular signals and signal-based APIs, eg `viewChild` instead of `@ViewChild` or `output` instead of `@Output`
1111
* use Angular schematics using `ng generate` to generate new files, that way you will follow prescribed conventions from angular.json
12-
* always escape characters like `@` in the template with `@` because this is now Angular reserved character
12+
* always escape characters like `@` (when part of text, not part of official API like `@if`, `@for`, ... ) in the template with `@` because this is now Angular reserved character
13+
* whenever you do changes on a file, run `prettier`
14+
15+
16+
## Imports styles
17+
* when adding imports to file (and component imports array), always follow this order
18+
* make sure to separate groups of imports with empty line (for main file imports, but NOT in the Angular component imports array)
19+
20+
1. group 1 - vendor
21+
a. first non-angular imports, like `lodash`, `date-fns`, etc.
22+
b. angular imports, like `@angular/core`, `@angular/common`, material, cdk,
23+
c. Angular related imports like `@ngrx/store`, `@ngrx/effects`, `@angular/router`, etc.
24+
d. RxJS imports like `rxjs` (never use `/operators`)
25+
e. other Angular related imports
26+
2. group 2 - local vendor (based on TS config paths)
27+
3. group 3 - "deep" application imports, eg across architectural boundary, eg from `core`, `model`, `ui`, ...
28+
4. group 4 - local imports, eg if the file is in `ui` folder, then imports from `ui` folder, if the file is in `core` folder, then imports from `core` folder, etc.
29+
30+
For the Angular component imports array
31+
1. vendor imports
32+
2. local vendor imports
33+
3. "deep" application imports
34+
4. local imports
35+
36+
Then within the single group, always sort the imports by the line length, if it has too much length so that it breaks on multiple lines, those are always before the single line ones
37+
38+
### Example
39+
40+
```typescript
41+
import {
42+
ChangeDetectionStrategy,
43+
Component,
44+
signal,
45+
inject
46+
} from '@angular/core';
47+
import { MatIconModule } from '@angular/material/icon';
48+
import { MatButtonModule } from '@angular/material/button';
49+
import { MatFormField, MatInput } from '@angular/material/input';
50+
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
51+
52+
import { restResource } from '@angular-experts/resource';
53+
54+
import { Todo } from '../../../model/todo.model';
55+
import { TodoItemComponent } from '../../../ui/todo-item/todo-item.component';
56+
import { TodoSkeletonComponent } from '../../../ui/todo-skeleton/todo-skeleton.component';
57+
```

projects/showcase/src/app/feature/example/basic/basic.component.ts

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
signal
5+
} from '@angular/core';
6+
import { MatIconModule } from '@angular/material/icon';
7+
import { MatButtonModule } from '@angular/material/button';
28
import { MatFormField, MatInput } from '@angular/material/input';
39
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
410

@@ -13,6 +19,8 @@ import { TodoSkeletonComponent } from '../../../ui/todo-skeleton/todo-skeleton.c
1319
imports: [
1420
MatInput,
1521
MatFormField,
22+
MatIconModule,
23+
MatButtonModule,
1624
MatProgressSpinnerModule,
1725
TodoItemComponent,
1826
TodoSkeletonComponent,
@@ -31,21 +39,36 @@ import { TodoSkeletonComponent } from '../../../ui/todo-skeleton/todo-skeleton.c
3139
}
3240
</div>
3341
34-
<mat-form-field appearance="outline">
35-
<input
36-
#todoDescriptionInputRef
37-
matInput
38-
placeholder="What am I going to do..."
39-
[value]="newTodo()"
40-
(input)="newTodo.set(todoDescriptionInputRef.value)"
41-
(keyup.enter)="createTodo()"
42-
[disabled]="todos.loading()"
43-
/>
44-
</mat-form-field>
42+
<div class="flex items-center gap-4">
43+
<mat-form-field
44+
appearance="outline"
45+
class="flex-grow"
46+
subscriptSizing="dynamic"
47+
>
48+
<input
49+
#todoDescriptionInputRef
50+
matInput
51+
placeholder="What am I going to do..."
52+
[value]="newTodo()"
53+
(input)="newTodo.set(todoDescriptionInputRef.value)"
54+
(keyup.enter)="createTodo()"
55+
[disabled]="todos.loading()"
56+
/>
57+
</mat-form-field>
58+
<button
59+
mat-fab
60+
color="primary"
61+
aria-label="Add todo"
62+
(click)="createTodo()"
63+
[disabled]="todos.loading() || newTodo().length === 0"
64+
>
65+
<mat-icon>add</mat-icon>
66+
</button>
67+
</div>
4568
4669
<div class="relative flex flex-col gap-4">
47-
@if (todos.loadingInitial()) {
4870
<showcase-todo-skeleton [repeat]="4" />
71+
@if (todos.loadingInitial()) {
4972
} @else {
5073
@for (todo of todos.values(); track todo.id) {
5174
<showcase-todo-item

projects/showcase/src/app/ui/todo-skeleton/todo-skeleton.component.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,36 @@
11
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
22

3-
import { MatCardModule } from '@angular/material/card';
4-
53
@Component({
64
selector: 'showcase-todo-skeleton',
7-
imports: [MatCardModule],
85
template: `
9-
@for (_ of [].constructor(repeat()); track $index) {
10-
<mat-card class="mb-4 p-4">
11-
<div class="flex w-full animate-pulse items-center justify-between">
12-
<!-- Left part: Placeholder for checkbox/icon and description -->
13-
<div class="flex w-full items-center gap-3">
14-
<!-- Checkbox placeholder -->
15-
<div class="h-6 w-6 rounded bg-gray-300"></div>
16-
<!-- Description placeholder -->
17-
<div class="h-4 w-3/4 rounded bg-gray-300"></div>
18-
</div>
19-
<!-- Right part: Placeholders for two icon buttons -->
20-
<div class="flex items-center gap-2">
21-
<div class="h-8 w-8 rounded-full bg-gray-300"></div>
22-
<!-- Placeholder for edit icon button -->
23-
<div class="h-8 w-8 rounded-full bg-gray-300"></div>
24-
<!-- Placeholder for delete icon button -->
6+
<div class="flex flex-col gap-4">
7+
@for (_ of [].constructor(repeat()); track $index) {
8+
<div class="card flex items-center justify-between">
9+
<div
10+
class="flex flex-grow animate-pulse items-center justify-between"
11+
>
12+
<!-- Left part: Placeholder for checkbox/icon and description -->
13+
<div class="flex flex-grow items-center gap-6">
14+
<!-- Checkbox placeholder -->
15+
<div class="h-6 w-6 rounded bg-gray-300"></div>
16+
<!-- Description placeholder -->
17+
<div class="h-4 w-3/4 rounded bg-gray-300"></div>
18+
</div>
19+
<!-- Right part: Placeholders for two icon buttons -->
20+
<div class="flex items-center gap-2">
21+
<div class="h-10 w-10 rounded-full bg-gray-300"></div>
22+
<div class="h-10 w-10 rounded-full bg-gray-300"></div>
23+
</div>
2524
</div>
2625
</div>
27-
</mat-card>
28-
}
26+
}
27+
</div>
2928
`,
3029
styles: `
3130
:host {
3231
display: block;
33-
width: 100%; /* Ensure it takes full width available */
32+
width: 100%;
3433
}
35-
/* Tailwind will be used for inline classes, but if specific :host styling is needed, it goes here */
3634
`,
3735
changeDetection: ChangeDetectionStrategy.OnPush,
3836
})

0 commit comments

Comments
 (0)