@foxeltech/angular-ui 0.4.0 → 0.7.44

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 (165) hide show
  1. package/archivo/archivo-400-latin-ext.woff2 +0 -0
  2. package/archivo/archivo-400-latin.woff2 +0 -0
  3. package/archivo/archivo-400-vietnamese.woff2 +0 -0
  4. package/archivo/archivo-500-latin-ext.woff2 +0 -0
  5. package/archivo/archivo-500-latin.woff2 +0 -0
  6. package/archivo/archivo-500-vietnamese.woff2 +0 -0
  7. package/archivo/archivo-600-latin-ext.woff2 +0 -0
  8. package/archivo/archivo-600-latin.woff2 +0 -0
  9. package/archivo/archivo-600-vietnamese.woff2 +0 -0
  10. package/archivo/archivo-700-latin-ext.woff2 +0 -0
  11. package/archivo/archivo-700-latin.woff2 +0 -0
  12. package/archivo/archivo-700-vietnamese.woff2 +0 -0
  13. package/components.css +12 -12
  14. package/dialogs.css +2 -2
  15. package/fesm2022/foxeltech-angular-ui-chart.mjs +185 -17
  16. package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -1
  17. package/fesm2022/foxeltech-angular-ui-flow.mjs +176 -3
  18. package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -1
  19. package/fesm2022/foxeltech-angular-ui-rich-text.mjs +2 -2
  20. package/fesm2022/foxeltech-angular-ui-rich-text.mjs.map +1 -1
  21. package/fesm2022/foxeltech-angular-ui.mjs +849 -69
  22. package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
  23. package/fonts.css +462 -138
  24. package/ibm-plex-mono/ibm-plex-mono-400-cyrillic-ext.woff2 +0 -0
  25. package/ibm-plex-mono/ibm-plex-mono-400-cyrillic.woff2 +0 -0
  26. package/ibm-plex-mono/ibm-plex-mono-400-latin-ext.woff2 +0 -0
  27. package/ibm-plex-mono/ibm-plex-mono-400-latin.woff2 +0 -0
  28. package/ibm-plex-mono/ibm-plex-mono-400-vietnamese.woff2 +0 -0
  29. package/ibm-plex-mono/ibm-plex-mono-500-cyrillic-ext.woff2 +0 -0
  30. package/ibm-plex-mono/ibm-plex-mono-500-cyrillic.woff2 +0 -0
  31. package/ibm-plex-mono/ibm-plex-mono-500-latin-ext.woff2 +0 -0
  32. package/ibm-plex-mono/ibm-plex-mono-500-latin.woff2 +0 -0
  33. package/ibm-plex-mono/ibm-plex-mono-500-vietnamese.woff2 +0 -0
  34. package/ibm-plex-mono/ibm-plex-mono-600-cyrillic-ext.woff2 +0 -0
  35. package/ibm-plex-mono/ibm-plex-mono-600-cyrillic.woff2 +0 -0
  36. package/ibm-plex-mono/ibm-plex-mono-600-latin-ext.woff2 +0 -0
  37. package/ibm-plex-mono/ibm-plex-mono-600-latin.woff2 +0 -0
  38. package/ibm-plex-mono/ibm-plex-mono-600-vietnamese.woff2 +0 -0
  39. package/jetbrains-mono/jetbrains-mono-400-cyrillic-ext.woff2 +0 -0
  40. package/jetbrains-mono/jetbrains-mono-400-cyrillic.woff2 +0 -0
  41. package/jetbrains-mono/jetbrains-mono-400-greek.woff2 +0 -0
  42. package/jetbrains-mono/jetbrains-mono-400-latin-ext.woff2 +0 -0
  43. package/jetbrains-mono/jetbrains-mono-400-latin.woff2 +0 -0
  44. package/jetbrains-mono/jetbrains-mono-400-vietnamese.woff2 +0 -0
  45. package/jetbrains-mono/jetbrains-mono-500-cyrillic-ext.woff2 +0 -0
  46. package/jetbrains-mono/jetbrains-mono-500-cyrillic.woff2 +0 -0
  47. package/jetbrains-mono/jetbrains-mono-500-greek.woff2 +0 -0
  48. package/jetbrains-mono/jetbrains-mono-500-latin-ext.woff2 +0 -0
  49. package/jetbrains-mono/jetbrains-mono-500-latin.woff2 +0 -0
  50. package/jetbrains-mono/jetbrains-mono-500-vietnamese.woff2 +0 -0
  51. package/jetbrains-mono/jetbrains-mono-600-cyrillic-ext.woff2 +0 -0
  52. package/jetbrains-mono/jetbrains-mono-600-cyrillic.woff2 +0 -0
  53. package/jetbrains-mono/jetbrains-mono-600-greek.woff2 +0 -0
  54. package/jetbrains-mono/jetbrains-mono-600-latin-ext.woff2 +0 -0
  55. package/jetbrains-mono/jetbrains-mono-600-latin.woff2 +0 -0
  56. package/jetbrains-mono/jetbrains-mono-600-vietnamese.woff2 +0 -0
  57. package/jetbrains-mono/jetbrains-mono-700-cyrillic-ext.woff2 +0 -0
  58. package/jetbrains-mono/jetbrains-mono-700-cyrillic.woff2 +0 -0
  59. package/jetbrains-mono/jetbrains-mono-700-greek.woff2 +0 -0
  60. package/jetbrains-mono/jetbrains-mono-700-latin-ext.woff2 +0 -0
  61. package/jetbrains-mono/jetbrains-mono-700-latin.woff2 +0 -0
  62. package/jetbrains-mono/jetbrains-mono-700-vietnamese.woff2 +0 -0
  63. package/newsreader/newsreader-400-latin-ext.woff2 +0 -0
  64. package/newsreader/newsreader-400-latin.woff2 +0 -0
  65. package/newsreader/newsreader-400-vietnamese.woff2 +0 -0
  66. package/newsreader/newsreader-500-latin-ext.woff2 +0 -0
  67. package/newsreader/newsreader-500-latin.woff2 +0 -0
  68. package/newsreader/newsreader-500-vietnamese.woff2 +0 -0
  69. package/newsreader/newsreader-600-latin-ext.woff2 +0 -0
  70. package/newsreader/newsreader-600-latin.woff2 +0 -0
  71. package/newsreader/newsreader-600-vietnamese.woff2 +0 -0
  72. package/package.json +5 -2
  73. package/source-serif-4/source-serif-4-400-cyrillic-ext.woff2 +0 -0
  74. package/source-serif-4/source-serif-4-400-cyrillic.woff2 +0 -0
  75. package/source-serif-4/source-serif-4-400-greek.woff2 +0 -0
  76. package/source-serif-4/source-serif-4-400-latin-ext.woff2 +0 -0
  77. package/source-serif-4/source-serif-4-400-latin.woff2 +0 -0
  78. package/source-serif-4/source-serif-4-400-vietnamese.woff2 +0 -0
  79. package/source-serif-4/source-serif-4-600-cyrillic-ext.woff2 +0 -0
  80. package/source-serif-4/source-serif-4-600-cyrillic.woff2 +0 -0
  81. package/source-serif-4/source-serif-4-600-greek.woff2 +0 -0
  82. package/source-serif-4/source-serif-4-600-latin-ext.woff2 +0 -0
  83. package/source-serif-4/source-serif-4-600-latin.woff2 +0 -0
  84. package/source-serif-4/source-serif-4-600-vietnamese.woff2 +0 -0
  85. package/src/lib/styles/components.css +12 -12
  86. package/src/lib/styles/dialogs.css +2 -2
  87. package/src/lib/styles/fonts.css +462 -138
  88. package/src/lib/styles/tailwind.css +1 -1
  89. package/src/lib/styles/theme.css +596 -69
  90. package/src/lib/styles/tokens.css +56 -19
  91. package/src/lib/styles/utilities.css +4 -1
  92. package/src/lib/ui/components/bar-list/bar-list.component.html +29 -0
  93. package/src/lib/ui/components/button/button.component.html +7 -5
  94. package/src/lib/ui/components/checkbox/checkbox.component.html +1 -1
  95. package/src/lib/ui/components/code-block/code-block.component.html +8 -0
  96. package/src/lib/ui/components/drawer/drawer.component.html +35 -12
  97. package/src/lib/ui/components/drawer/drawer.component.scss +104 -18
  98. package/src/lib/ui/components/facets-rail/facets-rail.component.html +42 -0
  99. package/src/lib/ui/components/filter-pills/filter-pills.component.html +16 -0
  100. package/src/lib/ui/components/kanban-board/kanban-board.component.scss +9 -9
  101. package/src/lib/ui/components/list-row/list-row.component.html +20 -0
  102. package/src/lib/ui/components/master-detail/master-detail.component.css +22 -0
  103. package/src/lib/ui/components/master-detail/master-detail.component.html +33 -1
  104. package/src/lib/ui/components/pending-badge/pending-badge.component.html +15 -0
  105. package/src/lib/ui/components/pipeline-stepper/pipeline-stepper.component.html +45 -0
  106. package/src/lib/ui/components/project-card/project-card.component.html +102 -0
  107. package/src/lib/ui/components/radio-button/radio-button.component.html +1 -1
  108. package/src/lib/ui/components/radio-button-toggle/radio-button-toggle.component.html +1 -1
  109. package/src/lib/ui/components/sample-frame/sample-frame.component.html +18 -0
  110. package/src/lib/ui/components/score-badge/score-badge.component.html +7 -0
  111. package/src/lib/ui/components/screenshot-frame/screenshot-frame.component.html +40 -0
  112. package/src/lib/ui/components/section-card/section-card.component.html +19 -6
  113. package/src/lib/ui/components/segmented/segmented.component.html +42 -0
  114. package/src/lib/ui/components/segmented/segmented.component.scss +12 -0
  115. package/src/lib/ui/components/slider/slider.component.html +2 -2
  116. package/src/lib/ui/components/sparkbar/sparkbar.component.html +14 -0
  117. package/src/lib/ui/components/stage-dots/stage-dots.component.html +13 -0
  118. package/src/lib/ui/components/stat-cards/stat-cards.component.html +44 -11
  119. package/src/lib/ui/components/status-dot/status-dot.component.html +13 -0
  120. package/src/lib/ui/components/tab-group/tab-group.component.html +37 -8
  121. package/src/lib/ui/components/tab-group/tab-group.component.scss +41 -1
  122. package/src/lib/ui/components/tag/tag.component.html +2 -2
  123. package/src/lib/ui/components/theme-picker/theme-picker.component.html +44 -0
  124. package/src/lib/ui/components/tree-diagram/tree-diagram.component.html +3 -3
  125. package/tailwind.css +1 -1
  126. package/theme.css +596 -69
  127. package/tokens.css +56 -19
  128. package/types/foxeltech-angular-ui-chart.d.ts +14 -2
  129. package/types/foxeltech-angular-ui-flow.d.ts +83 -1
  130. package/types/foxeltech-angular-ui.d.ts +636 -20
  131. package/utilities.css +4 -1
  132. package/BeVietnamPro/BeVietnamPro-Bold.woff2 +0 -0
  133. package/BeVietnamPro/BeVietnamPro-ExtraLight.woff2 +0 -0
  134. package/BeVietnamPro/BeVietnamPro-Light.woff2 +0 -0
  135. package/BeVietnamPro/BeVietnamPro-Medium.woff2 +0 -0
  136. package/BeVietnamPro/BeVietnamPro-Regular.woff2 +0 -0
  137. package/BeVietnamPro/BeVietnamPro-SemiBold.woff2 +0 -0
  138. package/BeVietnamPro/BeVietnamPro-Thin.woff2 +0 -0
  139. package/SpaceMono-Regular.woff2 +0 -0
  140. package/dm-sans/DMSans-Bold.woff2 +0 -0
  141. package/dm-sans/DMSans-Light.woff2 +0 -0
  142. package/dm-sans/DMSans-Medium.woff2 +0 -0
  143. package/dm-sans/DMSans-Regular.woff2 +0 -0
  144. package/dm-sans/DMSans-SemiBold.woff2 +0 -0
  145. package/dm-sans/DMSans-Thin.woff2 +0 -0
  146. package/orbitron/Orbitron-Bold.woff2 +0 -0
  147. package/orbitron/Orbitron-Medium.woff2 +0 -0
  148. package/orbitron/Orbitron-Regular.woff2 +0 -0
  149. package/orbitron/Orbitron-SemiBold.woff2 +0 -0
  150. package/roboto/Roboto-Black.woff2 +0 -0
  151. package/roboto/Roboto-BlackItalic.woff2 +0 -0
  152. package/roboto/Roboto-Bold.woff2 +0 -0
  153. package/roboto/Roboto-BoldCondensed.woff2 +0 -0
  154. package/roboto/Roboto-BoldCondensedItalic.woff2 +0 -0
  155. package/roboto/Roboto-BoldItalic.woff2 +0 -0
  156. package/roboto/Roboto-Condensed.woff2 +0 -0
  157. package/roboto/Roboto-CondensedItalic.woff2 +0 -0
  158. package/roboto/Roboto-Italic.woff2 +0 -0
  159. package/roboto/Roboto-Light.woff2 +0 -0
  160. package/roboto/Roboto-LightItalic.woff2 +0 -0
  161. package/roboto/Roboto-Medium.woff2 +0 -0
  162. package/roboto/Roboto-MediumItalic.woff2 +0 -0
  163. package/roboto/Roboto-Regular.woff2 +0 -0
  164. package/roboto/Roboto-Thin.woff2 +0 -0
  165. package/roboto/Roboto-ThinItalic.woff2 +0 -0
