@foxeltech/angular-ui 0.7.44 → 0.7.83

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 (53) hide show
  1. package/components.css +345 -19
  2. package/familjen-grotesk/familjen-grotesk-400-latin-ext.woff2 +0 -0
  3. package/familjen-grotesk/familjen-grotesk-400-latin.woff2 +0 -0
  4. package/familjen-grotesk/familjen-grotesk-400-vietnamese.woff2 +0 -0
  5. package/familjen-grotesk/familjen-grotesk-500-latin-ext.woff2 +0 -0
  6. package/familjen-grotesk/familjen-grotesk-500-latin.woff2 +0 -0
  7. package/familjen-grotesk/familjen-grotesk-500-vietnamese.woff2 +0 -0
  8. package/familjen-grotesk/familjen-grotesk-600-latin-ext.woff2 +0 -0
  9. package/familjen-grotesk/familjen-grotesk-600-latin.woff2 +0 -0
  10. package/familjen-grotesk/familjen-grotesk-600-vietnamese.woff2 +0 -0
  11. package/familjen-grotesk/familjen-grotesk-700-latin-ext.woff2 +0 -0
  12. package/familjen-grotesk/familjen-grotesk-700-latin.woff2 +0 -0
  13. package/familjen-grotesk/familjen-grotesk-700-vietnamese.woff2 +0 -0
  14. package/fesm2022/foxeltech-angular-ui-chart.mjs +197 -24
  15. package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
  16. package/fesm2022/foxeltech-angular-ui.mjs +585 -125
  17. package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
  18. package/fonts.css +104 -3
  19. package/package.json +1 -1
  20. package/src/lib/styles/components.css +345 -19
  21. package/src/lib/styles/fonts.css +104 -3
  22. package/src/lib/styles/tailwind.css +12 -0
  23. package/src/lib/styles/theme.css +160 -114
  24. package/src/lib/styles/tokens.css +25 -0
  25. package/src/lib/styles/utilities.css +18 -1
  26. package/src/lib/ui/components/button/button.component.html +2 -0
  27. package/src/lib/ui/components/drawer/drawer.component.scss +3 -1
  28. package/src/lib/ui/components/facets-rail/facets-rail.component.html +1 -6
  29. package/src/lib/ui/components/master-detail/master-detail.component.css +65 -13
  30. package/src/lib/ui/components/master-detail/master-detail.component.html +26 -8
  31. package/src/lib/ui/components/pending-badge/pending-badge.component.html +2 -2
  32. package/src/lib/ui/components/project-card/project-card.component.html +7 -3
  33. package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +38 -0
  34. package/src/lib/ui/components/route-skeleton/route-skeleton.component.html +184 -0
  35. package/src/lib/ui/components/sample-frame/sample-frame.component.html +6 -7
  36. package/src/lib/ui/components/section-card/section-card.component.html +4 -6
  37. package/src/lib/ui/components/segmented/segmented.component.html +3 -1
  38. package/src/lib/ui/components/skeleton-dashboard/skeleton-dashboard.component.html +24 -0
  39. package/src/lib/ui/components/skeleton-detail/skeleton-detail.component.html +8 -8
  40. package/src/lib/ui/components/skeleton-facets/skeleton-facets.component.html +15 -0
  41. package/src/lib/ui/components/skeleton-frame/skeleton-frame.component.html +59 -0
  42. package/src/lib/ui/components/skeleton-list/skeleton-list.component.html +6 -6
  43. package/src/lib/ui/components/switch/switch.component.html +1 -1
  44. package/src/lib/ui/components/toast/toast.component.html +6 -6
  45. package/tailwind.css +12 -0
  46. package/theme.css +160 -114
  47. package/tokens.css +25 -0
  48. package/types/foxeltech-angular-ui-chart.d.ts +76 -1
  49. package/types/foxeltech-angular-ui.d.ts +318 -39
  50. package/utilities.css +18 -1
  51. package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.html +0 -21
  52. package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.scss +0 -0
  53. package/src/lib/ui/components/tab-component/tab.component.html +0 -1
