tailjng 0.1.6 → 0.1.7

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 (176) hide show
  1. package/README.md +12 -4
  2. package/cli/execute/init-app.js +5 -2
  3. package/cli/execute/sync-app.js +14 -2
  4. package/cli/settings/colors-config-utils.js +43 -8
  5. package/cli/settings/icons-config-utils.js +62 -0
  6. package/cli/settings/path-utils.js +32 -2
  7. package/cli/settings/project-utils.js +7 -1
  8. package/cli/templates/app.generator.js +2 -2
  9. package/fesm2022/tailjng.mjs +247 -80
  10. package/fesm2022/tailjng.mjs.map +1 -1
  11. package/lib/services/static/theme.service.d.ts +39 -1
  12. package/lib/utils/theme/theme-variables.util.d.ts +31 -0
  13. package/package.json +1 -1
  14. package/public-api.d.ts +2 -1
  15. package/registry/components.json +41 -18
  16. package/src/colors.safelist.css +2 -2
  17. package/src/lib/components/.config/README.md +11 -0
  18. package/src/lib/components/.config/colors/README.md +38 -0
  19. package/src/lib/components/{colors-config → .config/colors}/colors.config.ts +5 -5
  20. package/src/lib/components/{colors-config → .config/colors}/colors.safelist.css +2 -2
  21. package/src/lib/components/.config/icons/README.md +26 -0
  22. package/src/lib/components/.config/icons/icons.lucide.ts +134 -0
  23. package/src/lib/components/.config/input/README.md +24 -0
  24. package/src/lib/components/.config/input/input.classes.ts +119 -0
  25. package/src/lib/components/alert/alert-dialog/dialog-alert.component.css +244 -2
  26. package/src/lib/components/alert/alert-dialog/dialog-alert.component.html +25 -38
  27. package/src/lib/components/alert/alert-dialog/dialog-alert.component.ts +66 -56
  28. package/src/lib/components/alert/alert-dialog/dialog-alert.types.ts +19 -0
  29. package/src/lib/components/alert/alert-toast/toast-alert.component.css +630 -12
  30. package/src/lib/components/alert/alert-toast/toast-alert.component.html +103 -102
  31. package/src/lib/components/alert/alert-toast/toast-alert.component.ts +485 -128
  32. package/src/lib/components/alert/alert-toast/toast-alert.types.ts +25 -0
  33. package/src/lib/components/badge/badge.component.html +34 -21
  34. package/src/lib/components/badge/badge.component.ts +140 -31
  35. package/src/lib/components/button/button.component.html +16 -10
  36. package/src/lib/components/button/button.component.ts +162 -22
  37. package/src/lib/components/card/card-complete/complete-card.component.html +2 -2
  38. package/src/lib/components/card/card-complete/complete-card.component.ts +26 -16
  39. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.html +2 -2
  40. package/src/lib/components/card/card-crud-complete/complete-crud-card.component.ts +26 -16
  41. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.css +97 -0
  42. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.html +54 -46
  43. package/src/lib/components/checkbox/checkbox-input/input-checkbox.component.ts +135 -64
  44. package/src/lib/components/checkbox/checkbox-input/input-checkbox.types.ts +3 -0
  45. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.css +112 -0
  46. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.html +28 -25
  47. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.component.ts +67 -15
  48. package/src/lib/components/checkbox/checkbox-switch/switch-checkbox.types.ts +1 -0
  49. package/src/lib/components/coach-mark/coach-mark.component.html +4 -22
  50. package/src/lib/components/coach-mark/coach-mark.component.scss +1 -1
  51. package/src/lib/components/coach-mark/coach-mark.component.ts +51 -18
  52. package/src/lib/components/coach-mark/coach-mark.directive.ts +133 -78
  53. package/src/lib/components/coach-mark/coach-mark.types.ts +12 -0
  54. package/src/lib/components/dialog/dialog.component.css +103 -1
  55. package/src/lib/components/dialog/dialog.component.html +46 -66
  56. package/src/lib/components/dialog/dialog.component.ts +136 -110
  57. package/src/lib/components/dialog/dialog.types.ts +19 -0
  58. package/src/lib/components/filter/filter-complete/complete-filter.component.html +16 -19
  59. package/src/lib/components/filter/filter-complete/complete-filter.component.scss +35 -0
  60. package/src/lib/components/filter/filter-complete/complete-filter.component.ts +58 -34
  61. package/src/lib/components/filter/filter-complete/complete-filter.types.ts +7 -0
  62. package/src/lib/components/filter/filter-complete/complete-filter.util.ts +16 -0
  63. package/src/lib/components/form/form-container/container-form.component.css +4 -0
  64. package/src/lib/components/form/form-container/container-form.component.html +2 -2
  65. package/src/lib/components/form/form-container/container-form.component.ts +72 -16
  66. package/src/lib/components/form/form-container/container-form.types.ts +42 -0
  67. package/src/lib/components/form/form-container/form-col-span.directive.ts +25 -0
  68. package/src/lib/components/form/form-sidebar/sidebar-form.component.css +276 -0
  69. package/src/lib/components/form/form-sidebar/sidebar-form.component.html +117 -125
  70. package/src/lib/components/form/form-sidebar/sidebar-form.component.ts +109 -34
  71. package/src/lib/components/form/form-sidebar/sidebar-form.types.ts +3 -0
  72. package/src/lib/components/{toggle-radio/toggle-radio.component.css → form/form-validation/validation-form.component.css} +0 -1
  73. package/src/lib/components/form/form-validation/validation-form.component.html +10 -6
  74. package/src/lib/components/form/form-validation/validation-form.component.ts +99 -12
  75. package/src/lib/components/form/form-validation/validation-form.types.ts +33 -0
  76. package/src/lib/components/icon/icon.component.html +8 -5
  77. package/src/lib/components/icon/icon.component.ts +111 -9
  78. package/src/lib/components/input/input/input.component.html +19 -16
  79. package/src/lib/components/input/input/input.component.ts +130 -53
  80. package/src/lib/components/input/input/input.types.ts +8 -0
  81. package/src/lib/components/input/input-file/file-input.component.html +65 -56
  82. package/src/lib/components/input/input-file/file-input.component.ts +276 -173
  83. package/src/lib/components/input/input-file/file-input.types.ts +2 -0
  84. package/src/lib/components/input/input-range/range-input.component.css +67 -0
  85. package/src/lib/components/input/input-range/range-input.component.html +50 -58
  86. package/src/lib/components/input/input-range/range-input.component.ts +148 -60
  87. package/src/lib/components/input/input-range/range-input.types.ts +7 -0
  88. package/src/lib/components/input/input-textarea/textarea-input.component.html +16 -7
  89. package/src/lib/components/input/input-textarea/textarea-input.component.ts +140 -50
  90. package/src/lib/components/input/input-textarea/textarea-input.types.ts +2 -0
  91. package/src/lib/components/label/label.component.html +17 -16
  92. package/src/lib/components/label/label.component.ts +70 -16
  93. package/src/lib/components/label/label.types.ts +2 -0
  94. package/src/lib/components/menu/menu-options-table/menu-options-defaults.ts +34 -0
  95. package/src/lib/components/menu/menu-options-table/options-table-menu.component.html +34 -20
  96. package/src/lib/components/menu/menu-options-table/options-table-menu.component.ts +211 -58
  97. package/src/lib/components/menu/menu-options-table/options-table-menu.types.ts +38 -0
  98. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.html +49 -52
  99. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.ts +112 -24
  100. package/src/lib/components/menu/options-coach-menu/options-coach-menu.types.ts +9 -0
  101. package/src/lib/components/mode-toggle/mode-toggle.component.html +11 -16
  102. package/src/lib/components/mode-toggle/mode-toggle.component.ts +69 -33
  103. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.html +4 -4
  104. package/src/lib/components/paginator/paginator-complete/complete-paginator.component.ts +31 -7
  105. package/src/lib/components/paginator/paginator-complete/complete-paginator.types.ts +12 -0
  106. package/src/lib/components/paginator/paginator-complete/complete-paginator.util.ts +36 -0
  107. package/src/lib/components/progress-bar/progress-bar.component.css +11 -0
  108. package/src/lib/components/progress-bar/progress-bar.component.html +41 -40
  109. package/src/lib/components/progress-bar/progress-bar.component.ts +95 -11
  110. package/src/lib/components/progress-bar/progress-bar.types.ts +2 -0
  111. package/src/lib/components/select/select-dropdown/dropdown-select.component.css +6 -0
  112. package/src/lib/components/select/select-dropdown/dropdown-select.component.html +54 -44
  113. package/src/lib/components/select/select-dropdown/dropdown-select.component.ts +450 -509
  114. package/src/lib/components/select/select-dropdown/dropdown-select.types.ts +43 -0
  115. package/src/lib/components/select/select-dropdown/dropdown-select.util.ts +179 -0
  116. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.css +6 -0
  117. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.html +131 -42
  118. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.component.ts +491 -475
  119. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.types.ts +22 -0
  120. package/src/lib/components/select/select-multi-dropdown/multi-dropdown-select.util.ts +20 -0
  121. package/src/lib/components/select/select-multi-table/multi-table-select.component.css +10 -0
  122. package/src/lib/components/select/select-multi-table/multi-table-select.component.html +76 -60
  123. package/src/lib/components/select/select-multi-table/multi-table-select.component.ts +250 -313
  124. package/src/lib/components/select/select-multi-table/multi-table-select.types.ts +10 -0
  125. package/src/lib/components/select/select-multi-table/multi-table-select.util.ts +5 -0
  126. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.css +212 -0
  127. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.html +62 -53
  128. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.ts +84 -27
  129. package/src/lib/components/sidebar/sidebar-static/static-sidebar.types.ts +2 -0
  130. package/src/lib/components/table/table-complete/complete-table.component.html +15 -17
  131. package/src/lib/components/table/table-complete/complete-table.component.ts +190 -338
  132. package/src/lib/components/table/table-complete/complete-table.types.ts +28 -0
  133. package/src/lib/components/table/table-complete/complete-table.util.ts +236 -0
  134. package/src/lib/components/table/table-complete/index.ts +2 -0
  135. package/src/lib/components/table/table-crud-complete/complete-crud-table.animations.ts +34 -0
  136. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.html +73 -128
  137. package/src/lib/components/table/table-crud-complete/complete-crud-table.component.ts +542 -829
  138. package/src/lib/components/table/table-crud-complete/complete-crud-table.types.ts +57 -0
  139. package/src/lib/components/table/table-crud-complete/complete-crud-table.util.ts +723 -0
  140. package/src/lib/components/table/table-crud-complete/index.ts +3 -0
  141. package/src/lib/components/theme-generator/theme-generator.component.css +21 -0
  142. package/src/lib/components/theme-generator/theme-generator.component.html +146 -116
  143. package/src/lib/components/theme-generator/theme-generator.component.ts +44 -24
  144. package/src/lib/components/toggle-radio/shared/toggle-options.types.ts +8 -0
  145. package/src/lib/components/toggle-radio/shared/toggle-options.util.ts +44 -0
  146. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.css +135 -0
  147. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.html +52 -0
  148. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.component.ts +198 -0
  149. package/src/lib/components/toggle-radio/toggle-radio/toggle-radio.types.ts +1 -0
  150. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.css +108 -0
  151. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.html +37 -0
  152. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.component.ts +193 -0
  153. package/src/lib/components/toggle-radio/toggle-segment/segment-toggle.types.ts +1 -0
  154. package/src/lib/components/tooltip/tooltip.directive.ts +12 -9
  155. package/src/lib/components/tooltip/tooltip.service.ts +331 -133
  156. package/src/lib/components/tooltip/tooltip.types.ts +9 -0
  157. package/src/lib/components/viewer/viewer-image/image-viewer.component.css +90 -4
  158. package/src/lib/components/viewer/viewer-image/image-viewer.component.html +52 -103
  159. package/src/lib/components/viewer/viewer-image/image-viewer.component.ts +182 -177
  160. package/src/lib/components/viewer/viewer-image/image-viewer.types.ts +3 -0
  161. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.css +177 -0
  162. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.html +74 -24
  163. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.ts +168 -15
  164. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.types.ts +1 -0
  165. package/src/styles.css +2 -2
  166. package/lib/services/static/icons.service.d.ts +0 -65
  167. package/src/lib/components/colors-config/README.md +0 -38
  168. package/src/lib/components/form/form-sidebar/sidebar-form.component.scss +0 -0
  169. package/src/lib/components/form/form-validation/validation-form.component.scss +0 -0
  170. package/src/lib/components/menu/menu-options-table/options-table-menu.component.scss +0 -0
  171. package/src/lib/components/menu/options-coach-menu/options-coach-menu.component.scss +0 -12
  172. package/src/lib/components/sidebar/sidebar-static/static-sidebar.component.scss +0 -0
  173. package/src/lib/components/toggle-radio/toggle-radio.component.html +0 -51
  174. package/src/lib/components/toggle-radio/toggle-radio.component.ts +0 -222
  175. package/src/lib/components/viewer/viewer-pdf/pdf-viewer.component.scss +0 -0
  176. package/tailjng-0.1.6.tgz +0 -0