@@ -3,16 +3,43 @@
3
3
  * Replaces the old JS preset (tailwind.config.js / lodash.merge sharing).
4
4
  *
5
5
  * Colour tokens point at the runtime RGB-triplet variables from theme.css
6
- * (dark default on :root, light via .light_theme) so theme switching keeps
7
- * working; opacity modifiers like bg-critical/10 are handled by v4's
8
- * color-mix() and need no <alpha-value> plumbing.
6
+ * (OgeniumDashboards --og-* per [data-og-theme] on <html>, porcelain default)
7
+ * so theme switching keeps working; opacity modifiers like bg-critical/10 are
8
+ * handled by v4's color-mix() and need no <alpha-value> plumbing.
9
9
  */
10
10
  /* `inline` is load-bearing: without it the rgb(var(--x)) values are baked
11
- into :root once, so .light_theme's runtime triplet overrides never reach
11
+ into :root once, so [data-og-theme]'s runtime triplet overrides never reach
12
12
  the utilities. inline makes every utility carry rgb(var(--x)) itself and
13
13
  resolve at the element that uses it — same behavior as v3. */
14
14
  @theme inline {
15
- /* Colours (names match the v3 preset so every existing class keeps working) */
15
+ /* ---- OG palette use these in new/restyled markup ---- */
16
+ --color-page: rgb(var(--og-page-bg));
17
+ --color-surface: rgb(var(--og-surface));
18
+ --color-surface-raised: rgb(var(--og-surface-raised));
19
+ --color-surface-sunken: rgb(var(--og-surface-sunken));
20
+ --color-border: rgb(var(--og-border));
21
+ --color-border-subtle: rgb(var(--og-border-subtle));
22
+ --color-strong: rgb(var(--og-text));
23
+ --color-body: rgb(var(--og-text-body));
24
+ --color-muted: rgb(var(--og-text-muted));
25
+ --color-faint: rgb(var(--og-text-faint));
26
+ --color-accent: rgb(var(--og-accent));
27
+ --color-accent-fg: rgb(var(--og-accent-fg));
28
+ --color-accent-contrast: rgb(var(--og-accent-contrast));
29
+ --color-accent-soft: rgb(var(--og-accent-soft-bg));
30
+ --color-accent-soft-fg: rgb(var(--og-accent-soft-fg));
31
+ --color-positive: rgb(var(--og-positive));
32
+ --color-positive-soft: rgb(var(--og-positive-soft-bg));
33
+ --color-negative: rgb(var(--og-negative));
34
+ --color-negative-soft: rgb(var(--og-negative-soft-bg));
35
+ --color-warning-soft: rgb(var(--og-warning-soft-bg));
36
+ --color-neutral-soft: rgb(var(--og-neutral-soft-bg));
37
+ --color-neutral-soft-fg: rgb(var(--og-neutral-soft-fg));
38
+ --color-track: rgb(var(--og-track));
39
+
40
+ /* ---- Legacy colour names (@deprecated — remove after the V2 campaign;
41
+ they resolve through the alias layer in theme.css, so every existing
42
+ class already renders the OG palette) ---- */
16
43
  --color-gradient-primary-start: rgb(var(--gradient-primary-start));
17
44
  --color-gradient-primary-end: rgb(var(--gradient-primary-end));
18
45
  --color-gradient-success-start: rgb(var(--gradient-success-start));
@@ -61,14 +88,22 @@
61
88
  --shadow-2xl: 0 25px 50px -12px rgb(var(--shadow-color) / 0.25);
62
89
  --inset-shadow-sm: inset 0 2px 4px 0 rgb(var(--shadow-color) / 0.05);
63
90
 
64
- /* v3 default border radii, incl. DEFAULT so bare `rounded` keeps working */
65
- --radius-sm: 0.125rem;
66
- --radius: 0.25rem;
67
- --radius-md: 0.375rem;
68
- --radius-lg: 0.5rem;
69
- --radius-xl: 0.75rem;
70
- --radius-2xl: 1rem;
71
- --radius-3xl: 1.5rem;
91
+ /* Radius is a THEME variable in V2 (blueprint/terminal are square, ivory
92
+ controls are pills) — never hardcode border-radius in components. */
93
+ --radius-frame: var(--og-radius-frame);
94
+ --radius-card: var(--og-radius-card);
95
+ --radius-control: var(--og-radius-control);
96
+ --radius-pill: var(--og-radius-pill);
97
+ /* Legacy fixed scale remapped onto the theme radii so blueprint/terminal go
98
+ square with zero template edits. `rounded-full` stays Tailwind-native
99
+ 9999px — avatars/dots must remain circular in every theme. */
100
+ --radius-sm: var(--og-radius-control);
101
+ --radius: var(--og-radius-control);
102
+ --radius-md: var(--og-radius-control);
103
+ --radius-lg: var(--og-radius-card);
104
+ --radius-xl: var(--og-radius-card);
105
+ --radius-2xl: var(--og-radius-card);
106
+ --radius-3xl: var(--og-radius-frame);
72
107
 
73
108
  /* Named spacing scale (gap-small, p-semi, mb-large, …) */
74
109
  --spacing-xs: 0.125rem;
@@ -79,12 +114,14 @@
79
114
  --spacing-xl: 1.25rem;
80
115
  --spacing-2xl: 1.5rem;
81
116
 
82
- /* Font families */
83
- --font-spmono: 'SpaceMonoRegular', monospace;
84
- --font-dmsans: 'DMSans', sans-serif;
85
- --font-orbitron: 'Orbitron', sans-serif;
86
- --font-bevn: 'BeVietnamPro', sans-serif;
87
- --font-roboto: 'Roboto', sans-serif;
117
+ /* Font families — theme variables (blueprint/terminal switch to mono).
118
+ V2 set: Archivo (body/display), JetBrains Mono / IBM Plex Mono (mono),
119
+ Source Serif 4 (prose), Newsreader (ivory/blueprint display). */
120
+ --font-body: var(--og-font-body);
121
+ --font-display: var(--og-font-display);
122
+ --font-mono: var(--og-font-mono);
123
+ --font-prose: var(--og-font-prose);
124
+ --font-figure: var(--og-font-figure);
88
125
 
89
126
  /* Font-size overrides (v3 preset values) */
90
127
  --text-xs: 0.75rem;
@@ -8,7 +8,10 @@
8
8
  }
