@sd-angular/core 19.0.0-beta.84 → 19.0.0-beta.85

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 (130) hide show
  1. package/README.md +716 -716
  2. package/assets/fonts/fonts.scss +115 -115
  3. package/assets/scss/bootstrap-4.6.2/_alert.scss +52 -52
  4. package/assets/scss/bootstrap-4.6.2/_badge.scss +54 -54
  5. package/assets/scss/bootstrap-4.6.2/_breadcrumb.scss +42 -42
  6. package/assets/scss/bootstrap-4.6.2/_button-group.scss +163 -163
  7. package/assets/scss/bootstrap-4.6.2/_buttons.scss +142 -142
  8. package/assets/scss/bootstrap-4.6.2/_card.scss +286 -286
  9. package/assets/scss/bootstrap-4.6.2/_carousel.scss +200 -200
  10. package/assets/scss/bootstrap-4.6.2/_close.scss +40 -40
  11. package/assets/scss/bootstrap-4.6.2/_code.scss +48 -48
  12. package/assets/scss/bootstrap-4.6.2/_custom-forms.scss +526 -526
  13. package/assets/scss/bootstrap-4.6.2/_dropdown.scss +192 -192
  14. package/assets/scss/bootstrap-4.6.2/_forms.scss +347 -347
  15. package/assets/scss/bootstrap-4.6.2/_functions.scss +190 -190
  16. package/assets/scss/bootstrap-4.6.2/_grid.scss +73 -73
  17. package/assets/scss/bootstrap-4.6.2/_images.scss +42 -42
  18. package/assets/scss/bootstrap-4.6.2/_input-group.scss +211 -211
  19. package/assets/scss/bootstrap-4.6.2/_jumbotron.scss +17 -17
  20. package/assets/scss/bootstrap-4.6.2/_list-group.scss +154 -154
  21. package/assets/scss/bootstrap-4.6.2/_media.scss +8 -8
  22. package/assets/scss/bootstrap-4.6.2/_mixins.scss +47 -47
  23. package/assets/scss/bootstrap-4.6.2/_modal.scss +240 -240
  24. package/assets/scss/bootstrap-4.6.2/_nav.scss +125 -125
  25. package/assets/scss/bootstrap-4.6.2/_navbar.scss +332 -332
  26. package/assets/scss/bootstrap-4.6.2/_pagination.scss +74 -74
  27. package/assets/scss/bootstrap-4.6.2/_popover.scss +170 -170
  28. package/assets/scss/bootstrap-4.6.2/_print.scss +132 -132
  29. package/assets/scss/bootstrap-4.6.2/_progress.scss +47 -47
  30. package/assets/scss/bootstrap-4.6.2/_reboot.scss +484 -484
  31. package/assets/scss/bootstrap-4.6.2/_root.scss +19 -19
  32. package/assets/scss/bootstrap-4.6.2/_spinners.scss +65 -65
  33. package/assets/scss/bootstrap-4.6.2/_tables.scss +185 -185
  34. package/assets/scss/bootstrap-4.6.2/_toasts.scss +46 -46
  35. package/assets/scss/bootstrap-4.6.2/_tooltip.scss +115 -115
  36. package/assets/scss/bootstrap-4.6.2/_transitions.scss +26 -26
  37. package/assets/scss/bootstrap-4.6.2/_type.scss +125 -125
  38. package/assets/scss/bootstrap-4.6.2/_utilities.scss +18 -18
  39. package/assets/scss/bootstrap-4.6.2/_variables.scss +1150 -1150
  40. package/assets/scss/bootstrap-4.6.2/bootstrap-grid.scss +30 -30
  41. package/assets/scss/bootstrap-4.6.2/bootstrap-reboot.scss +12 -12
  42. package/assets/scss/bootstrap-4.6.2/bootstrap.scss +44 -44
  43. package/assets/scss/bootstrap-4.6.2/mixins/_alert.scss +13 -13
  44. package/assets/scss/bootstrap-4.6.2/mixins/_background-variant.scss +23 -23
  45. package/assets/scss/bootstrap-4.6.2/mixins/_badge.scss +17 -17
  46. package/assets/scss/bootstrap-4.6.2/mixins/_border-radius.scss +76 -76
  47. package/assets/scss/bootstrap-4.6.2/mixins/_box-shadow.scss +20 -20
  48. package/assets/scss/bootstrap-4.6.2/mixins/_breakpoints.scss +123 -123
  49. package/assets/scss/bootstrap-4.6.2/mixins/_buttons.scss +110 -110
  50. package/assets/scss/bootstrap-4.6.2/mixins/_caret.scss +62 -62
  51. package/assets/scss/bootstrap-4.6.2/mixins/_clearfix.scss +7 -7
  52. package/assets/scss/bootstrap-4.6.2/mixins/_deprecate.scss +10 -10
  53. package/assets/scss/bootstrap-4.6.2/mixins/_float.scss +14 -14
  54. package/assets/scss/bootstrap-4.6.2/mixins/_forms.scss +195 -195
  55. package/assets/scss/bootstrap-4.6.2/mixins/_gradients.scss +45 -45
  56. package/assets/scss/bootstrap-4.6.2/mixins/_grid-framework.scss +80 -80
  57. package/assets/scss/bootstrap-4.6.2/mixins/_grid.scss +69 -69
  58. package/assets/scss/bootstrap-4.6.2/mixins/_hover.scss +37 -37
  59. package/assets/scss/bootstrap-4.6.2/mixins/_image.scss +36 -36
  60. package/assets/scss/bootstrap-4.6.2/mixins/_list-group.scss +21 -21
  61. package/assets/scss/bootstrap-4.6.2/mixins/_lists.scss +7 -7
  62. package/assets/scss/bootstrap-4.6.2/mixins/_nav-divider.scss +11 -11
  63. package/assets/scss/bootstrap-4.6.2/mixins/_pagination.scss +22 -22
  64. package/assets/scss/bootstrap-4.6.2/mixins/_reset-text.scss +17 -17
  65. package/assets/scss/bootstrap-4.6.2/mixins/_resize.scss +6 -6
  66. package/assets/scss/bootstrap-4.6.2/mixins/_screen-reader.scss +34 -34
  67. package/assets/scss/bootstrap-4.6.2/mixins/_size.scss +7 -7
  68. package/assets/scss/bootstrap-4.6.2/mixins/_table-row.scss +39 -39
  69. package/assets/scss/bootstrap-4.6.2/mixins/_text-emphasis.scss +17 -17
  70. package/assets/scss/bootstrap-4.6.2/mixins/_text-hide.scss +11 -11
  71. package/assets/scss/bootstrap-4.6.2/mixins/_text-truncate.scss +8 -8
  72. package/assets/scss/bootstrap-4.6.2/mixins/_transition.scss +26 -26
  73. package/assets/scss/bootstrap-4.6.2/mixins/_visibility.scss +8 -8
  74. package/assets/scss/bootstrap-4.6.2/utilities/_align.scss +8 -8
  75. package/assets/scss/bootstrap-4.6.2/utilities/_background.scss +19 -19
  76. package/assets/scss/bootstrap-4.6.2/utilities/_borders.scss +75 -75
  77. package/assets/scss/bootstrap-4.6.2/utilities/_clearfix.scss +3 -3
  78. package/assets/scss/bootstrap-4.6.2/utilities/_display.scss +26 -26
  79. package/assets/scss/bootstrap-4.6.2/utilities/_embed.scss +39 -39
  80. package/assets/scss/bootstrap-4.6.2/utilities/_flex.scss +51 -51
  81. package/assets/scss/bootstrap-4.6.2/utilities/_float.scss +11 -11
  82. package/assets/scss/bootstrap-4.6.2/utilities/_interactions.scss +5 -5
  83. package/assets/scss/bootstrap-4.6.2/utilities/_overflow.scss +5 -5
  84. package/assets/scss/bootstrap-4.6.2/utilities/_position.scss +32 -32
  85. package/assets/scss/bootstrap-4.6.2/utilities/_screenreaders.scss +11 -11
  86. package/assets/scss/bootstrap-4.6.2/utilities/_shadows.scss +6 -6
  87. package/assets/scss/bootstrap-4.6.2/utilities/_sizing.scss +20 -20
  88. package/assets/scss/bootstrap-4.6.2/utilities/_spacing.scss +73 -73
  89. package/assets/scss/bootstrap-4.6.2/utilities/_stretched-link.scss +19 -19
  90. package/assets/scss/bootstrap-4.6.2/utilities/_text.scss +72 -72
  91. package/assets/scss/bootstrap-4.6.2/utilities/_visibility.scss +13 -13
  92. package/assets/scss/bootstrap-4.6.2/vendor/_rfs.scss +228 -228
  93. package/assets/scss/core/print.scss +47 -47
  94. package/assets/scss/core/scrollbar.scss +30 -30
  95. package/assets/scss/core/typography.scss +121 -121
  96. package/fesm2022/sd-angular-core-components-anchor-v2.mjs +4 -4
  97. package/fesm2022/sd-angular-core-components-anchor-v2.mjs.map +1 -1
  98. package/fesm2022/sd-angular-core-components-avatar.mjs +2 -2
  99. package/fesm2022/sd-angular-core-components-avatar.mjs.map +1 -1
  100. package/fesm2022/sd-angular-core-components-chart.mjs +40 -40
  101. package/fesm2022/sd-angular-core-components-chart.mjs.map +1 -1
  102. package/fesm2022/sd-angular-core-components-quick-action.mjs.map +1 -1
  103. package/fesm2022/sd-angular-core-components-tab-router.mjs +4 -4
  104. package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
  105. package/fesm2022/sd-angular-core-components-table.mjs +18 -14
  106. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  107. package/fesm2022/sd-angular-core-components-upload-file.mjs +4 -4
  108. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  109. package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
  110. package/fesm2022/sd-angular-core-forms-directives.mjs.map +1 -1
  111. package/fesm2022/sd-angular-core-forms-models.mjs.map +1 -1
  112. package/fesm2022/sd-angular-core-forms-select.mjs +2 -2
  113. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  114. package/fesm2022/sd-angular-core-forms.mjs.map +1 -1
  115. package/fesm2022/sd-angular-core-modules-authom.mjs.map +1 -1
  116. package/fesm2022/sd-angular-core-modules-layout.mjs +2 -2
  117. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  118. package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
  119. package/fesm2022/sd-angular-core-services-api.mjs.map +1 -1
  120. package/fesm2022/sd-angular-core-services-cache.mjs.map +1 -1
  121. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  122. package/fesm2022/sd-angular-core-services-firebase.mjs.map +1 -1
  123. package/fesm2022/sd-angular-core-services-license.mjs.map +1 -1
  124. package/fesm2022/sd-angular-core-services-storage.mjs.map +1 -1
  125. package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
  126. package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
  127. package/fesm2022/sd-angular-core.mjs.map +1 -1
  128. package/package.json +66 -66
  129. package/sd-angular-core-19.0.0-beta.85.tgz +0 -0
  130. package/sd-angular-core-19.0.0-beta.84.tgz +0 -0
