@foxeltech/angular-ui 0.3.1 → 0.4.0

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 (54) hide show
  1. package/fesm2022/foxeltech-angular-ui-chart.mjs +392 -0
  2. package/fesm2022/foxeltech-angular-ui-chart.mjs.map +1 -0
  3. package/fesm2022/foxeltech-angular-ui-flow.mjs +38 -0
  4. package/fesm2022/foxeltech-angular-ui-flow.mjs.map +1 -0
  5. package/fesm2022/foxeltech-angular-ui-rich-text.mjs +156 -0
  6. package/fesm2022/foxeltech-angular-ui-rich-text.mjs.map +1 -0
  7. package/fesm2022/foxeltech-angular-ui.mjs +773 -1382
  8. package/fesm2022/foxeltech-angular-ui.mjs.map +1 -1
  9. package/fx-icons.svg +97 -0
  10. package/package.json +14 -8
  11. package/src/lib/styles/tailwind.css +0 -1
  12. package/src/lib/ui/components/button/button.component.html +14 -14
  13. package/src/lib/ui/components/checkbox/checkbox.component.html +2 -2
  14. package/src/lib/ui/components/circle-progress-bar/circle-progress-bar.component.html +2 -2
  15. package/src/lib/ui/components/datetime-picker/datetime-picker.component.html +49 -27
  16. package/src/lib/ui/components/datetime-picker/datetime-picker.component.scss +25 -137
  17. package/src/lib/ui/components/dnd-upload/dnd-upload.component.html +3 -3
  18. package/src/lib/ui/components/drawer/drawer.component.html +1 -1
  19. package/src/lib/ui/components/empty-state/empty-state.component.html +2 -2
  20. package/src/lib/ui/components/hero-icon/hero-icon.component.html +10 -9
  21. package/src/lib/ui/components/info-field/info-field.component.html +1 -1
  22. package/src/lib/ui/components/input/input.component.html +6 -6
  23. package/src/lib/ui/components/kanban-board/kanban-board.component.html +1 -1
  24. package/src/lib/ui/components/loading-panel/loading-panel.component.html +1 -1
  25. package/src/lib/ui/components/master-detail/master-detail.component.html +9 -9
  26. package/src/lib/ui/components/menu/menu.component.html +27 -0
  27. package/src/lib/ui/components/radio-button/radio-button.component.html +3 -3
  28. package/src/lib/ui/components/radio-button-toggle/radio-button-toggle.component.html +3 -3
  29. package/src/lib/ui/components/search-bar/search-bar.component.html +1 -1
  30. package/src/lib/ui/components/section-card/section-card.component.html +1 -1
  31. package/src/lib/ui/components/select/select.component.html +6 -6
  32. package/src/lib/ui/components/skeleton-detail/skeleton-detail.component.html +2 -2
  33. package/src/lib/ui/components/skeleton-list/skeleton-list.component.html +1 -1
  34. package/src/lib/ui/components/skeleton-table-loading/skeleton-table-loading.component.html +5 -5
  35. package/src/lib/ui/components/slider/slider.component.html +7 -7
  36. package/src/lib/ui/components/stat-cards/stat-cards.component.html +1 -1
  37. package/src/lib/ui/components/switch/switch.component.html +1 -1
  38. package/src/lib/ui/components/tab-group/tab-group.component.html +4 -4
  39. package/src/lib/ui/components/table-cell/table-cell.component.html +2 -2
  40. package/src/lib/ui/components/tag/tag.component.html +4 -4
  41. package/src/lib/ui/components/toast/toast.component.html +7 -7
  42. package/src/lib/ui/components/toast-container/toast-container.component.html +1 -0
  43. package/src/lib/ui/components/tree-diagram/tree-diagram.component.html +2 -2
  44. package/tailwind.css +0 -1
  45. package/types/foxeltech-angular-ui-chart.d.ts +53 -0
  46. package/types/foxeltech-angular-ui-flow.d.ts +14 -0
  47. package/types/foxeltech-angular-ui-rich-text.d.ts +35 -0
  48. package/types/foxeltech-angular-ui.d.ts +394 -387
  49. package/src/lib/ui/components/chart/chart.component.html +0 -20
  50. package/src/lib/ui/components/chart/chart.component.scss +0 -49
  51. package/src/lib/ui/components/flow-connection/flow-connection.component.html +0 -10
  52. package/src/lib/ui/components/flow-connection/flow-connection.component.scss +0 -0
  53. package/src/lib/ui/components/rich-text-area/rich-text-area.component.html +0 -3
  54. package/src/lib/ui/components/rich-text-area/rich-text-area.component.scss +0 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foxeltech/angular-ui",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -13,19 +13,13 @@