9
9
 
10
10
  @utility btn-common {
11
- @apply flex text-sm h-10 font-semibold px-xl py-normal items-center justify-center rounded-lg shadow-sm transition-all duration-200 disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed;
11
+ /* Compact size (anh 29/08): h-8 + 13px label the pre-V2 h-10 read too
12
+ large against the V2 density. leading-none: arbitrary text-[13px] inherits
13
+ lh 1.5 and Archivo's uneven leading floats the ink ~1px above center. */
14
+ @apply flex text-[13px] leading-none h-8 font-semibold px-large py-small items-center justify-center rounded-lg shadow-sm transition-all duration-200 disabled:opacity-50 disabled:pointer-events-none disabled:cursor-not-allowed;
12
15
  }
13
16
 
14
17
  @utility btn-menu-common {
@@ -0,0 +1,29 @@
1
+ <div class="flex flex-col gap-small">
2
+ @for (item of items(); track item.label) {
3
+ <button
4
+ type="button"
5
+ class="text-left rounded-control px-normal py-small -mx-normal transition-colors"
6
+ [class.hover:bg-surface-sunken]="interactive()"
7
+ [class.cursor-default]="!interactive()"
8
+ [tabindex]="interactive() ? 0 : -1"
9
+ (click)="onSelect(item)"
10
+ >
11
+ <div class="flex items-baseline justify-between gap-normal">
12
+ <span class="txt-body-medium-01 truncate">{{ item.label }}</span>
13
+ <span class="text-sm font-semibold text-strong shrink-0" style="font-family: var(--og-font-figure)">
14
+ {{ item.valueLabel ?? item.value }}
15
+ </span>
16
+ </div>
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)"
24
+ [style.width.%]="widthOf(item)"
25
+ ></div>
26
+ </div>
27
+ </button>
28
+ }
29
+ </div>
@@ -18,8 +18,8 @@
18
18
  [attr.fill]="