@@ -1,17 +1,33 @@
1
- <div class="flex flex-col lg:flex-row items-start gap-xl" [style.--fx-md-list-width]="listWidth()">
1
+ <!-- 3 cụm CHUNG chiều cao, mỗi cụm SCROLL RIÊNG (anh Tú chốt 03/09).
2
+ `items-stretch` + chiều cao đặt trên hàng (xem .fx-md-row trong CSS) để
3
+ cả ba pane bằng nhau; mỗi pane là flex-col + min-h-0 và chỉ phần thân
4
+ `overflow-auto flex-1` cuộn — header/filter/paginator đứng yên.
5
+ ⚠️ Hướng hàng + chiều cao do CONTAINER QUERY trong CSS quyết định (theo
6
+ bề rộng của chính hàng), KHÔNG phải `lg:flex-row`. Đừng thêm lại class
7
+ `lg:*` ở đây — `lg` trong lib là 1080px và viewport là proxy sai; xem
8
+ ghi chú đầu master-detail.component.css. -->
9
+ <div
10
+ class="fx-md-row flex flex-col items-stretch gap-xl"
11
+ [style.--fx-md-list-width]="listWidth()"
12
+ [style.--fx-md-pane-h]="paneHeight()"
13
+ >
2
14
  <!-- Facets rail (V2 3-column lookup) — wrapper always rendered so ng-content
3
15
  stays unconditional; hidden when the host projects nothing. -->
4
- <div class="fx-md-facets card-common bg-surface" [class.hidden]="!hasFacets">
5
- <ng-content select="[mdFacets]"></ng-content>
16
+ <div
17
+ class="fx-md-facets card-common bg-surface flex flex-col min-h-0"
18
+ [class.hidden]="!hasFacets"
19
+ >
20
+ <div class="flex-1 min-h-0 overflow-auto pr-xs">
21
+ <ng-content select="[mdFacets]"></ng-content>
22
+ </div>
6
23
  </div>
7
24
 
8
25
  <!-- Master: list pane -->
9
- <div class="fx-md-list card-common">
26
+ <div class="fx-md-list card-common flex flex-col min-h-0">
10
27
  <ng-content select="[mdHeader]"></ng-content>
11
28
  <ng-content select="[mdFilters]"></ng-content>
12
29
  <div
13
- class="flex flex-col gap-normal overflow-auto pr-xs"
14
- [style.maxHeight]="maxListHeight()"
30
+ class="flex flex-col gap-normal overflow-auto pr-xs flex-1 min-h-0"
15
31
  role="listbox"
16
32
  [attr.aria-label]="listLabel() || null"
17
33
  >
@@ -83,7 +99,8 @@
83
99
  </div>
84
100
 
85
101
  <!-- Detail pane -->
86
- <div class="w-full lg:flex-1 min-w-0">
102
+ <div class="fx-md-detail w-full min-w-0 flex flex-col min-h-0">
103
+ <div class="flex-1 min-h-0 overflow-auto">
87
104
  @if (table.selected) {
88
105
  @if (table.detailBusy) {
89
106
  <fx-ui-skeleton-detail [cards]="skeletonCards()"></fx-ui-skeleton-detail>
@@ -94,9 +111,10 @@
94
111
  ></ng-container>
95
112
  }
96
113
  } @else {
97
- <div class="card-common flex items-center justify-center h-[300px]">
114
+ <div class="card-common flex items-center justify-center h-full min-h-[300px]">
98
115
  <fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
99
116
  </div>
100
117
  }
118
+ </div>
101
119
  </div>
102
120
  </div>
@@ -1,12 +1,12 @@
1
1
  @if (variant() === 'dot') {
2
2
  <span
3
- class="inline-block w-[7px] h-[7px] rounded-full bg-warning/70 align-middle"
3
+ class="inline-block w-[7px] h-[7px] rounded-full bg-muted/60 align-middle"
4
4
  [attr.title]="tooltip"
5
5
  [attr.aria-label]="tooltip"
6
6
  ></span>
7
7
  } @else {
8
8
  <span
9
- class="inline-flex items-center gap-xs px-small py-[1px] rounded-pill border border-dashed border-warning/60 bg-warning/10 text-warning txt-tag-label whitespace-nowrap align-middle"
9
+ class="inline-flex items-center gap-xs px-small py-[1px] rounded-pill border border-border-subtle bg-neutral-soft text-muted txt-tag-label whitespace-nowrap align-middle"
10
10
  [attr.title]="tooltip"
11
11
  >
12
12
  <fx-ui-hero-icon icon="clock" [size]="11" class="flex" />
@@ -1,6 +1,6 @@
1
1
  <button
2
2
  type="button"
3
- class="card-common bg-surface w-full flex flex-col gap-normal hover:border-accent hover:shadow-md transition-all"
3
+ class="card-common bg-surface w-full flex-1 flex flex-col gap-normal hover:border-accent hover:shadow-md transition-all"
4
4
  (click)="open.emit(card())"
5
5
  >
6
6
  <!-- Header: avatar + name/sub + score chip (design 12) -->
@@ -13,7 +13,10 @@
13
13
  >{{ card().avatar }}</span
14
14
  >
15
15
  }