13
13
  "@angular/forms": "^20.3.0 || ^21.0.0",
14
14
  "@angular/material": "^20.2.7 || ^21.0.0",
15
15
  "@angular/cdk": "^20.2.7 || ^21.0.0",
16
- "lodash": "^4.17.21",
17
16
  "@ngx-translate/core": "^14.0.0",
18
- "@ngx-translate/http-loader": "^7.0.0",
19
- "moment": "^2.30.1"
17
+ "@ngx-translate/http-loader": "^7.0.0"
20
18
  },
21
19
  "dependencies": {
22
20
  "tslib": "^2.3.0",
23
- "@danielmoncada/angular-datetime-picker": "^21.0.0",
24
- "@danielmoncada/angular-datetime-picker-moment-adapter": "^8.0.0",
25
21
  "echarts": "^6.0.0",
26
22
  "ngx-echarts": "^20.0.2",
27
- "d3": "^7.9.0",
28
- "@dimaslz/ng-heroicons": "^1.19.1",
29
23
  "quill": "^2.0.3",
30
24
  "ngx-vflow": "^1.16.2",
31
25
  "@microsoft/signalr": "^10.0.0"
@@ -45,6 +39,18 @@
45
39
  ".": {
46
40
  "types": "./types/foxeltech-angular-ui.d.ts",
47
41
  "default": "./fesm2022/foxeltech-angular-ui.mjs"
42
+ },
43
+ "./chart": {
44
+ "types": "./types/foxeltech-angular-ui-chart.d.ts",
45
+ "default": "./fesm2022/foxeltech-angular-ui-chart.mjs"
46
+ },
47
+ "./flow": {
48
+ "types": "./types/foxeltech-angular-ui-flow.d.ts",
49
+ "default": "./fesm2022/foxeltech-angular-ui-flow.mjs"
50
+ },
51
+ "./rich-text": {
52
+ "types": "./types/foxeltech-angular-ui-rich-text.d.ts",
53
+ "default": "./fesm2022/foxeltech-angular-ui-rich-text.mjs"
48
54
  }
49
55
  },