package/README.md CHANGED
@@ -1,717 +1,717 @@
1
- # @sd-angular/core
2
-
3
- > **VI** — Thư viện UI nội bộ xây dựng trên Angular Material, hỗ trợ Angular 19+
4
- > **EN** — Internal UI component library built on Angular Material, supporting Angular 19+
5
-
6
- [![Version](https://img.shields.io/badge/version-19.0.0--beta.47-blue)](./package.json)
7
- [![Angular](https://img.shields.io/badge/Angular-19%2B-red)](https://angular.dev)
8
- [![Angular Material](https://img.shields.io/badge/Angular_Material-19%2B-purple)](https://material.angular.io)
9
-
10
- ---
11
-
12
- ## Table of Contents / Mục lục
13
-
14
- - [Getting Started / Cài đặt](#getting-started--cài-đặt)
15
- - [Theming / SCSS Customization](#theming--scss-customization)
16
- - [Components](#components)
17
- - [SdButton](#sdbutton)
18
- - [SdBadge](#sdbadge)
19
- - [SdSection](#sdsection)
20
- - [SdModal](#sdmodal)
21
- - [SdTable](#sdtable)
22
- - [SdAvatar](#sdavatar)
23
- - [Other Components](#other-components--các-component-khác)
24
- - [Form Components](#form-components)
25
- - [CRUD Patterns / Code mẫu CRUD](#crud-patterns--code-mẫu-crud)
26
- - [Contributing Guide / Hướng dẫn đóng góp](#contributing-guide--hướng-dẫn-đóng-góp)
27
-
28
- ---
29
-
30
- ## Getting Started / Cài đặt
31
-
32
- ### Prerequisites / Yêu cầu
33
-
34
- | Dependency | Version |
35
- |---|---|
36
- | `@angular/core` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
37
- | `@angular/material` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
38
- | `@angular/material-moment-adapter` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
39
-
40
- ### Installation / Cài đặt
41
-
42
- ```bash
43
- npm install @sd-angular/core
44
- ```
45
-
46
- ### Setup
47
-
48
- **1. Import global styles / Import style toàn cục**
49
-
50
- Thêm vào `angular.json` (hoặc `styles.scss` của app):
51
-
52
- ```json
53
- // angular.json
54
- {
55
- "styles": [
56
- "node_modules/@sd-angular/core/assets/scss/sd-core.scss"
57
- ]
58
- }
59
- ```
60
-
61
- hoặc trong `styles.scss`:
62
-
63
- ```scss
64
- @use '@sd-angular/core/assets/scss/sd-core';
65
- ```
66
-
67
- **2. Import Material Icons font / Font icon**
68
-
69
- Thêm vào `index.html`:
70
-
71
- ```html
72
- <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
73
- <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600&display=swap" rel="stylesheet" />
74
- ```
75
-
76
- **3. Configure providers / Cấu hình providers**
77
-
78
- ```typescript
79
- // app.config.ts
80
- import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
81
-
82
- export const appConfig: ApplicationConfig = {
83
- providers: [
84
- provideAnimationsAsync(),
85
- // ... các providers khác
86
- ],
87
- };
88
- ```
89
-
90
- ---
91
-
92
- ## Theming / SCSS Customization
93
-
94
- ### CSS Variables
95
-
96
- `@sd-angular/core` sử dụng CSS custom properties (variables) để quản lý màu sắc. Mỗi màu được expose dưới dạng `--sd-<color>`.
97
-
98
- **Available color tokens / Các biến màu sắc:**
99
-
100
- | Variable | Default | Description |
101
- |---|---|---|
102
- | `--sd-primary` | `#2A66F4` | Màu chính |
103
- | `--sd-primary-light` | `#EAF1FF` | Màu chính nhạt |
104
- | `--sd-primary-dark` | `#1C4AD9` | Màu chính đậm |
105
- | `--sd-secondary` | `#212121` | Màu phụ |
106
- | `--sd-secondary-light` | `#E9E9E9` | Màu phụ nhạt |
107
- | `--sd-success` | `#4CAF50` | Thành công |
108
- | `--sd-success-light` | `#DBEFDC` | Thành công nhạt |
109
- | `--sd-warning` | `#FF9600` | Cảnh báo |
110
- | `--sd-warning-light` | `#FFEACC` | Cảnh báo nhạt |
111
- | `--sd-error` | `#F82C13` | Lỗi |
112
- | `--sd-error-light` | `#FED5D0` | Lỗi nhạt |
113
- | `--sd-info` | `#2962FF` | Thông tin |
114
- | `--sd-info-light` | `#E7E9FF` | Thông tin nhạt |
115
- | `--sd-black500` | `#212121` | Xám đậm nhất |
116
- | `--sd-black400` | `#757575` | Xám đậm |
117
- | `--sd-black300` | `#BFBFBF` | Xám trung |
118
- | `--sd-black200` | `#E6E6E6` | Xám nhạt |
119
- | `--sd-black100` | `#F2F2F2` | Xám nhạt nhất |
120
-
121
- ### Custom Theme / Tuỳ chỉnh theme
122
-
123
- Ghi đè theme mặc định bằng cách truyền map SCSS vào mixin `theme()`:
124
-
125
- ```scss
126
- // styles.scss
127
- @use '@sd-angular/core/assets/scss/themes/default' as default;
128
-
129
- html {
130
- @include default.theme((
131
- primary: #7C3AED,
132
- primary-light: #EDE9FE,
133
- primary-dark: #5B21B6,
134
- success: #10B981,
135
- error: #EF4444,
136
- ));
137
- }
138
- ```
139
-
140
- Chỉ cần override các màu muốn thay đổi — các màu còn lại giữ nguyên giá trị mặc định.
141
-
142
- ### Utility Classes / Các class tiện ích
143
-
144
- Thư viện cung cấp sẵn các utility class:
145
-
146
- ```html
147
- <!-- Text color -->
148
- <span class="text-primary">Text màu primary</span>
149
- <span class="text-error">Text màu error</span>
150
-
151
- <!-- Background -->
152
- <div class="bg-primary-light">Background nhạt</div>
153
-
154
- <!-- Spacing (đơn vị px, từ 0–200) -->
155
- <div class="mt-16 mb-8 px-24">margin-top: 16px, padding: 0 24px</div>
156
-
157
- <!-- Gap -->
158
- <div class="d-flex gap-8">gap: 8px</div>
159
-
160
- <!-- Grid -->
161
- <div class="sd-grid-container grid-cols-3">
162
- <div class="col-span-2">Chiếm 2 cột</div>
163
- <div class="col-span-1">Chiếm 1 cột</div>
164
- </div>
165
-
166
- <!-- Bootstrap grid -->
167
- <div class="row">
168
- <div class="col-6">50%</div>
169
- <div class="col-6">50%</div>
170
- </div>
171
- ```
172
-
173
- ---
174
-
175
- ## Components
176
-
177
- > Tất cả component đều là **standalone** và sử dụng **Angular Signals** API.
178
- > All components are **standalone** and use **Angular Signals** API.
179
-
180
- ### SdButton
181
-
182
- ```typescript
183
- import { SdButton } from '@sd-angular/core/components/button';
184
- ```
185
-
186
- **Inputs:**
187
-
188
- | Input | Type | Default | Description |
189
- |---|---|---|---|
190
- | `type` | `'fill' \| 'light' \| 'outline' \| 'link'` | `'light'` | Kiểu nút |
191
- | `color` | `SdColor` | `'secondary'` | Màu sắc |
192
- | `size` | `'sm' \| 'md' \| 'lg'` | `'sm'` | Kích thước |
193
- | `title` | `string` | — | Nhãn nút |
194
- | `prefixIcon` | `string` | — | Icon Material trước text |
195
- | `suffixIcon` | `string` | — | Icon Material sau text |
196
- | `disabled` | `boolean` | `false` | Vô hiệu hoá |
197
- | `loading` | `boolean` | `false` | Trạng thái loading (tự chặn click) |
198
- | `tooltip` | `string` | — | Tooltip khi hover |
199
- | `width` | `string` | — | CSS width tuỳ chỉnh |
200
-
201
- **Output:** `(click): EventEmitter<Event>` — có throttle 300ms, tự chặn khi `disabled` hoặc `loading`.
202
-
203
- ```html
204
- <sd-button type="fill" color="primary" title="Lưu" prefixIcon="save" (click)="onSave()" />
205
- <sd-button type="outline" color="error" prefixIcon="delete" tooltip="Xoá" />
206
- <sd-button type="light" title="Huỷ" (click)="modal.close()" />
207
- <sd-button type="fill" color="primary" title="Đang xử lý" [loading]="true" />
208
- ```
209
-
210
- ---
211
-
212
- ### SdBadge
213
-
214
- ```typescript
215
- import { SdBadge } from '@sd-angular/core/components/badge';
216
- ```
217
-
218
- **Inputs:**
219
-
220
- | Input | Type | Default | Description |
221
- |---|---|---|---|
222
- | `type` | `'tag' \| 'round' \| 'icon'` | `'icon'` | Kiểu badge |
223
- | `color` | `SdColor` | `'secondary'` | Màu sắc |
224
- | `title` | `string \| number` | — | Nội dung hiển thị |
225
- | `icon` | `string` | — | Icon Material |
226
- | `size` | `SdSize` | `'sm'` | Kích thước |
227
- | `tooltip` | `string` | — | Tooltip |
228
-
229
- Shorthand color inputs (boolean): `primary`, `secondary`, `success`, `info`, `warning`, `error`.
230
-
231
- ```html
232
- <sd-badge type="tag" color="success" title="Hoạt động" />
233
- <sd-badge type="round" [warning]="true" title="Chờ duyệt" />
234
- <sd-badge type="icon" color="error" icon="close" title="Từ chối" />
235
- ```
236
-
237
- ---
238
-
239
- ### SdSection
240
-
241
- ```typescript
242
- import { SdSection } from '@sd-angular/core/components/section';
243
- ```
244
-
245
- **Inputs:**
246
-
247
- | Input | Type | Default | Description |
248
- |---|---|---|---|
249
- | `title` | `string` | *required* | Tiêu đề section |
250
- | `subTitle` | `string` | — | Tiêu đề phụ |
251
- | `icon` | `string` | — | Icon Material |
252
- | `iconColor` | `SdColor` | `'primary'` | Màu icon |
253
- | `collapsable` | `boolean` | `false` | Cho phép thu gọn |
254
- | `collapsed` | `boolean` | `false` | Trạng thái ban đầu thu gọn |
255
- | `hideHeader` | `boolean` | `false` | Ẩn phần header |
256
-
257
- ```html
258
- <sd-section title="Thông tin cơ bản" icon="person" iconColor="primary">
259
- <!-- nội dung -->
260
- </sd-section>
261
-
262
- <sd-section title="Cài đặt nâng cao" icon="settings" collapsable [collapsed]="true">
263
- <!-- nội dung ẩn mặc định -->
264
- </sd-section>
265
- ```
266
-
267
- ---
268
-
269
- ### SdModal
270
-
271
- ```typescript
272
- import { SdModal } from '@sd-angular/core/components/modal';
273
- ```
274
-
275
- **Inputs:**
276
-
277
- | Input | Type | Default | Description |
278
- |---|---|---|---|
279
- | `title` | `string` | — | Tiêu đề modal |
280
- | `color` | `SdColor` | `'primary'` | Màu header |
281
- | `width` | `'sx' \| 'sm' \| 'md' \| 'lg' \| string` | `'md'` | Độ rộng (md = 60vw) |
282
- | `height` | `string` | `'auto'` | Chiều cao |
283
- | `view` | `'dialog' \| 'bottom-sheet'` | auto | Tự động bottom-sheet trên mobile |
284
- | `lazyLoadContent` | `boolean` | `true` | Lazy render nội dung |
285
-
286
- **Output:** `(sdClosed): EventEmitter` — phát ra khi modal đóng.
287
-
288
- **Methods** (dùng qua `@ViewChild`):
289
- - `modal.open()` — mở modal
290
- - `modal.close()` — đóng modal
291
-
292
- > ⚠️ Nội dung modal phải đặt trong `<ng-template>`.
293
-
294
- ```html
295
- <sd-modal #myModal title="Thêm mới" width="md" (sdClosed)="onClosed()">
296
- <ng-template>
297
- <div class="modal-body p-16">
298
- <!-- nội dung form -->
299
- </div>
300
- <div class="modal-footer d-flex justify-content-end gap-8 p-16">
301
- <sd-button title="Huỷ" (click)="myModal.close()" />
302
- <sd-button type="fill" color="primary" title="Lưu" (click)="onSave()" />
303
- </div>
304
- </ng-template>
305
- </sd-modal>
306
-
307
- <sd-button title="Mở modal" prefixIcon="add" (click)="myModal.open()" />
308
- ```
309
-
310
- ---
311
-
312
- ### SdTable
313
-
314
- ```typescript
315
- import { SdTable } from '@sd-angular/core/components/table';
316
- import type { SdTableOption, SdTableColumn } from '@sd-angular/core/components/table';
317
- ```
318
-
319
- SdTable nhận một object `option` duy nhất kiểu `SdTableOption<T>`.
320
-
321
- **Column types / Kiểu cột:**
322
-
323
- | `type` | Mô tả |
324
- |---|---|
325
- | `'string'` | Văn bản |
326
- | `'number'` | Số (tự format) |
327
- | `'boolean'` | True/False |
328
- | `'date'` | Ngày |
329
- | `'datetime'` | Ngày giờ |
330
- | `'time'` | Giờ |
331
- | `'values'` | Enum từ danh sách cố định |
332
- | `'lazy-values'` | Enum load async |
333
- | `'children'` | Cột nhóm (multi-header) |
334
-
335
- **Local table:**
336
-
337
- ```typescript
338
- option: SdTableOption<Product> = {
339
- type: 'local',
340
- items: () => this.products,
341
- columns: [
342
- { field: 'code', type: 'string', title: 'Mã', width: '120px' },
343
- { field: 'name', type: 'string', title: 'Tên', sortable: true },
344
- { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
345
- { field: 'active', type: 'boolean', title: 'Kích hoạt',
346
- useBadge: (val) => ({ color: val ? 'success' : 'secondary', title: val ? 'Có' : 'Không' })
347
- },
348
- ],
349
- paginate: { pageSize: 20 },
350
- reload: { visible: true },
351
- };
352
- ```
353
-
354
- **Server-side table:**
355
-
356
- ```typescript
357
- option: SdTableOption<Product> = {
358
- type: 'server',
359
- items: async (filterRequest, pagingReq) => {
360
- const res = await this.service.search({
361
- keyword: filterRequest.keyword,
362
- page: pagingReq.page,
363
- pageSize: pagingReq.pageSize,
364
- });
365
- return { items: res.data, total: res.total };
366
- },
367
- columns: [...],
368
- command: {
369
- align: 'right',
370
- commands: [
371
- { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.onEdit(row) },
372
- { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
373
- ],
374
- },
375
- };
376
- ```
377
-
378
- ```html
379
- <sd-table #sdTable [option]="option" />
380
- ```
381
-
382
- ---
383
-
384
- ### SdAvatar
385
-
386
- ```typescript
387
- import { SdAvatar } from '@sd-angular/core/components/avatar';
388
- ```
389
-
390
- ```html
391
- <sd-avatar src="/api/avatar/123" name="Nguyễn Văn A" size="md" />
392
- <sd-avatar name="NVA" color="primary" size="lg" />
393
- ```
394
-
395
- ---
396
-
397
- ### Other Components / Các component khác
398
-
399
- | Component | Import | Mô tả |
400
- |---|---|---|
401
- | `SdTabRouter` | `@sd-angular/core/components/tab-router` | Tab navigation với Angular Router |
402
- | `SdSideDrawer` | `@sd-angular/core/components/side-drawer` | Drawer layout trái/phải |
403
- | `SdUploadFile` | `@sd-angular/core/components/upload-file` | Upload file |
404
- | `SdQuickAction` | `@sd-angular/core/components/quick-action` | Nút action dạng icon |
405
- | `SdHistory` | `@sd-angular/core/components/history` | Lịch sử thay đổi |
406
- | `SdImportExcel` | `@sd-angular/core/components/import-excel` | Wizard import Excel |
407
- | `SdQueryBuilder` | `@sd-angular/core/components/query-builder` | Visual query builder |
408
- | `SdWorkflow` | `@sd-angular/core/components/workflow` | Workflow builder |
409
- | `SdCodeEditor` | `@sd-angular/core/components/code-editor` | Code editor (PrismJS) |
410
- | `SdMiniEditor` | `@sd-angular/core/components/mini-editor` | Rich text editor nhỏ |
411
- | `SdDocumentBuilder` | `@sd-angular/core/components/document-builder` | Document builder |
412
- | `SdAnchorMain` | `@sd-angular/core/components/anchor` | Anchor / mục lục cuộn trang |
413
- | `SdView` | `@sd-angular/core/components/view` | View wrapper read-only |
414
-
415
- ---
416
-
417
- ## Form Components
418
-
419
- ```typescript
420
- import {
421
- SdInput, // Text input
422
- SdInputNumber, // Number input
423
- SdSelect, // Dropdown
424
- SdAutocomplete, // Autocomplete
425
- SdDate, // Date picker
426
- SdSearch, // Search với debounce
427
- } from '@sd-angular/core/forms';
428
- ```
429
-
430
- ```html
431
- <sd-input [(ngModel)]="form.name" label="Họ tên" [required]="true" />
432
-
433
- <sd-input-number [(ngModel)]="form.price" label="Đơn giá" [min]="0" suffix="VNĐ" />
434
-
435
- <sd-select [(ngModel)]="form.status" label="Trạng thái"
436
- [items]="statusList" valueField="value" displayField="label" />
437
-
438
- <sd-date [(ngModel)]="form.birthday" label="Ngày sinh" />
439
- ```
440
-
441
- ---
442
-
443
- ## CRUD Patterns / Code mẫu CRUD
444
-
445
- ### List Component
446
-
447
- ```typescript
448
- // product-list.component.ts
449
- import { Component, OnInit, ViewChild, signal } from '@angular/core';
450
- import { SdTable, SdTableOption } from '@sd-angular/core/components/table';
451
- import { SdButton } from '@sd-angular/core/components/button';
452
- import { SdModal } from '@sd-angular/core/components/modal';
453
- import { SdSection } from '@sd-angular/core/components/section';
454
- import { SdInput, SdSelect } from '@sd-angular/core/forms';
455
-
456
- interface Product {
457
- id: number;
458
- code: string;
459
- name: string;
460
- price: number;
461
- status: 'ACTIVE' | 'INACTIVE';
462
- }
463
-
464
- @Component({
465
- selector: 'app-product-list',
466
- templateUrl: './product-list.component.html',
467
- standalone: true,
468
- imports: [SdTable, SdButton, SdModal, SdSection, SdInput, SdSelect],
469
- })
470
- export class ProductListComponent implements OnInit {
471
- @ViewChild('formModal') formModal!: SdModal;
472
- @ViewChild('sdTable') sdTable?: SdTable<Product>;
473
-
474
- selectedItem: Product | null = null;
475
- formData: Partial<Product> = {};
476
- isSaving = signal(false);
477
-
478
- readonly STATUS_LIST = [
479
- { value: 'ACTIVE', label: 'Hoạt động' },
480
- { value: 'INACTIVE', label: 'Dừng' },
481
- ];
482
-
483
- option!: SdTableOption<Product>;
484
-
485
- constructor(private service: ProductService) {}
486
-
487
- ngOnInit() {
488
- this.option = {
489
- type: 'server',
490
- items: async (filter, paging) => this.service.search(filter, paging),
491
- columns: [
492
- { field: 'code', type: 'string', title: 'Mã', width: '120px' },
493
- { field: 'name', type: 'string', title: 'Tên', sortable: true },
494
- { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
495
- {
496
- field: 'status', type: 'values', title: 'Trạng thái',
497
- option: { items: this.STATUS_LIST, valueField: 'value', displayField: 'label' },
498
- useBadge: (val) => ({
499
- color: val === 'ACTIVE' ? 'success' : 'secondary',
500
- title: this.STATUS_LIST.find(s => s.value === val)?.label,
501
- }),
502
- },
503
- ],
504
- command: {
505
- align: 'right',
506
- commands: [
507
- { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.openForm(row) },
508
- { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
509
- ],
510
- },
511
- paginate: { pageSize: 20 },
512
- reload: { visible: true },
513
- };
514
- }
515
-
516
- openForm(item?: Product) {
517
- this.selectedItem = item || null;
518
- this.formData = item ? { ...item } : { status: 'ACTIVE' };
519
- this.formModal.open();
520
- }
521
-
522
- async onSave() {
523
- this.isSaving.set(true);
524
- try {
525
- if (this.selectedItem) {
526
- await this.service.update(this.selectedItem.id, this.formData);
527
- } else {
528
- await this.service.create(this.formData);
529
- }
530
- this.formModal.close();
531
- this.sdTable?.reload?.();
532
- } finally {
533
- this.isSaving.set(false);
534
- }
535
- }
536
-
537
- async onDelete(item: Product) {
538
- if (!confirm(`Xoá "${item.name}"?`)) return;
539
- await this.service.delete(item.id);
540
- this.sdTable?.reload?.();
541
- }
542
- }
543
- ```
544
-
545
- ### Template
546
-
547
- ```html
548
- <!-- product-list.component.html -->
549
- <div class="d-flex justify-content-between align-items-center mb-16">
550
- <h2>Danh sách sản phẩm</h2>
551
- <sd-button type="fill" color="primary" title="Thêm mới"
552
- prefixIcon="add" (click)="openForm()" />
553
- </div>
554
-
555
- <sd-table #sdTable [option]="option" />
556
-
557
- <sd-modal #formModal [title]="selectedItem ? 'Chỉnh sửa' : 'Thêm mới'" width="md">
558
- <ng-template>
559
- <div class="modal-body p-16">
560
- <sd-section title="Thông tin sản phẩm" icon="inventory">
561
- <div class="row">
562
- <div class="col-6">
563
- <sd-input [(ngModel)]="formData.code" label="Mã" [required]="true" />
564
- </div>
565
- <div class="col-6">
566
- <sd-select [(ngModel)]="formData.status" label="Trạng thái"
567
- [items]="STATUS_LIST" valueField="value" displayField="label" />
568
- </div>
569
- <div class="col-12">
570
- <sd-input [(ngModel)]="formData.name" label="Tên sản phẩm" [required]="true" />
571
- </div>
572
- <div class="col-6">
573
- <sd-input-number [(ngModel)]="formData.price" label="Đơn giá" suffix="VNĐ" />
574
- </div>
575
- </div>
576
- </sd-section>
577
- </div>
578
- <div class="modal-footer d-flex justify-content-end gap-8 p-16">
579
- <sd-button title="Huỷ" (click)="formModal.close()" />
580
- <sd-button type="fill" color="primary" title="Lưu"
581
- prefixIcon="save" [loading]="isSaving()" (click)="onSave()" />
582
- </div>
583
- </ng-template>
584
- </sd-modal>
585
- ```
586
-
587
- ---
588
-
589
- ## Contributing Guide / Hướng dẫn đóng góp
590
-
591
- ### Cấu trúc thư viện / Project structure
592
-
593
- ```
594
- sd-angular/
595
- ├── src/
596
- │ └── public-api.ts # Entry point chính
597
- ├── assets/
598
- │ └── scss/
599
- │ ├── sd-core.scss # SCSS entry (import vào app)
600
- │ ├── core/ # Base utilities (color, grid, form, ...)
601
- │ └── themes/ # Theme mặc định + Material theme
602
- ├── components/ # UI Components
603
- │ ├── button/
604
- │ ├── table/
605
- │ ├── modal/
606
- │ └── ...
607
- ├── forms/ # Form components
608
- │ ├── input/
609
- │ ├── select/
610
- │ └── ...
611
- ├── directives/ # Angular directives
612
- ├── pipes/ # Angular pipes
613
- ├── services/ # Shared services
614
- ├── utilities/ # Types, models, helpers
615
- └── modules/ # Feature modules (layout, permission, ...)
616
- ```
617
-
618
- ### Thêm component mới / Adding a new component
619
-
620
- **1. Tạo thư mục component:**
621
-
622
- ```
623
- components/
624
- └── my-component/
625
- ├── index.ts # Export public API
626
- ├── ng-package.json # ng-packagr entry
627
- └── src/
628
- ├── my-component.component.ts
629
- ├── my-component.component.html
630
- └── my-component.component.scss
631
- ```
632
-
633
- **2. `ng-package.json`:**
634
-
635
- ```json
636
- {
637
- "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
638
- "lib": {
639
- "entryFile": "index.ts"
640
- }
641
- }
642
- ```
643
-
644
- **3. `index.ts`:**
645
-
646
- ```typescript
647
- export * from './src/my-component.component';
648
- ```
649
-
650
- **4. Component template:**
651
-
652
- ```typescript
653
- // my-component.component.ts
654
- import { ChangeDetectionStrategy, Component, input } from '@angular/core';
655
- import { SdBaseSecureComponent } from '@sd-angular/core/components/base';
656
- import { SdColor } from '@sd-angular/core/utilities';
657
-
658
- @Component({
659
- selector: 'sd-my-component',
660
- templateUrl: './my-component.component.html',
661
- styleUrls: ['./my-component.component.scss'],
662
- changeDetection: ChangeDetectionStrategy.OnPush,
663
- standalone: true,
664
- imports: [],
665
- })
666
- export class SdMyComponent extends SdBaseSecureComponent {
667
- color = input<SdColor, SdColor | undefined | null>('primary', {
668
- transform: (value) => value || 'primary',
669
- });
670
-
671
- title = input<string | undefined | null>(undefined);
672
- }
673
- ```
674
-
675
- **5. Export từ `components/index.ts`:**
676
-
677
- ```typescript
678
- // components/index.ts
679
- export * from '@sd-angular/core/components/my-component';
680
- ```
681
-
682
- ### Quy ước / Conventions
683
-
684
- | Mục | Quy ước |
685
- |---|---|
686
- | Selector | `sd-<tên-component>` |
687
- | Class name | `Sd<TênComponent>` (Pascal) |
688
- | Input | Dùng `input<T>()` signal, **không** dùng `@Input()` decorator |
689
- | Null safety | Input transform phải handle `null/undefined` |
690
- | Base class | Extend `SdBaseSecureComponent` cho component có permission |
691
- | Change detection | Luôn dùng `ChangeDetectionStrategy.OnPush` |
692
- | Standalone | Luôn `standalone: true` |
693
- | Colors | Dùng `SdColor` type, không hardcode màu |
694
-
695
- ### Build
696
-
697
- ```bash
698
- # Build toàn bộ thư viện
699
- ng-packagr -p ng-package.json
700
-
701
- # Watch mode
702
- ng-packagr -p ng-package.json --watch
703
- ```
704
-
705
- ### Versioning
706
-
707
- Thư viện tuân theo [Semantic Versioning](https://semver.org):
708
-
709
- - `MAJOR` — Breaking changes (thay đổi API không tương thích)
710
- - `MINOR` — Tính năng mới (tương thích ngược)
711
- - `PATCH` — Bug fixes
712
-
713
- ---
714
-
715
- ## License
716
-
1
+ # @sd-angular/core
2
+
3
+ > **VI** — Thư viện UI nội bộ xây dựng trên Angular Material, hỗ trợ Angular 19+
4
+ > **EN** — Internal UI component library built on Angular Material, supporting Angular 19+
5
+
6
+ [![Version](https://img.shields.io/badge/version-19.0.0--beta.47-blue)](./package.json)
7
+ [![Angular](https://img.shields.io/badge/Angular-19%2B-red)](https://angular.dev)
8
+ [![Angular Material](https://img.shields.io/badge/Angular_Material-19%2B-purple)](https://material.angular.io)
9
+
10
+ ---
11
+
12
+ ## Table of Contents / Mục lục
13
+
14
+ - [Getting Started / Cài đặt](#getting-started--cài-đặt)
15
+ - [Theming / SCSS Customization](#theming--scss-customization)
16
+ - [Components](#components)
17
+ - [SdButton](#sdbutton)
18
+ - [SdBadge](#sdbadge)
19
+ - [SdSection](#sdsection)
20
+ - [SdModal](#sdmodal)
21
+ - [SdTable](#sdtable)
22
+ - [SdAvatar](#sdavatar)
23
+ - [Other Components](#other-components--các-component-khác)
24
+ - [Form Components](#form-components)
25
+ - [CRUD Patterns / Code mẫu CRUD](#crud-patterns--code-mẫu-crud)
26
+ - [Contributing Guide / Hướng dẫn đóng góp](#contributing-guide--hướng-dẫn-đóng-góp)
27
+
28
+ ---
29
+
30
+ ## Getting Started / Cài đặt
31
+
32
+ ### Prerequisites / Yêu cầu
33
+
34
+ | Dependency | Version |
35
+ |---|---|
36
+ | `@angular/core` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
37
+ | `@angular/material` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
38
+ | `@angular/material-moment-adapter` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
39
+
40
+ ### Installation / Cài đặt
41
+
42
+ ```bash
43
+ npm install @sd-angular/core
44
+ ```
45
+
46
+ ### Setup
47
+
48
+ **1. Import global styles / Import style toàn cục**
49
+
50
+ Thêm vào `angular.json` (hoặc `styles.scss` của app):
51
+
52
+ ```json
53
+ // angular.json
54
+ {
55
+ "styles": [
56
+ "node_modules/@sd-angular/core/assets/scss/sd-core.scss"
57
+ ]
58
+ }
59
+ ```
60
+
61
+ hoặc trong `styles.scss`:
62
+
63
+ ```scss
64
+ @use '@sd-angular/core/assets/scss/sd-core';
65
+ ```
66
+
67
+ **2. Import Material Icons font / Font icon**
68
+
69
+ Thêm vào `index.html`:
70
+
71
+ ```html
72
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
73
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600&display=swap" rel="stylesheet" />
74
+ ```
75
+
76
+ **3. Configure providers / Cấu hình providers**
77
+
78
+ ```typescript
79
+ // app.config.ts
80
+ import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
81
+
82
+ export const appConfig: ApplicationConfig = {
83
+ providers: [
84
+ provideAnimationsAsync(),
85
+ // ... các providers khác
86
+ ],
87
+ };
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Theming / SCSS Customization
93
+
94
+ ### CSS Variables
95
+
96
+ `@sd-angular/core` sử dụng CSS custom properties (variables) để quản lý màu sắc. Mỗi màu được expose dưới dạng `--sd-<color>`.
97
+
98
+ **Available color tokens / Các biến màu sắc:**
99
+
100
+ | Variable | Default | Description |
101
+ |---|---|---|
102
+ | `--sd-primary` | `#2A66F4` | Màu chính |
103
+ | `--sd-primary-light` | `#EAF1FF` | Màu chính nhạt |
104
+ | `--sd-primary-dark` | `#1C4AD9` | Màu chính đậm |
105
+ | `--sd-secondary` | `#212121` | Màu phụ |
106
+ | `--sd-secondary-light` | `#E9E9E9` | Màu phụ nhạt |
107
+ | `--sd-success` | `#4CAF50` | Thành công |
108
+ | `--sd-success-light` | `#DBEFDC` | Thành công nhạt |
109
+ | `--sd-warning` | `#FF9600` | Cảnh báo |
110
+ | `--sd-warning-light` | `#FFEACC` | Cảnh báo nhạt |
111
+ | `--sd-error` | `#F82C13` | Lỗi |
112
+ | `--sd-error-light` | `#FED5D0` | Lỗi nhạt |
113
+ | `--sd-info` | `#2962FF` | Thông tin |
114
+ | `--sd-info-light` | `#E7E9FF` | Thông tin nhạt |
115
+ | `--sd-black500` | `#212121` | Xám đậm nhất |
116
+ | `--sd-black400` | `#757575` | Xám đậm |
117
+ | `--sd-black300` | `#BFBFBF` | Xám trung |
118
+ | `--sd-black200` | `#E6E6E6` | Xám nhạt |
119
+ | `--sd-black100` | `#F2F2F2` | Xám nhạt nhất |
120
+
121
+ ### Custom Theme / Tuỳ chỉnh theme
122
+
123
+ Ghi đè theme mặc định bằng cách truyền map SCSS vào mixin `theme()`:
124
+
125
+ ```scss
126
+ // styles.scss
127
+ @use '@sd-angular/core/assets/scss/themes/default' as default;
128
+
129
+ html {
130
+ @include default.theme((
131
+ primary: #7C3AED,
132
+ primary-light: #EDE9FE,
133
+ primary-dark: #5B21B6,
134
+ success: #10B981,
135
+ error: #EF4444,
136
+ ));
137
+ }
138
+ ```
139
+
140
+ Chỉ cần override các màu muốn thay đổi — các màu còn lại giữ nguyên giá trị mặc định.
141
+
142
+ ### Utility Classes / Các class tiện ích
143
+
144
+ Thư viện cung cấp sẵn các utility class:
145
+
146
+ ```html
147
+ <!-- Text color -->
148
+ <span class="text-primary">Text màu primary</span>
149
+ <span class="text-error">Text màu error</span>
150
+
151
+ <!-- Background -->
152
+ <div class="bg-primary-light">Background nhạt</div>
153
+
154
+ <!-- Spacing (đơn vị px, từ 0–200) -->
155
+ <div class="mt-16 mb-8 px-24">margin-top: 16px, padding: 0 24px</div>
156
+
157
+ <!-- Gap -->
158
+ <div class="d-flex gap-8">gap: 8px</div>
159
+
160
+ <!-- Grid -->
161
+ <div class="sd-grid-container grid-cols-3">
162
+ <div class="col-span-2">Chiếm 2 cột</div>
163
+ <div class="col-span-1">Chiếm 1 cột</div>
164
+ </div>
165
+
166
+ <!-- Bootstrap grid -->
167
+ <div class="row">
168
+ <div class="col-6">50%</div>
169
+ <div class="col-6">50%</div>
170
+ </div>
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Components
176
+
177
+ > Tất cả component đều là **standalone** và sử dụng **Angular Signals** API.
178
+ > All components are **standalone** and use **Angular Signals** API.
179
+
180
+ ### SdButton
181
+
182
+ ```typescript
183
+ import { SdButton } from '@sd-angular/core/components/button';
184
+ ```
185
+
186
+ **Inputs:**
187
+
188
+ | Input | Type | Default | Description |
189
+ |---|---|---|---|
190
+ | `type` | `'fill' \| 'light' \| 'outline' \| 'link'` | `'light'` | Kiểu nút |
191
+ | `color` | `SdColor` | `'secondary'` | Màu sắc |
192
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'sm'` | Kích thước |
193
+ | `title` | `string` | — | Nhãn nút |
194
+ | `prefixIcon` | `string` | — | Icon Material trước text |
195
+ | `suffixIcon` | `string` | — | Icon Material sau text |
196
+ | `disabled` | `boolean` | `false` | Vô hiệu hoá |
197
+ | `loading` | `boolean` | `false` | Trạng thái loading (tự chặn click) |
198
+ | `tooltip` | `string` | — | Tooltip khi hover |
199
+ | `width` | `string` | — | CSS width tuỳ chỉnh |
200
+
201
+ **Output:** `(click): EventEmitter<Event>` — có throttle 300ms, tự chặn khi `disabled` hoặc `loading`.
202
+
203
+ ```html
204
+ <sd-button type="fill" color="primary" title="Lưu" prefixIcon="save" (click)="onSave()" />
205
+ <sd-button type="outline" color="error" prefixIcon="delete" tooltip="Xoá" />
206
+ <sd-button type="light" title="Huỷ" (click)="modal.close()" />
207
+ <sd-button type="fill" color="primary" title="Đang xử lý" [loading]="true" />
208
+ ```
209
+
210
+ ---
211
+
212
+ ### SdBadge
213
+
214
+ ```typescript
215
+ import { SdBadge } from '@sd-angular/core/components/badge';
216
+ ```
217
+
218
+ **Inputs:**
219
+
220
+ | Input | Type | Default | Description |
221
+ |---|---|---|---|
222
+ | `type` | `'tag' \| 'round' \| 'icon'` | `'icon'` | Kiểu badge |
223
+ | `color` | `SdColor` | `'secondary'` | Màu sắc |
224
+ | `title` | `string \| number` | — | Nội dung hiển thị |
225
+ | `icon` | `string` | — | Icon Material |
226
+ | `size` | `SdSize` | `'sm'` | Kích thước |
227
+ | `tooltip` | `string` | — | Tooltip |
228
+
229
+ Shorthand color inputs (boolean): `primary`, `secondary`, `success`, `info`, `warning`, `error`.
230
+
231
+ ```html
232
+ <sd-badge type="tag" color="success" title="Hoạt động" />
233
+ <sd-badge type="round" [warning]="true" title="Chờ duyệt" />
234
+ <sd-badge type="icon" color="error" icon="close" title="Từ chối" />
235
+ ```
236
+
237
+ ---
238
+
239
+ ### SdSection
240
+
241
+ ```typescript
242
+ import { SdSection } from '@sd-angular/core/components/section';
243
+ ```
244
+
245
+ **Inputs:**
246
+
247
+ | Input | Type | Default | Description |
248
+ |---|---|---|---|
249
+ | `title` | `string` | *required* | Tiêu đề section |
250
+ | `subTitle` | `string` | — | Tiêu đề phụ |
251
+ | `icon` | `string` | — | Icon Material |
252
+ | `iconColor` | `SdColor` | `'primary'` | Màu icon |
253
+ | `collapsable` | `boolean` | `false` | Cho phép thu gọn |
254
+ | `collapsed` | `boolean` | `false` | Trạng thái ban đầu thu gọn |
255
+ | `hideHeader` | `boolean` | `false` | Ẩn phần header |
256
+
257
+ ```html
258
+ <sd-section title="Thông tin cơ bản" icon="person" iconColor="primary">
259
+ <!-- nội dung -->
260
+ </sd-section>
261
+
262
+ <sd-section title="Cài đặt nâng cao" icon="settings" collapsable [collapsed]="true">
263
+ <!-- nội dung ẩn mặc định -->
264
+ </sd-section>
265
+ ```
266
+
267
+ ---
268
+
269
+ ### SdModal
270
+
271
+ ```typescript
272
+ import { SdModal } from '@sd-angular/core/components/modal';
273
+ ```
274
+
275
+ **Inputs:**
276
+
277
+ | Input | Type | Default | Description |
278
+ |---|---|---|---|
279
+ | `title` | `string` | — | Tiêu đề modal |
280
+ | `color` | `SdColor` | `'primary'` | Màu header |
281
+ | `width` | `'sx' \| 'sm' \| 'md' \| 'lg' \| string` | `'md'` | Độ rộng (md = 60vw) |
282
+ | `height` | `string` | `'auto'` | Chiều cao |
283
+ | `view` | `'dialog' \| 'bottom-sheet'` | auto | Tự động bottom-sheet trên mobile |
284
+ | `lazyLoadContent` | `boolean` | `true` | Lazy render nội dung |
285
+
286
+ **Output:** `(sdClosed): EventEmitter` — phát ra khi modal đóng.
287
+
288
+ **Methods** (dùng qua `@ViewChild`):
289
+ - `modal.open()` — mở modal
290
+ - `modal.close()` — đóng modal
291
+
292
+ > ⚠️ Nội dung modal phải đặt trong `<ng-template>`.
293
+
294
+ ```html
295
+ <sd-modal #myModal title="Thêm mới" width="md" (sdClosed)="onClosed()">
296
+ <ng-template>
297
+ <div class="modal-body p-16">
298
+ <!-- nội dung form -->
299
+ </div>
300
+ <div class="modal-footer d-flex justify-content-end gap-8 p-16">
301
+ <sd-button title="Huỷ" (click)="myModal.close()" />
302
+ <sd-button type="fill" color="primary" title="Lưu" (click)="onSave()" />
303
+ </div>
304
+ </ng-template>
305
+ </sd-modal>
306
+
307
+ <sd-button title="Mở modal" prefixIcon="add" (click)="myModal.open()" />
308
+ ```
309
+
310
+ ---
311
+
312
+ ### SdTable
313
+
314
+ ```typescript
315
+ import { SdTable } from '@sd-angular/core/components/table';
316
+ import type { SdTableOption, SdTableColumn } from '@sd-angular/core/components/table';
317
+ ```
318
+
319
+ SdTable nhận một object `option` duy nhất kiểu `SdTableOption<T>`.
320
+
321
+ **Column types / Kiểu cột:**
322
+
323
+ | `type` | Mô tả |
324
+ |---|---|
325
+ | `'string'` | Văn bản |
326
+ | `'number'` | Số (tự format) |
327
+ | `'boolean'` | True/False |
328
+ | `'date'` | Ngày |
329
+ | `'datetime'` | Ngày giờ |
330
+ | `'time'` | Giờ |
331
+ | `'values'` | Enum từ danh sách cố định |
332
+ | `'lazy-values'` | Enum load async |
333
+ | `'children'` | Cột nhóm (multi-header) |
334
+
335
+ **Local table:**
336
+
337
+ ```typescript
338
+ option: SdTableOption<Product> = {
339
+ type: 'local',
340
+ items: () => this.products,
341
+ columns: [
342
+ { field: 'code', type: 'string', title: 'Mã', width: '120px' },
343
+ { field: 'name', type: 'string', title: 'Tên', sortable: true },
344
+ { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
345
+ { field: 'active', type: 'boolean', title: 'Kích hoạt',
346
+ useBadge: (val) => ({ color: val ? 'success' : 'secondary', title: val ? 'Có' : 'Không' })
347
+ },
348
+ ],
349
+ paginate: { pageSize: 20 },
350
+ reload: { visible: true },
351
+ };
352
+ ```
353
+
354
+ **Server-side table:**
355
+
356
+ ```typescript
357
+ option: SdTableOption<Product> = {
358
+ type: 'server',
359
+ items: async (filterRequest, pagingReq) => {
360
+ const res = await this.service.search({
361
+ keyword: filterRequest.keyword,
362
+ page: pagingReq.page,
363
+ pageSize: pagingReq.pageSize,
364
+ });
365
+ return { items: res.data, total: res.total };
366
+ },
367
+ columns: [...],
368
+ command: {
369
+ align: 'right',
370
+ commands: [
371
+ { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.onEdit(row) },
372
+ { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
373
+ ],
374
+ },
375
+ };
376
+ ```
377
+
378
+ ```html
379
+ <sd-table #sdTable [option]="option" />
380
+ ```
381
+
382
+ ---
383
+
384
+ ### SdAvatar
385
+
386
+ ```typescript
387
+ import { SdAvatar } from '@sd-angular/core/components/avatar';
388
+ ```
389
+
390
+ ```html
391
+ <sd-avatar src="/api/avatar/123" name="Nguyễn Văn A" size="md" />
392
+ <sd-avatar name="NVA" color="primary" size="lg" />
393
+ ```
394
+
395
+ ---
396
+
397
+ ### Other Components / Các component khác
398
+
399
+ | Component | Import | Mô tả |
400
+ |---|---|---|
401
+ | `SdTabRouter` | `@sd-angular/core/components/tab-router` | Tab navigation với Angular Router |
402
+ | `SdSideDrawer` | `@sd-angular/core/components/side-drawer` | Drawer layout trái/phải |
403
+ | `SdUploadFile` | `@sd-angular/core/components/upload-file` | Upload file |
404
+ | `SdQuickAction` | `@sd-angular/core/components/quick-action` | Nút action dạng icon |
405
+ | `SdHistory` | `@sd-angular/core/components/history` | Lịch sử thay đổi |
406
+ | `SdImportExcel` | `@sd-angular/core/components/import-excel` | Wizard import Excel |
407
+ | `SdQueryBuilder` | `@sd-angular/core/components/query-builder` | Visual query builder |
408
+ | `SdWorkflow` | `@sd-angular/core/components/workflow` | Workflow builder |
409
+ | `SdCodeEditor` | `@sd-angular/core/components/code-editor` | Code editor (PrismJS) |
410
+ | `SdMiniEditor` | `@sd-angular/core/components/mini-editor` | Rich text editor nhỏ |
411
+ | `SdDocumentBuilder` | `@sd-angular/core/components/document-builder` | Document builder |
412
+ | `SdAnchorMain` | `@sd-angular/core/components/anchor` | Anchor / mục lục cuộn trang |
413
+ | `SdView` | `@sd-angular/core/components/view` | View wrapper read-only |
414
+
415
+ ---
416
+
417
+ ## Form Components
418
+
419
+ ```typescript
420
+ import {
421
+ SdInput, // Text input
422
+ SdInputNumber, // Number input
423
+ SdSelect, // Dropdown
424
+ SdAutocomplete, // Autocomplete
425
+ SdDate, // Date picker
426
+ SdSearch, // Search với debounce
427
+ } from '@sd-angular/core/forms';
428
+ ```
429
+
430
+ ```html
431
+ <sd-input [(ngModel)]="form.name" label="Họ tên" [required]="true" />
432
+
433
+ <sd-input-number [(ngModel)]="form.price" label="Đơn giá" [min]="0" suffix="VNĐ" />
434
+
435
+ <sd-select [(ngModel)]="form.status" label="Trạng thái"
436
+ [items]="statusList" valueField="value" displayField="label" />
437
+
438
+ <sd-date [(ngModel)]="form.birthday" label="Ngày sinh" />
439
+ ```
440
+
441
+ ---
442
+
443
+ ## CRUD Patterns / Code mẫu CRUD
444
+
445
+ ### List Component
446
+
447
+ ```typescript
448
+ // product-list.component.ts
449
+ import { Component, OnInit, ViewChild, signal } from '@angular/core';
450
+ import { SdTable, SdTableOption } from '@sd-angular/core/components/table';
451
+ import { SdButton } from '@sd-angular/core/components/button';
452
+ import { SdModal } from '@sd-angular/core/components/modal';
453
+ import { SdSection } from '@sd-angular/core/components/section';
454
+ import { SdInput, SdSelect } from '@sd-angular/core/forms';
455
+
456
+ interface Product {
457
+ id: number;
458
+ code: string;
459
+ name: string;
460
+ price: number;
461
+ status: 'ACTIVE' | 'INACTIVE';
462
+ }
463
+
464
+ @Component({
465
+ selector: 'app-product-list',
466
+ templateUrl: './product-list.component.html',
467
+ standalone: true,
468
+ imports: [SdTable, SdButton, SdModal, SdSection, SdInput, SdSelect],
469
+ })
470
+ export class ProductListComponent implements OnInit {
471
+ @ViewChild('formModal') formModal!: SdModal;
472
+ @ViewChild('sdTable') sdTable?: SdTable<Product>;
473
+
474
+ selectedItem: Product | null = null;
475
+ formData: Partial<Product> = {};
476
+ isSaving = signal(false);
477
+
478
+ readonly STATUS_LIST = [
479
+ { value: 'ACTIVE', label: 'Hoạt động' },
480
+ { value: 'INACTIVE', label: 'Dừng' },
481
+ ];
482
+
483
+ option!: SdTableOption<Product>;
484
+
485
+ constructor(private service: ProductService) {}
486
+
487
+ ngOnInit() {
488
+ this.option = {
489
+ type: 'server',
490
+ items: async (filter, paging) => this.service.search(filter, paging),
491
+ columns: [
492
+ { field: 'code', type: 'string', title: 'Mã', width: '120px' },
493
+ { field: 'name', type: 'string', title: 'Tên', sortable: true },
494
+ { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
495
+ {
496
+ field: 'status', type: 'values', title: 'Trạng thái',
497
+ option: { items: this.STATUS_LIST, valueField: 'value', displayField: 'label' },
498
+ useBadge: (val) => ({
499
+ color: val === 'ACTIVE' ? 'success' : 'secondary',
500
+ title: this.STATUS_LIST.find(s => s.value === val)?.label,
501
+ }),
502
+ },
503
+ ],
504
+ command: {
505
+ align: 'right',
506
+ commands: [
507
+ { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.openForm(row) },
508
+ { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
509
+ ],
510
+ },
511
+ paginate: { pageSize: 20 },
512
+ reload: { visible: true },
513
+ };
514
+ }
515
+
516
+ openForm(item?: Product) {
517
+ this.selectedItem = item || null;
518
+ this.formData = item ? { ...item } : { status: 'ACTIVE' };
519
+ this.formModal.open();
520
+ }
521
+
522
+ async onSave() {
523
+ this.isSaving.set(true);
524
+ try {
525
+ if (this.selectedItem) {
526
+ await this.service.update(this.selectedItem.id, this.formData);
527
+ } else {
528
+ await this.service.create(this.formData);
529
+ }
530
+ this.formModal.close();
531
+ this.sdTable?.reload?.();
532
+ } finally {
533
+ this.isSaving.set(false);
534
+ }
535
+ }
536
+
537
+ async onDelete(item: Product) {
538
+ if (!confirm(`Xoá "${item.name}"?`)) return;
539
+ await this.service.delete(item.id);
540
+ this.sdTable?.reload?.();
541
+ }
542
+ }
543
+ ```
544
+
545
+ ### Template
546
+
547
+ ```html
548
+ <!-- product-list.component.html -->
549
+ <div class="d-flex justify-content-between align-items-center mb-16">
550
+ <h2>Danh sách sản phẩm</h2>
551
+ <sd-button type="fill" color="primary" title="Thêm mới"
552
+ prefixIcon="add" (click)="openForm()" />
553
+ </div>
554
+
555
+ <sd-table #sdTable [option]="option" />
556
+
557
+ <sd-modal #formModal [title]="selectedItem ? 'Chỉnh sửa' : 'Thêm mới'" width="md">
558
+ <ng-template>
559
+ <div class="modal-body p-16">
560
+ <sd-section title="Thông tin sản phẩm" icon="inventory">
561
+ <div class="row">
562
+ <div class="col-6">
563
+ <sd-input [(ngModel)]="formData.code" label="Mã" [required]="true" />
564
+ </div>
565
+ <div class="col-6">
566
+ <sd-select [(ngModel)]="formData.status" label="Trạng thái"
567
+ [items]="STATUS_LIST" valueField="value" displayField="label" />
568
+ </div>
569
+ <div class="col-12">
570
+ <sd-input [(ngModel)]="formData.name" label="Tên sản phẩm" [required]="true" />
571
+ </div>
572
+ <div class="col-6">
573
+ <sd-input-number [(ngModel)]="formData.price" label="Đơn giá" suffix="VNĐ" />
574
+ </div>
575
+ </div>
576
+ </sd-section>
577
+ </div>
578
+ <div class="modal-footer d-flex justify-content-end gap-8 p-16">
579
+ <sd-button title="Huỷ" (click)="formModal.close()" />
580
+ <sd-button type="fill" color="primary" title="Lưu"
581
+ prefixIcon="save" [loading]="isSaving()" (click)="onSave()" />
582
+ </div>
583
+ </ng-template>
584
+ </sd-modal>
585
+ ```
586
+
587
+ ---
588
+
589
+ ## Contributing Guide / Hướng dẫn đóng góp
590
+
591
+ ### Cấu trúc thư viện / Project structure
592
+
593
+ ```
594
+ sd-angular/
595
+ ├── src/
596
+ │ └── public-api.ts # Entry point chính
597
+ ├── assets/
598
+ │ └── scss/
599
+ │ ├── sd-core.scss # SCSS entry (import vào app)
600
+ │ ├── core/ # Base utilities (color, grid, form, ...)
601
+ │ └── themes/ # Theme mặc định + Material theme
602
+ ├── components/ # UI Components
603
+ │ ├── button/
604
+ │ ├── table/
605
+ │ ├── modal/
606
+ │ └── ...
607
+ ├── forms/ # Form components
608
+ │ ├── input/
609
+ │ ├── select/
610
+ │ └── ...
611
+ ├── directives/ # Angular directives
612
+ ├── pipes/ # Angular pipes
613
+ ├── services/ # Shared services
614
+ ├── utilities/ # Types, models, helpers
615
+ └── modules/ # Feature modules (layout, permission, ...)
616
+ ```
617
+
618
+ ### Thêm component mới / Adding a new component
619
+
620
+ **1. Tạo thư mục component:**
621
+
622
+ ```
623
+ components/
624
+ └── my-component/
625
+ ├── index.ts # Export public API
626
+ ├── ng-package.json # ng-packagr entry
627
+ └── src/
628
+ ├── my-component.component.ts
629
+ ├── my-component.component.html
630
+ └── my-component.component.scss
631
+ ```
632
+
633
+ **2. `ng-package.json`:**
634
+
635
+ ```json
636
+ {
637
+ "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
638
+ "lib": {
639
+ "entryFile": "index.ts"
640
+ }
641
+ }
642
+ ```
643
+
644
+ **3. `index.ts`:**
645
+
646
+ ```typescript
647
+ export * from './src/my-component.component';
648
+ ```
649
+
650
+ **4. Component template:**
651
+
652
+ ```typescript
653
+ // my-component.component.ts
654
+ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
655
+ import { SdBaseSecureComponent } from '@sd-angular/core/components/base';
656
+ import { SdColor } from '@sd-angular/core/utilities';
657
+
658
+ @Component({
659
+ selector: 'sd-my-component',
660
+ templateUrl: './my-component.component.html',
661
+ styleUrls: ['./my-component.component.scss'],
662
+ changeDetection: ChangeDetectionStrategy.OnPush,
663
+ standalone: true,
664
+ imports: [],
665
+ })
666
+ export class SdMyComponent extends SdBaseSecureComponent {
667
+ color = input<SdColor, SdColor | undefined | null>('primary', {
668
+ transform: (value) => value || 'primary',
669
+ });
670
+
671
+ title = input<string | undefined | null>(undefined);
672
+ }
673
+ ```
674
+
675
+ **5. Export từ `components/index.ts`:**
676
+
677
+ ```typescript
678
+ // components/index.ts
679
+ export * from '@sd-angular/core/components/my-component';
680
+ ```
681
+
682
+ ### Quy ước / Conventions
683
+
684
+ | Mục | Quy ước |
685
+ |---|---|
686
+ | Selector | `sd-<tên-component>` |
687
+ | Class name | `Sd<TênComponent>` (Pascal) |
688
+ | Input | Dùng `input<T>()` signal, **không** dùng `@Input()` decorator |
689
+ | Null safety | Input transform phải handle `null/undefined` |
690
+ | Base class | Extend `SdBaseSecureComponent` cho component có permission |
691
+ | Change detection | Luôn dùng `ChangeDetectionStrategy.OnPush` |
692
+ | Standalone | Luôn `standalone: true` |
693
+ | Colors | Dùng `SdColor` type, không hardcode màu |
694
+
695
+ ### Build
696
+
697
+ ```bash
698
+ # Build toàn bộ thư viện
699
+ ng-packagr -p ng-package.json
700
+
701
+ # Watch mode
702
+ ng-packagr -p ng-package.json --watch
703
+ ```
704
+
705
+ ### Versioning
706
+
707
+ Thư viện tuân theo [Semantic Versioning](https://semver.org):
708
+
709
+ - `MAJOR` — Breaking changes (thay đổi API không tương thích)
710
+ - `MINOR` — Tính năng mới (tương thích ngược)
711
+ - `PATCH` — Bug fixes
712
+
713
+ ---
714
+
715
+ ## License
716
+
717
717
  Internal use only — © SD Team