19
19
  buttonVariant() !== 'alternative' ? 'rgb(var(--text-primary))' : 'rgb(var(--primary))'
20
20
  "
21
- width="20"
22
- height="20"
21
+ width="16"
22
+ height="16"
23
23
  viewBox="0 0 24 24"
24
24
  xmlns="http://www.w3.org/2000/svg"
25
25
  >
@@ -219,17 +219,19 @@
219
219
  @if (icon) {
220
220
  <fx-ui-hero-icon
221
221
  [icon]="icon"
222
- [size]="20"
222
+ [size]="16"
223
223
  class="flex"
224
224
  [ngClass]="{
225
225
  'text-text-inverse': buttonVariant() === 'default',
226
226
  'text-text-primary': buttonVariant() === 'alternative',
227
- 'text-white': buttonVariant() === 'primary' || buttonVariant() === 'success',
227
+ 'text-accent-contrast': buttonVariant() === 'primary',
228
+ 'text-surface': buttonVariant() === 'success',
228
229
  }"
229
230
  ></fx-ui-hero-icon>
230
231
  }
231
232
  @if (label) {
232
- <div>{{ label }}</div>
233
+ <!-- text-box: optical centering — Archivo's leading floats ink up otherwise -->
234
+ <div class="[text-box:trim-both_cap_alphabetic]">{{ label }}</div>
233
235
  }