@@ -0,0 +1,43 @@
1
+ /** Static options list or API-backed searchable select. */
2
+ export type DropdownSelectType = 'dropdown' | 'searchable';
3
+
4
+ /** Sort order for searchable API requests. */
5
+ export type DropdownSortOrder = 'ASC' | 'DESC';
6
+
7
+ /** Normalized option shown in the panel list. */
8
+ export type DropdownOption<TOriginal = unknown> = {
9
+ value: unknown;
10
+ text: string;
11
+ original?: TOriginal;
12
+ };
13
+
14
+ /** Placeholder map for `{param}` segments in {@link JDropdownSelectComponent.endpoint}. */
15
+ export type DropdownSelectDynamicParams = Record<string, unknown>;
16
+
17
+ /** Default filters sent as `filter[key]` query params (searchable mode). */
18
+ export type DropdownSelectDefaultFilters = { [key: string]: any };
19
+
20
+ export type DropdownPanelPosition = {
21
+ /** Viewport `top` in pixels after positioning. */
22
+ top: number;
23
+ /** Viewport `left` in pixels after positioning. */
24
+ left: number;
25
+ /** Final panel width in pixels. */
26
+ width: number;
27
+ };
28
+
29
+ /** Tunables for {@link positionDropdownPanel}. */
30
+ export type DropdownPanelPositionConfig = {
31
+ /** Minimum distance from viewport edges. */
32
+ viewportPadding: number;
33
+ /** Gap between trigger and panel. */
34
+ dropdownGap: number;
35
+ /** Stacking order (`position: fixed`). */
36
+ dropdownZIndex: number;
37
+ /** Minimum panel width when trigger is narrower. */
38
+ minPanelWidth: number;
39
+ /** Minimum scrollable list height when constrained. */
40
+ minOptionsHeight: number;
41
+ /** CSS selector for the scrollable options container. */
42
+ optionsListSelector: string;
43
+ };
@@ -0,0 +1,179 @@
1
+ import type {
2
+ DropdownOption,
3
+ DropdownPanelPosition,
4
+ DropdownPanelPositionConfig,
5
+ } from './dropdown-select.types';
6
+
7
+ /** Default viewport positioning values for {@link JDropdownSelectComponent}. */
8
+ export const DROPDOWN_PANEL_DEFAULTS: DropdownPanelPositionConfig = {
9
+ viewportPadding: 8,
10
+ dropdownGap: 4,
11
+ dropdownZIndex: 1100,
12
+ minPanelWidth: 250,
13
+ minOptionsHeight: 80,
14
+ optionsListSelector: '.jdropdown-options-list',
15
+ };
16
+
17
+ /** Reads a nested property using dot notation (`a.b.c`). */
18
+ export function getNestedValue(obj: unknown, path: string): unknown {
19
+ let value: unknown = obj;
20
+
21
+ for (const part of path.split('.')) {
22
+ if (value == null || typeof value !== 'object') {
23
+ return '';
24
+ }
25
+
26
+ value = (value as Record<string, unknown>)[part];
27
+ }
28
+
29
+ return value ?? '';
30
+ }
31
+
32
+ /** Builds the display label for one option object. */
33
+ export function resolveOptionLabel(
34
+ option: Record<string, unknown>,
35
+ optionLabel: string | string[],
36
+ labelSeparator: string,
37
+ ): string {
38
+ if (Array.isArray(optionLabel)) {
39
+ return optionLabel
40
+ .map((key) => String(getNestedValue(option, key)))
41
+ .filter(Boolean)
42
+ .join(labelSeparator);
43
+ }
44
+
45
+ return String(getNestedValue(option, optionLabel));
46
+ }
47
+
48
+ /** Maps primitive arrays (`['a', 'b']`) to panel options. */
49
+ export function mapPrimitiveOptions(options: unknown[]): DropdownOption[] {
50
+ return options.map((option) => ({
51
+ value: option,
52
+ text: String(option),
53
+ }));
54
+ }
55
+
56
+ /** Maps object arrays to panel options using `optionLabel` / `optionValue`. */
57
+ export function mapObjectOptions(
58
+ options: Record<string, unknown>[],
59
+ optionLabel: string | string[],
60
+ optionValue: string,
61
+ labelSeparator: string,
62
+ ): DropdownOption[] {
63
+ return options.map((option) => ({
64
+ value: option[optionValue],
65
+ text: resolveOptionLabel(option, optionLabel, labelSeparator),
66
+ original: option,
67
+ }));
68
+ }
69
+
70
+ /** Local text filter for static dropdown mode. */
71
+ export function filterOptionsBySearch(
72
+ options: DropdownOption[],
73
+ searchTerm: string,
74
+ ): DropdownOption[] {
75
+ if (!searchTerm.trim()) {
76
+ return [...options];
77
+ }
78
+
79
+ const normalized = searchTerm.toLowerCase();
80
+ return options.filter((option) => option.text.toLowerCase().includes(normalized));
81
+ }
82
+
83
+ /** Replaces `{param}` placeholders in a CRUD endpoint template. */
84
+ export function rebuildEndpointUrl(
85
+ rawEndpoint: string,
86
+ dynamicParams: Record<string, unknown>,
87
+ ): string {
88
+ return rawEndpoint.replace(/\{([^}]+)\}/g, (_, key: string) =>
89
+ String(dynamicParams?.[key] ?? ''),
90
+ );
91
+ }
92
+
93
+ /** Clears dynamic max-height applied while positioning the panel. */
94
+ export function resetDropdownPanelStyles(
95
+ panel: HTMLElement,
96
+ optionsListSelector = DROPDOWN_PANEL_DEFAULTS.optionsListSelector,
97
+ ): void {
98
+ const optionsList = panel.querySelector(optionsListSelector) as HTMLElement | null;
99
+ if (optionsList) {
100
+ optionsList.style.maxHeight = '';
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Positions the floating panel with `position: fixed`, flipping above/below
106
+ * the trigger and clamping within the viewport.
107
+ */
108
+ export function positionDropdownPanel(
109
+ panel: HTMLElement,
110
+ buttonRect: DOMRect,
111
+ config: DropdownPanelPositionConfig = DROPDOWN_PANEL_DEFAULTS,
112
+ ): DropdownPanelPosition {
113
+ resetDropdownPanelStyles(panel, config.optionsListSelector);
114
+
115
+ const width = Math.max(buttonRect.width, config.minPanelWidth);
116
+
117
+ panel.style.position = 'fixed';
118
+ panel.style.zIndex = String(config.dropdownZIndex);
119
+ panel.style.margin = '0';
120
+ panel.style.width = `${width}px`;
121
+ panel.style.minWidth = `${config.minPanelWidth}px`;
122
+
123
+ const viewportWidth = document.documentElement.clientWidth;
124
+ const viewportHeight = window.innerHeight;
125
+ const panelHeight = panel.offsetHeight;
126
+ const spaceBelow = viewportHeight - buttonRect.bottom - config.viewportPadding;
127
+ const spaceAbove = buttonRect.top - config.viewportPadding;
128
+
129
+ let top = buttonRect.bottom + config.dropdownGap;
130
+
131
+ if (panelHeight > spaceBelow && panelHeight <= spaceAbove) {
132
+ top = buttonRect.top - panelHeight - config.dropdownGap;
133
+ } else if (panelHeight > spaceBelow && panelHeight > spaceAbove) {
134
+ const optionsList = panel.querySelector(config.optionsListSelector) as HTMLElement | null;
135
+ const chromeHeight = optionsList ? panelHeight - optionsList.offsetHeight : panelHeight;
136
+
137
+ if (spaceBelow >= spaceAbove) {
138
+ top = buttonRect.bottom + config.dropdownGap;
139
+ if (optionsList) {
140
+ optionsList.style.maxHeight = `${Math.max(
141
+ config.minOptionsHeight,
142
+ spaceBelow - config.dropdownGap - chromeHeight,
143
+ )}px`;
144
+ }
145
+ } else {
146
+ const constrainedOptionsHeight = Math.max(
147
+ config.minOptionsHeight,
148
+ spaceAbove - config.dropdownGap - chromeHeight,
149
+ );
150
+
151
+ if (optionsList) {
152
+ optionsList.style.maxHeight = `${constrainedOptionsHeight}px`;
153
+ }
154
+
155
+ const adjustedPanelHeight = chromeHeight + (optionsList?.offsetHeight ?? constrainedOptionsHeight);
156
+ top = buttonRect.top - adjustedPanelHeight - config.dropdownGap;
157
+ }
158
+ }
159
+
160
+ const finalPanelHeight = panel.offsetHeight;
161
+ top = Math.max(
162
+ config.viewportPadding,
163
+ Math.min(top, viewportHeight - finalPanelHeight - config.viewportPadding),
164
+ );
165
+
166
+ let left = buttonRect.left;
167
+ const panelWidth = panel.offsetWidth;
168
+
169
+ if (left + panelWidth > viewportWidth - config.viewportPadding) {
170
+ left = viewportWidth - panelWidth - config.viewportPadding;
171
+ }
172
+
173
+ left = Math.max(config.viewportPadding, left);
174
+
175
+ panel.style.top = `${top}px`;
176
+ panel.style.left = `${left}px`;
177
+
178
+ return { top, left, width: panelWidth };
179
+ }
@@ -2,3 +2,9 @@
2
2
  display: block;
3
3
  width: 100%;
4
4
  }
5
+
6
+ /* Floating panel — coordinates set in TS via positionDropdownPanel */
7
+ .jdropdown-panel {
8
+ position: fixed;
9
+ z-index: 1100;
10
+ }
@@ -1,5 +1,6 @@
1
+ <!-- Trigger -->
1
2
  <div class="relative w-full">
2
- <div class="w-auto" #selectButton>
3
+ <div #selectButton class="w-auto">
3
4
  <button
4
5
  type="button"
5
6
  [disabled]="isDisabled || isLoading"
@@ -7,91 +8,179 @@
7
8
  class="flex w-full h-[40px] max-[400px]:h-[35px] text-[12px] items-center justify-between px-3 py-2 text-sm bg-background dark:bg-dark-background border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary select-none"
8
9
  [ngClass]="{ 'opacity-50 cursor-not-allowed pointer-events-none': isDisabled || isLoading }"
9
10
  >
10
- <span class="truncate text-black dark:text-white" [ngClass]="{ 'opacity-70 text-muted-foreground dark:text-dark-muted-foreground': selectedValues.length === 0 }">
11
+ <span
12
+ class="truncate text-black dark:text-white"
13
+ [ngClass]="{
14
+ 'opacity-70 text-muted-foreground dark:text-dark-muted-foreground':
15
+ selectedValues.length === 0,
16
+ }"
17
+ >
11
18
  {{ displayLabel }}
