@foxeltech/angular-ui 0.4.0 → 0.7.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/archivo/archivo-400-latin-ext.woff2 +0 -0
- package/archivo/archivo-400-latin.woff2 +0 -0
- package/archivo/archivo-400-vietnamese.woff2 +0 -0
- package/archivo/archivo-500-latin-ext.woff2 +0 -0
- package/archivo/archivo-500-latin.woff2 +0 -0
- package/archivo/archivo-500-vietnamese.woff2 +0 -0
- package/archivo/archivo-600-latin-ext.woff2 +0 -0
- package/archivo/archivo-600-latin.woff2 +0 -0
- package/archivo/archivo-600-vietnamese.woff2 +0 -0
- package/archivo/archivo-700-latin-ext.woff2 +0 -0
- package/archivo/archivo-700-latin.woff2 +0 -0
- package/archivo/archivo-700-vietnamese.woff2 +0 -0
- package/components.css +332 -30
- package/dialogs.css +2 -2
- package/familjen-grotesk/familjen-grotesk-400-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-400-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-400-vietnamese.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-500-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-500-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-500-vietnamese.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-600-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-600-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-600-vietnamese.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-700-latin-ext.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-700-latin.woff2 +0 -0
- package/familjen-grotesk/familjen-grotesk-700-vietnamese.woff2 +0 -0
- package/fesm2022/foxeltech-angular-ui-chart.mjs +381 -40
- package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui-flow.mjs +176 -3
- package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui-rich-text.mjs +2 -2
- package/fesm2022/foxeltech-angular-ui-rich-text.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui.mjs +1327 -151
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/fonts.css +558 -133
- package/ibm-plex-mono/ibm-plex-mono-400-cyrillic-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-cyrillic.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-latin-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-latin.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-400-vietnamese.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-cyrillic-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-cyrillic.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-latin-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-latin.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-500-vietnamese.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-cyrillic-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-cyrillic.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-latin-ext.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-latin.woff2 +0 -0
- package/ibm-plex-mono/ibm-plex-mono-600-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-400-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-500-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-600-vietnamese.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-cyrillic-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-cyrillic.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-greek.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-latin-ext.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-latin.woff2 +0 -0
- package/jetbrains-mono/jetbrains-mono-700-vietnamese.woff2 +0 -0
- package/newsreader/newsreader-400-latin-ext.woff2 +0 -0
- package/newsreader/newsreader-400-latin.woff2 +0 -0
- package/newsreader/newsreader-400-vietnamese.woff2 +0 -0
- package/newsreader/newsreader-500-latin-ext.woff2 +0 -0
- package/newsreader/newsreader-500-latin.woff2 +0 -0
- package/newsreader/newsreader-500-vietnamese.woff2 +0 -0
- package/newsreader/newsreader-600-latin-ext.woff2 +0 -0
- package/newsreader/newsreader-600-latin.woff2 +0 -0
- package/newsreader/newsreader-600-vietnamese.woff2 +0 -0
- package/package.json +5 -2
- package/source-serif-4/source-serif-4-400-cyrillic-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-cyrillic.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-greek.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-latin-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-latin.woff2 +0 -0
- package/source-serif-4/source-serif-4-400-vietnamese.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-cyrillic-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-cyrillic.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-greek.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-latin-ext.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-latin.woff2 +0 -0
- package/source-serif-4/source-serif-4-600-vietnamese.woff2 +0 -0
- package/src/lib/styles/components.css +332 -30
- package/src/lib/styles/dialogs.css +2 -2
- package/src/lib/styles/fonts.css +558 -133
- package/src/lib/styles/tailwind.css +13 -1
- package/src/lib/styles/theme.css +642 -69
- package/src/lib/styles/tokens.css +81 -19
- package/src/lib/styles/utilities.css +16 -1
- package/src/lib/ui/components/bar-list/bar-list.component.html +29 -0
- package/src/lib/ui/components/button/button.component.html +9 -5
- package/src/lib/ui/components/checkbox/checkbox.component.html +1 -1
- package/src/lib/ui/components/code-block/code-block.component.html +8 -0
- package/src/lib/ui/components/drawer/drawer.component.html +35 -12
- package/src/lib/ui/components/drawer/drawer.component.scss +106 -18
- package/src/lib/ui/components/facets-rail/facets-rail.component.html +37 -0
- package/src/lib/ui/components/filter-pills/filter-pills.component.html +16 -0
- package/src/lib/ui/components/kanban-board/kanban-board.component.scss +9 -9
- package/src/lib/ui/components/list-row/list-row.component.html +20 -0
- package/src/lib/ui/components/master-detail/master-detail.component.css +79 -5
- package/src/lib/ui/components/master-detail/master-detail.component.html +57 -7
- package/src/lib/ui/components/pending-badge/pending-badge.component.html +15 -0
- package/src/lib/ui/components/pipeline-stepper/pipeline-stepper.component.html +45 -0
- package/src/lib/ui/components/project-card/project-card.component.html +102 -0
- package/src/lib/ui/components/radio-button/radio-button.component.html +1 -1
- package/src/lib/ui/components/radio-button-toggle/radio-button-toggle.component.html +1 -1
- package/src/lib/ui/components/route-skeleton/route-skeleton.component.css +38 -0
- package/src/lib/ui/components/route-skeleton/route-skeleton.component.html +184 -0
- package/src/lib/ui/components/sample-frame/sample-frame.component.html +18 -0
- package/src/lib/ui/components/score-badge/score-badge.component.html +7 -0
- package/src/lib/ui/components/screenshot-frame/screenshot-frame.component.html +40 -0
- package/src/lib/ui/components/section-card/section-card.component.html +15 -6
- package/src/lib/ui/components/segmented/segmented.component.html +42 -0
- package/src/lib/ui/components/segmented/segmented.component.scss +12 -0
- package/src/lib/ui/components/skeleton-dashboard/skeleton-dashboard.component.html +24 -0
- package/src/lib/ui/components/skeleton-detail/skeleton-detail.component.html +8 -8
- package/src/lib/ui/components/skeleton-facets/skeleton-facets.component.html +15 -0
- package/src/lib/ui/components/skeleton-frame/skeleton-frame.component.html +59 -0
- package/src/lib/ui/components/skeleton-list/skeleton-list.component.html +6 -6
- package/src/lib/ui/components/slider/slider.component.html +2 -2
- package/src/lib/ui/components/sparkbar/sparkbar.component.html +14 -0
- package/src/lib/ui/components/stage-dots/stage-dots.component.html +13 -0
- package/src/lib/ui/components/stat-cards/stat-cards.component.html +44 -11
- package/src/lib/ui/components/status-dot/status-dot.component.html +13 -0
- package/src/lib/ui/components/switch/switch.component.html +1 -1
- package/src/lib/ui/components/tab-group/tab-group.component.html +37 -8
- package/src/lib/ui/components/tab-group/tab-group.component.scss +41 -1
- package/src/lib/ui/components/tag/tag.component.html +2 -2
- package/src/lib/ui/components/theme-picker/theme-picker.component.html +44 -0
- package/src/lib/ui/components/toast/toast.component.html +6 -6
- package/src/lib/ui/components/tree-diagram/tree-diagram.component.html +3 -3
- package/tailwind.css +13 -1
- package/theme.css +642 -69
- package/tokens.css +81 -19
- package/types/foxeltech-angular-ui-chart.d.ts +90 -3
- package/types/foxeltech-angular-ui-flow.d.ts +83 -1
- package/types/foxeltech-angular-ui.d.ts +909 -48
- package/utilities.css +16 -1
- package/BeVietnamPro/BeVietnamPro-Bold.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-ExtraLight.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Light.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Medium.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Regular.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-SemiBold.woff2 +0 -0
- package/BeVietnamPro/BeVietnamPro-Thin.woff2 +0 -0
- package/SpaceMono-Regular.woff2 +0 -0
- package/dm-sans/DMSans-Bold.woff2 +0 -0
- package/dm-sans/DMSans-Light.woff2 +0 -0
- package/dm-sans/DMSans-Medium.woff2 +0 -0
- package/dm-sans/DMSans-Regular.woff2 +0 -0
- package/dm-sans/DMSans-SemiBold.woff2 +0 -0
- package/dm-sans/DMSans-Thin.woff2 +0 -0
- package/orbitron/Orbitron-Bold.woff2 +0 -0
- package/orbitron/Orbitron-Medium.woff2 +0 -0
- package/orbitron/Orbitron-Regular.woff2 +0 -0
- package/orbitron/Orbitron-SemiBold.woff2 +0 -0
- package/roboto/Roboto-Black.woff2 +0 -0
- package/roboto/Roboto-BlackItalic.woff2 +0 -0
- package/roboto/Roboto-Bold.woff2 +0 -0
- package/roboto/Roboto-BoldCondensed.woff2 +0 -0
- package/roboto/Roboto-BoldCondensedItalic.woff2 +0 -0
- package/roboto/Roboto-BoldItalic.woff2 +0 -0
- package/roboto/Roboto-Condensed.woff2 +0 -0
- package/roboto/Roboto-CondensedItalic.woff2 +0 -0
- package/roboto/Roboto-Italic.woff2 +0 -0
- package/roboto/Roboto-Light.woff2 +0 -0
- package/roboto/Roboto-LightItalic.woff2 +0 -0
- package/roboto/Roboto-Medium.woff2 +0 -0
- package/roboto/Roboto-MediumItalic.woff2 +0 -0
- package/roboto/Roboto-Regular.woff2 +0 -0
- package/roboto/Roboto-Thin.woff2 +0 -0
- package/roboto/Roboto-ThinItalic.woff2 +0 -0
- package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.html +0 -21
- package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.scss +0 -0
- package/src/lib/ui/components/tab-component/tab.component.html +0 -1
|
@@ -1,11 +1,85 @@
|
|
|
1
|
-
/*
|
|
2
|
-
.
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Layout 3 cụm quyết định theo BỀ RỘNG CỦA CHÍNH HÀNG ĐÓ, không theo viewport.
|
|
3
|
+
|
|
4
|
+
🔬 Vì sao đổi (truy ra 03/09): trước đây dùng `@media (min-width: 1080px)`
|
|
5
|
+
cộng với `lg:flex-row` (mà `lg` trong lib này là **1080px**, không phải
|
|
6
|
+
1024px). Viewport là proxy SAI cho câu hỏi "có đủ chỗ cho 3 cụm không":
|
|
7
|
+
hàng thật = viewport − drawer(261px) − padding. Đo được:
|
|
8
|
+
|
|
9
|
+
viewport 1080 → hàng 787px → facets 230 + list 420 + gap ≈ 690
|
|
10
|
+
⇒ cụm detail chỉ còn **97px**
|
|
11
|
+
viewport 1280 → hàng 987px ⇒ detail 297px (vẫn vỡ)
|
|
12
|
+
viewport 1440 → hàng 1147px ⇒ detail 457px (mới dùng được)
|
|
13
|
+
|
|
14
|
+
Nên có HAI vùng hỏng: <1080px xếp dọc dù màn to (đúng cái anh Tú gặp: OS
|
|
15
|
+
scaling 125-150% hoặc zoom kéo viewport xuống dưới 1080), và 1080-1280px
|
|
16
|
+
"ba cột nhưng detail quá hẹp".
|
|
17
|
+
|
|
18
|
+
Container query chữa cả hai: hàng tự biết mình rộng bao nhiêu, nên drawer
|
|
19
|
+
thu lại là có thêm chỗ ngay, và không phụ thuộc thang breakpoint toàn cục.
|
|
20
|
+
============================================================================ */
|
|
21
|
+
|
|
22
|
+
/* Host là containment context. `display: block` là BẮT BUỘC — host component
|
|
23
|
+
mặc định `display: inline` thì `container-type: inline-size` không có
|
|
24
|
+
inline-size xác định để đo. */
|
|
25
|
+
:host {
|
|
26
|
+
display: block;
|
|
27
|
+
container-type: inline-size;
|
|
28
|
+
container-name: fx-md;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Mặc định: xếp dọc, mỗi cụm full width, chiều cao tự nhiên.
|
|
32
|
+
KHÔNG ép chiều cao khi xếp dọc — sẽ sinh ba hộp cuộn tí hon lồng trong
|
|
33
|
+
trang đang cuộn, tệ hơn hẳn. */
|
|
34
|
+
.fx-md-list,
|
|
35
|
+
.fx-md-facets {
|
|
3
36
|
width: 100%;
|
|
4
37
|
}
|
|
5
38
|
|
|
6
|
-
|
|
39
|
+
/*
|
|
40
|
+
* Ngưỡng 968px = ĐÚNG tổng bề rộng tối thiểu dùng được của ba cụm:
|
|
41
|
+
* facets 200 + list 320 + detail 400 + 2 gap (~48) = 968px
|
|
42
|
+
* Dưới ngưỡng thì thật sự không đủ chỗ → xếp dọc là đúng, không phải lỗi.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ Đừng làm tròn lên 1000px: viewport 1280 (rất phổ biến) cho hàng đúng
|
|
45
|
+
* 987px — trượt ngưỡng 1000 chỉ vì 13px dù thừa sức xếp 3 cụm ở mức sàn
|
|
46
|
+
* (đo được: facets 200 / list 320 / detail 419).
|
|
47
|
+
*/
|
|
48
|
+
@container fx-md (min-width: 968px) {
|
|
49
|
+
.fx-md-row {
|
|
50
|
+
flex-direction: row;
|
|
51
|
+
height: var(--fx-md-pane-h, calc(100vh - 300px));
|
|
52
|
+
min-height: 420px;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Panes CO GIÃN: facets/list nhường chỗ để cụm detail luôn có sàn 400px.
|
|
56
|
+
`--fx-md-list-width` (input `listWidth`) giờ là bề rộng MONG MUỐN kiêm
|
|
57
|
+
tối đa của cụm list, không còn là con số cứng. */
|
|
58
|
+
.fx-md-facets {
|
|
59
|
+
flex: 0 1 230px;
|
|
60
|
+
min-width: 200px;
|
|
61
|
+
max-width: 260px;
|
|
62
|
+
}
|
|
63
|
+
|
|
7
64
|
.fx-md-list {
|
|
8
|
-
width:
|
|
9
|
-
flex
|
|
65
|
+
width: auto;
|
|
66
|
+
flex: 0 1 var(--fx-md-list-width, 420px);
|
|
67
|
+
min-width: 320px;
|
|
68
|
+
max-width: var(--fx-md-list-width, 420px);
|
|
10
69
|
}
|
|
70
|
+
|
|
71
|
+
.fx-md-detail {
|
|
72
|
+
flex: 1 1 400px;
|
|
73
|
+
min-width: 400px;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* prevnext style: the Material paginator stays as the paging engine but is
|
|
78
|
+
visually removed (display:none would break its measurements on re-show). */
|
|
79
|
+
.fx-md-paginator-hidden {
|
|
80
|
+
position: absolute !important;
|
|
81
|
+
width: 1px;
|
|
82
|
+
height: 1px;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
clip-path: inset(50%);
|
|
11
85
|
}
|
|
@@ -1,11 +1,33 @@
|
|
|
1
|
-
|
|
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
|
+
>
|
|
14
|
+
<!-- Facets rail (V2 3-column lookup) — wrapper always rendered so ng-content
|
|
15
|
+
stays unconditional; hidden when the host projects nothing. -->
|
|
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>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
2
25
|
<!-- Master: list pane -->
|
|
3
|
-
<div class="fx-md-list card-common">
|
|
26
|
+
<div class="fx-md-list card-common flex flex-col min-h-0">
|
|
4
27
|
<ng-content select="[mdHeader]"></ng-content>
|
|
5
28
|
<ng-content select="[mdFilters]"></ng-content>
|
|
6
29
|
<div
|
|
7
|
-
class="flex flex-col gap-normal overflow-auto pr-xs"
|
|
8
|
-
[style.maxHeight]="maxListHeight()"
|
|
30
|
+
class="flex flex-col gap-normal overflow-auto pr-xs flex-1 min-h-0"
|
|
9
31
|
role="listbox"
|
|
10
32
|
[attr.aria-label]="listLabel() || null"
|
|
11
33
|
>
|
|
@@ -24,7 +46,7 @@
|
|
|
24
46
|
class="text-left rounded-lg border p-semi transition-colors"
|
|
25
47
|
[ngClass]="
|
|
26
48
|
isSelected(row)
|
|
27
|
-
? 'border-
|
|
49
|
+
? 'border-accent bg-accent-soft/60'
|
|
28
50
|
: 'border-border-default hover:border-border-interactive bg-bg-primary'
|
|
29
51
|
"
|
|
30
52
|
>
|
|
@@ -44,14 +66,41 @@
|
|
|
44
66
|
wires page events — a binding here would double-fetch every change. -->
|
|
45
67
|
<mat-paginator
|
|
46
68
|
class="mt-normal"
|
|
69
|
+
[class.fx-md-paginator-hidden]="paginationStyle() === 'prevnext'"
|
|
47
70
|
[length]="table.total"
|
|
48
71
|
[pageSize]="table.pageSize"
|
|
49
72
|
[pageSizeOptions]="pageSizeOptions()"
|
|
50
73
|
></mat-paginator>
|
|
74
|
+
@if (paginationStyle() === 'prevnext') {
|
|
75
|
+
<div class="flex items-center justify-between mt-normal">
|
|
76
|
+
<span class="txt-utility-description" style="font-family: var(--og-font-figure)">
|
|
77
|
+
{{ rangeText() }}
|
|
78
|
+
</span>
|
|
79
|
+
<div class="flex gap-small">
|
|
80
|
+
<button
|
|
81
|
+
type="button"
|
|
82
|
+
class="btn-alternative !h-8 !px-semi text-sm"
|
|
83
|
+
[disabled]="!paginator().hasPreviousPage()"
|
|
84
|
+
(click)="paginator().previousPage()"
|
|
85
|
+
>
|
|
86
|
+
{{ prevLabel() }}
|
|
87
|
+
</button>
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class="btn-alternative !h-8 !px-semi text-sm"
|
|
91
|
+
[disabled]="!paginator().hasNextPage()"
|
|
92
|
+
(click)="paginator().nextPage()"
|
|
93
|
+
>
|
|
94
|
+
{{ nextLabel() }}
|
|
95
|
+
</button>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
}
|
|
51
99
|
</div>
|
|
52
100
|
|
|
53
101
|
<!-- Detail pane -->
|
|
54
|
-
<div class="w-full
|
|
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">
|
|
55
104
|
@if (table.selected) {
|
|
56
105
|
@if (table.detailBusy) {
|
|
57
106
|
<fx-ui-skeleton-detail [cards]="skeletonCards()"></fx-ui-skeleton-detail>
|
|
@@ -62,9 +111,10 @@
|
|
|
62
111
|
></ng-container>
|
|
63
112
|
}
|
|
64
113
|
} @else {
|
|
65
|
-
<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]">
|
|
66
115
|
<fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
|
|
67
116
|
</div>
|
|
68
117
|
}
|
|
118
|
+
</div>
|
|
69
119
|
</div>
|
|
70
120
|
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
@if (variant() === 'dot') {
|
|
2
|
+
<span
|
|
3
|
+
class="inline-block w-[7px] h-[7px] rounded-full bg-warning/70 align-middle"
|
|
4
|
+
[attr.title]="tooltip"
|
|
5
|
+
[attr.aria-label]="tooltip"
|
|
6
|
+
></span>
|
|
7
|
+
} @else {
|
|
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"
|
|
10
|
+
[attr.title]="tooltip"
|
|
11
|
+
>
|
|
12
|
+
<fx-ui-hero-icon icon="clock" [size]="11" class="flex" />
|
|
13
|
+
{{ label() }}
|
|
14
|
+
</span>
|
|
15
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<ol class="flex items-start overflow-x-auto pb-small" role="list">
|
|
2
|
+
@for (stage of stages(); track stage.id; let i = $index; let last = $last) {
|
|
3
|
+
<li class="flex items-start" [class.flex-1]="!last" [attr.aria-current]="stateOf(stage) === 'active' ? 'step' : null">
|
|
4
|
+
<button
|
|
5
|
+
type="button"
|
|
6
|
+
class="flex flex-col items-center gap-xs min-w-20 px-xs"
|
|
7
|
+
[class.cursor-pointer]="canClick(stage)"
|
|
8
|
+
[class.cursor-default]="!canClick(stage)"
|
|
9
|
+
[tabindex]="canClick(stage) ? 0 : -1"
|
|
10
|
+
(click)="onStageClick(stage, i)"
|
|
11
|
+
>
|
|
12
|
+
<span
|
|
13
|
+
class="w-8 h-8 rounded-full border-2 flex items-center justify-center text-sm font-semibold transition-colors"
|
|
14
|
+
[ngClass]="circleClass(stage)"
|
|
15
|
+
>
|
|
16
|
+
@switch (stateOf(stage)) {
|
|
17
|
+
@case ('done') {
|
|
18
|
+
<fx-ui-hero-icon icon="check" [size]="16" class="flex" />
|
|
19
|
+
}
|
|
20
|
+
@case ('error') {
|
|
21
|
+
<fx-ui-hero-icon icon="exclamation-triangle" [size]="16" class="flex" />
|
|
22
|
+
}
|
|
23
|
+
@default {
|
|
24
|
+
{{ i + 1 }}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
</span>
|
|
28
|
+
<span class="text-xs text-center whitespace-nowrap" [ngClass]="labelClass(stage)">
|
|
29
|
+
{{ stage.label }}
|
|
30
|
+
</span>
|
|
31
|
+
@if (stage.description) {
|
|
32
|
+
<span class="txt-utility-description text-center whitespace-nowrap">
|
|
33
|
+
{{ stage.description }}
|
|
34
|
+
</span>
|
|
35
|
+
}
|
|
36
|
+
</button>
|
|
37
|
+
@if (!last) {
|
|
38
|
+
<div class="flex-1 h-[2px] mt-4 mx-xs rounded-pill min-w-6" [ngClass]="connectorClass(stage)"></div>
|
|
39
|
+
}
|
|
40
|
+
</li>
|
|
41
|
+
}
|
|
42
|
+
</ol>
|
|
43
|
+
<div class="mt-large">
|
|
44
|
+
<ng-content></ng-content>
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<button
|
|
2
|
+
type="button"
|
|
3
|
+
class="card-common bg-surface w-full flex flex-col gap-normal hover:border-accent hover:shadow-md transition-all"
|
|
4
|
+
(click)="open.emit(card())"
|
|
5
|
+
>
|
|
6
|
+
<!-- Header: avatar + name/sub + score chip (design 12) -->
|
|
7
|
+
<div class="flex items-start justify-between gap-normal w-full">
|
|
8
|
+
<div class="flex items-start gap-normal min-w-0">
|
|
9
|
+
@if (card().avatar) {
|
|
10
|
+
<span
|
|
11
|
+
class="w-9 h-9 rounded-control bg-accent-soft text-accent flex items-center justify-center shrink-0 txt-utility-caption font-bold uppercase"
|
|
12
|
+
style="font-family: var(--og-font-figure)"
|
|
13
|
+
>{{ card().avatar }}</span
|
|
14
|
+
>
|
|
15
|
+
}
|
|
16
|
+
<div class="min-w-0">
|
|
17
|
+
<div class="txt-heading-06 truncate text-left">{{ card().name }}</div>
|
|
18
|
+
@if (card().sub; as sub) {
|
|
19
|
+
<div class="flex items-center gap-xs txt-utility-description mt-xs">
|
|
20
|
+
@if (sub.icon) {
|
|
21
|
+
<fx-ui-hero-icon [icon]="sub.icon" [size]="13" class="flex shrink-0" />
|
|
22
|
+
}
|
|
23
|
+
<span class="truncate">{{ sub.label }}</span>
|
|
24
|
+
</div>
|
|
25
|
+
} @else if (card().description) {
|
|
26
|
+
<div class="txt-card-content truncate mt-xs text-left">{{ card().description }}</div>
|
|
27
|
+
}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
@if (card().score !== undefined) {
|
|
31
|
+
<div class="text-right shrink-0">
|
|
32
|
+
<span
|
|
33
|
+
class="txt-tag-label px-normal py-[2px] rounded-pill whitespace-nowrap"
|
|
34
|
+
[ngClass]="scoreChipClass()"
|
|
35
|
+
>{{ card().score }}</span
|
|
36
|
+
>
|
|
37
|
+
@if (card().scoreLabel) {
|
|
38
|
+
<div class="txt-utility-description mt-xs">{{ card().scoreLabel }}</div>
|
|
39
|
+
}
|
|
40
|
+
</div>
|
|
41
|
+
}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
@if (card().chips?.length) {
|
|
45
|
+
<div class="flex flex-wrap gap-xs w-full">
|
|
46
|
+
@for (chip of card().chips; track chip.label) {
|
|
47
|
+
<span class="txt-tag-label px-normal py-[1px] rounded-pill" [ngClass]="chipClass(chip)">
|
|
48
|
+
{{ chip.label }}
|
|
49
|
+
</span>
|
|
50
|
+
}
|
|
51
|
+
</div>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
<!-- Design-12 risk trend: toned blocks + caption line -->
|
|
55
|
+
@if (card().trend; as trend) {
|
|
56
|
+
<div class="w-full">
|
|
57
|
+
<div class="flex gap-xs w-full">
|
|
58
|
+
@for (tone of trend.tones; track $index) {
|
|
59
|
+
<span class="flex-1 h-4 rounded-[2px]" [ngClass]="blockClass(tone)"></span>
|
|
60
|
+
}
|
|
61
|
+
</div>
|
|
62
|
+
@if (trend.caption || trend.aside) {
|
|
63
|
+
<div class="flex items-center justify-between mt-xs txt-utility-description font-mono">
|
|
64
|
+
<span>{{ trend.caption }}</span>
|
|
65
|
+
<span>{{ trend.aside }}</span>
|
|
66
|
+
</div>
|
|
67
|
+
}
|
|
68
|
+
</div>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@if (card().spark?.length) {
|
|
72
|
+
<fx-ui-sparkbar class="w-full" [values]="card().spark!" [height]="24" />
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
<!-- Metric columns: figure + caption (design 12) -->
|
|
76
|
+
@if (card().metrics?.length) {
|
|
77
|
+
<div
|
|
78
|
+
class="grid gap-normal w-full"
|
|
79
|
+
[style.grid-template-columns]="'repeat(' + card().metrics!.length + ', minmax(0, 1fr))'"
|
|
80
|
+
>
|
|
81
|
+
@for (m of card().metrics; track m.label) {
|
|
82
|
+
<div class="min-w-0 text-left">
|
|
83
|
+
<div class="txt-heading-06" style="font-family: var(--og-font-figure)">{{ m.value }}</div>
|
|
84
|
+
<div class="txt-utility-description truncate">{{ m.label }}</div>
|
|
85
|
+
</div>
|
|
86
|
+
}
|
|
87
|
+
</div>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@if (card().meta || card().openLabel) {
|
|
91
|
+
<div
|
|
92
|
+
class="flex items-center justify-between w-full pt-normal border-t border-border-default"
|
|
93
|
+
>
|
|
94
|
+
<span class="txt-utility-description text-left">{{ card().meta }}</span>
|
|
95
|
+
@if (card().openLabel) {
|
|
96
|
+
<span class="txt-utility-caption font-bold text-accent whitespace-nowrap"
|
|
97
|
+
>{{ card().openLabel }} ›</span
|
|
98
|
+
>
|
|
99
|
+
}
|
|
100
|
+
</div>
|
|
101
|
+
}
|
|
102
|
+
</button>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
[attr.aria-checked]="value === opt.value"
|
|
32
32
|
(click)="onSelectValue(opt.value)"
|
|
33
33
|
[disabled]="disabled"
|
|
34
|
-
[class.text-
|
|
34
|
+
[class.text-strong]="value !== opt.value"
|
|
35
35
|
class="flex-1 px-normal w-full flex items-center gap-normal h-10 rounded-md hover:bg-bg-hover transition-all duration-150 disabled:opacity-60 disabled:cursor-not-allowed"
|
|
36
36
|
[matTooltip]="opt.tooltip || ''"
|
|
37
37
|
matTooltipPosition="right"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
[attr.aria-checked]="value === opt.value"
|
|
17
17
|
(click)="onSelectValue(opt.value)"
|
|
18
18
|
[disabled]="disabled"
|
|
19
|
-
[class.text-
|
|
19
|
+
[class.text-strong]="value !== opt.value"
|
|
20
20
|
class="flex-1 bg-bg-primary flex items-center pl-normal gap-normal h-10 rounded-md hover:bg-bg-hover transition-all duration-150 disabled:opacity-60 disabled:cursor-not-allowed"
|
|
21
21
|
>
|
|
22
22
|
<span
|
|
@@ -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>
|
|
@@ -0,0 +1,18 @@
|
|
|
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>
|
|
6
|
+
<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"
|
|
8
|
+
[attr.title]="tooltip"
|
|
9
|
+
>
|
|
10
|
+
<fx-ui-hero-icon icon="clock" [size]="11" class="flex" />
|
|
11
|
+
{{ pendingLabel() }}
|
|
12
|
+
</span>
|
|
13
|
+
</div>
|
|
14
|
+
<!-- Sample content is non-interactive: nothing here reflects real state. -->
|
|
15
|
+
<div class="pointer-events-none select-none opacity-90">
|
|
16
|
+
<ng-content></ng-content>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<span
|
|
2
|
+
class="inline-flex items-center justify-center rounded-control whitespace-nowrap"
|
|
3
|
+
[ngClass]="[badgeClass(), sizeClass()]"
|
|
4
|
+
style="font-family: var(--og-font-figure)"
|
|
5
|
+
>
|
|
6
|
+
{{ score() }}@if (outOf() !== null) {<span class="opacity-60"> / {{ outOf() }}</span>}
|
|
7
|
+
</span>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@if (label()) {
|
|
2
|
+
<div class="txt-utility-caption font-bold text-text-secondary mb-normal">{{ label() }}</div>
|
|
3
|
+
}
|
|
4
|
+
<div class="rounded-control border border-border bg-surface overflow-hidden">
|
|
5
|
+
<!-- Window chrome: traffic lights + mono file name + capture time -->
|
|
6
|
+
<div class="flex items-center gap-small px-normal py-small border-b border-border bg-surface-sunken">
|
|
7
|
+
<span class="flex items-center gap-xs shrink-0">
|
|
8
|
+
<span class="w-2 h-2 rounded-full bg-critical/70"></span>
|
|
9
|
+
<span class="w-2 h-2 rounded-full bg-warning/70"></span>
|
|
10
|
+
<span class="w-2 h-2 rounded-full bg-success/70"></span>
|
|
11
|
+
</span>
|
|
12
|
+
@if (fileName()) {
|
|
13
|
+
<span class="txt-utility-caption text-text-secondary truncate" style="font-family: var(--og-font-mono)">
|
|
14
|
+
{{ fileName() }}
|
|
15
|
+
</span>
|
|
16
|
+
}
|
|
17
|
+
@if (timestamp()) {
|
|
18
|
+
<span class="txt-utility-caption text-text-placeholder shrink-0" style="font-family: var(--og-font-mono)">
|
|
19
|
+
· {{ timestamp() }}
|
|
20
|
+
</span>
|
|
21
|
+
}
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
@if (src()) {
|
|
25
|
+
<img [src]="src()" [alt]="alt() || fileName()" class="block w-full object-cover" [style.height]="height()" />
|
|
26
|
+
} @else {
|
|
27
|
+
<div
|
|
28
|
+
class="flex flex-col items-center justify-center gap-normal px-large text-center"
|
|
29
|
+
[style.height]="height()"
|
|
30
|
+
>
|
|
31
|
+
<fx-ui-hero-icon icon="camera" [size]="22" class="flex text-text-placeholder"></fx-ui-hero-icon>
|
|
32
|
+
@if (caption()) {
|
|
33
|
+
<span class="txt-utility-caption text-text-placeholder max-w-[280px]">{{ caption() }}</span>
|
|
34
|
+
}
|
|
35
|
+
</div>
|
|
36
|
+
}
|
|
37
|
+
</div>
|
|
38
|
+
@if (src() && caption()) {
|
|
39
|
+
<div class="txt-utility-caption text-text-placeholder mt-normal">{{ caption() }}</div>
|
|
40
|
+
}
|