234
236
  </div>
235
237
  </ng-container>
@@ -25,7 +25,7 @@
25
25
  <span class="absolute inset-0 flex items-center justify-center">
26
26
  <svg
27
27
  xmlns="http://www.w3.org/2000/svg"
28
- class="text-white w-4 h-4 transition-opacity duration-150"
28
+ class="text-accent-contrast w-4 h-4 transition-opacity duration-150"
29
29
  viewBox="0 0 24 24"
30
30
  fill="none"
31
31
  stroke="currentColor"
@@ -0,0 +1,8 @@
1
+ @if (label()) {
2
+ <div class="txt-utility-caption font-bold text-text-secondary mb-normal">{{ label() }}</div>
3
+ }
4
+ <pre
5
+ class="m-0 p-large rounded-control border border-border bg-surface overflow-auto txt-body-compact-01 text-strong"
6
+ [style.max-height]="maxHeight() || null"
7
+ style="font-family: var(--og-font-mono); white-space: pre; tab-size: 2"
8
+ >{{ code() }}</pre>
@@ -1,18 +1,19 @@
1
1
  <div class="drawer-host-wrapper">
2
- <a
3
- href="/"
4
- class="h-[60px]"
5
- [ngClass]="{ 'flex items-center justify-center': !isExpanded, 'p-4': isExpanded }"
6
- >
2
+ <a href="/" class="drawer-brand" [class.is-collapsed]="!isExpanded">
7
3
  @if (logo) {
8
- <img
9
- [src]="logo"
10
- alt="Home"
11
- class="h-[20px] w-auto transition-all duration-300 ease-in-out"
12
- [ngClass]="{ 'scale-90': !isExpanded, 'ml-5': isExpanded }"
13
- />
4
+ <img [src]="logo" alt="Home" class="drawer-brand-mark" />
14
5
  } @else {
15
- <div class="h-[30px] w-auto transition-all duration-300 ease-in-out"></div>
6
+ <div class="drawer-brand-mark"></div>
7
+ }
8
+ @if (isExpanded && (logoText || logoSubtext)) {
9
+ <span class="drawer-brand-copy">
10
+ @if (logoText) {
11
+ <span class="drawer-brand-text">{{ logoText }}</span>
12
+ }
13
+ @if (logoSubtext) {
14
+ <span class="drawer-brand-subtext">{{ logoSubtext }}</span>
15
+ }
16
+ </span>
16
17
  }
17
18
  </a>
18
19
  <div class="drawer-container" [@drawerExpand]="getDrawerState()">
@@ -25,6 +26,20 @@
25
26
  }
26
27
  </nav>
27
28
 
29
+ <!-- V2: bottom section (Administration group, user block, …) -->
30
+ <div class="mt-auto">
31
+ @if (footerItems().length) {
32
+ <nav class="drawer-footer-nav">
33
+ @for (item of footerItems(); track item) {
34
+ <ng-container
35
+ *ngTemplateOutlet="drawerItem; context: { $implicit: item, level: 0 }"
36
+ ></ng-container>
37
+ }
38
+ </nav>
39
+ }
40
+ <ng-content select="[drawerFooter]"></ng-content>
41
+ </div>
42
+
28
43
  <!-- Version -->
29
44
  @if (version) {
30
45
  <div class="drawer-version" [matTooltip]="!isExpanded ? version : ''" matTooltipPosition="right">
@@ -59,6 +74,13 @@
59
74
 
60
75
  <!-- Recursive Template for Drawer Items -->
61
76
  <ng-template #drawerItem let-item let-level="level">
77
+ @if (item.heading) {
78
+ @if (isExpanded) {
79
+ <div class="drawer-section-label">{{ item.label }}</div>
80
+ } @else {
81
+ <div class="drawer-section-rule"></div>
82
+ }
83
+ } @else {
62
84
  <div class="drawer-item-wrapper">
63
85
  <!-- Item Button/Link -->
64
86
  @if (item.route && !item.children) {
@@ -125,4 +147,5 @@
125
147
  </div>
126
148
  }
127
149
  </div>
150
+ }
128
151
  </ng-template>
@@ -6,11 +6,75 @@
6
6
  }
7
7
 
