@foxeltech/angular-ui 0.7.75 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxeltech/angular-ui",
3
- "version": "0.7.75",
3
+ "version": "0.7.83",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -339,14 +339,27 @@ input[type='password'] {
339
339
  @apply font-mono text-sm text-text-primary tracking-normal;
340
340
  }
341
341
 
342
+ /* ---- Nút: viền hairline + bóng mờ ở MỌI biến thể (anh Tú 04/09) --------
343
+ Demo `.og-button`: border `--og-rule`, hover `0 2px 7px rgba(0,0,0,.12)`;
344
+ `.bi-newbtn`: viền tối hơn nền 1 bậc + `0 1px 2px rgba(0,0,0,.15)`. Nút nền
345
+ đặc dùng viền inset tối 8% để không phải khai màu viền riêng cho gradient. */
342
346
  .btn-default {
343
347
  @apply btn-common bg-button-default hover:opacity-75 text-text-inverse;
348
+ box-shadow:
349
+ inset 0 0 0 1px rgb(0 0 0 / 0.08),
350
+ 0 1px 2px rgb(0 0 0 / 0.12);
344
351
  }
345
352
  .btn-primary {
346
353
  @apply btn-common bg-gradient-primary hover:opacity-75 text-text-focus;
354
+ box-shadow:
355
+ inset 0 0 0 1px rgb(0 0 0 / 0.08),
356
+ 0 1px 2px rgb(0 0 0 / 0.12);
347
357
  }
348
358
  .btn-success {
349
359
  @apply btn-common bg-gradient-success hover:opacity-75 text-surface;
360
+ box-shadow:
361
+ inset 0 0 0 1px rgb(0 0 0 / 0.08),
362
+ 0 1px 2px rgb(0 0 0 / 0.12);
350
363
  }
351
364
  /* Nút hành động chính theo demo (`.bi-newbtn`): màu cố định, không phải accent. */
352
365
  /* ---- Tông severity theo ĐÚNG mô hình demo (đo 03/09) -----------------
@@ -604,13 +617,24 @@ input[type='password'] {
604
617
  }
605
618
 
606
619
  .btn-cta {
607
- @apply btn-common bg-cta hover:bg-cta-hover text-white;
620
+ @apply btn-common bg-cta hover:bg-cta-hover text-white border border-cta-hover;
608
621
  box-shadow:
609
622
  inset 0 1px 0 rgb(255 255 255 / 0.18),
610
623
  0 1px 2px rgb(0 0 0 / 0.15);
611
624
  }
625
+ .btn-cta:hover {
626
+ box-shadow:
627
+ inset 0 1px 0 rgb(255 255 255 / 0.18),
628
+ 0 2px 6px rgb(0 0 0 / 0.2);
629
+ }
630
+ /* Outline = `.og-button--outline`: nền surface, viền --og-border, bóng 1px;
631
+ hover nổi lên 0 2px 7px như demo. */
612
632
  .btn-alternative {
613
- @apply btn-common hover:bg-bg-hover text-text-primary border border-border-default;
633
+ @apply btn-common bg-surface hover:bg-bg-hover text-text-primary border border-border-default;
634
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
635
+ }
636
+ .btn-alternative:hover {
637
+ box-shadow: 0 2px 7px rgb(0 0 0 / 0.12);
614
638
  }
615
639
  .btn-menu-top {
616
640
  @apply btn-menu-common border-b border-border-default pt-small pb-semi;
@@ -31,7 +31,12 @@
31
31
  }
32
32
 
33
33
  @utility card-common {
34
- @apply text-left px-xl py-large border border-border-default rounded-lg shadow-sm transition-colors duration-500 ease-in-out;
34
+ @apply text-left px-xl py-large border border-border-default rounded-lg transition-colors duration-500 ease-in-out;
35
+ /* Demo `.og-panel` / `.og-statgrid`: hairline border + two soft layers.
36
+ Replaces Tailwind shadow-sm, which reads harder at the edges. */
37
+ box-shadow:
38
+ 0 1px 2px rgb(0 0 0 / 0.04),
39
+ 0 3px 8px rgb(0 0 0 / 0.05);
35
40
  }