50
56
  "sideEffects": [
@@ -8,7 +8,6 @@
8
8
  @import './components.css' layer(components);
9
9
  @import './dialogs.css' layer(components);
10
10
  @import './animations.css';
11
- @import '@danielmoncada/angular-datetime-picker/assets/style/picker.min.css';
12
11
 
13
12
  html,
14
13
  body {
@@ -1,22 +1,22 @@
1
1
  <button
2
- [attr.type]="type"
3
- [attr.aria-label]="ariaLabel || null"
4
- [attr.aria-busy]="loading || null"
5
- [class]="class"
2
+ [attr.type]="type()"
3
+ [attr.aria-label]="ariaLabel() || null"
4
+ [attr.aria-busy]="loading() || null"
5
+ [class]="class()"
6
6
  [ngClass]="{
7
- 'btn-default': buttonVariant === 'default',
8
- 'btn-primary': buttonVariant === 'primary',
9
- 'btn-success': buttonVariant === 'success',
10
- 'btn-alternative': buttonVariant === 'alternative',
7
+ 'btn-default': buttonVariant() === 'default',
8
+ 'btn-primary': buttonVariant() === 'primary',
9
+ 'btn-success': buttonVariant() === 'success',
10
+ 'btn-alternative': buttonVariant() === 'alternative',
11
11
  }"
12
- [disabled]="disabled || loading"
12
+ [disabled]="disabled() || loading()"
13
13
  (click)="onClick()"
14
14
  >
15
- @if (loading) {
15
+ @if (loading()) {
16
16
  <div class="flex w-full items-center justify-center" aria-hidden="true">
17
17
  <svg
18
18
  [attr.fill]="
19
- buttonVariant !== 'alternative' ? 'rgb(var(--text-primary))' : 'rgb(var(--primary))'
19
+ buttonVariant() !== 'alternative' ? 'rgb(var(--text-primary))' : 'rgb(var(--primary))'
20
20
  "
21
21
  width="20"
22
22
  height="20"
@@ -222,9 +222,9 @@
222
222
  [size]="20"
223
223
  class="flex"
224
224
  [ngClass]="{
225
- 'text-text-inverse': buttonVariant === 'default',
226
- 'text-text-primary': buttonVariant === 'alternative',
227
- 'text-white': buttonVariant === 'primary' || buttonVariant === 'success',
225
+ 'text-text-inverse': buttonVariant() === 'default',
226
+ 'text-text-primary': buttonVariant() === 'alternative',
227
+ 'text-white': buttonVariant() === 'primary' || buttonVariant() === 'success',
228
228
  }"
229
229
  ></fx-ui-hero-icon>
230
230
  }
@@ -15,8 +15,8 @@
15
15
  <span
16
16
  class="relative w-4 h-4 border rounded-full transition-colors duration-150 shrink-0 peer-focus-visible:ring-2 peer-focus-visible:ring-primary/50"
17
17
  [ngClass]="{
18
- 'rounded-full': rounded,
19
- 'rounded-sm': !rounded,
18
+ 'rounded-full': rounded(),
19
+ 'rounded-sm': !rounded(),
20
20
  'border-border-strong bg-transparent': !value,
21
21
  'border-transparent bg-gradient-primary': value,
22
22
  }"
@@ -28,7 +28,7 @@
28
28
  style="transition: stroke-dashoffset 300ms linear"
29
29
  ></circle>
30
30
 