8
8
  .drawer-host-wrapper {
9
- @apply relative h-full border-r border-border-default;
9
+ /* Column flex: logo (fixed) + container (flex-1). The container was h-full
10
+ UNDER a 60px logo -> the drawer overflowed the viewport by 60px and the
11
+ footer (account block) sagged below the fold; opening its menu then
12
+ scroll-jumped the page. */
13
+ /* Nền SECONDARY (--og-page-bg) đặt ở WRAPPER chứ không ở .drawer-container:
14
+ hàng brand nằm ngoài container, nếu tô ở container thì 60px trên cùng
15
+ lộ nền của trang. Sáng: drawer xám, nội dung trắng; tối: drawer tối hơn
16
+ surface — đúng quy ước sidebar của cả hai chiều. */
17
+ @apply relative h-full border-r border-border-default flex flex-col bg-page;
18
+ }
19
+
20
+ /* Brand row — phải thẳng hàng với icon của nav item.
21
+ Icon nav nằm ở 24px tính từ mép drawer: 8px của .drawer-container (px-2)
22
+ + 16px padding-left inline của .drawer-item. Brand nằm NGOÀI container
23
+ nên phải tự cộng đủ 24px (trước đây p-4 + ml-5 = 36px → lệch 12px). */
24
+ .drawer-brand {
25
+ @apply h-[60px] shrink-0 flex items-center gap-3 no-underline overflow-hidden;
26
+ padding-left: 24px;
27
+ padding-right: 8px;
28
+ transition: padding 300ms cubic-bezier(0.4, 0, 0.2, 1);
29
+ /* max-width là BẮT BUỘC: .drawer-host-wrapper là flex column có width auto
30
+ nên bề rộng của nó = max bề rộng các con. Chỉ .drawer-container được
31
+ animate 70/260px, còn hàng brand thì tự do — một tagline dài (VD "Binary
32
+ Exploit Intelligence Platform") sẽ NỚI RỘNG cả drawer thay vì xuống dòng.
33
+ Hai con số dưới đây phải khớp trigger drawerExpand trong drawer.component.ts. */
34
+ max-width: 260px;
35
+
36
+ /* Thu gọn: drawer rộng 70px, icon nav (20px @ 24px) có tâm 34px ≈ tâm
37
+ drawer 35px → căn giữa là khớp, không cần trừ bì. */
38
+ &.is-collapsed {
39
+ @apply justify-center;
40
+ padding-left: 0;
41
+ padding-right: 0;
42
+ max-width: 70px;
43
+ }
44
+ }
45
+
46
+ .drawer-brand-mark {
47
+ @apply h-[20px] w-auto shrink-0;
48
+ }
49
+
50
+ /* Cột chữ: title + subtitle xếp dọc. min-w-0 để truncate hoạt động khi
51
+ drawer bị bóp — thiếu nó thì chữ đẩy tràn ra ngoài thay vì cắt. */
52
+ .drawer-brand-copy {
53
+ @apply flex flex-col min-w-0 leading-tight;
54
+ }
55
+
56
+ /* Wordmark: dùng CHỮ THẬT chứ không phải ảnh text_logo.png — ảnh đó là
57
+ navy đặc nên chìm hẳn trên theme dark; chữ ăn --og-text nên đúng cả 6. */
58
+ .drawer-brand-text {
59
+ @apply truncate text-[15px] font-semibold text-text-primary select-none;
60
+ font-family: var(--og-font-display);
61
+ letter-spacing: -0.01em;
62
+ }
63
+
64
+ /* Subtitle: 11px muted, cùng cỡ với .drawer-version và caption
65
+ ADMINISTRATION nên không tự sinh ra một bậc typography mới. */
66
+ /* line-clamp-2 chứ không phải truncate: tagline dài (VD "Binary Exploit
67
+ Intelligence Platform") không lọt một dòng trong cột chữ ~188px của drawer
68
+ 260px, truncate sẽ cắt mất chữ. Hai dòng vẫn nằm gọn trong 60px của
69
+ .drawer-brand (18.75 + 1 + 27.5 = 47px). */
70
+ .drawer-brand-subtext {
71
+ @apply line-clamp-2 text-[11px] text-text-placeholder select-none mt-[1px];
72
+ letter-spacing: var(--og-tracking-label);
10
73
  }
11
74
 
12
75
  .drawer-container {
13
- @apply px-2 h-full bg-bg-primary flex flex-col overflow-auto;
76
+ /* Nền do .drawer-host-wrapper lo container để trong suốt. */
77
+ @apply px-2 flex-1 min-h-0 flex flex-col overflow-hidden;
14
78
  transition: width 300ms cubic-bezier(0.4, 0, 0.2, 1);
15
79
  }
16
80
 
@@ -33,6 +97,10 @@
33
97
  }
34
98
  }
35
99
 
100
+ .drawer-footer-nav {
101
+ @apply border-t border-border-default pt-2 pb-1 px-0;
102
+ }
103
+
36
104
  .drawer-nav {
37
105
  @apply flex-1 overflow-y-auto overflow-x-hidden py-2;
38
106
 
@@ -54,6 +122,15 @@
54
122
  }
55
123
  }
56
124
 
125
+ .drawer-section-label {
126
+ @apply px-large pt-4 pb-1 text-[11px] font-semibold uppercase text-faint select-none;
127
+ letter-spacing: var(--og-tracking-label);
128
+ }
129
+
130
+ .drawer-section-rule {
131
+ @apply mx-3 my-3 border-t border-border-default;
132
+ }
133
+
57
134
  .drawer-item-wrapper {
58
135
  @apply relative;
59
136
  }
@@ -62,15 +139,35 @@
62
139
  @apply flex items-center mt-1 w-full min-h-8 py-2 px-large border-none bg-transparent cursor-pointer no-underline text-text-primary relative rounded;
63
140
  transition: all 200ms ease;
64
141
 
142
+ /* Hover = glass mờ hơn: chỉ translucent, chưa có viền/shadow, để không
143
+ nhầm với hàng đang chọn. */
65
144
  &:hover {
66
- @apply bg-bg-hover text-text-primary scale-95;
145
+ @apply text-text-primary scale-95;
146
+ background: rgb(var(--og-surface-raised) / 0.5);
67
147
  }
68
148
 