36
41
 
37
42
  @utility tag-common {
@@ -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) {
@@ -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,4 +1,6 @@
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
6
  <span class="txt-eyebrow">{{ title() }}</span>
@@ -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"
@@ -719,7 +719,23 @@ declare function toneBadge(tone: Tone | string | null | undefined): string;
719
719
  * nơi cần nhấn mạnh nhất; đừng dùng cho badge có chấm bên trong (chấm sẽ chìm).
720
720
  */
721
721
  declare function toneBadgeSolid(tone: Tone | string | null | undefined): string;
722
- /** Chỉ MÀU của tông, không kèm hình dạng pill — cho ô icon, chip, ô nền. */
722
+ /**
723
+ * Chỉ MÀU của tông, không kèm hình dạng pill — cho ô icon, chip, ô nền.
724
+ *
725
+ * ⚠️ Đây là họ SOLID: `tone-tint-critical` đặt `background: --og-text`
726
+ * (gần đen) VÀ `color: --og-surface` (trắng) trên CÙNG element.
727
+ *
728
+ * Hệ quả bắt buộc nhớ:
729
+ * 1. Chỉ dùng cho **badge một phần tử**. Với hộp nhiều dòng thì con sẽ cần màu
730
+ * chữ riêng, và nếu con ghi đè bằng `toneFg()` (đỏ sẫm) thì thành đỏ sẫm
731
+ * trên gần đen — không đọc được. Đã bị đúng vậy ở hộp RISK của
732
+ * `info-binary-detail` (anh Tú 03/09).
733
+ * 2. **KHÔNG** đặt `toneFg()` lên phần tử con của một phần tử đã có
734
+ * `toneTint()` — nó phá màu tương phản mà `toneTint` vừa đặt.
735
+ *
736
+ * Hộp/khối nhiều dòng thì dùng `toneScale()`: nền nhạt (color-mix 12%) + chữ
737
+ * đúng tông, con chỉ cần THỪA HƯỞNG.
738
+ */
723
739
  declare function toneTint(tone: Tone | string | null | undefined): string;
724
740
  /**
725
741
  * CHỮ theo tông — dùng thành viên dễ đọc nhất của mỗi họ severity, KHÔNG phải
@@ -1837,6 +1853,10 @@ declare class MeterComponent {
1837
1853
  * - titleStyle 'heading' (default): icon + heading, the pre-V2 look
1838
1854
  * - titleStyle 'eyebrow': the V2 design's panel header — small tracked
1839
1855
  * caption on the left, muted [aside] text on the right
1856
+ *
1857
+ * The host is a flex column (not the custom element's inline default) so that
1858
+ * in a CSS grid row every card stretches to the row height and panels line up
1859
+ * (anh Tú 04/09); a footer projected with `mt-auto` sticks to the bottom.
1840
1860
  */
1841
1861
  declare class SectionCardComponent {
1842
1862
  /** Caller-translated heading. */
@@ -2023,6 +2043,7 @@ declare class ThemePickerComponent {
2023
2043
  * <fx-ui-pending-badge [field]="'confirmed-chains weekly series'" />
2024
2044
  *
2025
2045
  * Keep the copy factual: it states that data is missing, never fakes a value.
2046
+ * Styling is neutral on purpose (anh Tú 04/09): a hint, not a warning.
2026
2047
  */
2027
2048
  declare class PendingBadgeComponent {
2028
2049
  /** Caller-translated chip text. */
@@ -2044,10 +2065,11 @@ declare class PendingBadgeComponent {
2044
2065
  * reviewed before its endpoint exists (anh Tú 29/08: "muốn có full giao diện
2045
2066
  * trước để review").
2046
2067
  *
2047
- * The frame makes the status unmistakable dashed outline, a corner label and
2048
- * the pending marker naming the missing field so sample shapes can never be
2049
- * mistaken for measured values. Drop the wrapper (and the sample input) the
2050
- * moment the API lands.
2068
+ * The marker is deliberately quiet (anh 04/09: no yellow frame, just a
2069
+ * subtle "Pending API" chip): a neutral chip above the block, with the label
2070
+ * and missing field in its tooltip. The content itself is non-interactive so
2071
+ * sample shapes are never mistaken for live state. Drop the wrapper (and the
2072
+ * sample input) the moment the API lands.
2051
2073
  *
2052
2074
  * The pending chip is inlined rather than composed from fx-ui-pending-badge:
2053
2075
  * one lib component importing another inside the same barrel tripped NG0919
@@ -2058,7 +2080,7 @@ declare class PendingBadgeComponent {
2058
2080
  * </fx-ui-sample-frame>
2059
2081
  */
2060
2082
  declare class SampleFrameComponent {
2061
- /** Caller-translated corner label. */
2083
+ /** Caller-translated block label — surfaces in the chip tooltip only. */
2062
2084
  readonly label: _angular_core.InputSignal<string>;
2063
2085
  /** Field the backend still owes — shown on the pending badge tooltip. */
2064
2086
  readonly field: _angular_core.InputSignal<string>;
@@ -2156,17 +2178,29 @@ declare class SegmentedComponent {
2156
2178
  * (design refs 04/05).
2157
2179
  */
2158
2180
  readonly variant: _angular_core.InputSignal<"default" | "pill">;
2181
+ /**
2182
+ * 'equal' (default): every segment takes the same width (view toggles such
2183
+ * as Catalog | Table). 'content': each segment is as wide as its label —
2184
+ * the demo's detail-pane tabs ("Affected software · Summary · Evidence"),
2185
+ * where equal columns would truncate the long label in a narrow pane.
2186
+ */
2187
+ readonly fit: _angular_core.InputSignal<"equal" | "content">;
2188
+ private readonly optionEls;
2189
+ /** Measured box of the active segment — only used when fit === 'content'. */
2190
+ private readonly thumbBox;
2191
+ constructor();
2159
2192
  /** Index the sliding thumb sits on; -1 hides it (no/unknown selection). */
2160
2193
  readonly activeIndex: _angular_core.Signal<number>;
2161
2194
  /** Equal-width tracks → the thumb is 1/n of the inner width and slides by
2162
2195
  * multiples of its own width. Pure CSS transform, nothing to measure. */
2163
2196
  readonly thumbWidth: _angular_core.Signal<string>;
2164
2197
  readonly thumbTransform: _angular_core.Signal<string>;
2198
+ readonly gridClass: _angular_core.Signal<"auto-cols-auto" | "auto-cols-fr">;
2165
2199
  select(option: FxSegmentedOption): void;
2166
2200
  thumbClass(): string;
2167
2201
  optionClass(option: FxSegmentedOption): string;
2168
2202
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SegmentedComponent, never>;
2169
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SegmentedComponent, "fx-ui-segmented", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
2203
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SegmentedComponent, "fx-ui-segmented", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "fit": { "alias": "fit"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
2170
2204
  }
2171
2205
 
2172
2206
  interface FxListRowBadge {
package/utilities.css CHANGED
@@ -31,7 +31,12 @@
31
31
  }
32
32
 
33
33
  @utility card-common {
34
- @apply text-left px-xl py-large border border-border-default rounded-lg shadow-sm transition-colors duration-500 ease-in-out;
34
+ @apply text-left px-xl py-large border border-border-default rounded-lg transition-colors duration-500 ease-in-out;
35
+ /* Demo `.og-panel` / `.og-statgrid`: hairline border + two soft layers.
36
+ Replaces Tailwind shadow-sm, which reads harder at the edges. */
37
+ box-shadow:
38
+ 0 1px 2px rgb(0 0 0 / 0.04),
39
+ 0 3px 8px rgb(0 0 0 / 0.05);
35
40
  }
36
41
 
37
42
  @utility tag-common {