@libs-ui/components-spinner 0.2.356-41 → 0.2.356-43
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/README.md
CHANGED
|
@@ -1,32 +1,30 @@
|
|
|
1
1
|
# @libs-ui/components-spinner
|
|
2
2
|
|
|
3
|
-
> Component hiển thị hiệu ứng loading (spinner)
|
|
3
|
+
> Component hiển thị hiệu ứng loading (spinner) với animation xoay mượt mà, hỗ trợ nhiều kích thước và chế độ màu sắc.
|
|
4
4
|
|
|
5
5
|
## Giới thiệu
|
|
6
6
|
|
|
7
|
-
`LibsUiComponentsSpinnerComponent` là một standalone Angular component hiển thị spinner loading
|
|
7
|
+
`LibsUiComponentsSpinnerComponent` là một standalone Angular component hiển thị spinner loading dựa trên CSS `conic-gradient` và `mask`. Component hỗ trợ 4 kích thước chuẩn, 2 màu sắc (xanh và trắng), và 2 chế độ vị trí: normal flow và absolute (tự căn giữa container cha). Phù hợp để thể hiện trạng thái loading cục bộ (trong button, card) hoặc loading overlay toàn vùng.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Tính năng
|
|
10
10
|
|
|
11
|
-
- ✅
|
|
12
|
-
- ✅ 4 kích thước chuẩn:
|
|
13
|
-
- ✅ 2 chế độ màu:
|
|
14
|
-
- ✅ 2 chế độ vị trí:
|
|
11
|
+
- ✅ Animation xoay mượt mà dùng CSS `conic-gradient` — không cần SVG icon hay ảnh ngoài
|
|
12
|
+
- ✅ 4 kích thước chuẩn: `large` (64px), `medium` (20px), `small` (16px), `smaller` (12px)
|
|
13
|
+
- ✅ 2 chế độ màu: xanh (`blue`) và trắng (`white`)
|
|
14
|
+
- ✅ 2 chế độ vị trí: normal flow và absolute (tự căn giữa tuyệt đối)
|
|
15
|
+
- ✅ Standalone component, OnPush, không phụ thuộc thư viện ngoài
|
|
15
16
|
|
|
16
17
|
## Khi nào sử dụng
|
|
17
18
|
|
|
18
|
-
- Khi đang tải dữ liệu cục bộ
|
|
19
|
-
- Khi
|
|
20
|
-
-
|
|
19
|
+
- Khi đang tải dữ liệu cục bộ trong button, card, hoặc input để thay cho text "Loading..."
|
|
20
|
+
- Khi cần overlay loading trên một vùng content (container, modal, section)
|
|
21
|
+
- Khi cần loading toàn trang hoặc toàn modal với chế độ `spin-absolute-*`
|
|
22
|
+
- Khi muốn biểu thị trạng thái xử lý (submit form, sync dữ liệu) ở button nhỏ với `size="smaller"`
|
|
21
23
|
|
|
22
24
|
## Cài đặt
|
|
23
25
|
|
|
24
26
|
```bash
|
|
25
|
-
# npm
|
|
26
27
|
npm install @libs-ui/components-spinner
|
|
27
|
-
|
|
28
|
-
# yarn
|
|
29
|
-
yarn add @libs-ui/components-spinner
|
|
30
28
|
```
|
|
31
29
|
|
|
32
30
|
## Import
|
|
@@ -42,73 +40,171 @@ import { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';
|
|
|
42
40
|
export class YourComponent {}
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
|
|
43
|
+
Export type đi kèm:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { TYPE_SPINNER } from '@libs-ui/components-spinner';
|
|
47
|
+
```
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
## Ví dụ sử dụng
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
### 1. Spinner mặc định (medium, absolute blue)
|
|
50
52
|
|
|
51
53
|
```html
|
|
52
|
-
<
|
|
54
|
+
<div class="relative w-full h-[200px]">
|
|
55
|
+
<libs_ui-components-spinner />
|
|
56
|
+
</div>
|
|
53
57
|
```
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
> Container cha cần `position: relative` khi dùng type mặc định `spin-absolute-blue`.
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
### 2. Spinner inline trong button
|
|
58
62
|
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
```typescript
|
|
64
|
+
import { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';
|
|
65
|
+
import { Component, signal } from '@angular/core';
|
|
66
|
+
|
|
67
|
+
@Component({
|
|
68
|
+
standalone: true,
|
|
69
|
+
imports: [LibsUiComponentsSpinnerComponent],
|
|
70
|
+
template: `
|
|
71
|
+
<button
|
|
72
|
+
class="px-4 py-2 rounded-md bg-blue-600 text-white flex items-center gap-2"
|
|
73
|
+
(click)="handlerSave($event)">
|
|
74
|
+
@if (isSaving()) {
|
|
75
|
+
<libs_ui-components-spinner
|
|
76
|
+
type="spin-white"
|
|
77
|
+
size="small" />
|
|
78
|
+
}
|
|
79
|
+
<span>{{ isSaving() ? 'Đang lưu...' : 'Lưu' }}</span>
|
|
80
|
+
</button>
|
|
81
|
+
`,
|
|
82
|
+
})
|
|
83
|
+
export class SaveButtonComponent {
|
|
84
|
+
protected isSaving = signal(false);
|
|
85
|
+
|
|
86
|
+
protected handlerSave(event: Event): void {
|
|
87
|
+
event.stopPropagation();
|
|
88
|
+
this.isSaving.set(true);
|
|
89
|
+
// gọi API, rồi set false khi xong
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 3. Spinner overlay trên vùng content
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { LibsUiComponentsSpinnerComponent } from '@libs-ui/components-spinner';
|
|
98
|
+
import { Component, signal } from '@angular/core';
|
|
99
|
+
|
|
100
|
+
@Component({
|
|
101
|
+
standalone: true,
|
|
102
|
+
imports: [LibsUiComponentsSpinnerComponent],
|
|
103
|
+
template: `
|
|
104
|
+
<div class="relative w-full min-h-[300px] border rounded-lg">
|
|
105
|
+
<!-- Nội dung chính -->
|
|
106
|
+
<div class="p-6">
|
|
107
|
+
<h2 class="text-lg font-semibold">Danh sách sản phẩm</h2>
|
|
108
|
+
<p class="text-gray-500 mt-2">Nội dung sẽ hiển thị ở đây...</p>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<!-- Overlay loading khi đang tải -->
|
|
112
|
+
@if (isLoading()) {
|
|
113
|
+
<div class="absolute inset-0 bg-white/70 rounded-lg">
|
|
114
|
+
<libs_ui-components-spinner
|
|
115
|
+
type="spin-absolute-blue"
|
|
116
|
+
size="large" />
|
|
117
|
+
</div>
|
|
118
|
+
}
|
|
119
|
+
</div>
|
|
120
|
+
`,
|
|
121
|
+
})
|
|
122
|
+
export class ProductListComponent {
|
|
123
|
+
protected isLoading = signal(true);
|
|
124
|
+
}
|
|
66
125
|
```
|
|
67
126
|
|
|
68
|
-
###
|
|
127
|
+
### 4. So sánh các kích thước
|
|
128
|
+
|
|
129
|
+
```html
|
|
130
|
+
<!-- large: 64px — dùng cho overlay toàn vùng lớn -->
|
|
131
|
+
<libs_ui-components-spinner type="spin-blue" size="large" />
|
|
69
132
|
|
|
70
|
-
|
|
133
|
+
<!-- medium: 20px — kích thước mặc định, dùng cho card, section -->
|
|
134
|
+
<libs_ui-components-spinner type="spin-blue" size="medium" />
|
|
135
|
+
|
|
136
|
+
<!-- small: 16px — dùng trong button tiêu chuẩn -->
|
|
137
|
+
<libs_ui-components-spinner type="spin-blue" size="small" />
|
|
138
|
+
|
|
139
|
+
<!-- smaller: 12px — dùng trong button nhỏ, icon compact -->
|
|
140
|
+
<libs_ui-components-spinner type="spin-white" size="smaller" />
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 5. Spinner màu trắng trên nền tối
|
|
71
144
|
|
|
72
145
|
```html
|
|
73
|
-
<div class="
|
|
146
|
+
<div class="bg-blue-600 rounded-lg p-6 flex items-center gap-3">
|
|
74
147
|
<libs_ui-components-spinner
|
|
75
|
-
type="spin-
|
|
76
|
-
size="
|
|
148
|
+
type="spin-white"
|
|
149
|
+
size="medium" />
|
|
150
|
+
<span class="text-white font-medium">Đang xử lý...</span>
|
|
77
151
|
</div>
|
|
78
152
|
```
|
|
79
153
|
|
|
80
|
-
##
|
|
154
|
+
## @Input()
|
|
81
155
|
|
|
82
|
-
|
|
156
|
+
| Input | Type | Default | Mô tả | Ví dụ |
|
|
157
|
+
|---|---|---|---|---|
|
|
158
|
+
| `[type]` | `TYPE_SPINNER` | `'spin-absolute-blue'` | Chế độ hiển thị — xác định màu sắc và kiểu vị trí của spinner | `type="spin-blue"` |
|
|
159
|
+
| `[size]` | `'large' \| 'medium' \| 'small' \| 'smaller'` | `'medium'` | Kích thước spinner: `large`=64px, `medium`=20px, `small`=16px, `smaller`=12px | `size="large"` |
|
|
83
160
|
|
|
84
|
-
|
|
161
|
+
## Types & Interfaces
|
|
85
162
|
|
|
86
|
-
|
|
163
|
+
```typescript
|
|
164
|
+
import { TYPE_SPINNER } from '@libs-ui/components-spinner';
|
|
87
165
|
|
|
88
|
-
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
166
|
+
type TYPE_SPINNER =
|
|
167
|
+
| 'spin-absolute-blue' // position: absolute, căn giữa container cha — màu xanh
|
|
168
|
+
| 'spin-absolute-white' // position: absolute, căn giữa container cha — màu trắng
|
|
169
|
+
| 'spin-blue' // normal flow (position: relative) — màu xanh
|
|
170
|
+
| 'spin-white'; // normal flow (position: relative) — màu trắng
|
|
171
|
+
```
|
|
92
172
|
|
|
93
|
-
###
|
|
173
|
+
### Bảng đối chiếu type
|
|
94
174
|
|
|
95
|
-
|
|
175
|
+
| `type` | Màu sắc | Vị trí | Dùng khi |
|
|
176
|
+
|---|---|---|---|
|
|
177
|
+
| `spin-absolute-blue` | Xanh | `position: absolute`, căn giữa | Overlay loading trên container cha có `relative` |
|
|
178
|
+
| `spin-absolute-white` | Trắng | `position: absolute`, căn giữa | Overlay loading trên nền tối có `relative` |
|
|
179
|
+
| `spin-blue` | Xanh | Normal flow | Inline trong button, card trên nền sáng |
|
|
180
|
+
| `spin-white` | Trắng | Normal flow | Inline trong button, card trên nền tối/màu |
|
|
96
181
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
182
|
+
## Lưu ý quan trọng
|
|
183
|
+
|
|
184
|
+
⚠️ **Reactivity của `size`**: Kích thước (`width`, `height`) được tính một lần duy nhất tại `ngOnInit`. Thay đổi input `size` sau khi component đã render sẽ **không** cập nhật kích thước spinner. Nếu cần thay đổi size động, dùng `@if` để destroy và re-create component:
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<!-- Cách xử lý thay đổi size động -->
|
|
188
|
+
@if (showSpinner()) {
|
|
189
|
+
<libs_ui-components-spinner [type]="spinnerType()" [size]="spinnerSize()" />
|
|
190
|
+
}
|
|
103
191
|
```
|
|
104
192
|
|
|
105
|
-
|
|
193
|
+
⚠️ **Container cha với `spin-absolute-*`**: Khi dùng `type="spin-absolute-blue"` hoặc `type="spin-absolute-white"`, component tự áp dụng `position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%)`. Bắt buộc container cha phải có `position: relative` để spinner căn giữa chính xác:
|
|
106
194
|
|
|
107
|
-
|
|
195
|
+
```html
|
|
196
|
+
<!-- ✅ ĐÚNG — container có position: relative -->
|
|
197
|
+
<div class="relative w-full h-[200px]">
|
|
198
|
+
<libs_ui-components-spinner type="spin-absolute-blue" size="large" />
|
|
199
|
+
</div>
|
|
108
200
|
|
|
109
|
-
|
|
201
|
+
<!-- ❌ SAI — thiếu relative, spinner có thể bị offset sai -->
|
|
202
|
+
<div class="w-full h-[200px]">
|
|
203
|
+
<libs_ui-components-spinner type="spin-absolute-blue" size="large" />
|
|
204
|
+
</div>
|
|
205
|
+
```
|
|
110
206
|
|
|
111
|
-
⚠️ **
|
|
207
|
+
⚠️ **Giá trị `null`/`undefined` cho input**: Cả `type` và `size` đều có transform guard — nếu truyền vào `null` hay `undefined`, component tự fallback về giá trị mặc định (`'spin-absolute-blue'` và `'medium'`).
|
|
112
208
|
|
|
113
209
|
## Demo
|
|
114
210
|
|
|
@@ -116,4 +212,4 @@ Component sử dụng CSS `conic-gradient` và `mask` để tạo hình dạng s
|
|
|
116
212
|
npx nx serve core-ui
|
|
117
213
|
```
|
|
118
214
|
|
|
119
|
-
Truy cập:
|
|
215
|
+
Truy cập: http://localhost:4500/components/spinner
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { signal, input,
|
|
2
|
+
import { signal, input, Component, ChangeDetectionStrategy } from '@angular/core';
|
|
3
3
|
|
|
4
4
|
class LibsUiComponentsSpinnerComponent {
|
|
5
5
|
// #region PROPERTY
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"libs-ui-components-spinner.mjs","sources":["../../../../../libs-ui/components/spinner/src/spinner.component.ts","../../../../../libs-ui/components/spinner/src/spinner.component.html","../../../../../libs-ui/components/spinner/src/libs-ui-components-spinner.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, input, OnInit, signal } from '@angular/core';\nimport { TYPE_SPINNER } from './interfaces/spinner.type';\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-spinner',\n templateUrl: 'spinner.component.html',\n styleUrls: ['./spinner.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class LibsUiComponentsSpinnerComponent implements OnInit {\n // #region PROPERTY\n protected width = signal<string>('100px');\n protected height = signal<string>('100px');\n\n // #region INPUT\n readonly type = input<TYPE_SPINNER, TYPE_SPINNER>('spin-absolute-blue', { transform: (value) => value || 'spin-absolute-blue' });\n readonly size = input<'large' | 'medium' | 'small' | 'smaller', 'large' | 'medium' | 'small' | 'smaller'>('medium', { transform: (value) => value || 'medium' });\n\n ngOnInit(): void {\n switch (this.size()) {\n case 'large':\n this.width.set('64px');\n this.height.set('64px');\n break;\n\n case 'medium':\n this.width.set('20px');\n this.height.set('20px');\n break;\n\n case 'small':\n this.width.set('16px');\n this.height.set('16px');\n break;\n\n case 'smaller':\n this.width.set('12px');\n this.height.set('12px');\n break;\n }\n }\n}\n","<div\n class=\"libs-ui-spinner-image\"\n [class.libs-ui-spinner-image-absolute]=\"type() === 'spin-absolute-white' || type() === 'spin-absolute-blue'\">\n <div\n class=\"spin-loading\"\n [attr.color]=\"type() === 'spin-blue' || type() === 'spin-absolute-blue' ? 'blue' : 'white'\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"></div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;MAWa,gCAAgC,CAAA;;AAEjC,IAAA,KAAK,GAAG,MAAM,CAAS,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"libs-ui-components-spinner.mjs","sources":["../../../../../libs-ui/components/spinner/src/spinner.component.ts","../../../../../libs-ui/components/spinner/src/spinner.component.html","../../../../../libs-ui/components/spinner/src/libs-ui-components-spinner.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, input, OnInit, signal } from '@angular/core';\nimport { TYPE_SPINNER } from './interfaces/spinner.type';\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'libs_ui-components-spinner',\n templateUrl: 'spinner.component.html',\n styleUrls: ['./spinner.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n standalone: true,\n})\nexport class LibsUiComponentsSpinnerComponent implements OnInit {\n // #region PROPERTY\n protected width = signal<string>('100px');\n protected height = signal<string>('100px');\n\n // #region INPUT\n readonly type = input<TYPE_SPINNER, TYPE_SPINNER>('spin-absolute-blue', { transform: (value) => value || 'spin-absolute-blue' });\n readonly size = input<'large' | 'medium' | 'small' | 'smaller', 'large' | 'medium' | 'small' | 'smaller'>('medium', { transform: (value) => value || 'medium' });\n\n ngOnInit(): void {\n switch (this.size()) {\n case 'large':\n this.width.set('64px');\n this.height.set('64px');\n break;\n\n case 'medium':\n this.width.set('20px');\n this.height.set('20px');\n break;\n\n case 'small':\n this.width.set('16px');\n this.height.set('16px');\n break;\n\n case 'smaller':\n this.width.set('12px');\n this.height.set('12px');\n break;\n }\n }\n}\n","<div\n class=\"libs-ui-spinner-image\"\n [class.libs-ui-spinner-image-absolute]=\"type() === 'spin-absolute-white' || type() === 'spin-absolute-blue'\">\n <div\n class=\"spin-loading\"\n [attr.color]=\"type() === 'spin-blue' || type() === 'spin-absolute-blue' ? 'blue' : 'white'\"\n [style.width]=\"width()\"\n [style.height]=\"height()\"></div>\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;MAWa,gCAAgC,CAAA;;AAEjC,IAAA,KAAK,GAAG,MAAM,CAAS,OAAO,CAAC,CAAC;AAChC,IAAA,MAAM,GAAG,MAAM,CAAS,OAAO,CAAC,CAAC;;AAGlC,IAAA,IAAI,GAAG,KAAK,CAA6B,oBAAoB,EAAE,EAAE,SAAS,EAAE,CAAC,KAAK,KAAK,KAAK,IAAI,oBAAoB,EAAE,CAAC,CAAC;AACxH,IAAA,IAAI,GAAG,KAAK,CAAqF,QAAQ,EAAE,EAAE,SAAS,EAAE,CAAC,KAAK,KAAK,KAAK,IAAI,QAAQ,EAAE,CAAC,CAAC;IAEjK,QAAQ,GAAA;AACN,QAAA,QAAQ,IAAI,CAAC,IAAI,EAAE;AACjB,YAAA,KAAK,OAAO;AACV,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACvB,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACxB,MAAM;AAER,YAAA,KAAK,QAAQ;AACX,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACvB,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACxB,MAAM;AAER,YAAA,KAAK,OAAO;AACV,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACvB,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACxB,MAAM;AAER,YAAA,KAAK,SAAS;AACZ,gBAAA,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACvB,gBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACxB,MAAM;SACT;KACF;wGA/BU,gCAAgC,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA,CAAA;AAAhC,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,gCAAgC,4UCX7C,oXASA,EAAA,MAAA,EAAA,CAAA,4xCAAA,CAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,CAAA,CAAA;;4FDEa,gCAAgC,EAAA,UAAA,EAAA,CAAA;kBAR5C,SAAS;AAEE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,4BAA4B,EAGrB,eAAA,EAAA,uBAAuB,CAAC,MAAM,cACnC,IAAI,EAAA,QAAA,EAAA,oXAAA,EAAA,MAAA,EAAA,CAAA,4xCAAA,CAAA,EAAA,CAAA;;;AETlB;;AAEG;;;;"}
|