149
+ /* Hàng đang chọn = GLASS, KHÔNG dùng màu primary (anh Tú chốt 31/08):
150
+ một tấm surface trong suốt nổi trên nền secondary của drawer + viền
151
+ hairline + shadow nhẹ. Chữ/icon giữ màu text thường.
152
+ - surface-RAISED (không phải surface) vì ở charcoal raised (#211c1b)
153
+ sáng hơn page-bg (#0e0e10) nên tấm kính vẫn nổi; ở porcelain cả hai
154
+ đều là trắng nên không khác gì.
155
+ - Viền vẽ bằng inset box-shadow chứ không phải `border`: .drawer-item
156
+ khai border-none, thêm border thật sẽ đẩy layout 1px.
157
+ - backdrop-blur chỉ có tác dụng khi nền sau có hoạ tiết/gradient; nền
158
+ phẳng thì hiệu ứng đến từ độ trong + viền + shadow. */
69
159
  &.active {
70
- @apply bg-gradient-primary text-white font-medium;
160
+ @apply text-text-primary font-medium;
161
+ background: rgb(var(--og-surface-raised) / 0.8);
162
+ backdrop-filter: blur(12px);
163
+ /* shadow viết literal chứ không dùng var(--shadow-sm): biến đó do @theme
164
+ của Tailwind sinh, không chắc có mặt trong scope của component SCSS. */
165
+ box-shadow:
166
+ inset 0 0 0 1px rgb(var(--og-border)),
167
+ 0 1px 2px 0 rgb(var(--shadow-color) / 0.05);
71
168
 
72
- .item-icon {
73
- @apply text-white;
169
+ &:hover {
170
+ background: rgb(var(--og-surface-raised) / 0.95);
74
171
  }
75
172
  }
76
173
 
@@ -114,21 +211,10 @@
114
211
 
115
212
  .drawer-version {
116
213
  @apply flex items-center justify-center py-3 px-4 text-text-placeholder text-[11px] border-t border-border-default select-none;
117
- font-family: 'Roboto', monospace;
214
+ font-family: var(--og-font-mono);
118
215
  }
119
216
 
120
217
  .drawer-version-dot {
121
218
  @apply block w-1.5 h-1.5 rounded-full bg-text-placeholder;
122
219
  }
123
220
 
124
- /* Collapsed mode: center the icons */
125
- .drawer-container[style*='width: 64px'] {
126
- .drawer-item {
127
- @apply justify-center;
128
- padding-left: 16px !important;
129
- }
130
-
131
- .item-content {
132
- @apply justify-center;
133
- }
134
- }
@@ -0,0 +1,42 @@
1
+ <div class="flex flex-col gap-large">
2
+ @if (hasSelection()) {
3
+ <button type="button" class="txt-link self-end" (click)="reset()">
4
+ {{ resetLabel() }}
5
+ </button>
6
+ }
7
+ @for (group of groups(); track group.id) {
8
+ <div role="group" [attr.aria-label]="group.label">
9
+ <div
10
+ class="txt-table-header uppercase mb-small"
11
+ [style.letter-spacing]="'var(--og-tracking-label)'"
12
+ >
13
+ {{ group.label }}
14
+ </div>
15
+ <div class="flex flex-col gap-xs">
16
+ @for (option of group.options; track option.value) {
17
+ <button
18
+ type="button"
19
+ (click)="toggle(group, option)"
20
+ [attr.aria-pressed]="isSelected(group, option)"
21
+ class="flex items-center gap-normal px-normal py-small rounded-control text-left transition-colors"
22
+ [ngClass]="
23
+ isSelected(group, option)
24
+ ? 'bg-accent-soft text-accent-soft-fg font-medium'
25
+ : 'text-body hover:bg-surface-sunken'
26
+ "
27
+ >
28
+ @if (option.tone) {
29
+ <fx-ui-status-dot [tone]="option.tone" [size]="7" />
30
+ }
31
+ <span class="text-sm truncate flex-1">{{ option.label }}</span>
32
+ @if (option.count !== undefined) {
33
+ <span class="txt-tag-label text-faint" style="font-family: var(--og-font-figure)">
34
+ {{ option.count }}
35
+ </span>
36
+ }
37
+ </button>
38
+ }
39
+ </div>
40
+ </div>
41
+ }
42
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="flex flex-wrap items-center gap-small">
2
+ @for (option of options(); track option.value) {
3
+ <button
4
+ type="button"
5
+ [attr.aria-pressed]="isActive(option)"
6
+ class="flex items-center gap-small px-semi py-xs rounded-full border txt-utility-caption font-bold cursor-pointer transition-colors"
7
+ [ngClass]="pillClass(option)"
8
+ (click)="valueChange.emit(option.value)"
9
+ >
10
+ {{ option.label }}
11
+ @if (option.count !== null && option.count !== undefined) {
12
+ <span [ngClass]="countClass(option)" style="font-family: var(--og-font-figure)">{{ option.count }}</span>
13
+ }
14
+ </button>
15
+ }
16
+ </div>
@@ -10,20 +10,20 @@
10
10
  }
11
11
 
12
12
  .kanban-column {
13
- @apply flex flex-col min-w-[300px] bg-gray-50 dark:bg-gray-800 rounded-lg;
13
+ @apply flex flex-col min-w-[300px] bg-surface-sunken rounded-lg;
14
14
  flex-shrink: 0;
15
15
  }
16
16
 
17
17
  .kanban-column-header {
18
- @apply flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700;
18
+ @apply flex items-center justify-between p-4 border-b border-border;
19
19
  }
20
20
 
21
21
  .kanban-column-title {
22
- @apply text-lg font-semibold text-gray-900 dark:text-gray-100 m-0;
22
+ @apply text-lg font-semibold text-strong m-0;
23
23
  }
24
24
 
25
25
  .kanban-column-count {
26
- @apply px-2 py-1 text-sm font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-full;
26
+ @apply px-2 py-1 text-sm font-medium bg-neutral-soft text-neutral-soft-fg rounded-full;
27
27
  }