12
19
  </span>
13
20
 
14
21
  <div class="flex items-center">
15
22
  @if (showClear && selectedValues.length > 0) {
16
- <button type="button" (click)="clearSelection($event)"
17
- class="pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer">
18
- <lucide-icon [name]="iconsService.icons.close" size="14"></lucide-icon>
23
+ <button
24
+ type="button"
25
+ (click)="clearSelection($event)"
26
+ class="pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
27
+ >
28
+ <JIcon
29
+ [icon]="Icons.X"
30
+ [size]="14"
31
+ iconClass="text-gray-400"
32
+ [ariaHidden]="true"
33
+ />
19
34
  </button>
20
35
  }
21
36
  @if (!isLoading) {
22
- <lucide-icon [name]="iconsService.icons.chevronDown" size="16" class="transition duration-300 ease-in-out text-gray-400" [ngClass]="{ 'rotate-180': isDropdownOpen }"></lucide-icon>
37
+ <JIcon
38
+ [icon]="Icons.ChevronDown"
39
+ [size]="16"
40
+ iconClass="transition duration-300 ease-in-out text-gray-400"
41
+ [ngClasses]="{ 'rotate-180': isDropdownOpen }"
42
+ [ariaHidden]="true"
43
+ />
23
44
  } @else {
24
- <lucide-icon [name]="iconsService.icons.loading" size="16" class="text-gray-400 animate-spin"></lucide-icon>
45
+ <JIcon
46
+ [icon]="Icons.Loader2"
47
+ [size]="16"
48
+ iconClass="text-gray-400 animate-spin"
49
+ [ariaHidden]="true"
50
+ />
25
51
  }
