@sd-angular/core 19.0.12 → 19.0.14

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 (167) hide show
  1. package/README.md +712 -712
  2. package/assets/STYLE-GUIDE.md +601 -601
  3. package/assets/images/coming-soon.svg +22 -22
  4. package/assets/images/data-empty.svg +20 -20
  5. package/assets/images/expired.svg +21 -21
  6. package/assets/images/file-error.svg +27 -27
  7. package/assets/images/filter-empty.svg +19 -19
  8. package/assets/images/filter-required.svg +20 -20
  9. package/assets/images/forbidden.svg +14 -14
  10. package/assets/images/image-error.svg +26 -26
  11. package/assets/images/maintenance.svg +22 -22
  12. package/assets/images/not-found.svg +19 -19
  13. package/assets/images/offline.svg +19 -19
  14. package/assets/images/submitted.svg +17 -17
  15. package/assets/images/success.svg +17 -17
  16. package/assets/images/unauthorized.svg +21 -21
  17. package/assets/images/unknown-error.svg +17 -17
  18. package/assets/scss/ckeditor5.scss +88 -88
  19. package/assets/scss/core/_inline-edit.scss +105 -105
  20. package/assets/scss/core/color.scss +73 -73
  21. package/assets/scss/core/form.scss +317 -317
  22. package/assets/scss/core/image.scss +35 -35
  23. package/assets/scss/core/utilities/_base.scss +333 -333
  24. package/assets/scss/core/utilities/_border.scss +22 -22
  25. package/assets/scss/core/utilities/_display.scss +10 -10
  26. package/assets/scss/core/utilities/_elevation.scss +80 -80
  27. package/assets/scss/core/utilities/_flexbox.scss +54 -54
  28. package/assets/scss/core/utilities/_gap.scss +9 -9
  29. package/assets/scss/core/utilities/_index.scss +17 -17
  30. package/assets/scss/core/utilities/_misc.scss +20 -20
  31. package/assets/scss/core/utilities/_overflow.scss +9 -9
  32. package/assets/scss/core/utilities/_position.scss +8 -8
  33. package/assets/scss/core/utilities/_sizing.scss +28 -28
  34. package/assets/scss/core/utilities/_spacing.scss +31 -31
  35. package/assets/scss/core/utilities/_typography.scss +99 -99
  36. package/assets/scss/sd-core.scss +16 -16
  37. package/assets/scss/themes/default.scss +45 -45
  38. package/assets/scss/themes/material-theme.scss +101 -101
  39. package/components/preview/src/preview-pdf/pdf-worker-inline.generated.d.ts +7 -0
  40. package/components/table/src/services/table-export/table-export.service.d.ts +3 -1
  41. package/components/table/src/table.component.d.ts +1 -1
  42. package/fesm2022/sd-angular-core-components-anchor.mjs +6 -6
  43. package/fesm2022/sd-angular-core-components-anchor.mjs.map +1 -1
  44. package/fesm2022/sd-angular-core-components-autoid-inspector.mjs +2 -2
  45. package/fesm2022/sd-angular-core-components-autoid-inspector.mjs.map +1 -1
  46. package/fesm2022/sd-angular-core-components-badge.mjs +2 -2
  47. package/fesm2022/sd-angular-core-components-badge.mjs.map +1 -1
  48. package/fesm2022/sd-angular-core-components-base.mjs.map +1 -1
  49. package/fesm2022/sd-angular-core-components-button.mjs +2 -2
  50. package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
  51. package/fesm2022/sd-angular-core-components-chart.mjs +40 -40
  52. package/fesm2022/sd-angular-core-components-chart.mjs.map +1 -1
  53. package/fesm2022/sd-angular-core-components-ckeditor-styles.mjs.map +1 -1
  54. package/fesm2022/sd-angular-core-components-code-editor.mjs +2 -2
  55. package/fesm2022/sd-angular-core-components-code-editor.mjs.map +1 -1
  56. package/fesm2022/sd-angular-core-components-document-builder.mjs +77 -77
  57. package/fesm2022/sd-angular-core-components-document-builder.mjs.map +1 -1
  58. package/fesm2022/sd-angular-core-components-editor.mjs +2 -2
  59. package/fesm2022/sd-angular-core-components-editor.mjs.map +1 -1
  60. package/fesm2022/sd-angular-core-components-form-generic.mjs +106 -106
  61. package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
  62. package/fesm2022/sd-angular-core-components-history.mjs +2 -2
  63. package/fesm2022/sd-angular-core-components-history.mjs.map +1 -1
  64. package/fesm2022/sd-angular-core-components-import-excel.mjs +2 -2
  65. package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
  66. package/fesm2022/sd-angular-core-components-inform.mjs.map +1 -1
  67. package/fesm2022/sd-angular-core-components-mini-editor.mjs +2 -2
  68. package/fesm2022/sd-angular-core-components-mini-editor.mjs.map +1 -1
  69. package/fesm2022/sd-angular-core-components-modal-resizable.mjs +2 -2
  70. package/fesm2022/sd-angular-core-components-modal-resizable.mjs.map +1 -1
  71. package/fesm2022/sd-angular-core-components-modal.mjs +2 -2
  72. package/fesm2022/sd-angular-core-components-modal.mjs.map +1 -1
  73. package/fesm2022/sd-angular-core-components-operator.mjs +2 -2
  74. package/fesm2022/sd-angular-core-components-operator.mjs.map +1 -1
  75. package/fesm2022/sd-angular-core-components-org-chart.mjs +2 -2
  76. package/fesm2022/sd-angular-core-components-org-chart.mjs.map +1 -1
  77. package/fesm2022/sd-angular-core-components-preview.mjs +32 -6
  78. package/fesm2022/sd-angular-core-components-preview.mjs.map +1 -1
  79. package/fesm2022/sd-angular-core-components-query-bar.mjs +10 -10
  80. package/fesm2022/sd-angular-core-components-query-bar.mjs.map +1 -1
  81. package/fesm2022/sd-angular-core-components-query-builder.mjs +2 -2
  82. package/fesm2022/sd-angular-core-components-query-builder.mjs.map +1 -1
  83. package/fesm2022/sd-angular-core-components-section.mjs +4 -4
  84. package/fesm2022/sd-angular-core-components-section.mjs.map +1 -1
  85. package/fesm2022/sd-angular-core-components-side-drawer.mjs +2 -2
  86. package/fesm2022/sd-angular-core-components-side-drawer.mjs.map +1 -1
  87. package/fesm2022/sd-angular-core-components-stepper.mjs +2 -2
  88. package/fesm2022/sd-angular-core-components-stepper.mjs.map +1 -1
  89. package/fesm2022/sd-angular-core-components-tab-router.mjs +6 -6
  90. package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
  91. package/fesm2022/sd-angular-core-components-tab.mjs +2 -2
  92. package/fesm2022/sd-angular-core-components-tab.mjs.map +1 -1
  93. package/fesm2022/sd-angular-core-components-table.mjs +32 -17
  94. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  95. package/fesm2022/sd-angular-core-components-upload-file.mjs +4 -4
  96. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  97. package/fesm2022/sd-angular-core-components-view.mjs +2 -2
  98. package/fesm2022/sd-angular-core-components-view.mjs.map +1 -1
  99. package/fesm2022/sd-angular-core-configurations.mjs.map +1 -1
  100. package/fesm2022/sd-angular-core-directives.mjs +20 -20
  101. package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
  102. package/fesm2022/sd-angular-core-forms-autocomplete.mjs +2 -2
  103. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  104. package/fesm2022/sd-angular-core-forms-checkbox.mjs +2 -2
  105. package/fesm2022/sd-angular-core-forms-checkbox.mjs.map +1 -1
  106. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +2 -2
  107. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
  108. package/fesm2022/sd-angular-core-forms-chip.mjs +2 -2
  109. package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
  110. package/fesm2022/sd-angular-core-forms-date-range.mjs +2 -2
  111. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  112. package/fesm2022/sd-angular-core-forms-date.mjs +114 -35
  113. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  114. package/fesm2022/sd-angular-core-forms-datetime.mjs +6 -6
  115. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  116. package/fesm2022/sd-angular-core-forms-directives.mjs.map +1 -1
  117. package/fesm2022/sd-angular-core-forms-inline-text.mjs +2 -2
  118. package/fesm2022/sd-angular-core-forms-inline-text.mjs.map +1 -1
  119. package/fesm2022/sd-angular-core-forms-input-color.mjs +2 -2
  120. package/fesm2022/sd-angular-core-forms-input-color.mjs.map +1 -1
  121. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  122. package/fesm2022/sd-angular-core-forms-input.mjs +2 -2
  123. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  124. package/fesm2022/sd-angular-core-forms-label.mjs +2 -2
  125. package/fesm2022/sd-angular-core-forms-label.mjs.map +1 -1
  126. package/fesm2022/sd-angular-core-forms-models.mjs.map +1 -1
  127. package/fesm2022/sd-angular-core-forms-radio.mjs +2 -2
  128. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  129. package/fesm2022/sd-angular-core-forms-select.mjs +2 -2
  130. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  131. package/fesm2022/sd-angular-core-forms-switch.mjs +2 -2
  132. package/fesm2022/sd-angular-core-forms-switch.mjs.map +1 -1
  133. package/fesm2022/sd-angular-core-forms-textarea.mjs +2 -2
  134. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  135. package/fesm2022/sd-angular-core-handlers.mjs.map +1 -1
  136. package/fesm2022/sd-angular-core-i18n.mjs +20 -0
  137. package/fesm2022/sd-angular-core-i18n.mjs.map +1 -1
  138. package/fesm2022/sd-angular-core-interceptors.mjs.map +1 -1
  139. package/fesm2022/sd-angular-core-modules-auth.mjs.map +1 -1
  140. package/fesm2022/sd-angular-core-modules-authom.mjs.map +1 -1
  141. package/fesm2022/sd-angular-core-modules-keycloak.mjs.map +1 -1
  142. package/fesm2022/sd-angular-core-modules-layout.mjs +145 -46
  143. package/fesm2022/sd-angular-core-modules-layout.mjs.map +1 -1
  144. package/fesm2022/sd-angular-core-modules-permission.mjs.map +1 -1
  145. package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
  146. package/fesm2022/sd-angular-core-services-api.mjs.map +1 -1
  147. package/fesm2022/sd-angular-core-services-cache.mjs.map +1 -1
  148. package/fesm2022/sd-angular-core-services-confirm.mjs +2 -2
  149. package/fesm2022/sd-angular-core-services-confirm.mjs.map +1 -1
  150. package/fesm2022/sd-angular-core-services-docx.mjs.map +1 -1
  151. package/fesm2022/sd-angular-core-services-excel.mjs.map +1 -1
  152. package/fesm2022/sd-angular-core-services-license.mjs.map +1 -1
  153. package/fesm2022/sd-angular-core-services-loading.mjs +5 -5
  154. package/fesm2022/sd-angular-core-services-loading.mjs.map +1 -1
  155. package/fesm2022/sd-angular-core-services-notify.mjs +12 -12
  156. package/fesm2022/sd-angular-core-services-notify.mjs.map +1 -1
  157. package/fesm2022/sd-angular-core-services-storage.mjs.map +1 -1
  158. package/fesm2022/sd-angular-core-utilities-data-state.mjs.map +1 -1
  159. package/fesm2022/sd-angular-core-utilities-extensions.mjs.map +1 -1
  160. package/forms/date/src/date.component.d.ts +9 -1
  161. package/i18n/src/en.d.ts +4 -0
  162. package/modules/layout/components/sidebar-v1/main.component.d.ts +1 -0
  163. package/modules/layout/index.d.ts +1 -0
  164. package/modules/layout/utils/index.d.ts +1 -0
  165. package/modules/layout/utils/tab-name.util.d.ts +8 -0
  166. package/package.json +41 -41
  167. package/sd-angular-core-19.0.12.tgz +0 -0