31
- @if(showPercent){
31
+ @if(showPercent()){
32
32
  <text
33
33
  [attr.x]="radius"
34
34
  [attr.y]="radius"
@@ -37,7 +37,7 @@
37
37
  class="text-xs font-medium"
38
38
  fill="currentColor"
39
39
  >
40
- {{ percent }}%
40
+ {{ percent() }}%
41
41
  </text>
42
42
  }
43
43
  </svg>
@@ -2,39 +2,61 @@
2
2
  @if (label) {
3
3
  <label class="txt-field-label" [attr.for]="fieldId">
4
4
  {{ label }}
5
- @if (required) {
5
+ @if (required()) {
6
6
  <span class="txt-required" aria-hidden="true">*</span>
7
7
  }
8
8
  </label>
9
9
  }
10
10
 
11
- <div class="relative">
12
- <input
13
- [id]="fieldId"
14
- [owlDateTimeTrigger]="picker"
15
- [owlDateTime]="picker"
16
- [placeholder]="placeholder"
17
- [disabled]="disabled"
18
- [min]="minDate"
19
- [max]="maxDate"
20
- (blur)="onBlur()"
21
- [value]="value"
22
- readonly
23
- class="input-default pr-9"
24
- />
25
- <span
26
- [owlDateTimeTrigger]="picker"
27
- class="absolute inset-y-0 right-0 flex items-center pr-2.5 cursor-pointer text-text-placeholder hover:text-text-primary transition-colors"
28
- >
29
- <fx-ui-hero-icon icon="calendar" [size]="20" class="text-text-primary"></fx-ui-hero-icon>
30
- </span>
11
+ <div class="flex gap-normal">
12
+ @if (showDate) {
13
+ <div class="relative flex-1">
14
+ <input
15
+ [id]="fieldId"
16
+ [matDatepicker]="datePicker"
17
+ [placeholder]="placeholder()"
18
+ [disabled]="disabled"
19
+ [min]="min"
20
+ [max]="max"
21
+ [value]="dateValue"
22
+ (dateChange)="onDateChange($event.value)"
23
+ (click)="!disabled && datePicker.open()"
24
+ (blur)="onBlur()"
25
+ readonly
26
+ class="input-default pr-9"
27
+ />
28
+ <span
29
+ (click)="!disabled && datePicker.open()"
30
+ class="absolute inset-y-0 right-0 flex items-center pr-2.5 cursor-pointer text-text-placeholder hover:text-text-primary transition-colors"
31
+ >
32
+ <fx-ui-hero-icon icon="calendar" [size]="20" class="text-text-primary"></fx-ui-hero-icon>
33
+ </span>
34
+ <mat-datepicker #datePicker></mat-datepicker>
35
+ </div>
36
+ }
37
+ @if (showTime) {
38
+ <div class="relative" [class.flex-1]="!showDate" [class.w-32]="showDate">
39
+ <input
40
+ [matTimepicker]="timePicker"
41
+ placeholder="HH:mm"
42
+ [disabled]="disabled"
43
+ [value]="dateValue"
44
+ (valueChange)="onTimeChange($event)"
45
+ (click)="!disabled && timePicker.open()"
46
+ (blur)="onBlur()"
47
+ readonly
48
+ class="input-default pr-9"
49
+ />
50
+ <span
51
+ (click)="!disabled && timePicker.open()"
52
+ class="absolute inset-y-0 right-0 flex items-center pr-2.5 cursor-pointer text-text-placeholder hover:text-text-primary transition-colors"
53
+ >
54
+ <fx-ui-hero-icon icon="clock" [size]="20" class="text-text-primary"></fx-ui-hero-icon>
55
+ </span>
56
+ <mat-timepicker #timePicker></mat-timepicker>
57
+ </div>
58
+ }
31
59
  </div>
32
- <owl-date-time
33
- #picker
34
- [startAt]="startAt"
35
- [pickerType]="type"
36
- (afterPickerClosed)="onValueChange($event)"
37
- ></owl-date-time>
38
60
  @if (invalid) {
39
61
  <div class="txt-invalid">{{ getErrorMessage(label) }}</div>
40
62
  }
@@ -1,147 +1,35 @@
1
1
  /* Tailwind v4: component styles compile in isolation — @reference pulls in the theme for @apply without re-emitting it. */
2
2
  @reference '../../../styles/tailwind.css';
3
3
 
4
- // ─── Owl DateTime Picker Custom Styles ───────────────────────────────────────
4
+ /* ─── Material date/time picker popups, themed with the fx tokens ──────────
5
+ (Replaces the old Owl picker skin — the popups render in an overlay, so
6
+ everything below is ::ng-deep on the panel classes.) */
5
7
 
6
- // Container
7
- ::ng-deep .owl-dt-container {
8
+ ::ng-deep .mat-datepicker-content,
9
+ ::ng-deep mat-timepicker .mat-timepicker-panel,
10
+ ::ng-deep .mat-timepicker-panel {
8
11
  @apply bg-bg-primary rounded-xl shadow-xl border border-border-default;
9
- width: 320px !important;
10
- }
11
-
12
- // Calendar table base
13
- ::ng-deep .owl-dt-calendar-table {
14
- @apply w-full border-collapse;
15
- }
16
-
17
- // Default cell hover — override red debug color
18
- ::ng-deep .owl-dt-calendar-table :not(.owl-dt-calendar-cell-disabled):hover > .owl-dt-calendar-cell-content:not(.owl-dt-calendar-cell-selected) {
19
- @apply bg-bg-hover;
20
- }
21
-
22
- // ── Current-month cell text color ─────────────────────────────────────────────
23
- // No !important here — lets out-month and disabled !important overrides win.
24
- // A direct declaration still beats the library's inherited rgba(0,0,0,.85).
25
- ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-cell-content {
26
12
  color: rgb(var(--text-primary));
27
- }
28
-
29
- // Selected cell
30
- ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-cell-selected {
31
- background-color: rgb(var(--primary)) !important;
32
- color: #fff !important;
33
- }
34
-
35
- // Today highlight (not selected)
36
- ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-cell-today:not(.owl-dt-calendar-cell-selected) {
37
- border-color: rgb(var(--primary)) !important;
38
- }
39
-
40
- // ── Disabled dates: text-text-placeholder ────────────────────────────────────
41
- // owl-dt-calendar-cell-disabled is on the <td>, content span is a direct child
42
- ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-cell-disabled > .owl-dt-calendar-cell-content {
43
- color: rgb(var(--text-placeholder)) !important;
44
- opacity: 0.6 !important;
45
- cursor: not-allowed;
46
- pointer-events: none;
47
- }
48
-
49
- // ── Other-month dates: text-text-secondary ────────────────────────────────────
50
- // In v20, owl-dt-calendar-cell-out is applied ON the <span.owl-dt-calendar-cell-content>
51
- // itself (via [ngClass]), NOT on the parent <td>. So target it directly.
52
- ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-cell-content.owl-dt-calendar-cell-out {
53
- color: rgb(var(--text-secondary)) !important;
54
- opacity: 0.6 !important;
55
- }
56
-
57
- // ── Timer input ───────────────────────────────────────────────────────────────
58
- ::ng-deep .owl-dt-timer-content .owl-dt-timer-input {
59
- @apply bg-bg-primary border border-border-strong text-text-primary rounded-lg;
60
- }
61
-
62
- // ── Period button (hide arrow) ────────────────────────────────────────────────
63
- ::ng-deep .owl-dt-control-period-button .owl-dt-control-button-arrow {
64
- display: none;
65
- }
66
13
 
67
- // ── Set / Cancel footer buttons ───────────────────────────────────────────────
68
- // Library sets .owl-dt-container-buttons { height: 2em; color: #3f51b5 } — override both
69
- ::ng-deep .owl-dt-container-buttons {
70
- height: auto !important;
71
- color: rgb(var(--text-primary)) !important; // reset inherited primary color on text
72
- display: flex !important;
73
- align-items: center !important;
74
- justify-content: flex-end !important;
75
- gap: 8px;
76
- padding: 8px 12px;
77
- border-top: 1px solid rgb(var(--border-default));
78
- }
79
-
80
- // Both buttons — explicit 32px height so inner height:100% resolves correctly
81
- ::ng-deep .owl-dt-container-buttons .owl-dt-container-control-button {
82
- width: auto !important;
83
- height: 32px !important;
84
- border-radius: 6px !important;
85
- width: 60px !important;
86
- padding-top: 4px !important;
87
- padding-bottom: 4px !important;
88
- font-size: 13px !important;
89
- font-weight: 600 !important;
90
- border: 1px solid rgb(var(--border-default)) !important;
91
- background-color: rgb(var(--bg-primary)) !important;
92
- color: rgb(var(--text-primary)) !important;
93
- cursor: pointer;
94
- transition: opacity 150ms ease;
95
-
96
- &:hover {
97
- background-color: rgb(var(--bg-hover)) !important;
98
- }
99
- }
100
-
101
- // "Set" = last button — primary fill
102
- ::ng-deep .owl-dt-container-buttons .owl-dt-container-control-button:last-child {
103
- background-color: rgb(var(--primary)) !important;
104
- border-color: rgb(var(--primary)) !important;
105
- color: #fff !important;
106
-
14
+ /* Material M3 token overrides so calendar internals follow the theme */
15
+ --mat-datepicker-calendar-container-background-color: rgb(var(--bg-primary));
16
+ --mat-datepicker-calendar-container-text-color: rgb(var(--text-primary));
17
+ --mat-datepicker-calendar-date-text-color: rgb(var(--text-primary));
18
+ --mat-datepicker-calendar-date-selected-state-background-color: rgb(var(--primary));
19
+ --mat-datepicker-calendar-date-selected-state-text-color: #fff;
20
+ --mat-datepicker-calendar-date-today-outline-color: rgb(var(--primary));
21
+ --mat-datepicker-calendar-date-hover-state-background-color: rgb(var(--bg-primary-hover));
22
+ --mat-datepicker-calendar-header-text-color: rgb(var(--text-secondary));
23
+ --mat-datepicker-calendar-period-button-text-color: rgb(var(--text-primary));
24
+ --mat-datepicker-calendar-navigation-button-icon-color: rgb(var(--text-primary));
25
+ --mat-datepicker-calendar-date-disabled-state-text-color: rgb(var(--text-placeholder));
26
+ --mat-timepicker-container-background-color: rgb(var(--bg-primary));
27
+ }
28
+
29
+ ::ng-deep .mat-timepicker-panel .mat-mdc-option {
30
+ color: rgb(var(--text-primary));
107
31
 
108
- &:hover {
109
- opacity: 0.85;
32
+ &:hover:not(.mdc-list-item--disabled) {
33
+ background-color: rgb(var(--bg-primary-hover));
110
34
  }
111
35
  }
112
-
113
- // Inner content: height:100% now resolves against fixed 32px button, and
114
- // background must be transparent so the button's own bg shows through
115
- ::ng-deep .owl-dt-container-buttons .owl-dt-container-control-button .owl-dt-control-button-content {
116
- height: 100% !important;
117
- width: 100% !important;
118
- padding: 0 !important;
119
- background-color: transparent !important;
120
- color: inherit !important;
121
- display: flex !important;
122
- align-items: center !important;
123
- justify-content: center !important;
124
- }
125
-
126
- // ── Header / navigation ───────────────────────────────────────────────────────
127
- ::ng-deep .owl-dt-calendar-control-content .owl-dt-control-button-content {
128
- @apply text-text-primary font-medium;
129
- }
130
-
131
- ::ng-deep .owl-dt-control-arrow-button .owl-dt-control-button-content {
132
- @apply text-text-primary;
133
- }
134
-
135
- ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-header {
136
- @apply text-text-secondary;
137
- }
138
-
139
- ::ng-deep .owl-dt-calendar-table .owl-dt-calendar-header .owl-dt-weekdays th {
140
- @apply text-text-secondary font-medium;
141
- }
142
-
143
- // ── Popup container border ────────────────────────────────────────────────────
144
- ::ng-deep .owl-dt-inline-container,
145
- ::ng-deep .owl-dt-popup-container {
146
- @apply rounded-xl shadow-lg border border-border-strong;
147
- }
@@ -14,7 +14,7 @@
14
14
  (keydown.enter)="onAreaClick(fileInput, $event)"
15
15
  >
16
16
  <fx-ui-hero-icon
17
- [icon]="icon"
17
+ [icon]="icon()"
18
18
  [size]="40"
19
19
  class="flex dnd-icon"
20
20
  ></fx-ui-hero-icon>
@@ -28,14 +28,14 @@
28
28
  } @else {
29
29
  Any file type
30
30
  }
31
- — Max {{ maxSizeMB }} MB
31
+ — Max {{ maxSizeMB() }} MB
32
32
  </div>
33
33
 
34
34
  <input
35
35
  #fileInput
36
36
  type="file"
37
37
  class="dnd-input"
38
- [attr.multiple]="multiple ? '' : null"
38
+ [attr.multiple]="multiple() ? '' : null"
39
39
  [attr.accept]="accept"
40
40
  (change)="onFileSelected($event)"
41
41
  />
@@ -18,7 +18,7 @@
18
18
  <div class="drawer-container" [@drawerExpand]="getDrawerState()">
19
19
  <!-- Navigation Items -->
20
20
  <nav class="drawer-nav">
21
- @for (item of items; track item) {
21
+ @for (item of items(); track item) {
22
22
  <ng-container
23
23
  *ngTemplateOutlet="drawerItem; context: { $implicit: item, level: 0 }"
24
24
  ></ng-container>
@@ -1,9 +1,9 @@
1
1
  <div
2
2
  class="flex flex-col items-center justify-center gap-small w-full"
3
- [style.minHeight]="height"
3
+ [style.minHeight]="height()"
4
4
  >
5
5
  @if (icon) {
6
6
  <fx-ui-hero-icon class="text-text-placeholder" [icon]="icon" [size]="24"></fx-ui-hero-icon>
7
7
  }
8
- <div class="txt-default text-text-placeholder">{{ message }}</div>
8
+ <div class="txt-default text-text-placeholder">{{ message() }}</div>
9
9
  </div>
@@ -1,9 +1,10 @@
1
- <ng-heroicons
2
- [stroke]="stroke"
3
- [icon]="icon"
4
- [size]="size"
5
- [solid]="solid"
6
- [outline]="outline"
7
- [color]="color"
8
- [class]="class"
9
- />
1
+ <svg
2
+ [attr.width]="size() || 24"
3
+ [attr.height]="size() || 24"
4
+ [attr.stroke-width]="solid() ? null : stroke()"
5
+ [style.color]="color() || null"
6
+ [class]="class()"
7
+ aria-hidden="true"
8
+ >
9
+ <use [attr.href]="href" />
10
+ </svg>
@@ -1,4 +1,4 @@
1
1
  <div class="flex flex-col gap-small min-w-0">
2
- <span class="txt-field-label !mb-0">{{ label }}</span>
2
+ <span class="txt-field-label !mb-0">{{ label() }}</span>
3
3
  <span class="txt-default" [ngClass]="valueClass">{{ displayValue }}</span>
4
4
  </div>
@@ -1,8 +1,8 @@
1
- <div class="mb-4 text-left w-full text-text-primary" [class]="class">
1
+ <div class="mb-4 text-left w-full text-text-primary" [class]="class()">
2
2
  @if (label) {
3
3
  <label class="block txt-field-label" [attr.for]="fieldId">
4
4
  {{ label }}
5
- @if (required) {
5
+ @if (required()) {
6
6
  <span class="txt-required" aria-hidden="true">*</span>
7
7
  }
8
8
  @if (tooltip) {
@@ -24,16 +24,16 @@
24
24
  [appTrimOnBlurCollapse]="true"
25
25
  [id]="fieldId"
26
26
  [type]="displayType"
27
- [placeholder]="placeholder"
27
+ [placeholder]="placeholder()"
28
28
  [disabled]="disabled"
29
29
  [value]="value"
30
30
  (input)="onInput($event)"
31
31
  (blur)="onBlur()"
32
32
  (focus)="onFocus()"
33
- [attr.maxlength]="maxlength"
33
+ [attr.maxlength]="maxlength()"
34
34
  [attr.aria-invalid]="invalid || null"
35
35
  [attr.aria-describedby]="invalid ? fieldId + '-error' : null"
36
- [attr.aria-required]="required ? true : null"
36
+ [attr.aria-required]="required() ? true : null"
37
37
  [class]="type === 'otp' ? 'input-otp' : 'input-default'"
38
38
  [class.input-invalid]="invalid"
39
39
  [class.pr-8]="type === 'password' || suffixIcon"
@@ -48,7 +48,7 @@
48
48
  [attr.aria-label]="
49
49
  type === 'password'
50
50
  ? (showPassword ? 'Hide password' : 'Show password')
51
- : (suffixAriaLabel || null)
51
+ : (suffixAriaLabel() || null)
52
52
  "
53
53
  [attr.aria-pressed]="type === 'password' ? showPassword : null"
54
54
  >
@@ -1,6 +1,6 @@
1
1
  <div class="kanban-board">
2
2
  <div class="kanban-columns">
3
- @for (column of columns; track column.id) {
3
+ @for (column of columns(); track column.id) {
4
4
  <div class="kanban-column">
5
5
  <div class="kanban-column-header">
6
6
  <h3 class="kanban-column-title">{{ column.title }}</h3>
@@ -1,5 +1,5 @@
1
1
  <!-- Full-screen glass overlay -->
2
- @if (show) {
2
+ @if (show()) {
3
3
  <div
4
4
  class="fixed inset-0 z-50 flex items-center justify-center"
5
5
  aria-hidden="false"
@@ -1,13 +1,13 @@
1
- <div class="flex flex-col lg:flex-row items-start gap-xl" [style.--fx-md-list-width]="listWidth">
1
+ <div class="flex flex-col lg:flex-row items-start gap-xl" [style.--fx-md-list-width]="listWidth()">
2
2
  <!-- Master: list pane -->
3
3
  <div class="fx-md-list card-common">
4
4
  <ng-content select="[mdHeader]"></ng-content>
5
5
  <ng-content select="[mdFilters]"></ng-content>
6
6
  <div
7
7
  class="flex flex-col gap-normal overflow-auto pr-xs"
8
- [style.maxHeight]="maxListHeight"
8
+ [style.maxHeight]="maxListHeight()"
9
9
  role="listbox"
10
- [attr.aria-label]="listLabel || null"
10
+ [attr.aria-label]="listLabel() || null"
11
11
  >
12
12
  @if (table.loading) {
13
13
  <fx-ui-skeleton-list [rows]="table.pageSize"></fx-ui-skeleton-list>
@@ -29,13 +29,13 @@
29
29
  "
30
30
  >
31
31
  <ng-container
32
- [ngTemplateOutlet]="rowTemplate"
32
+ [ngTemplateOutlet]="rowTemplate()"
33
33
  [ngTemplateOutletContext]="{ $implicit: row, selected: isSelected(row) }"
34
34
  ></ng-container>
35
35
  </button>
36
36
  }
37
37
  @if (!table.dataSource.data.length) {
38
- <fx-ui-empty-state [message]="emptyMessage"></fx-ui-empty-state>
38
+ <fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
39
39
  }
40
40
  }
41
41
  </div>
@@ -46,7 +46,7 @@
46
46
  class="mt-normal"
47
47
  [length]="table.total"
48
48
  [pageSize]="table.pageSize"
49
- [pageSizeOptions]="pageSizeOptions"
49
+ [pageSizeOptions]="pageSizeOptions()"
50
50
  ></mat-paginator>
51
51
  </div>
52
52
 
@@ -54,16 +54,16 @@
54
54
  <div class="w-full lg:flex-1 min-w-0">
55
55
  @if (table.selected) {
56
56
  @if (table.detailBusy) {
57
- <fx-ui-skeleton-detail [cards]="skeletonCards"></fx-ui-skeleton-detail>
57
+ <fx-ui-skeleton-detail [cards]="skeletonCards()"></fx-ui-skeleton-detail>
58
58
  } @else {
59
59
  <ng-container
60
- [ngTemplateOutlet]="detailTemplate"
60
+ [ngTemplateOutlet]="detailTemplate()"
61
61
  [ngTemplateOutletContext]="{ $implicit: table.detail, selected: table.selected }"
62
62
  ></ng-container>
63
63
  }
64
64
  } @else {
65
65
  <div class="card-common flex items-center justify-center h-[300px]">
66
- <fx-ui-empty-state [message]="emptyMessage"></fx-ui-empty-state>
66
+ <fx-ui-empty-state [message]="emptyMessage()"></fx-ui-empty-state>
67
67
  </div>
68
68
  }
69
69
  </div>
@@ -0,0 +1,27 @@
1
+ <button
2
+ type="button"
3
+ class="inline-flex items-center justify-center rounded-lg p-1 hover:bg-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
4
+ [matMenuTriggerFor]="menu"
5
+ [disabled]="disabled()"
6
+ [attr.aria-label]="ariaLabel()"
7
+ >
8
+ <ng-content></ng-content>
9
+ </button>
10
+
11
+ <mat-menu #menu="matMenu">
12
+ @for (item of items(); track item.label) {
13
+ <button
14
+ mat-menu-item
15
+ type="button"
16
+ [disabled]="item.disabled"
17
+ (click)="select.emit(item)"
18
+ >
19
+ <span class="flex items-center gap-normal" [class]="toneClass(item)">
20
+ @if (item.icon) {
21
+ <fx-ui-hero-icon [icon]="item.icon" [size]="18"></fx-ui-hero-icon>
22
+ }
23
+ <span>{{ item.label }}</span>
24
+ </span>
25
+ </button>
26
+ }
27
+ </mat-menu>
@@ -1,6 +1,6 @@
1
1
  <div
2
2
  class="w-full rounded-lg overflow-hidden"
3
- [class]="class"
3
+ [class]="class()"
4
4
  role="radiogroup"
5
5
  [attr.aria-labelledby]="label ? groupId + '-label' : null"
6
6
  >
@@ -8,7 +8,7 @@
8
8
  @if (label) {
9
9
  <span class="txt-field-label block" [id]="groupId + '-label'">
10
10
  {{ label }}
11
- @if (required) {
11
+ @if (required()) {
12
12
  <span class="txt-required" aria-hidden="true">*</span>
13
13
  }
14
14
  @if (tooltip) {
@@ -24,7 +24,7 @@
24
24
  </span>
25
25
  }
26
26
 
27
- @for (opt of options; track opt) {
27
+ @for (opt of options(); track opt) {
28
28
  <button
29
29
  type="button"
30
30
  role="radio"