28
28
 
29
29
  .kanban-column-content {
@@ -32,7 +32,7 @@
32
32
  }
33
33
 
34
34
  .kanban-item {
35
- @apply bg-white dark:bg-gray-700 rounded-lg shadow-sm border border-gray-200 dark:border-gray-600;
35
+ @apply bg-surface-raised rounded-lg shadow-sm border border-border;
36
36
  @apply transition-shadow duration-200;
37
37
  cursor: move;
38
38
 
@@ -42,18 +42,18 @@
42
42
  }
43
43
 
44
44
  .kanban-item-default {
45
- @apply p-4 text-gray-900 dark:text-gray-100;
45
+ @apply p-4 text-strong;
46
46
  }
47
47
 
48
48
  .kanban-item-placeholder {
49
- @apply bg-gray-200 dark:bg-gray-600 rounded-lg border-2 border-dashed border-gray-400 dark:border-gray-500;
49
+ @apply bg-neutral-soft rounded-lg border-2 border-dashed border-border-strong;
50
50
  @apply opacity-50;
51
51
  min-height: 60px;
52
52
  }
53
53
 
54
54
  .kanban-column-empty {
55
- @apply flex items-center justify-center h-32 text-gray-400 dark:text-gray-500 text-sm;
56
- @apply border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg;
55
+ @apply flex items-center justify-center h-32 text-faint text-sm;
56
+ @apply border-2 border-dashed border-border rounded-lg;
57
57
  }
58
58
 
59
59
  // CDK Drag and Drop Styles
@@ -0,0 +1,20 @@
1
+ <div class="flex items-center gap-normal min-w-0">
2
+ @if (dotTone() !== undefined) {
3
+ <fx-ui-status-dot [tone]="dotTone() ?? null" [pulse]="dotPulse()" />
4
+ }
5
+ <span class="txt-body-medium-01 truncate flex-1">{{ title() }}</span>
6
+ @for (badge of badges(); track badge.label) {
7
+ <span
8
+ class="txt-tag-label px-normal py-[1px] rounded-pill whitespace-nowrap"
9
+ [ngClass]="badgeClass(badge)"
10
+ >
11
+ {{ badge.label }}
12
+ </span>
13
+ }
14
+ </div>
15
+ @if (meta()) {
16
+ <div class="txt-utility-description mt-xs truncate" [class.ml-large]="dotTone() !== undefined">
17
+ {{ meta() }}
18
+ </div>
19
+ }
20
+ <ng-content></ng-content>
@@ -9,3 +9,25 @@
9
9
  flex-shrink: 0;
10
10
  }
11
11
  }
12
+
13
+ /* Facets rail: full-width when stacked; fixed narrow column on lg+. */
14
+ .fx-md-facets {
15
+ width: 100%;
16
+ }
17
+
18
+ @media (min-width: 1080px) {
19
+ .fx-md-facets {
20
+ width: 230px;
21
+ flex-shrink: 0;
22
+ }
23
+ }
24
+
25
+ /* prevnext style: the Material paginator stays as the paging engine but is
26
+ visually removed (display:none would break its measurements on re-show). */
27
+ .fx-md-paginator-hidden {
28
+ position: absolute !important;
29
+ width: 1px;
30
+ height: 1px;
31
+ overflow: hidden;
32
+ clip-path: inset(50%);
33
+ }
@@ -1,4 +1,10 @@
1
1
  <div class="flex flex-col lg:flex-row items-start gap-xl" [style.--fx-md-list-width]="listWidth()">
2
+ <!-- Facets rail (V2 3-column lookup) — wrapper always rendered so ng-content
3
+ 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>
6
+ </div>
7
+
2
8
  <!-- Master: list pane -->
3
9
  <div class="fx-md-list card-common">
4
10
  <ng-content select="[mdHeader]"></ng-content>
@@ -24,7 +30,7 @@
24
30
  class="text-left rounded-lg border p-semi transition-colors"
25
31
  [ngClass]="
26
32
  isSelected(row)
27
- ? 'border-gradient-primary-start bg-gradient-primary-start/5'
33
+ ? 'border-accent bg-accent-soft/60'
28
34
  : 'border-border-default hover:border-border-interactive bg-bg-primary'
29
35
  "
30
36
  >
@@ -44,10 +50,36 @@
44
50
  wires page events — a binding here would double-fetch every change. -->
45
51
  <mat-paginator
46
52
  class="mt-normal"
53
+ [class.fx-md-paginator-hidden]="paginationStyle() === 'prevnext'"
47
54
  [length]="table.total"
48
55
  [pageSize]="table.pageSize"
49
56
  [pageSizeOptions]="pageSizeOptions()"
50
57
  ></mat-paginator>
58
+ @if (paginationStyle() === 'prevnext') {
59
+ <div class="flex items-center justify-between mt-normal">
60
+ <span class="txt-utility-description" style="font-family: var(--og-font-figure)">
61
+ {{ rangeText() }}
62
+ </span>
63
+ <div class="flex gap-small">
64
+ <button
65
+ type="button"
66
+ class="btn-alternative !h-8 !px-semi text-sm"
67
+ [disabled]="!paginator().hasPreviousPage()"
68
+ (click)="paginator().previousPage()"
69
+ >
70
+ {{ prevLabel() }}
71
+ </button>
72
+ <button
73
+ type="button"
74
+ class="btn-alternative !h-8 !px-semi text-sm"
75
+ [disabled]="!paginator().hasNextPage()"
76
+ (click)="paginator().nextPage()"
77
+ >
78
+ {{ nextLabel() }}
79
+ </button>
80
+ </div>
81
+ </div>
82
+ }
51
83
  </div>
52
84
 
53
85
  <!-- Detail pane -->