16
- <div class="min-w-0">
16
+ <!-- min-h 46px = name (24) + mt-xs (2) + sub (20): reserves the two-line
17
+ header even when a card has no sub line, so trend/metric rows line
18
+ up across the grid row (measured 04/09). -->
19
+ <div class="min-w-0 min-h-[46px]">
17
20
  <div class="txt-heading-06 truncate text-left">{{ card().name }}</div>
18
21
  @if (card().sub; as sub) {
19
22
  <div class="flex items-center gap-xs txt-utility-description mt-xs">
@@ -88,8 +91,9 @@
88
91
  }
89
92
 
90
93
  @if (card().meta || card().openLabel) {
94
+ <!-- mt-auto: footers line up across a grid row whatever each card holds -->
91
95
  <div
92
- class="flex items-center justify-between w-full pt-normal border-t border-border-default"
96
+ class="flex items-center justify-between w-full pt-normal mt-auto border-t border-border-default"
93
97
  >
94
98
  <span class="txt-utility-description text-left">{{ card().meta }}</span>
95
99
  @if (card().openLabel) {
@@ -0,0 +1,38 @@
1
+ /* Cùng cơ chế với master-detail: hàng 3 cụm quyết định theo BỀ RỘNG CỦA CHÍNH
2
+ NÓ (container query), không theo viewport. Nếu chỗ này còn dùng `lg:` thì
3
+ skeleton và màn thật sẽ lật ở hai ngưỡng khác nhau — xem ghi chú đầu
4
+ master-detail.component.css. */
5
+ :host {
6
+ display: block;
7
+ container-type: inline-size;
8
+ container-name: fx-rs;
9
+ }
10
+
11
+ .fx-rs-row {
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: var(--spacing-xl, 1.5rem);
15
+ }
16
+
17
+ /* Ngưỡng + bề rộng panes giữ y hệt master-detail để skeleton không nhảy hình
18
+ khi dữ liệu về. */
19
+ @container fx-rs (min-width: 968px) {
20
+ .fx-rs-row {
21
+ flex-direction: row;
22
+ align-items: stretch;
23
+ height: calc(100vh - 300px);
24
+ min-height: 420px;
25
+ }
26
+
27
+ .fx-rs-facets {
28
+ flex: 0 1 230px;
29
+ min-width: 200px;
30
+ max-width: 260px;
31
+ }
32
+
33
+ .fx-rs-list {
34
+ flex: 0 1 420px;
35
+ min-width: 320px;
36
+ max-width: 420px;
37
+ }
38
+ }
@@ -0,0 +1,184 @@
1
+ <div class="w-full flex flex-col gap-xl">
2
+ @for (b of blocks(); track $index) {
3
+ @switch (b.kind) {
4
+ @case ('heading') {
5
+ <div data-fx-skel="heading" class="flex flex-col md:flex-row md:items-start justify-between gap-normal">
6
+ <div class="flex flex-col gap-small">
7
+ <div class="skeleton-load h-7 w-64"></div>
8
+ <div class="skeleton-load h-3 w-32"></div>
9
+ </div>
10
+ @if (items(b, 2).length) {
11
+ <div class="flex items-center gap-normal">
12
+ @for (a of items(b, 2); track a) {
13
+ <div class="skeleton-load h-9 rounded-lg" [class]="a === 0 ? 'w-28' : 'w-32'"></div>
14
+ }
15
+ </div>
16
+ }
17
+ </div>
18
+ }
19
+
20
+ @case ('toolbar') {
21
+ <div data-fx-skel="toolbar" class="flex flex-wrap items-center gap-normal">
22
+ <div class="skeleton-load h-9 w-72 rounded-lg"></div>
23
+ @for (f of items(b, 2); track f) {
24
+ <div class="skeleton-load h-7 rounded-full" [class]="f % 2 ? 'w-24' : 'w-20'"></div>
25
+ }
26
+ </div>
27
+ }
28
+
29
+ @case ('table') {
30
+ <!-- Bảng: mỗi Ô là một `.skeleton-load` riêng, đúng cách
31
+ `fx-ui-table-cell [loading]` làm trên bảng thật. -->
32
+ <div data-fx-skel="table" class="card-common overflow-x-auto">
33
+ <table class="w-full">
34
+ <thead>
35
+ <tr class="border-b border-border-default">
36
+ @for (c of cols(b); track c) {
37
+ <th class="px-normal py-semi text-left">
38
+ <div class="skeleton-load h-3" [style.width.%]="headWidth(c)"></div>
39
+ </th>
40
+ }
41
+ </tr>
42
+ </thead>
43
+ <tbody>
44
+ @for (r of rows(b); track r) {
45
+ <tr class="border-b border-border-subtle">
46
+ @for (c of cols(b); track c) {
47
+ <td class="px-normal py-semi align-middle">
48
+ <div class="skeleton-load h-4" [style.width.%]="cellWidth(r, c)"></div>
49
+ </td>
50
+ }
51
+ </tr>
52
+ }
53
+ </tbody>
54
+ </table>
55
+ </div>
56
+ }
57
+
58
+ @case ('stat-cards') {
59
+ <div data-fx-skel="stat-cards" class="grid gap-normal grid-cols-2 md:grid-cols-4">
60
+ @for (i of items(b, 4); track i) {
61
+ <div class="card-common flex flex-col gap-normal">
62
+ <div class="skeleton-load h-3 w-16"></div>
63
+ <div class="skeleton-load h-8 w-24"></div>
64
+ <div class="skeleton-load h-3 w-20"></div>
65
+ </div>
66
+ }
67
+ </div>
68
+ }
69
+
70
+ @case ('kpi-strip') {
71
+ <!-- MỘT card chia cột (kiểu app-dash-kpi-row), không phải các tile rời -->
72
+ <div data-fx-skel="kpi-strip" class="card-common !p-0">
73
+ <div class="grid grid-cols-2 md:grid-cols-4">
74
+ @for (i of items(b, 4); track i) {
75
+ <div
76
+ class="flex flex-col gap-normal px-xl py-large border-border-subtle"
77
+ [class.border-l]="!$first"
78
+ >
79
+ <div class="skeleton-load h-3 w-20"></div>
80
+ <div class="skeleton-load h-9 w-28"></div>
81
+ <div class="skeleton-load h-3 w-24"></div>
82
+ </div>
83
+ }
84
+ </div>
85
+ </div>
86
+ }
87
+
88
+ @case ('panels') {
89
+ <div data-fx-skel="panels" class="grid gap-xl" [class]="gridClass(b)">
90
+ @for (i of items(b, 2); track i) {
91
+ <div class="card-common flex flex-col gap-large">
92
+ <div class="skeleton-load h-4 w-1/3"></div>
93
+ <div class="flex items-end gap-normal h-40">
94
+ @for (x of bars; track x) {
95
+ <div class="skeleton-load flex-1 rounded-t" [style.height.%]="barHeight(x)"></div>
96
+ }
97
+ </div>
98
+ </div>
99
+ }
100
+ </div>
101
+ }
102
+
103
+ @case ('cards') {
104
+ <div data-fx-skel="cards" class="grid gap-xl" [class]="gridClass(b)">
105
+ @for (i of items(b, 6); track i) {
106
+ <div class="card-common flex flex-col gap-normal">
107
+ <div class="flex items-center gap-normal">
108
+ <div class="skeleton-load w-9 h-9 rounded-lg shrink-0"></div>
109
+ <div class="flex flex-col gap-small flex-1 min-w-0">
110
+ <div class="skeleton-load h-4 w-3/4"></div>
111
+ <div class="skeleton-load h-3 w-1/2"></div>
112
+ </div>
113
+ <div class="skeleton-load h-5 w-14 rounded-full shrink-0"></div>
114
+ </div>
115
+ <div class="grid grid-cols-4 gap-normal pt-normal border-t border-border-subtle">
116
+ @for (m of [0, 1, 2, 3]; track m) {
117
+ <div class="flex flex-col gap-small">
118
+ <div class="skeleton-load h-2.5 w-full"></div>
119
+ <div class="skeleton-load h-4 w-2/3"></div>
120
+ </div>
121
+ }
122
+ </div>
123
+ </div>
124
+ }
125
+ </div>
126
+ }
127
+
128
+ @case ('card') {
129
+ <div data-fx-skel="card" class="card-common flex flex-col gap-large">
130
+ <div class="skeleton-load h-4 w-1/3"></div>
131
+ <div class="flex flex-col gap-normal">
132
+ @for (r of [0, 1, 2, 3]; track r) {
133
+ <div class="skeleton-load h-3" [style.width.%]="cellWidth(r, 1)"></div>
134
+ }
135
+ </div>
136
+ </div>
137
+ }
138
+
139
+ @case ('tabs') {
140
+ <div data-fx-skel="tabs" class="flex items-center gap-large border-b border-border-default pb-normal">
141
+ @for (t of items(b, 6); track t) {
142
+ <div class="flex items-center gap-small">
143
+ <div class="skeleton-load w-4 h-4"></div>
144
+ <div class="skeleton-load h-3 w-16"></div>
145
+ </div>
146
+ }
147
+ </div>
148
+ }
149
+
150
+ @case ('facets') {
151
+ <div data-fx-skel="facets" class="fx-rs-row">
152
+ <div class="fx-rs-facets card-common overflow-hidden">
153
+ <fx-ui-skeleton-facets />
154
+ </div>
155
+ <div class="fx-rs-list card-common overflow-hidden">
156
+ <fx-ui-skeleton-list [rows]="6" />
157
+ </div>
158
+ <div class="flex-1 min-w-0 overflow-hidden">
159
+ <fx-ui-skeleton-detail />
160
+ </div>
161
+ </div>
162
+ }
163
+
164
+ @case ('detail') {
165
+ <fx-ui-skeleton-detail data-fx-skel="detail" />
166
+ }
167
+
168
+ @case ('form') {
169
+ <div data-fx-skel="form" class="card-common flex flex-col gap-large">
170
+ @for (r of [0, 1, 2, 3]; track r) {
171
+ <div class="flex flex-col gap-small">
172
+ <div class="skeleton-load h-3 w-24"></div>
173
+ <div class="skeleton-load h-9 w-full rounded-lg"></div>
174
+ </div>
175
+ }
176
+ <div class="flex justify-end gap-normal">
177
+ <div class="skeleton-load h-9 w-24 rounded-lg"></div>
178
+ <div class="skeleton-load h-9 w-28 rounded-lg"></div>
179
+ </div>
180
+ </div>
181
+ }
182
+ }
183
+ }
184
+ </div>
@@ -1,10 +1,9 @@
1
- <div class="relative rounded-lg border border-dashed border-warning/50 bg-warning/[0.03] p-normal">
2
- <!-- flex-wrap + nowrap label: in narrow rails the chip drops to the next line
3
- instead of the label breaking mid-word ("SAMPLE / LAYOUT"). -->
4
- <div class="flex flex-wrap items-center gap-small mb-normal">
5
- <span class="txt-tag-label text-warning uppercase tracking-wide whitespace-nowrap">{{ label() }}</span>
1
+ <div class="relative flex flex-col flex-1 min-h-0">
2
+ <!-- One quiet chip marks the block as sample data; the tooltip names the
3
+ missing field. flex-wrap so the chip drops a line in narrow rails. -->
4
+ <div class="flex flex-wrap items-center gap-small mb-small">
6
5
  <span
7
- class="inline-flex items-center gap-xs px-small py-[1px] rounded-pill border border-dashed border-warning/60 bg-warning/10 text-warning txt-tag-label whitespace-nowrap"
6
+ class="inline-flex items-center gap-xs px-small py-[1px] rounded-pill border border-border-subtle bg-neutral-soft text-muted txt-tag-label whitespace-nowrap"
8
7
  [attr.title]="tooltip"
9
8
  >
10
9
  <fx-ui-hero-icon icon="clock" [size]="11" class="flex" />
@@ -12,7 +11,7 @@
12
11
  </span>
13
12
  </div>
14
13
  <!-- Sample content is non-interactive: nothing here reflects real state. -->
15
- <div class="pointer-events-none select-none opacity-90">
14
+ <div class="pointer-events-none select-none opacity-90 flex-1 min-h-0 flex flex-col">
16
15
  <ng-content></ng-content>
17
16
  </div>
18
17
  </div>
@@ -1,11 +1,9 @@
1
- <div class="card-common">
1
+ <!-- flex column filling the host: cards in one grid row share a height and a
2
+ projected footer can sit at the bottom with `mt-auto` (design 01). -->
3
+ <div class="card-common bg-surface flex flex-col flex-1 min-h-0">
2
4
  @if (titleStyle() === 'eyebrow') {
3
5
  <div class="flex items-baseline justify-between gap-normal mb-large">
4
- <span
5
- class="txt-table-header uppercase"
6
- style="letter-spacing: var(--og-tracking-label)"
7
- >{{ title() }}</span
8
- >
6
+ <span class="txt-eyebrow">{{ title() }}</span>
9
7
  @if (aside()) {
10
8
  <span class="txt-utility-description text-right">{{ aside() }}</span>
11
9
  }
@@ -1,5 +1,6 @@
1
1
  <div
2
- class="relative inline-grid max-w-full grid-flow-col auto-cols-fr items-stretch p-[3px] bg-surface-sunken border border-border rounded-full"
2
+ class="relative inline-grid max-w-full grid-flow-col items-stretch p-[3px] bg-surface-sunken border border-border rounded-full"
3
+ [class]="gridClass()"
3
4
  role="group"
4
5
  [attr.aria-label]="ariaLabel()"
5
6
  >
@@ -15,6 +16,7 @@
15
16
  }
16
17
  @for (option of options(); track option.value) {
17
18
  <button
19
+ #opt
18
20
  type="button"
19
21
  (click)="select(option)"
20
22
  [disabled]="option.disabled"
@@ -0,0 +1,24 @@
1
+ <div class="flex flex-col gap-xl">
2
+ <div class="grid gap-xl grid-cols-2 md:grid-cols-4">
3
+ @for (k of kpiList(); track k) {
4
+ <div class="card-common flex flex-col gap-normal">
5
+ <div class="h-3 w-16 rounded skeleton-load"></div>
6
+ <div class="h-7 w-24 rounded skeleton-load"></div>
7
+ <div class="h-3 w-20 rounded skeleton-load"></div>
8
+ </div>
9
+ }
10
+ </div>
11
+
12
+ <div class="grid gap-xl grid-cols-1 md:grid-cols-2">
13
+ @for (p of panelList(); track p) {
14
+ <div class="card-common flex flex-col gap-large">
15
+ <div class="h-4 w-1/3 rounded skeleton-load"></div>
16
+ <div class="flex items-end gap-normal h-40">
17
+ @for (b of barList(); track b) {
18
+ <div class="flex-1 rounded-t skeleton-load" [style.height.%]="barHeight(b)"></div>
19
+ }
20
+ </div>
21
+ </div>
22
+ }
23
+ </div>
24
+ </div>
@@ -1,20 +1,20 @@
1
- <div class="flex flex-col gap-xl animate-pulse">
1
+ <div class="flex flex-col gap-xl">
2
2
  <div class="card-common">
3
3
  <div class="flex items-start gap-normal">
4
- <div class="w-10 h-10 rounded-lg bg-bg-hover shrink-0"></div>
4
+ <div class="w-10 h-10 rounded-lg skeleton-load shrink-0"></div>
5
5
  <div class="flex-1 min-w-0 flex flex-col gap-normal">
6
- <div class="h-5 w-1/2 rounded bg-bg-hover"></div>
7
- <div class="h-3 w-3/4 rounded bg-bg-hover"></div>
6
+ <div class="h-5 w-1/2 rounded skeleton-load"></div>
7
+ <div class="h-3 w-3/4 rounded skeleton-load"></div>
8
8
  </div>
9
9
  </div>
10
10
  </div>
11
11
 
12
- @for (c of range(cards()); track c) {
12
+ @for (c of cardList(); track c) {
13
13
  <div class="card-common flex flex-col gap-large">
14
- <div class="h-4 w-1/3 rounded bg-bg-hover"></div>
14
+ <div class="h-4 w-1/3 rounded skeleton-load"></div>
15
15
  <div class="flex flex-col gap-normal">
16
- @for (r of range(rows()); track r) {
17
- <div class="h-3 rounded bg-bg-hover" [style.width.%]="rowWidth(r)"></div>
16
+ @for (r of rowList(); track r) {
17
+ <div class="h-3 rounded skeleton-load" [style.width.%]="rowWidth(r)"></div>
18
18
  }
19
19
  </div>
20
20
  </div>
@@ -0,0 +1,15 @@
1
+ <div class="flex flex-col gap-xl">
2
+ @for (g of groupList(); track g) {
3
+ <div class="flex flex-col gap-normal">
4
+ <div class="h-3 w-20 rounded skeleton-load"></div>
5
+ <div class="flex flex-col gap-small">
6
+ @for (r of rowList(); track r) {
7
+ <div class="flex items-center gap-normal">
8
+ <div class="w-3.5 h-3.5 rounded skeleton-load shrink-0"></div>
9
+ <div class="h-3 rounded skeleton-load" [style.width.%]="rowWidth(r)"></div>
10
+ </div>
11
+ }
12
+ </div>
13
+ </div>
14
+ }
15
+ </div>
@@ -0,0 +1,59 @@
1
+ <div class="flex h-screen w-full overflow-hidden bg-bg-primary">
2
+ <!-- drawer -->
3
+ <div
4
+ class="hidden md:flex w-[260px] shrink-0 flex-col gap-xl border-r border-border-subtle p-semi"
5
+ >
6
+ <div class="flex items-center gap-normal">
7
+ <div class="w-6 h-6 rounded skeleton-load shrink-0"></div>
8
+ <div class="flex flex-col gap-small flex-1 min-w-0">
9
+ <div class="h-3.5 w-20 rounded skeleton-load"></div>
10
+ <div class="h-2.5 w-full rounded skeleton-load"></div>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="flex flex-col gap-small">
15
+ @for (n of navList(); track n) {
16
+ <div class="flex items-center gap-normal py-1">
17
+ <div class="w-4 h-4 rounded skeleton-load shrink-0"></div>
18
+ <div class="h-3 rounded skeleton-load" [style.width.%]="navWidth(n)"></div>
19
+ </div>
20
+ }
21
+ </div>
22
+
23
+ <div class="mt-auto flex items-center gap-normal border-t border-border-subtle pt-semi">
24
+ <div class="w-8 h-8 rounded-full skeleton-load shrink-0"></div>
25
+ <div class="flex flex-col gap-small flex-1 min-w-0">
26
+ <div class="h-3 w-24 rounded skeleton-load"></div>
27
+ <div class="h-2.5 w-32 rounded skeleton-load"></div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="flex flex-1 min-w-0 flex-col">
33
+ <!-- topbar -->
34
+ <div
35
+ class="flex h-14 shrink-0 items-center gap-normal border-b border-border-subtle px-semi"
36
+ >
37
+ <div class="h-3.5 w-40 rounded skeleton-load"></div>
38
+ <div class="ml-auto h-8 w-32 rounded-lg skeleton-load"></div>
39
+ </div>
40
+
41
+ <!-- content: dựng đúng composition của route đích nếu đã biết -->
42
+ <div class="flex-1 min-h-0 overflow-hidden p-semi">
43
+ @if (contentHint(); as hint) {
44
+ <fx-ui-route-skeleton [hint]="hint" />
45
+ } @else {
46
+ <div class="flex flex-col gap-xl">
47
+ <div class="h-6 w-1/4 rounded skeleton-load"></div>
48
+ <div class="grid gap-xl grid-cols-2 md:grid-cols-4">
49
+ <div class="h-24 rounded-lg skeleton-load"></div>
50
+ <div class="h-24 rounded-lg skeleton-load"></div>
51
+ <div class="h-24 rounded-lg skeleton-load"></div>
52
+ <div class="h-24 rounded-lg skeleton-load"></div>
53
+ </div>
54
+ <div class="h-64 rounded-lg skeleton-load"></div>
55
+ </div>
56
+ }
57
+ </div>
58
+ </div>
59
+ </div>
@@ -1,12 +1,12 @@
1
- <div class="flex flex-col gap-normal animate-pulse">
2
- @for (r of range(rows()); track r) {
1
+ <div class="flex flex-col gap-normal">
2
+ @for (r of rowList(); track r) {
3
3
  <div class="rounded-lg border border-border-default p-semi">
4
4
  <div class="flex items-center gap-normal mb-small">
5
- <div class="w-8 h-8 rounded-lg bg-bg-hover shrink-0"></div>
6
- <div class="h-4 flex-1 rounded bg-bg-hover"></div>
7
- <div class="h-5 w-12 rounded-full bg-bg-hover shrink-0"></div>
5
+ <div class="w-8 h-8 rounded-lg skeleton-load shrink-0"></div>
6
+ <div class="h-4 flex-1 rounded skeleton-load"></div>
7
+ <div class="h-5 w-12 rounded-full skeleton-load shrink-0"></div>
8
8
  </div>
9
- <div class="h-3 w-3/4 rounded bg-bg-hover"></div>
9
+ <div class="h-3 w-3/4 rounded skeleton-load"></div>
10
10
  </div>
11
11
  }
12
12
  </div>
@@ -6,7 +6,7 @@
6
6
  [disabled]="disabled"
7
7
  class="relative flex items-center w-10 h-5 rounded-full transition-colors duration-300 cursor-pointer disabled:cursor-not-allowed focus-visible:ring-2 focus-visible:ring-primary/50"
8
8
  [ngClass]="{
9
- 'bg-success': value && !disabled,
9
+ 'tone-solid-success': value && !disabled,
10
10
  'bg-button-default': !value && !disabled,
11
11
  'bg-bg-hover': disabled
12
12
  }"
@@ -4,8 +4,8 @@
4
4
  [attr.role]="type() === 'error' ? 'alert' : 'status'"
5
5
  class="flex border-l-[3px] items-center bg-bg-primary gap-semi min-w-[250px] max-w-[400px] px-large py-semi rounded shadow-lg transition-all duration-300 ease-in-out transform opacity-100 translate-y-0"
6
6
  [ngClass]="{
7
- 'border-success': type() === 'success',
8
- 'border-critical': type() === 'error',
7
+ 'tone-border-success': type() === 'success',
8
+ 'tone-border-danger': type() === 'error',
9
9
  'border-warning': type() === 'warning',
10
10
  'border-information': type() === 'info'
11
11
  }"
@@ -16,28 +16,28 @@
16
16
  icon="check-circle"
17
17
  [solid]="true"
18
18
  [size]="30"
19
- class="text-success"
19
+ class="tone-fg-success"
20
20
  ></fx-ui-hero-icon>
21
21
  } @case('error') {
22
22
  <fx-ui-hero-icon
23
23
  icon="exclamation-circle"
24
24
  [solid]="true"
25
25
  [size]="30"
26
- class="text-critical"
26
+ class="tone-fg-danger"
27
27
  ></fx-ui-hero-icon>
28
28
  } @case('warning') {
29
29
  <fx-ui-hero-icon
30
30
  icon="exclamation-triangle"
31
31
  [solid]="true"
32
32
  [size]="30"
33
- class="text-warning"
33
+ class="tone-fg-warning"
34
34
  ></fx-ui-hero-icon>
35
35
  } @default {
36
36
  <fx-ui-hero-icon
37
37
  icon="information-circle"
38
38
  [solid]="true"
39
39
  [size]="30"
40
- class="text-information"
40
+ class="tone-fg-information"
41
41
  ></fx-ui-hero-icon>
42
42
  } }
43
43
  </div>
package/tailwind.css CHANGED
@@ -18,3 +18,15 @@ body {
18
18
  width: 100%;
19
19
  background: rgb(var(--bg-primary));
20
20
  }
21
+
22
+ /* Typography nền lấy từ design demo: 13.5px / 1.45 / antialiased.
23
+ ⚠️ ĐẶT TRÊN `body`, TUYỆT ĐỐI KHÔNG ĐẶT TRÊN `html`. `rem` luôn quy chiếu
24
+ về phần tử gốc (`html`), nên set font-size ở `html` sẽ đổi gốc rem
25
+ 16px → 13.5px và làm CO 15,6% toàn bộ thang spacing (--spacing-* tính
26
+ bằng rem) lẫn mọi cỡ chữ text-* — chứ không chỉ chữ thừa hưởng.
27
+ Demo cũng đặt trên một DIV (.og-root), giữ rem = 16px. */
28
+ body {
29
+ font-size: var(--og-fs-md);
30
+ line-height: 1.45;
31
+ -webkit-font-smoothing: antialiased;
32
+ }