package/README.md CHANGED
@@ -1,370 +1,370 @@
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
- [![Angular](https://img.shields.io/badge/Angular-19%2B-red)](https://angular.dev)
7
- [![Angular Material](https://img.shields.io/badge/Angular_Material-19%2B-purple)](https://material.angular.io)
8
-
9
- ---
10
-
11
- ## Table of Contents / Mục lục
12
-
13
- - [Getting Started / Cài đặt](#getting-started--cài-đặt)
14
- - [Theming / SCSS Customization](#theming--scss-customization)
15
- - [Components](#components)
16
- - [SdButton](#sdbutton)
17
- - [SdBadge](#sdbadge)
18
- - [SdSection](#sdsection)
19
- - [SdModal](#sdmodal)
20
- - [SdTable](#sdtable)
21
- - [SdAvatar](#sdavatar)
22
- - [Other Components](#other-components--các-component-khác)
23
- - [Form Components](#form-components)
24
- - [CRUD Patterns / Code mẫu CRUD](#crud-patterns--code-mẫu-crud)
25
- - [Contributing Guide / Hướng dẫn đóng góp](#contributing-guide--hướng-dẫn-đóng-góp)
26
-
27
- ---
28
-
29
- ## Getting Started / Cài đặt
30
-
31
- ### Prerequisites / Yêu cầu
32
-
33
- | Dependency | Version |
34
- |---|---|
35
- | `@angular/core` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
36
- | `@angular/material` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
37
- | `@angular/material-date-fns-adapter` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
38
- | `date-fns` | `^3 \|\| ^4` |
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` | `Color` | `'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` | `Color` | `'secondary'` | Màu sắc |
224
- | `title` | `string \| number` | — | Nội dung hiển thị |
225
- | `icon` | `string` | — | Icon Material |
226
- | `size` | `Size` | `'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` | `Color` | `'primary'` | Màu icon |
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
+ [![Angular](https://img.shields.io/badge/Angular-19%2B-red)](https://angular.dev)
7
+ [![Angular Material](https://img.shields.io/badge/Angular_Material-19%2B-purple)](https://material.angular.io)
8
+
9
+ ---
10
+
11
+ ## Table of Contents / Mục lục
12
+
13
+ - [Getting Started / Cài đặt](#getting-started--cài-đặt)
14
+ - [Theming / SCSS Customization](#theming--scss-customization)
15
+ - [Components](#components)
16
+ - [SdButton](#sdbutton)
17
+ - [SdBadge](#sdbadge)
18
+ - [SdSection](#sdsection)
19
+ - [SdModal](#sdmodal)
20
+ - [SdTable](#sdtable)
21
+ - [SdAvatar](#sdavatar)
22
+ - [Other Components](#other-components--các-component-khác)
23
+ - [Form Components](#form-components)
24
+ - [CRUD Patterns / Code mẫu CRUD](#crud-patterns--code-mẫu-crud)
25
+ - [Contributing Guide / Hướng dẫn đóng góp](#contributing-guide--hướng-dẫn-đóng-góp)
26
+
27
+ ---
28
+
29
+ ## Getting Started / Cài đặt
30
+
31
+ ### Prerequisites / Yêu cầu
32
+
33
+ | Dependency | Version |
34
+ |---|---|
35
+ | `@angular/core` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
36
+ | `@angular/material` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
37
+ | `@angular/material-date-fns-adapter` | `^19.0.0 \|\| ^20.0.0 \|\| ^21.0.0` |
38
+ | `date-fns` | `^3 \|\| ^4` |
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` | `Color` | `'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` | `Color` | `'secondary'` | Màu sắc |
224
+ | `title` | `string \| number` | — | Nội dung hiển thị |
225
+ | `icon` | `string` | — | Icon Material |
226
+ | `size` | `Size` | `'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` | `Color` | `'primary'` | Màu icon |
253
253
  | `collapsible` | `boolean` | `false` | Cho phép thu gọn |
254
254
  | `collapsable` | `boolean` | `false` | Deprecated alias của `collapsible` |
255
- | `collapsed` | `boolean` | `false` | Trạng thái ban đầu thu gọn |
256
- | `hideHeader` | `boolean` | `false` | Ẩn phần header |
257
-
258
- ```html
259
- <sd-section title="Thông tin cơ bản" icon="person" iconColor="primary">
260
- <!-- nội dung -->
261
- </sd-section>
262
-
255
+ | `collapsed` | `boolean` | `false` | Trạng thái ban đầu thu gọn |
256
+ | `hideHeader` | `boolean` | `false` | Ẩn phần header |
257
+
258
+ ```html
259
+ <sd-section title="Thông tin cơ bản" icon="person" iconColor="primary">
260
+ <!-- nội dung -->
261
+ </sd-section>
262
+
263
263
  <sd-section title="Cài đặt nâng cao" icon="settings" collapsible [collapsed]="true">
264
- <!-- nội dung ẩn mặc định -->
265
- </sd-section>
266
- ```
267
-
268
- ---
269
-
270
- ### SdModal
271
-
272
- ```typescript
273
- import { SdModal } from '@sd-angular/core/components/modal';
274
- ```
275
-
276
- **Inputs:**
277
-
278
- | Input | Type | Default | Description |
279
- |---|---|---|---|
280
- | `title` | `string` | — | Tiêu đề modal |
281
- | `color` | `Color` | `'primary'` | Màu header |
282
- | `width` | `'sx' \| 'sm' \| 'md' \| 'lg' \| string` | `'md'` | Độ rộng (md = 60vw) |
283
- | `height` | `string` | `'auto'` | Chiều cao |
284
- | `view` | `'dialog' \| 'bottom-sheet'` | auto | Tự động bottom-sheet trên mobile |
285
- | `lazyLoadContent` | `boolean` | `true` | Lazy render nội dung |
286
-
287
- **Output:** `(sdClosed): EventEmitter` — phát ra khi modal đóng.
288
-
289
- **Methods** (dùng qua `@ViewChild`):
290
- - `modal.open()` — mở modal
291
- - `modal.close()` — đóng modal
292
-
293
- > ⚠️ Nội dung modal phải đặt trong `<ng-template>`.
294
-
295
- ```html
296
- <sd-modal #myModal title="Thêm mới" width="md" (sdClosed)="onClosed()">
297
- <ng-template>
298
- <div class="modal-body p-16">
299
- <!-- nội dung form -->
300
- </div>
301
- <div class="modal-footer d-flex justify-content-end gap-8 p-16">
302
- <sd-button title="Huỷ" (click)="myModal.close()" />
303
- <sd-button type="fill" color="primary" title="Lưu" (click)="onSave()" />
304
- </div>
305
- </ng-template>
306
- </sd-modal>
307
-
308
- <sd-button title="Mở modal" prefixIcon="add" (click)="myModal.open()" />
309
- ```
310
-
311
- ---
312
-
313
- ### SdTable
314
-
315
- ```typescript
316
- import { SdTable } from '@sd-angular/core/components/table';
317
- import type { SdTableOption, SdTableColumn } from '@sd-angular/core/components/table';
318
- ```
319
-
320
- SdTable nhận một object `option` duy nhất kiểu `SdTableOption<T>`.
321
-
322
- **Column types / Kiểu cột:**
323
-
324
- | `type` | Mô tả |
325
- |---|---|
326
- | `'string'` | Văn bản |
327
- | `'number'` | Số (tự format) |
328
- | `'boolean'` | True/False |
329
- | `'date'` | Ngày |
330
- | `'datetime'` | Ngày giờ |
331
- | `'time'` | Giờ |
332
- | `'values'` | Enum từ danh sách cố định |
333
- | `'lazy-values'` | Enum load async |
334
- | `'children'` | Cột nhóm (multi-header) |
335
-
336
- **Local table:**
337
-
338
- ```typescript
339
- option: SdTableOption<Product> = {
340
- type: 'local',
341
- items: () => this.products,
342
- columns: [
343
- { field: 'code', type: 'string', title: 'Mã', width: '120px' },
344
- { field: 'name', type: 'string', title: 'Tên', sortable: true },
345
- { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
346
- { field: 'active', type: 'boolean', title: 'Kích hoạt',
347
- useBadge: (val) => ({ color: val ? 'success' : 'secondary', title: val ? 'Có' : 'Không' })
348
- },
349
- ],
350
- paginate: { pageSize: 20 },
351
- reload: { visible: true },
352
- };
353
- ```
354
-
355
- **Server-side table:**
356
-
357
- ```typescript
358
- option: SdTableOption<Product> = {
359
- type: 'server',
360
- items: async (filterRequest, pagingReq) => {
361
- const res = await this.service.search({
362
- keyword: filterRequest.keyword,
363
- page: pagingReq.page,
364
- pageSize: pagingReq.pageSize,
365
- });
366
- return { items: res.data, total: res.total };
367
- },
264
+ <!-- nội dung ẩn mặc định -->
265
+ </sd-section>
266
+ ```
267
+
268
+ ---
269
+
270
+ ### SdModal
271
+
272
+ ```typescript
273
+ import { SdModal } from '@sd-angular/core/components/modal';
274
+ ```
275
+
276
+ **Inputs:**
277
+
278
+ | Input | Type | Default | Description |
279
+ |---|---|---|---|
280
+ | `title` | `string` | — | Tiêu đề modal |
281
+ | `color` | `Color` | `'primary'` | Màu header |
282
+ | `width` | `'sx' \| 'sm' \| 'md' \| 'lg' \| string` | `'md'` | Độ rộng (md = 60vw) |
283
+ | `height` | `string` | `'auto'` | Chiều cao |
284
+ | `view` | `'dialog' \| 'bottom-sheet'` | auto | Tự động bottom-sheet trên mobile |
285
+ | `lazyLoadContent` | `boolean` | `true` | Lazy render nội dung |
286
+
287
+ **Output:** `(sdClosed): EventEmitter` — phát ra khi modal đóng.
288
+
289
+ **Methods** (dùng qua `@ViewChild`):
290
+ - `modal.open()` — mở modal
291
+ - `modal.close()` — đóng modal
292
+
293
+ > ⚠️ Nội dung modal phải đặt trong `<ng-template>`.
294
+
295
+ ```html
296
+ <sd-modal #myModal title="Thêm mới" width="md" (sdClosed)="onClosed()">
297
+ <ng-template>
298
+ <div class="modal-body p-16">
299
+ <!-- nội dung form -->
300
+ </div>
301
+ <div class="modal-footer d-flex justify-content-end gap-8 p-16">
302
+ <sd-button title="Huỷ" (click)="myModal.close()" />
303
+ <sd-button type="fill" color="primary" title="Lưu" (click)="onSave()" />
304
+ </div>
305
+ </ng-template>
306
+ </sd-modal>
307
+
308
+ <sd-button title="Mở modal" prefixIcon="add" (click)="myModal.open()" />
309
+ ```
310
+
311
+ ---
312
+
313
+ ### SdTable
314
+
315
+ ```typescript
316
+ import { SdTable } from '@sd-angular/core/components/table';
317
+ import type { SdTableOption, SdTableColumn } from '@sd-angular/core/components/table';
318
+ ```
319
+
320
+ SdTable nhận một object `option` duy nhất kiểu `SdTableOption<T>`.
321
+
322
+ **Column types / Kiểu cột:**
323
+
324
+ | `type` | Mô tả |
325
+ |---|---|
326
+ | `'string'` | Văn bản |
327
+ | `'number'` | Số (tự format) |
328
+ | `'boolean'` | True/False |
329
+ | `'date'` | Ngày |
330
+ | `'datetime'` | Ngày giờ |
331
+ | `'time'` | Giờ |
332
+ | `'values'` | Enum từ danh sách cố định |
333
+ | `'lazy-values'` | Enum load async |
334
+ | `'children'` | Cột nhóm (multi-header) |
335
+
336
+ **Local table:**
337
+
338
+ ```typescript
339
+ option: SdTableOption<Product> = {
340
+ type: 'local',
341
+ items: () => this.products,
342
+ columns: [
343
+ { field: 'code', type: 'string', title: 'Mã', width: '120px' },
344
+ { field: 'name', type: 'string', title: 'Tên', sortable: true },
345
+ { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
346
+ { field: 'active', type: 'boolean', title: 'Kích hoạt',
347
+ useBadge: (val) => ({ color: val ? 'success' : 'secondary', title: val ? 'Có' : 'Không' })
348
+ },
349
+ ],
350
+ paginate: { pageSize: 20 },
351
+ reload: { visible: true },
352
+ };
353
+ ```
354
+
355
+ **Server-side table:**
356
+
357
+ ```typescript
358
+ option: SdTableOption<Product> = {
359
+ type: 'server',
360
+ items: async (filterRequest, pagingReq) => {
361
+ const res = await this.service.search({
362
+ keyword: filterRequest.keyword,
363
+ page: pagingReq.page,
364
+ pageSize: pagingReq.pageSize,
365
+ });
366
+ return { items: res.data, total: res.total };
367
+ },
368
368
  columns: [
369
369
  { field: 'code', type: 'string', title: 'Mã', width: '120px' },
370
370
  { field: 'name', type: 'string', title: 'Tên', sortable: true },
@@ -372,42 +372,42 @@ option: SdTableOption<Product> = {
372
372
  useBadge: (val) => ({ color: val === 'ACTIVE' ? 'success' : 'secondary', title: val })
373
373
  },
374
374
  ],
375
- command: {
376
- align: 'right',
377
- commands: [
378
- { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.onEdit(row) },
379
- { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
380
- ],
381
- },
382
- };
383
- ```
384
-
385
- ```html
386
- <sd-table #sdTable [option]="option" />
387
- ```
388
-
389
- ---
390
-
391
- ### SdAvatar
392
-
393
- ```typescript
394
- import { SdAvatar } from '@sd-angular/core/components/avatar';
395
- ```
396
-
397
- ```html
398
- <sd-avatar src="/api/avatar/123" name="Nguyễn Văn A" size="md" />
399
- <sd-avatar name="NVA" color="primary" size="lg" />
400
- ```
401
-
402
- ---
403
-
404
- ### Other Components / Các component khác
405
-
406
- | Component | Import | Mô tả |
407
- |---|---|---|
408
- | `SdTabRouter` | `@sd-angular/core/components/tab-router` | Tab navigation với Angular Router |
409
- | `SdSideDrawer` | `@sd-angular/core/components/side-drawer` | Drawer layout trái/phải |
410
- | `SdUploadFile` | `@sd-angular/core/components/upload-file` | Upload file |
375
+ command: {
376
+ align: 'right',
377
+ commands: [
378
+ { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.onEdit(row) },
379
+ { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
380
+ ],
381
+ },
382
+ };
383
+ ```
384
+
385
+ ```html
386
+ <sd-table #sdTable [option]="option" />
387
+ ```
388
+
389
+ ---
390
+
391
+ ### SdAvatar
392
+
393
+ ```typescript
394
+ import { SdAvatar } from '@sd-angular/core/components/avatar';
395
+ ```
396
+
397
+ ```html
398
+ <sd-avatar src="/api/avatar/123" name="Nguyễn Văn A" size="md" />
399
+ <sd-avatar name="NVA" color="primary" size="lg" />
400
+ ```
401
+
402
+ ---
403
+
404
+ ### Other Components / Các component khác
405
+
406
+ | Component | Import | Mô tả |
407
+ |---|---|---|
408
+ | `SdTabRouter` | `@sd-angular/core/components/tab-router` | Tab navigation với Angular Router |
409
+ | `SdSideDrawer` | `@sd-angular/core/components/side-drawer` | Drawer layout trái/phải |
410
+ | `SdUploadFile` | `@sd-angular/core/components/upload-file` | Upload file |
411
411
  | `SdQuickAction` | `@sd-angular/core/components/quick-action` | Nút action dạng icon |
412
412
  | `SdHistory` | `@sd-angular/core/components/history` | Lịch sử thay đổi |
413
413
  | `SdImportExcel` | `@sd-angular/core/components/import-excel` | Wizard import Excel |
@@ -417,316 +417,316 @@ import { SdAvatar } from '@sd-angular/core/components/avatar';
417
417
  | `SdQueryBuilder` | `@sd-angular/core/components/query-builder` | Visual query builder |
418
418
  | `SdSplitter` | `@sd-angular/core/components/splitter` | Layout chia vùng kéo giãn |
419
419
  | `SdCodeEditor` | `@sd-angular/core/components/code-editor` | Code editor (PrismJS) |
420
- | `SdMiniEditor` | `@sd-angular/core/components/mini-editor` | Rich text editor nhỏ |
421
- | `SdDocumentBuilder` | `@sd-angular/core/components/document-builder` | Document builder |
422
- | `SdAnchorMain` | `@sd-angular/core/components/anchor` | Anchor / mục lục cuộn trang |
423
- | `SdView` | `@sd-angular/core/components/view` | View wrapper read-only |
424
-
425
- ---
426
-
427
- ## Form Components
428
-
429
- ```typescript
430
- import {
431
- SdInput, // Text input
432
- SdInputNumber, // Number input
433
- SdSelect, // Dropdown
434
- SdAutocomplete, // Autocomplete
435
- SdDate, // Date picker
436
- } from '@sd-angular/core/forms';
437
- ```
438
-
439
- ```html
440
- <sd-input [(model)]="form.name" label="Họ tên" [required]="true" />
441
-
442
- <sd-input-number [(model)]="form.price" label="Đơn giá" [min]="0" suffix="VNĐ" />
443
-
444
- <sd-select [(model)]="form.status" label="Trạng thái"
445
- [items]="statusList" valueField="value" displayField="label" />
446
-
447
- <sd-date [(model)]="form.birthday" label="Ngày sinh" />
448
- ```
449
-
450
- ---
451
-
452
- ## CRUD Patterns / Code mẫu CRUD
453
-
454
- ### List Component
455
-
456
- ```typescript
457
- // product-list.component.ts
458
- import { Component, OnInit, ViewChild, signal } from '@angular/core';
459
- import { SdTable, SdTableOption } from '@sd-angular/core/components/table';
460
- import { SdButton } from '@sd-angular/core/components/button';
461
- import { SdModal } from '@sd-angular/core/components/modal';
462
- import { SdSection } from '@sd-angular/core/components/section';
463
- import { SdInput, SdSelect } from '@sd-angular/core/forms';
464
-
465
- interface Product {
466
- id: number;
467
- code: string;
468
- name: string;
469
- price: number;
470
- status: 'ACTIVE' | 'INACTIVE';
471
- }
472
-
473
- @Component({
474
- selector: 'app-product-list',
475
- templateUrl: './product-list.component.html',
476
- standalone: true,
477
- imports: [SdTable, SdButton, SdModal, SdSection, SdInput, SdSelect],
478
- })
479
- export class ProductListComponent implements OnInit {
480
- @ViewChild('formModal') formModal!: SdModal;
481
- @ViewChild('sdTable') sdTable?: SdTable<Product>;
482
-
483
- selectedItem: Product | null = null;
484
- formData: Partial<Product> = {};
485
- isSaving = signal(false);
486
-
487
- readonly STATUS_LIST = [
488
- { value: 'ACTIVE', label: 'Hoạt động' },
489
- { value: 'INACTIVE', label: 'Dừng' },
490
- ];
491
-
492
- option!: SdTableOption<Product>;
493
-
494
- constructor(private service: ProductService) {}
495
-
496
- ngOnInit() {
497
- this.option = {
498
- type: 'server',
499
- items: async (filter, paging) => this.service.search(filter, paging),
500
- columns: [
501
- { field: 'code', type: 'string', title: 'Mã', width: '120px' },
502
- { field: 'name', type: 'string', title: 'Tên', sortable: true },
503
- { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
504
- {
505
- field: 'status', type: 'values', title: 'Trạng thái',
506
- option: { items: this.STATUS_LIST, valueField: 'value', displayField: 'label' },
507
- useBadge: (val) => ({
508
- color: val === 'ACTIVE' ? 'success' : 'secondary',
509
- title: this.STATUS_LIST.find(s => s.value === val)?.label,
510
- }),
511
- },
512
- ],
513
- command: {
514
- align: 'right',
515
- commands: [
516
- { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.openForm(row) },
517
- { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
518
- ],
519
- },
520
- paginate: { pageSize: 20 },
521
- reload: { visible: true },
522
- };
523
- }
524
-
525
- openForm(item?: Product) {
526
- this.selectedItem = item || null;
527
- this.formData = item ? { ...item } : { status: 'ACTIVE' };
528
- this.formModal.open();
529
- }
530
-
531
- async onSave() {
532
- this.isSaving.set(true);
533
- try {
534
- if (this.selectedItem) {
535
- await this.service.update(this.selectedItem.id, this.formData);
536
- } else {
537
- await this.service.create(this.formData);
538
- }
539
- this.formModal.close();
540
- this.sdTable?.reload?.();
541
- } finally {
542
- this.isSaving.set(false);
543
- }
544
- }
545
-
546
- async onDelete(item: Product) {
547
- if (!confirm(`Xoá "${item.name}"?`)) return;
548
- await this.service.delete(item.id);
549
- this.sdTable?.reload?.();
550
- }
551
- }
552
- ```
553
-
554
- ### Template
555
-
556
- ```html
557
- <!-- product-list.component.html -->
558
- <div class="d-flex justify-content-between align-items-center mb-16">
559
- <h2>Danh sách sản phẩm</h2>
560
- <sd-button type="fill" color="primary" title="Thêm mới"
561
- prefixIcon="add" (click)="openForm()" />
562
- </div>
563
-
564
- <sd-table #sdTable [option]="option" />
565
-
566
- <sd-modal #formModal [title]="selectedItem ? 'Chỉnh sửa' : 'Thêm mới'" width="md">
567
- <ng-template>
568
- <div class="modal-body p-16">
569
- <sd-section title="Thông tin sản phẩm" icon="inventory">
570
- <div class="row">
571
- <div class="col-6">
572
- <sd-input [(model)]="formData.code" label="Mã" [required]="true" />
573
- </div>
574
- <div class="col-6">
575
- <sd-select [(model)]="formData.status" label="Trạng thái"
576
- [items]="STATUS_LIST" valueField="value" displayField="label" />
577
- </div>
578
- <div class="col-12">
579
- <sd-input [(model)]="formData.name" label="Tên sản phẩm" [required]="true" />
580
- </div>
581
- <div class="col-6">
582
- <sd-input-number [(model)]="formData.price" label="Đơn giá" suffix="VNĐ" />
583
- </div>
584
- </div>
585
- </sd-section>
586
- </div>
587
- <div class="modal-footer d-flex justify-content-end gap-8 p-16">
588
- <sd-button title="Huỷ" (click)="formModal.close()" />
589
- <sd-button type="fill" color="primary" title="Lưu"
590
- prefixIcon="save" [loading]="isSaving()" (click)="onSave()" />
591
- </div>
592
- </ng-template>
593
- </sd-modal>
594
- ```
595
-
596
- ---
597
-
598
- ## Contributing Guide / Hướng dẫn đóng góp
599
-
600
- ### Cấu trúc thư viện / Project structure
601
-
602
- ```
603
- sd-angular/
604
- ├── src/
605
- │ └── public-api.ts # Entry point chính
606
- ├── assets/
607
- │ └── scss/
608
- │ ├── sd-core.scss # SCSS entry (import vào app)
609
- │ ├── core/ # Base utilities (color, grid, form, ...)
610
- │ └── themes/ # Theme mặc định + Material theme
611
- ├── components/ # UI Components
612
- │ ├── button/
613
- │ ├── table/
614
- │ ├── modal/
615
- │ └── ...
616
- ├── forms/ # Form components
617
- │ ├── input/
618
- │ ├── select/
619
- │ └── ...
620
- ├── directives/ # Angular directives
621
- ├── pipes/ # Angular pipes
622
- ├── services/ # Shared services
623
- ├── utilities/ # Types, models, helpers
624
- └── modules/ # Feature modules (layout, permission, ...)
625
- ```
626
-
627
- ### Thêm component mới / Adding a new component
628
-
629
- **1. Tạo thư mục component:**
630
-
631
- ```
632
- components/
633
- └── my-component/
634
- ├── index.ts # Export public API
635
- ├── ng-package.json # ng-packagr entry
636
- └── src/
637
- ├── my-component.component.ts
638
- ├── my-component.component.html
639
- └── my-component.component.scss
640
- ```
641
-
642
- **2. `ng-package.json`:**
643
-
644
- ```json
645
- {
646
- "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
647
- "lib": {
648
- "entryFile": "index.ts"
649
- }
650
- }
651
- ```
652
-
653
- **3. `index.ts`:**
654
-
655
- ```typescript
656
- export * from './src/my-component.component';
657
- ```
658
-
659
- **4. Component template:**
660
-
661
- ```typescript
662
- // my-component.component.ts
663
- import { ChangeDetectionStrategy, Component, input } from '@angular/core';
664
- import { SdBaseSecureComponent } from '@sd-angular/core/components/base';
665
- import { Color } from '@sd-angular/core/utilities';
666
-
667
- @Component({
668
- selector: 'sd-my-component',
669
- templateUrl: './my-component.component.html',
670
- styleUrl: './my-component.component.scss',
671
- changeDetection: ChangeDetectionStrategy.OnPush,
672
- standalone: true,
673
- imports: [],
674
- })
675
- export class SdMyComponent extends SdBaseSecureComponent {
676
- color = input<Color, Color | undefined | null>('primary', {
677
- transform: (value) => value || 'primary',
678
- });
679
-
680
- title = input<string | undefined | null>(undefined);
681
- }
682
- ```
683
-
684
- **5. Export từ `components/index.ts`:**
685
-
686
- ```typescript
687
- // components/index.ts
688
- export * from '@sd-angular/core/components/my-component';
689
- ```
690
-
691
- ### Quy ước / Conventions
692
-
693
- | Mục | Quy ước |
694
- |---|---|
695
- | Selector | `sd-<tên-component>` |
696
- | Class name | `Sd<TênComponent>` (Pascal) |
697
- | Input | Dùng `input<T>()` signal, **không** dùng `@Input()` decorator |
698
- | Null safety | Input transform phải handle `null/undefined` |
699
- | Base class | Extend `SdBaseSecureComponent` cho component có permission |
700
- | Change detection | Luôn dùng `ChangeDetectionStrategy.OnPush` |
701
- | Standalone | Luôn `standalone: true` |
702
- | Colors | Dùng `Color` type, không hardcode màu |
703
-
704
- ### Build
705
-
706
- ```bash
707
- # Build toàn bộ thư viện
708
- ng-packagr -p ng-package.json
709
-
710
- # Watch mode
711
- ng-packagr -p ng-package.json --watch
712
- ```
713
-
714
- ### Versioning
715
-
716
- Thư viện tuân theo [Semantic Versioning](https://semver.org):
717
-
718
- - `MAJOR` — Breaking changes (thay đổi API không tương thích)
719
- - `MINOR` — Tính năng mới (tương thích ngược)
720
- - `PATCH` — Bug fixes
721
-
722
- ---
723
-
724
- ## QA / E2E
725
-
726
- Core UI components expose runtime state via lowercase `data-*` attributes for e2e selectors. The full catalog, component matrix, selector cookbook, and YAML schema for AI agents live in `docs/E2E-ATTRIBUTES.md` in the source repository.
727
-
728
- ---
729
-
730
- ## License
731
-
420
+ | `SdMiniEditor` | `@sd-angular/core/components/mini-editor` | Rich text editor nhỏ |
421
+ | `SdDocumentBuilder` | `@sd-angular/core/components/document-builder` | Document builder |
422
+ | `SdAnchorMain` | `@sd-angular/core/components/anchor` | Anchor / mục lục cuộn trang |
423
+ | `SdView` | `@sd-angular/core/components/view` | View wrapper read-only |
424
+
425
+ ---
426
+
427
+ ## Form Components
428
+
429
+ ```typescript
430
+ import {
431
+ SdInput, // Text input
432
+ SdInputNumber, // Number input
433
+ SdSelect, // Dropdown
434
+ SdAutocomplete, // Autocomplete
435
+ SdDate, // Date picker
436
+ } from '@sd-angular/core/forms';
437
+ ```
438
+
439
+ ```html
440
+ <sd-input [(model)]="form.name" label="Họ tên" [required]="true" />
441
+
442
+ <sd-input-number [(model)]="form.price" label="Đơn giá" [min]="0" suffix="VNĐ" />
443
+
444
+ <sd-select [(model)]="form.status" label="Trạng thái"
445
+ [items]="statusList" valueField="value" displayField="label" />
446
+
447
+ <sd-date [(model)]="form.birthday" label="Ngày sinh" />
448
+ ```
449
+
450
+ ---
451
+
452
+ ## CRUD Patterns / Code mẫu CRUD
453
+
454
+ ### List Component
455
+
456
+ ```typescript
457
+ // product-list.component.ts
458
+ import { Component, OnInit, ViewChild, signal } from '@angular/core';
459
+ import { SdTable, SdTableOption } from '@sd-angular/core/components/table';
460
+ import { SdButton } from '@sd-angular/core/components/button';
461
+ import { SdModal } from '@sd-angular/core/components/modal';
462
+ import { SdSection } from '@sd-angular/core/components/section';
463
+ import { SdInput, SdSelect } from '@sd-angular/core/forms';
464
+
465
+ interface Product {
466
+ id: number;
467
+ code: string;
468
+ name: string;
469
+ price: number;
470
+ status: 'ACTIVE' | 'INACTIVE';
471
+ }
472
+
473
+ @Component({
474
+ selector: 'app-product-list',
475
+ templateUrl: './product-list.component.html',
476
+ standalone: true,
477
+ imports: [SdTable, SdButton, SdModal, SdSection, SdInput, SdSelect],
478
+ })
479
+ export class ProductListComponent implements OnInit {
480
+ @ViewChild('formModal') formModal!: SdModal;
481
+ @ViewChild('sdTable') sdTable?: SdTable<Product>;
482
+
483
+ selectedItem: Product | null = null;
484
+ formData: Partial<Product> = {};
485
+ isSaving = signal(false);
486
+
487
+ readonly STATUS_LIST = [
488
+ { value: 'ACTIVE', label: 'Hoạt động' },
489
+ { value: 'INACTIVE', label: 'Dừng' },
490
+ ];
491
+
492
+ option!: SdTableOption<Product>;
493
+
494
+ constructor(private service: ProductService) {}
495
+
496
+ ngOnInit() {
497
+ this.option = {
498
+ type: 'server',
499
+ items: async (filter, paging) => this.service.search(filter, paging),
500
+ columns: [
501
+ { field: 'code', type: 'string', title: 'Mã', width: '120px' },
502
+ { field: 'name', type: 'string', title: 'Tên', sortable: true },
503
+ { field: 'price', type: 'number', title: 'Đơn giá', align: 'right' },
504
+ {
505
+ field: 'status', type: 'values', title: 'Trạng thái',
506
+ option: { items: this.STATUS_LIST, valueField: 'value', displayField: 'label' },
507
+ useBadge: (val) => ({
508
+ color: val === 'ACTIVE' ? 'success' : 'secondary',
509
+ title: this.STATUS_LIST.find(s => s.value === val)?.label,
510
+ }),
511
+ },
512
+ ],
513
+ command: {
514
+ align: 'right',
515
+ commands: [
516
+ { icon: 'edit', color: 'primary', title: 'Sửa', click: (row) => this.openForm(row) },
517
+ { icon: 'delete', color: 'error', title: 'Xoá', click: (row) => this.onDelete(row) },
518
+ ],
519
+ },
520
+ paginate: { pageSize: 20 },
521
+ reload: { visible: true },
522
+ };
523
+ }
524
+
525
+ openForm(item?: Product) {
526
+ this.selectedItem = item || null;
527
+ this.formData = item ? { ...item } : { status: 'ACTIVE' };
528
+ this.formModal.open();
529
+ }
530
+
531
+ async onSave() {
532
+ this.isSaving.set(true);
533
+ try {
534
+ if (this.selectedItem) {
535
+ await this.service.update(this.selectedItem.id, this.formData);
536
+ } else {
537
+ await this.service.create(this.formData);
538
+ }
539
+ this.formModal.close();
540
+ this.sdTable?.reload?.();
541
+ } finally {
542
+ this.isSaving.set(false);
543
+ }
544
+ }
545
+
546
+ async onDelete(item: Product) {
547
+ if (!confirm(`Xoá "${item.name}"?`)) return;
548
+ await this.service.delete(item.id);
549
+ this.sdTable?.reload?.();
550
+ }
551
+ }
552
+ ```
553
+
554
+ ### Template
555
+
556
+ ```html
557
+ <!-- product-list.component.html -->
558
+ <div class="d-flex justify-content-between align-items-center mb-16">
559
+ <h2>Danh sách sản phẩm</h2>
560
+ <sd-button type="fill" color="primary" title="Thêm mới"
561
+ prefixIcon="add" (click)="openForm()" />
562
+ </div>
563
+
564
+ <sd-table #sdTable [option]="option" />
565
+
566
+ <sd-modal #formModal [title]="selectedItem ? 'Chỉnh sửa' : 'Thêm mới'" width="md">
567
+ <ng-template>
568
+ <div class="modal-body p-16">
569
+ <sd-section title="Thông tin sản phẩm" icon="inventory">
570
+ <div class="row">
571
+ <div class="col-6">
572
+ <sd-input [(model)]="formData.code" label="Mã" [required]="true" />
573
+ </div>
574
+ <div class="col-6">
575
+ <sd-select [(model)]="formData.status" label="Trạng thái"
576
+ [items]="STATUS_LIST" valueField="value" displayField="label" />
577
+ </div>
578
+ <div class="col-12">
579
+ <sd-input [(model)]="formData.name" label="Tên sản phẩm" [required]="true" />
580
+ </div>
581
+ <div class="col-6">
582
+ <sd-input-number [(model)]="formData.price" label="Đơn giá" suffix="VNĐ" />
583
+ </div>
584
+ </div>
585
+ </sd-section>
586
+ </div>
587
+ <div class="modal-footer d-flex justify-content-end gap-8 p-16">
588
+ <sd-button title="Huỷ" (click)="formModal.close()" />
589
+ <sd-button type="fill" color="primary" title="Lưu"
590
+ prefixIcon="save" [loading]="isSaving()" (click)="onSave()" />
591
+ </div>
592
+ </ng-template>
593
+ </sd-modal>
594
+ ```
595
+
596
+ ---
597
+
598
+ ## Contributing Guide / Hướng dẫn đóng góp
599
+
600
+ ### Cấu trúc thư viện / Project structure
601
+
602
+ ```
603
+ sd-angular/
604
+ ├── src/
605
+ │ └── public-api.ts # Entry point chính
606
+ ├── assets/
607
+ │ └── scss/
608
+ │ ├── sd-core.scss # SCSS entry (import vào app)
609
+ │ ├── core/ # Base utilities (color, grid, form, ...)
610
+ │ └── themes/ # Theme mặc định + Material theme
611
+ ├── components/ # UI Components
612
+ │ ├── button/
613
+ │ ├── table/
614
+ │ ├── modal/
615
+ │ └── ...
616
+ ├── forms/ # Form components
617
+ │ ├── input/
618
+ │ ├── select/
619
+ │ └── ...
620
+ ├── directives/ # Angular directives
621
+ ├── pipes/ # Angular pipes
622
+ ├── services/ # Shared services
623
+ ├── utilities/ # Types, models, helpers
624
+ └── modules/ # Feature modules (layout, permission, ...)
625
+ ```
626
+
627
+ ### Thêm component mới / Adding a new component
628
+
629
+ **1. Tạo thư mục component:**
630
+
631
+ ```
632
+ components/
633
+ └── my-component/
634
+ ├── index.ts # Export public API
635
+ ├── ng-package.json # ng-packagr entry
636
+ └── src/
637
+ ├── my-component.component.ts
638
+ ├── my-component.component.html
639
+ └── my-component.component.scss
640
+ ```
641
+
642
+ **2. `ng-package.json`:**
643
+
644
+ ```json
645
+ {
646
+ "$schema": "../../../../node_modules/ng-packagr/ng-package.schema.json",
647
+ "lib": {
648
+ "entryFile": "index.ts"
649
+ }
650
+ }
651
+ ```
652
+
653
+ **3. `index.ts`:**
654
+
655
+ ```typescript
656
+ export * from './src/my-component.component';
657
+ ```
658
+
659
+ **4. Component template:**
660
+
661
+ ```typescript
662
+ // my-component.component.ts
663
+ import { ChangeDetectionStrategy, Component, input } from '@angular/core';
664
+ import { SdBaseSecureComponent } from '@sd-angular/core/components/base';
665
+ import { Color } from '@sd-angular/core/utilities';
666
+
667
+ @Component({
668
+ selector: 'sd-my-component',
669
+ templateUrl: './my-component.component.html',
670
+ styleUrl: './my-component.component.scss',
671
+ changeDetection: ChangeDetectionStrategy.OnPush,
672
+ standalone: true,
673
+ imports: [],
674
+ })
675
+ export class SdMyComponent extends SdBaseSecureComponent {
676
+ color = input<Color, Color | undefined | null>('primary', {
677
+ transform: (value) => value || 'primary',
678
+ });
679
+
680
+ title = input<string | undefined | null>(undefined);
681
+ }
682
+ ```
683
+
684
+ **5. Export từ `components/index.ts`:**
685
+
686
+ ```typescript
687
+ // components/index.ts
688
+ export * from '@sd-angular/core/components/my-component';
689
+ ```
690
+
691
+ ### Quy ước / Conventions
692
+
693
+ | Mục | Quy ước |
694
+ |---|---|
695
+ | Selector | `sd-<tên-component>` |
696
+ | Class name | `Sd<TênComponent>` (Pascal) |
697
+ | Input | Dùng `input<T>()` signal, **không** dùng `@Input()` decorator |
698
+ | Null safety | Input transform phải handle `null/undefined` |
699
+ | Base class | Extend `SdBaseSecureComponent` cho component có permission |
700
+ | Change detection | Luôn dùng `ChangeDetectionStrategy.OnPush` |
701
+ | Standalone | Luôn `standalone: true` |
702
+ | Colors | Dùng `Color` type, không hardcode màu |
703
+
704
+ ### Build
705
+
706
+ ```bash
707
+ # Build toàn bộ thư viện
708
+ ng-packagr -p ng-package.json
709
+
710
+ # Watch mode
711
+ ng-packagr -p ng-package.json --watch
712
+ ```
713
+
714
+ ### Versioning
715
+
716
+ Thư viện tuân theo [Semantic Versioning](https://semver.org):
717
+
718
+ - `MAJOR` — Breaking changes (thay đổi API không tương thích)
719
+ - `MINOR` — Tính năng mới (tương thích ngược)
720
+ - `PATCH` — Bug fixes
721
+
722
+ ---
723
+
724
+ ## QA / E2E
725
+
726
+ Core UI components expose runtime state via lowercase `data-*` attributes for e2e selectors. The full catalog, component matrix, selector cookbook, and YAML schema for AI agents live in `docs/E2E-ATTRIBUTES.md` in the source repository.
727
+
728
+ ---
729
+
730
+ ## License
731
+
732
732
  Internal use only — © SD Team