26
52
  </div>
27
53
  </button>
28
54
  </div>
29
55
  </div>
30
56
 
57
+ <!-- Floating panel (position: fixed — see dropdown-select.util) -->
31
58
  @if (isDropdownOpen) {
32
- <div @modalTransition
33
- class="absolute z-[100] min-w-[250px] mt-1 bg-background dark:bg-dark-background rounded-lg shadow-lg border border-border border-dark-border"
34
- [style.width.px]="dropdownWidth"
35
- [style.top.px]="dropdownTop"
36
- [style.left.px]="dropdownLeft">
59
+ <div
60
+ #dropdownPanel
61
+ @modalTransition
62
+ class="jdropdown-panel min-w-[250px] bg-background dark:bg-dark-background rounded-lg shadow-lg border border-border border-dark-border"
63
+ [style.width.px]="dropdownWidth"
64
+ >
37
65
  <div class="pt-1 pl-3 pr-3 pb-3">
38
- <div class="text-[10px] font-medium text-gray-500 dark:text-gray-500 mb-1">{{ title }}</div>
66
+ <div class="text-[10px] font-medium text-gray-500 dark:text-gray-500 mb-1">
67
+ {{ title }}
68
+ </div>
39
69
 
40
- <!-- Search (local o remota según 'type') -->
70
+ <!-- Search (local or remote depending on `type`) -->
41
71
  @if (isSearch) {
42
72
  <div class="mb-2 relative">
43
- <input type="text"
44
- [(ngModel)]="searchTerm"
45
- (input)="onSearchInput()"
46
- placeholder="Buscar..."
47
- class="input bg-dark-foreground dark:bg-foreground text-black dark:text-white w-full px-3 py-2 text-sm max-[400px]:h-[35px] max-[400px]:text-[12px] border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary" />
73
+ <input
74
+ type="text"
75
+ [(ngModel)]="searchTerm"
76
+ (input)="onSearchInput()"
77
+ placeholder="Buscar..."
78
+ class="input bg-dark-foreground dark:bg-foreground text-black dark:text-white w-full px-3 py-2 text-sm max-[400px]:h-[35px] max-[400px]:text-[12px] border border-border dark:border-dark-border rounded focus:outline-none focus:ring-2 focus:ring-primary"
79
+ />
48
80
  <div class="absolute flex right-3 top-1/2 transform -translate-y-1/2">
49
81
  @if (searchTerm) {
50
- <button type="button" (click)="searchTerm=''; onSearchInput()"
51
- class="pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer">
52
- <lucide-icon [name]="iconsService.icons.close" size="16"></lucide-icon>
82
+ <button
83
+ type="button"
84
+ (click)="clearSearchTerm()"
85
+ class="pr-1 mr-1 text-gray-400 hover:text-gray-600 focus:outline-none cursor-pointer"
86
+ >
87
+ <JIcon
88
+ [icon]="Icons.X"
89
+ [size]="16"
90
+ iconClass="text-gray-400"
91
+ [ariaHidden]="true"
92
+ />
53
93
  </button>
54
94
  }
55
- <lucide-icon [name]="iconsService.icons.search" size="16" class="text-gray-400"></lucide-icon>
95
+ <JIcon
96
+ [icon]="Icons.Search"
97
+ [size]="16"
98
+ iconClass="text-gray-400"
99
+ [ariaHidden]="true"
100
+ />
56
101
  </div>
57
102
  </div>
58
103
  }
59
104
 
60
- <div class="max-h-60 max-[400px]:max-h-36 overflow-auto flex flex-col gap-1 scroll-element">
105
+ <!-- Option list -->
106
+ <div
107
+ class="jdropdown-options-list max-h-60 max-[400px]:max-h-36 overflow-auto flex flex-col gap-1 scroll-element"
108
+ >
61
109
  @if (isLoading) {
62
- <div class="flex gap-3 text-black/50 dark:text-white/50 items-center justify-center py-4">
63
- <lucide-icon [name]="iconsService.icons.loading" size="20" class="animate-spin"></lucide-icon>
110
+ <div
111
+ class="flex gap-3 text-black/50 dark:text-white/50 items-center justify-center py-4"
112
+ >
113
+ <JIcon
114
+ [icon]="Icons.Loader2"
115
+ [size]="20"
116
+ iconClass="animate-spin"
117
+ [ariaHidden]="true"
118
+ />
64
119
  Cargando...
65
120
  </div>
66
121
  } @else {
67
-
68
- <!-- Select All -->
69
122
  @if (filteredProcessedOptions.length > 0 && enableSelectAll) {
70
- <div onKeyPress (click)="toggleSelectAll()"
71
- class="flex gap-5 items-center border border-accent dark:border-dark-accent/50 px-4 py-2 cursor-pointer group rounded-md transition-all hover:bg-accent/50 hover:dark:bg-dark-accent/40"
72
- [ngClass]="{'bg-accent! dark:bg-dark-accent/80!': isAllSelected()}">
123
+ <div
124
+ onKeyPress
125
+ (click)="toggleSelectAll()"
126
+ class="flex gap-5 items-center border border-accent dark:border-dark-accent/50 px-4 py-2 cursor-pointer group rounded-md transition-all hover:bg-accent/50 hover:dark:bg-dark-accent/40"
127
+ [ngClass]="{ 'bg-accent! dark:bg-dark-accent/80!': isAllSelected() }"
128
+ >
73
129
  <input type="checkbox" class="hidden" [checked]="isAllSelected()" />
74
130
  @if (isAllSelected()) {
75
- <lucide-icon [name]="iconsService.icons.check" size="15" class="transition text-black dark:text-white" />
131
+ <JIcon
132
+ [icon]="Icons.Check"
133
+ [size]="15"
134
+ iconClass="transition text-black dark:text-white"
135
+ [ariaHidden]="true"
136
+ />
76
137
  } @else {
77
- <lucide-icon [name]="iconsService.icons.squareDashedMousePointer" size="15" class="transition text-black dark:text-white opacity-40" />
138
+ <JIcon
139
+ [icon]="Icons.SquareDashedMousePointer"
140
+ [size]="15"
141
+ iconClass="transition text-black dark:text-white opacity-40"
142
+ [ariaHidden]="true"
143
+ />
78
144
  }
79
- <label class="text-black dark:text-white text-sm max-[400px]:h-[35px] max-[400px]:text-[12px] font-medium w-full">{{ labelSelectAll }}</label>
145
+ <label
146
+ class="text-black dark:text-white text-sm max-[400px]:h-[35px] max-[400px]:text-[12px] font-medium w-full"
147
+ >{{ labelSelectAll }}</label
148
+ >
80
149
  </div>
81
150
  }
82
151
 
83
- <!-- Opciones (filtradas) -->
84
152
  @for (option of filteredProcessedOptions; track option.value) {
85
- <div onKeyPress (click)="toggleOption(option)"
86
- class="flex gap-5 items-center border border-accent dark:border-dark-accent/50 px-4 py-2 cursor-pointer group rounded-md transition-all hover:bg-accent/50 hover:dark:bg-dark-accent/40"
87
- [ngClass]="{'bg-accent! dark:bg-dark-accent/80!' : selectedValues.includes(option.value)}">
88
- <input type="checkbox" class="hidden" [checked]="selectedValues.includes(option.value)" />
153
+ <div
154
+ onKeyPress
155
+ (click)="toggleOption(option)"
156
+ class="flex gap-5 items-center border border-accent dark:border-dark-accent/50 px-4 py-2 cursor-pointer group rounded-md transition-all hover:bg-accent/50 hover:dark:bg-dark-accent/40"
157
+ [ngClass]="{
158
+ 'bg-accent! dark:bg-dark-accent/80!': selectedValues.includes(option.value),
159
+ }"
160
+ >
161
+ <input
162
+ type="checkbox"
163
+ class="hidden"
164
+ [checked]="selectedValues.includes(option.value)"
165
+ />
89
166
  @if (selectedValues.includes(option.value)) {
90
- <lucide-icon [name]="iconsService.icons.check" size="15" class="transition text-black dark:text-white" />
167
+ <JIcon
168
+ [icon]="Icons.Check"
169
+ [size]="15"
170
+ iconClass="transition text-black dark:text-white"
171
+ [ariaHidden]="true"
172
+ />
91
173
  } @else {
92
- <lucide-icon [name]="iconsService.icons.squareDashedMousePointer" size="15" class="transition text-black dark:text-white opacity-40" />
174
+ <JIcon
175
+ [icon]="Icons.SquareDashedMousePointer"
176
+ [size]="15"
177
+ iconClass="transition text-black dark:text-white opacity-40"
178
+ [ariaHidden]="true"
179
+ />
93
180
  }
94
- <label class="text-black dark:text-white text-sm font-medium w-full">{{ option.text }}</label>
181
+ <label class="text-black dark:text-white text-sm font-medium w-full">{{
182
+ option.text
183
+ }}</label>
95
184
  </div>
96
185
  }
97
186
  @if (filteredProcessedOptions.length === 0) {