@foxeltech/angular-ui 0.7.108 → 0.7.131
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/components.css +24 -5
- package/fesm2022/foxeltech-angular-ui-chart.mjs +25 -1
- package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui-flow.mjs +42 -11
- package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
- package/fesm2022/foxeltech-angular-ui.mjs +102 -28
- package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
- package/package.json +3 -2
- package/src/lib/styles/components.css +24 -5
- package/src/lib/styles/tokens.css +3 -0
- package/src/lib/styles/utilities.css +9 -4
- package/src/lib/ui/components/bar-list/bar-list.component.html +19 -18
- package/src/lib/ui/components/bar-list/bar-list.component.scss +63 -0
- package/src/lib/ui/components/breadcrumb/breadcrumb.component.css +31 -0
- package/src/lib/ui/components/breadcrumb/breadcrumb.component.html +6 -1
- package/src/lib/ui/components/button/button.component.html +37 -225
- package/src/lib/ui/components/button/button.component.scss +30 -0
- package/src/lib/ui/components/drawer/drawer.component.html +32 -7
- package/src/lib/ui/components/drawer/drawer.component.scss +114 -17
- package/src/lib/ui/components/filter-pills/filter-pills.component.html +4 -1
- package/src/lib/ui/components/filter-pills/filter-pills.component.scss +23 -0
- package/src/lib/ui/components/master-detail/master-detail.component.css +36 -8
- package/src/lib/ui/components/master-detail/master-detail.component.html +4 -2
- package/src/lib/ui/components/section-card/section-card.component.html +10 -5
- package/src/lib/ui/components/section-card/section-card.component.scss +28 -0
- package/src/lib/ui/components/stat-cards/stat-cards.component.css +48 -0
- package/src/lib/ui/components/stat-cards/stat-cards.component.html +16 -7
- package/tokens.css +3 -0
- package/types/foxeltech-angular-ui-flow.d.ts +24 -2
- package/types/foxeltech-angular-ui.d.ts +92 -5
- package/utilities.css +9 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foxeltech/angular-ui",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.131",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"@microsoft/signalr": "^10.0.0",
|
|
27
27
|
"d3-drag": "^3.0.0",
|
|
28
28
|
"d3-selection": "^3.0.0",
|
|
29
|
-
"d3-zoom": "^3.0.0"
|
|
29
|
+
"d3-zoom": "^3.0.0",
|
|
30
|
+
"lucide-angular": "^1.0.0"
|
|
30
31
|
},
|
|
31
32
|
"exports": {
|
|
32
33
|
"./tailwind.css": "./tailwind.css",
|
|
@@ -361,11 +361,12 @@ input[type='password'] {
|
|
|
361
361
|
inset 0 0 0 1px rgb(0 0 0 / 0.08),
|
|
362
362
|
0 1px 2px rgb(0 0 0 / 0.12);
|
|
363
363
|
}
|
|
364
|
-
/* Icon-only button (no label): square
|
|
365
|
-
|
|
366
|
-
|
|
364
|
+
/* Icon-only button (no label): square, no side padding, so the glyph sits
|
|
365
|
+
dead-centre (anh Tú 04/09). The width tracks `btn-common`'s height — 27px
|
|
366
|
+
since 17/09 — so it stays square; consumers may still size it with
|
|
367
|
+
utilities (`w-9 h-9`), which win in the utilities layer. */
|
|
367
368
|
.fx-btn--icon {
|
|
368
|
-
width:
|
|
369
|
+
width: 27px;
|
|
369
370
|
padding-left: 0;
|
|
370
371
|
padding-right: 0;
|
|
371
372
|
}
|
|
@@ -631,6 +632,10 @@ input[type='password'] {
|
|
|
631
632
|
0 1px 2px rgb(0 0 0 / 0.15);
|
|
632
633
|
}
|
|
633
634
|
.btn-cta:hover {
|
|
635
|
+
/* The edge darkens a step past the fill — reference `.bi-newbtn:hover`
|
|
636
|
+
(measured 16/09). Without it the button only changed fill and shadow, so
|
|
637
|
+
the hover read softer than the design's. */
|
|
638
|
+
border-color: var(--color-cta-hover-border);
|
|
634
639
|
box-shadow:
|
|
635
640
|
inset 0 1px 0 rgb(255 255 255 / 0.18),
|
|
636
641
|
0 2px 6px rgb(0 0 0 / 0.2);
|
|
@@ -971,7 +976,9 @@ input[type='password'] {
|
|
|
971
976
|
font-size: var(--og-fs-xs);
|
|
972
977
|
line-height: 1.5;
|
|
973
978
|
white-space: nowrap;
|
|
974
|
-
|
|
979
|
+
/* 400, per the reference's risk badge ("High · 82", measured 16/09). At 500
|
|
980
|
+
it sat a step heavier than every other 11.5px label around it. */
|
|
981
|
+
font-weight: 400;
|
|
975
982
|
}
|
|
976
983
|
.fx-badge--negative {
|
|
977
984
|
background: rgb(var(--og-negative-soft-bg));
|
|
@@ -1032,3 +1039,15 @@ input[type='password'] {
|
|
|
1032
1039
|
.fx-statusdot--accent::before {
|
|
1033
1040
|
background: rgb(var(--og-accent));
|
|
1034
1041
|
}
|
|
1042
|
+
|
|
1043
|
+
/* A tooltip must never take the pointer. Material gives its overlay pane
|
|
1044
|
+
`pointer-events: auto`, and with the drawer COLLAPSED a nav item's tooltip
|
|
1045
|
+
opens at x≈70..110 while the collapse/expand button sits at 58..82 — the
|
|
1046
|
+
tooltip covered the button's right half and swallowed the click, which is
|
|
1047
|
+
why the toggle "sometimes" did nothing (MEASURED 16/09). Nothing in a
|
|
1048
|
+
tooltip is interactive, so this is safe everywhere. */
|
|
1049
|
+
.mat-mdc-tooltip-panel,
|
|
1050
|
+
.mat-mdc-tooltip,
|
|
1051
|
+
.mat-mdc-tooltip-panel > * {
|
|
1052
|
+
pointer-events: none !important;
|
|
1053
|
+
}
|
|
@@ -53,6 +53,9 @@
|
|
|
53
53
|
|
|
54
54
|
--color-cta: #c2582a;
|
|
55
55
|
--color-cta-hover: #b34e24;
|
|
56
|
+
/* Border of the CTA while hovered — the reference's `.bi-newbtn:hover`
|
|
57
|
+
darkens the edge one more step than the fill (16/09). */
|
|
58
|
+
--color-cta-hover-border: #a3461f;
|
|
56
59
|
|
|
57
60
|
/* ---- Legacy colour names (@deprecated — remove after the V2 campaign;
|
|
58
61
|
they resolve through the alias layer in theme.css, so every existing
|
|
@@ -8,10 +8,15 @@
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
@utility btn-common {
|
|
11
|
-
/*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/* The demo's `.og-button--sm` (anh Tú 17/09: compact như New Analysis) —
|
|
12
|
+
27px tall, 11.5px at weight 400, 12px sides, 7px radius. MEASURED off the
|
|
13
|
+
reference's own `.og-button--sm.bi-newbtn`, which is the topbar CTA: it
|
|
14
|
+
wore this shape through `!` overrides because the shared button was a size
|
|
15
|
+
above it. The h-8 / 13px / 600 this replaces was itself a step down from
|
|
16
|
+
pre-V2 h-10 (29/08); against V2 panel density it still read one notch
|
|
17
|
+
heavy. leading-none: an arbitrary font-size inherits lh 1.5 and Archivo's
|
|
18
|
+
uneven leading floats the ink ~1px above centre. */
|
|
19
|
+
@apply flex text-[length:var(--og-fs-xs)] leading-none h-[27px] font-normal px-[12px] py-[4px] items-center justify-center rounded-control shadow-sm transition-all duration-200 disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
@utility btn-menu-common {
|
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
<!-- Ranked list, to the reference's "Top projects by risk" (measured in the
|
|
2
|
+
browser 16/09): the SCORE leads, right-aligned in a 70px column, then the
|
|
3
|
+
label in a 150px column, then a 3px track taking the rest. It used to be
|
|
4
|
+
label-left / score-right with a 5px full-width track underneath, which is a
|
|
5
|
+
different object — two lines per row instead of one. -->
|
|
6
|
+
<div class="fx-barlist flex flex-col">
|
|
7
|
+
@for (item of items(); track item.label; let first = $first) {
|
|
3
8
|
<button
|
|
4
9
|
type="button"
|
|
5
|
-
class="text-left
|
|
10
|
+
class="fx-barlist__row text-left flex items-center"
|
|
6
11
|
[class.hover:bg-surface-sunken]="interactive()"
|
|
7
12
|
[class.cursor-default]="!interactive()"
|
|
8
13
|
[tabindex]="interactive() ? 0 : -1"
|
|
9
14
|
(click)="onSelect(item)"
|
|
10
15
|
>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@if (item.meta) {
|
|
18
|
-
<div class="txt-utility-description truncate">{{ item.meta }}</div>
|
|
19
|
-
}
|
|
20
|
-
<div class="mt-xs h-[var(--og-track-h)] rounded-pill bg-track overflow-hidden">
|
|
21
|
-
<div
|
|
22
|
-
class="h-full rounded-pill transition-all duration-500"
|
|
23
|
-
[ngClass]="barClass(item)"
|
|
16
|
+
<span class="fx-barlist__score shrink-0">{{ item.valueLabel ?? item.value }}</span>
|
|
17
|
+
<span class="fx-barlist__label shrink-0 truncate">{{ item.label }}</span>
|
|
18
|
+
<span class="fx-barlist__track flex-1">
|
|
19
|
+
<span
|
|
20
|
+
class="fx-barlist__fill block"
|
|
21
|
+
[class.fx-barlist__fill--lead]="first"
|
|
24
22
|
[style.width.%]="widthOf(item)"
|
|
25
|
-
></
|
|
26
|
-
</
|
|
23
|
+
></span>
|
|
24
|
+
</span>
|
|
27
25
|
</button>
|
|
26
|
+
@if (item.meta) {
|
|
27
|
+
<div class="fx-barlist__meta truncate">{{ item.meta }}</div>
|
|
28
|
+
}
|
|
28
29
|
}
|
|
29
30
|
</div>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@reference '../../../styles/tailwind.css';
|
|
2
|
+
|
|
3
|
+
/* Metrics from the reference (measured 16/09): rows 8px apart, each 18px tall
|
|
4
|
+
with a 10px gap between its three cells. */
|
|
5
|
+
.fx-barlist {
|
|
6
|
+
gap: 8px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.fx-barlist__row {
|
|
10
|
+
gap: 10px;
|
|
11
|
+
min-height: 18px;
|
|
12
|
+
/* A right gutter so the bar stops short of the panel edge (anh Tú 16/09).
|
|
13
|
+
The reference's row is 387px wide and its 147px track happens to end level
|
|
14
|
+
with the card's padding; this panel is twice that, so a track that fills
|
|
15
|
+
the remainder runs right up to the edge and reads as an overflow. */
|
|
16
|
+
padding: 0 24px 0 0;
|
|
17
|
+
background: transparent;
|
|
18
|
+
border: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Score first, right-aligned in its own column — that alignment is what makes
|
|
22
|
+
the numbers read as a ranked column rather than trailing labels. */
|
|
23
|
+
.fx-barlist__score {
|
|
24
|
+
width: 70px;
|
|
25
|
+
text-align: right;
|
|
26
|
+
font-family: var(--og-font-mono);
|
|
27
|
+
font-size: var(--og-fs-sm);
|
|
28
|
+
font-weight: 400;
|
|
29
|
+
color: rgb(var(--og-text));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.fx-barlist__label {
|
|
33
|
+
width: 150px;
|
|
34
|
+
font-size: var(--og-fs-sm);
|
|
35
|
+
font-weight: 400;
|
|
36
|
+
color: rgb(var(--og-text-muted));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* A 3px hairline track, square ends — not the 5px pill it was. */
|
|
40
|
+
.fx-barlist__track {
|
|
41
|
+
height: 3px;
|
|
42
|
+
background: rgb(var(--og-track));
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Every bar is muted EXCEPT the leader, which takes the accent. The reference
|
|
47
|
+
ranks by one colour and highlights the top row; tone-per-row turned the
|
|
48
|
+
panel into a severity chart, which is the donut's job, not this one. */
|
|
49
|
+
.fx-barlist__fill {
|
|
50
|
+
height: 100%;
|
|
51
|
+
background: rgb(var(--og-text-muted));
|
|
52
|
+
transition: width 500ms ease;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.fx-barlist__fill--lead {
|
|
56
|
+
background: rgb(var(--og-accent));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.fx-barlist__meta {
|
|
60
|
+
font-size: var(--og-fs-2xs);
|
|
61
|
+
color: rgb(var(--og-text-faint));
|
|
62
|
+
padding-left: 80px;
|
|
63
|
+
}
|
|
@@ -260,3 +260,34 @@
|
|
|
260
260
|
.text-faint { color: rgb(var(--og-text-faint)); }
|
|
261
261
|
.text-strong { color: rgb(var(--og-text)); }
|
|
262
262
|
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
263
|
+
|
|
264
|
+
/* CTA crumb — the reference's "+ Create project" at the end of the Project
|
|
265
|
+
Management trail (measured 16/09: 24px tall, 4px 10px 4px 8px, gap 5,
|
|
266
|
+
10.5px/600, radius 7, a light accent fill with an accent border).
|
|
267
|
+
The exact demo colours are #eaf1fa / #b9cde6 / #2a4f7c; the accent-soft
|
|
268
|
+
TOKENS are used instead so the pill follows the theme in all six — they sit
|
|
269
|
+
within ~3 of those values in light mode, and the border is mixed from the
|
|
270
|
+
accent rather than hard-coded because no token matches it. */
|
|
271
|
+
.fx-crumb-cta {
|
|
272
|
+
appearance: none;
|
|
273
|
+
display: inline-flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
gap: 5px;
|
|
276
|
+
min-width: 0;
|
|
277
|
+
padding: 4px 10px 4px 8px;
|
|
278
|
+
line-height: 1.5;
|
|
279
|
+
border: 1px solid color-mix(in srgb, rgb(var(--og-accent)) 35%, transparent);
|
|
280
|
+
border-radius: var(--og-radius-control);
|
|
281
|
+
background: rgb(var(--og-accent-soft-bg));
|
|
282
|
+
color: rgb(var(--og-accent-soft-fg));
|
|
283
|
+
font-size: var(--og-fs-2xs);
|
|
284
|
+
font-weight: 600;
|
|
285
|
+
white-space: nowrap;
|
|
286
|
+
cursor: pointer;
|
|
287
|
+
transition: background-color 130ms ease, border-color 130ms ease;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.fx-crumb-cta:hover {
|
|
291
|
+
background: color-mix(in srgb, rgb(var(--og-accent)) 16%, rgb(var(--og-accent-soft-bg)));
|
|
292
|
+
border-color: color-mix(in srgb, rgb(var(--og-accent)) 55%, transparent);
|
|
293
|
+
}
|
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
@if (i > 0) {
|
|
4
4
|
<span aria-hidden="true" class="fx-crumb-sep">/</span>
|
|
5
5
|
}
|
|
6
|
-
@if (
|
|
6
|
+
@if (crumb.cta) {
|
|
7
|
+
<button type="button" class="fx-crumb-cta" (click)="go($event, crumb.url || '')">
|
|
8
|
+
<fx-ui-hero-icon [icon]="crumb.icon || 'plus'" [size]="13" class="flex shrink-0" />
|
|
9
|
+
<span class="truncate">{{ crumb.label }}</span>
|
|
10
|
+
</button>
|
|
11
|
+
} @else if (isChip(crumb)) {
|
|
7
12
|
<span class="relative inline-flex min-w-0">
|
|
8
13
|
<button
|
|
9
14
|
type="button"
|
|
@@ -14,229 +14,41 @@
|
|
|
14
14
|
[disabled]="disabled() || loading()"
|
|
15
15
|
(click)="onClick()"
|
|
16
16
|
>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
fill="freeze"
|
|
55
|
-
/>
|
|
56
|
-
<animate
|
|
57
|
-
id="spinner_lo66"
|
|
58
|
-
begin="spinner_Aguh.end"
|
|
59
|
-
attributeName="r"
|
|
60
|
-
calcMode="spline"
|
|
61
|
-
dur="0.5s"
|
|
62
|
-
keySplines=".36,.6,.31,1"
|
|
63
|
-
values="3;0"
|
|
64
|
-
fill="freeze"
|
|
65
|
-
/>
|
|
66
|
-
<animate
|
|
67
|
-
id="spinner_z0Or"
|
|
68
|
-
begin="spinner_lo66.end"
|
|
69
|
-
attributeName="cx"
|
|
70
|
-
dur="0.001s"
|
|
71
|
-
values="20;4"
|
|
72
|
-
fill="freeze"
|
|
73
|
-
/>
|
|
74
|
-
</circle>
|
|
75
|
-
<circle cx="4" cy="12" r="3">
|
|
76
|
-
<animate
|
|
77
|
-
begin="0;spinner_z0Or.end"
|
|
78
|
-
attributeName="cx"
|
|
79
|
-
calcMode="spline"
|
|
80
|
-
dur="0.5s"
|
|
81
|
-
keySplines=".36,.6,.31,1"
|
|
82
|
-
values="4;12"
|
|
83
|
-
fill="freeze"
|
|
84
|
-
/>
|
|
85
|
-
<animate
|
|
86
|
-
begin="spinner_OLMs.end"
|
|
87
|
-
attributeName="cx"
|
|
88
|
-
calcMode="spline"
|
|
89
|
-
dur="0.5s"
|
|
90
|
-
keySplines=".36,.6,.31,1"
|
|
91
|
-
values="12;20"
|
|
92
|
-
fill="freeze"
|
|
93
|
-
/>
|
|
94
|
-
<animate
|
|
95
|
-
id="spinner_JsnR"
|
|
96
|
-
begin="spinner_UHR2.end"
|
|
97
|
-
attributeName="r"
|
|
98
|
-
calcMode="spline"
|
|
99
|
-
dur="0.5s"
|
|
100
|
-
keySplines=".36,.6,.31,1"
|
|
101
|
-
values="3;0"
|
|
102
|
-
fill="freeze"
|
|
103
|
-
/>
|
|
104
|
-
<animate
|
|
105
|
-
id="spinner_Aguh"
|
|
106
|
-
begin="spinner_JsnR.end"
|
|
107
|
-
attributeName="cx"
|
|
108
|
-
dur="0.001s"
|
|
109
|
-
values="20;4"
|
|
110
|
-
fill="freeze"
|
|
111
|
-
/>
|
|
112
|
-
<animate
|
|
113
|
-
begin="spinner_Aguh.end"
|
|
114
|
-
attributeName="r"
|
|
115
|
-
calcMode="spline"
|
|
116
|
-
dur="0.5s"
|
|
117
|
-
keySplines=".36,.6,.31,1"
|
|
118
|
-
values="0;3"
|
|
119
|
-
fill="freeze"
|
|
120
|
-
/>
|
|
121
|
-
</circle>
|
|
122
|
-
<circle cx="12" cy="12" r="3">
|
|
123
|
-
<animate
|
|
124
|
-
begin="0;spinner_z0Or.end"
|
|
125
|
-
attributeName="cx"
|
|
126
|
-
calcMode="spline"
|
|
127
|
-
dur="0.5s"
|
|
128
|
-
keySplines=".36,.6,.31,1"
|
|
129
|
-
values="12;20"
|
|
130
|
-
fill="freeze"
|
|
131
|
-
/>
|
|
132
|
-
<animate
|
|
133
|
-
id="spinner_hSjk"
|
|
134
|
-
begin="spinner_OLMs.end"
|
|
135
|
-
attributeName="r"
|
|
136
|
-
calcMode="spline"
|
|
137
|
-
dur="0.5s"
|
|
138
|
-
keySplines=".36,.6,.31,1"
|
|
139
|
-
values="3;0"
|
|
140
|
-
fill="freeze"
|
|
141
|
-
/>
|
|
142
|
-
<animate
|
|
143
|
-
id="spinner_UHR2"
|
|
144
|
-
begin="spinner_hSjk.end"
|
|
145
|
-
attributeName="cx"
|
|
146
|
-
dur="0.001s"
|
|
147
|
-
values="20;4"
|
|
148
|
-
fill="freeze"
|
|
149
|
-
/>
|
|
150
|
-
<animate
|
|
151
|
-
begin="spinner_UHR2.end"
|
|
152
|
-
attributeName="r"
|
|
153
|
-
calcMode="spline"
|
|
154
|
-
dur="0.5s"
|
|
155
|
-
keySplines=".36,.6,.31,1"
|
|
156
|
-
values="0;3"
|
|
157
|
-
fill="freeze"
|
|
158
|
-
/>
|
|
159
|
-
<animate
|
|
160
|
-
begin="spinner_Aguh.end"
|
|
161
|
-
attributeName="cx"
|
|
162
|
-
calcMode="spline"
|
|
163
|
-
dur="0.5s"
|
|
164
|
-
keySplines=".36,.6,.31,1"
|
|
165
|
-
values="4;12"
|
|
166
|
-
fill="freeze"
|
|
167
|
-
/>
|
|
168
|
-
</circle>
|
|
169
|
-
<circle cx="20" cy="12" r="3">
|
|
170
|
-
<animate
|
|
171
|
-
id="spinner_4v5M"
|
|
172
|
-
begin="0;spinner_z0Or.end"
|
|
173
|
-
attributeName="r"
|
|
174
|
-
calcMode="spline"
|
|
175
|
-
dur="0.5s"
|
|
176
|
-
keySplines=".36,.6,.31,1"
|
|
177
|
-
values="3;0"
|
|
178
|
-
fill="freeze"
|
|
179
|
-
/>
|
|
180
|
-
<animate
|
|
181
|
-
id="spinner_OLMs"
|
|
182
|
-
begin="spinner_4v5M.end"
|
|
183
|
-
attributeName="cx"
|
|
184
|
-
dur="0.001s"
|
|
185
|
-
values="20;4"
|
|
186
|
-
fill="freeze"
|
|
187
|
-
/>
|
|
188
|
-
<animate
|
|
189
|
-
begin="spinner_OLMs.end"
|
|
190
|
-
attributeName="r"
|
|
191
|
-
calcMode="spline"
|
|
192
|
-
dur="0.5s"
|
|
193
|
-
keySplines=".36,.6,.31,1"
|
|
194
|
-
values="0;3"
|
|
195
|
-
fill="freeze"
|
|
196
|
-
/>
|
|
197
|
-
<animate
|
|
198
|
-
begin="spinner_UHR2.end"
|
|
199
|
-
attributeName="cx"
|
|
200
|
-
calcMode="spline"
|
|
201
|
-
dur="0.5s"
|
|
202
|
-
keySplines=".36,.6,.31,1"
|
|
203
|
-
values="4;12"
|
|
204
|
-
fill="freeze"
|
|
205
|
-
/>
|
|
206
|
-
<animate
|
|
207
|
-
begin="spinner_Aguh.end"
|
|
208
|
-
attributeName="cx"
|
|
209
|
-
calcMode="spline"
|
|
210
|
-
dur="0.5s"
|
|
211
|
-
keySplines=".36,.6,.31,1"
|
|
212
|
-
values="12;20"
|
|
213
|
-
fill="freeze"
|
|
214
|
-
/>
|
|
215
|
-
</circle>
|
|
216
|
-
</svg>
|
|
217
|
-
</div>
|
|
218
|
-
} @else {
|
|
219
|
-
<ng-container>
|
|
220
|
-
<div class="flex items-center justify-center gap-small">
|
|
221
|
-
@if (icon) {
|
|
222
|
-
<fx-ui-hero-icon
|
|
223
|
-
[icon]="icon"
|
|
224
|
-
[size]="16"
|
|
225
|
-
class="flex"
|
|
226
|
-
[ngClass]="{
|
|
227
|
-
'text-text-inverse': buttonVariant() === 'default',
|
|
228
|
-
'text-text-primary': buttonVariant() === 'alternative',
|
|
229
|
-
'text-accent-contrast': buttonVariant() === 'primary',
|
|
230
|
-
'text-surface': buttonVariant() === 'success',
|
|
231
|
-
'text-white': buttonVariant() === 'cta',
|
|
232
|
-
}"
|
|
233
|
-
></fx-ui-hero-icon>
|
|
234
|
-
}
|
|
235
|
-
@if (label) {
|
|
236
|
-
<!-- text-box: optical centering — Archivo's leading floats ink up otherwise -->
|
|
237
|
-
<div class="[text-box:trim-both_cap_alphabetic]">{{ label }}</div>
|
|
238
|
-
}
|
|
239
|
-
</div>
|
|
240
|
-
</ng-container>
|
|
241
|
-
}
|
|
17
|
+
<!-- Loading keeps the LABEL, as the sign-in button does: a button that
|
|
18
|
+
swaps its text for a spinner collapses to a stub, and the row jumps.
|
|
19
|
+
The ring is that button's `.li-spin`, in the foreground the variant
|
|
20
|
+
already uses for its icon so it reads on a coloured ground. -->
|
|
21
|
+
<!-- 6px, the reference button's own gap; `gap-small` is 4px. -->
|
|
22
|
+
<div class="flex items-center justify-center gap-[6px]">
|
|
23
|
+
@if (loading()) {
|
|
24
|
+
<span
|
|
25
|
+
class="fx-btn__spin"
|
|
26
|
+
aria-hidden="true"
|
|
27
|
+
[ngClass]="{
|
|
28
|
+
'text-text-inverse': buttonVariant() === 'default',
|
|
29
|
+
'text-text-primary': buttonVariant() === 'alternative',
|
|
30
|
+
'text-accent-contrast': buttonVariant() === 'primary',
|
|
31
|
+
'text-surface': buttonVariant() === 'success',
|
|
32
|
+
'text-white': buttonVariant() === 'cta',
|
|
33
|
+
}"
|
|
34
|
+
></span>
|
|
35
|
+
} @else if (icon) {
|
|
36
|
+
<fx-ui-hero-icon
|
|
37
|
+
[icon]="icon"
|
|
38
|
+
[size]="13"
|
|
39
|
+
class="flex"
|
|
40
|
+
[ngClass]="{
|
|
41
|
+
'text-text-inverse': buttonVariant() === 'default',
|
|
42
|
+
'text-text-primary': buttonVariant() === 'alternative',
|
|
43
|
+
'text-accent-contrast': buttonVariant() === 'primary',
|
|
44
|
+
'text-surface': buttonVariant() === 'success',
|
|
45
|
+
'text-white': buttonVariant() === 'cta',
|
|
46
|
+
}"
|
|
47
|
+
></fx-ui-hero-icon>
|
|
48
|
+
}
|
|
49
|
+
@if (label) {
|
|
50
|
+
<!-- text-box: optical centering — Archivo's leading floats ink up otherwise -->
|
|
51
|
+
<div class="[text-box:trim-both_cap_alphabetic]">{{ label }}</div>
|
|
52
|
+
}
|
|
53
|
+
</div>
|
|
242
54
|
</button>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/* Loading ring — the sign-in button's `.li-spin`, sized to sit beside a label.
|
|
4
|
+
`currentColor` so the variant's own foreground class colours it. */
|
|
5
|
+
.fx-btn__spin {
|
|
6
|
+
display: inline-block;
|
|
7
|
+
/* 12px, matching the button's 13px icon — 14 crowded the 27px box. */
|
|
8
|
+
width: 12px;
|
|
9
|
+
height: 12px;
|
|
10
|
+
flex: 0 0 auto;
|
|
11
|
+
border-radius: 50%;
|
|
12
|
+
border: 2px solid currentColor;
|
|
13
|
+
border-top-color: transparent;
|
|
14
|
+
opacity: 0.9;
|
|
15
|
+
animation: fx-btn-spin 0.8s linear infinite;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@keyframes fx-btn-spin {
|
|
19
|
+
to {
|
|
20
|
+
transform: rotate(360deg);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* A ring that has stopped still marks the button as busy, and `aria-busy`
|
|
25
|
+
carries the state regardless. */
|
|
26
|
+
@media (prefers-reduced-motion: reduce) {
|
|
27
|
+
.fx-btn__spin {
|
|
28
|
+
animation: none;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
<div class="drawer-host-wrapper">
|
|
2
2
|
<a href="/" class="drawer-brand" [class.is-collapsed]="!isExpanded">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
<!-- Logo sits in its own sunken tile (reference `.sb-logo`, measured 16/09):
|
|
4
|
+
the tile is what carries the hover border, so the mark keeps its own
|
|
5
|
+
size and nothing shifts by a pixel when that border appears. -->
|
|
6
|
+
<span class="drawer-brand-logo">
|
|
7
|
+
@if (logo) {
|
|
8
|
+
<img [src]="logo" alt="Home" class="drawer-brand-mark" />
|
|
9
|
+
} @else {
|
|
10
|
+
<span class="drawer-brand-mark"></span>
|
|
11
|
+
}
|
|
12
|
+
</span>
|
|
8
13
|
@if (isExpanded && (logoText || logoSubtext)) {
|
|
9
14
|
<span class="drawer-brand-copy">
|
|
10
15
|
@if (logoText) {
|
|
@@ -87,6 +92,7 @@
|
|
|
87
92
|
<a
|
|
88
93
|
[routerLink]="item.route"
|
|
89
94
|
class="drawer-item"
|
|
95
|
+
[class.is-collapsed]="!isExpanded"
|
|
90
96
|
[class.active]="isActiveRoute(item.route)"
|
|
91
97
|
[class.has-children]="item.children && item.children.length > 0"
|
|
92
98
|
[style.paddingLeft.px]="isExpanded ? 8 + level * 16 : 16"
|
|
@@ -96,7 +102,16 @@
|
|
|
96
102
|
(click)="onItemClick(item, $event)"
|
|
97
103
|
>
|
|
98
104
|
<div class="item-content">
|
|
99
|
-
|
|
105
|
+
@if (item.iconData) {
|
|
106
|
+
<lucide-icon
|
|
107
|
+
[img]="item.iconData"
|
|
108
|
+
[size]="isExpanded ? 13 : 18"
|
|
109
|
+
class="item-icon"
|
|
110
|
+
[class.item-icon--solid]="item.iconSolid"
|
|
111
|
+
/>
|
|
112
|
+
} @else {
|
|
113
|
+
<fx-ui-hero-icon [icon]="item.icon" [size]="isExpanded ? 13 : 18" class="item-icon"></fx-ui-hero-icon>
|
|
114
|
+
}
|
|
100
115
|
@if (isExpanded) {
|
|
101
116
|
<span class="item-label">{{ item.label }}</span>
|
|
102
117
|
}
|
|
@@ -108,6 +123,7 @@
|
|
|
108
123
|
<button
|
|
109
124
|
type="button"
|
|
110
125
|
class="drawer-item"
|
|
126
|
+
[class.is-collapsed]="!isExpanded"
|
|
111
127
|
[class.active]="isActiveRoute(item.route)"
|
|
112
128
|
[class.has-children]="item.children && item.children.length > 0"
|
|
113
129
|
[style.paddingLeft.px]="isExpanded ? 8 + level * 16 : 16"
|
|
@@ -117,7 +133,16 @@
|
|
|
117
133
|
(click)="onItemClick(item, $event)"
|
|
118
134
|
>
|
|
119
135
|
<div class="item-content">
|
|
120
|
-
|
|
136
|
+
@if (item.iconData) {
|
|
137
|
+
<lucide-icon
|
|
138
|
+
[img]="item.iconData"
|
|
139
|
+
[size]="isExpanded ? 13 : 18"
|
|
140
|
+
class="item-icon"
|
|
141
|
+
[class.item-icon--solid]="item.iconSolid"
|
|
142
|
+
/>
|
|
143
|
+
} @else {
|
|
144
|
+
<fx-ui-hero-icon [icon]="item.icon" [size]="isExpanded ? 13 : 18" class="item-icon"></fx-ui-hero-icon>
|
|
145
|
+
}
|
|
121
146
|
@if (isExpanded) {
|
|
122
147
|
<span class="item-label">{{ item.label }}</span>
|
|
123
148
|
}
|