ezfw-core 1.0.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 (154) hide show
  1. package/components/EzBaseComponent.ts +648 -0
  2. package/components/EzComponent.ts +89 -0
  3. package/components/EzInput.module.scss +183 -0
  4. package/components/EzInput.ts +104 -0
  5. package/components/EzLabel.ts +22 -0
  6. package/components/EzOutlet.ts +181 -0
  7. package/components/HtmlWrapper.ts +305 -0
  8. package/components/avatar/EzAvatar.module.scss +200 -0
  9. package/components/avatar/EzAvatar.ts +130 -0
  10. package/components/badge/EzBadge.module.scss +202 -0
  11. package/components/badge/EzBadge.ts +77 -0
  12. package/components/button/EzButton.module.scss +402 -0
  13. package/components/button/EzButton.ts +175 -0
  14. package/components/button/EzButtonGroup.ts +48 -0
  15. package/components/card/EzCard.module.scss +71 -0
  16. package/components/card/EzCard.ts +120 -0
  17. package/components/chart/EzBarChart.ts +47 -0
  18. package/components/chart/EzChart.module.scss +14 -0
  19. package/components/chart/EzChart.ts +279 -0
  20. package/components/chart/EzDoughnutChart.ts +47 -0
  21. package/components/chart/EzLineChart.ts +53 -0
  22. package/components/checkbox/EzCheckbox.module.scss +145 -0
  23. package/components/checkbox/EzCheckbox.ts +115 -0
  24. package/components/dataview/EzDataView.module.scss +115 -0
  25. package/components/dataview/EzDataView.ts +355 -0
  26. package/components/dataview/modes/EzDataViewCards.ts +322 -0
  27. package/components/dataview/modes/EzDataViewGrid.ts +76 -0
  28. package/components/datepicker/EzDatePicker.module.scss +348 -0
  29. package/components/datepicker/EzDatePicker.ts +519 -0
  30. package/components/dialog/EzDialog.module.scss +180 -0
  31. package/components/dropdown/EzDropdown.module.scss +107 -0
  32. package/components/dropdown/EzDropdown.ts +235 -0
  33. package/components/feed/EzActivityFeed.module.scss +90 -0
  34. package/components/feed/EzActivityFeed.ts +78 -0
  35. package/components/form/EzForm.ts +364 -0
  36. package/components/form/EzValidators.test.js +421 -0
  37. package/components/form/EzValidators.ts +202 -0
  38. package/components/grid/EzGrid.scss +88 -0
  39. package/components/grid/EzGrid.ts +1085 -0
  40. package/components/grid/EzGridContainer.ts +104 -0
  41. package/components/grid/body/EzGridBody.scss +283 -0
  42. package/components/grid/body/EzGridBody.ts +549 -0
  43. package/components/grid/body/EzGridCell.ts +211 -0
  44. package/components/grid/body/EzGridRow.ts +196 -0
  45. package/components/grid/filter/EzGridFilters.scss +78 -0
  46. package/components/grid/filter/EzGridFilters.ts +285 -0
  47. package/components/grid/footer/EzGridFooter.scss +136 -0
  48. package/components/grid/footer/EzGridFooter.ts +448 -0
  49. package/components/grid/header/EzGridHeader.scss +199 -0
  50. package/components/grid/header/EzGridHeader.ts +430 -0
  51. package/components/grid/query/EzGridQuery.ts +81 -0
  52. package/components/grid/state/EzGridColumns.ts +155 -0
  53. package/components/grid/state/EzGridController.ts +470 -0
  54. package/components/grid/state/EzGridLifecycle.ts +136 -0
  55. package/components/grid/state/EzGridNormalizers.test.js +273 -0
  56. package/components/grid/state/EzGridNormalizers.ts +162 -0
  57. package/components/grid/state/EzGridParts.ts +233 -0
  58. package/components/grid/state/EzGridPersistence.ts +140 -0
  59. package/components/grid/state/EzGridRemote.test.js +573 -0
  60. package/components/grid/state/EzGridRemote.ts +335 -0
  61. package/components/grid/state/EzGridSelection.ts +231 -0
  62. package/components/grid/state/EzGridSort.ts +286 -0
  63. package/components/grid/title/EzGridActionBar.ts +98 -0
  64. package/components/grid/title/EzGridTitle.ts +114 -0
  65. package/components/grid/title/EzGridTitleBar.scss +65 -0
  66. package/components/grid/title/EzGridTitleBar.ts +87 -0
  67. package/components/grid/types.ts +607 -0
  68. package/components/panel/EzPanel.module.scss +133 -0
  69. package/components/panel/EzPanel.ts +147 -0
  70. package/components/radio/EzRadio.module.scss +190 -0
  71. package/components/radio/EzRadio.ts +149 -0
  72. package/components/select/EzSelect.module.scss +153 -0
  73. package/components/select/EzSelect.ts +238 -0
  74. package/components/skeleton/EzSkeleton.module.scss +95 -0
  75. package/components/skeleton/EzSkeleton.ts +70 -0
  76. package/components/store/EzStore.ts +344 -0
  77. package/components/switch/EzSwitch.module.scss +164 -0
  78. package/components/switch/EzSwitch.ts +117 -0
  79. package/components/tabs/EzTabPanel.module.scss +181 -0
  80. package/components/tabs/EzTabPanel.ts +402 -0
  81. package/components/textarea/EzTextarea.module.scss +131 -0
  82. package/components/textarea/EzTextarea.ts +161 -0
  83. package/components/timepicker/EzTimePicker.module.scss +282 -0
  84. package/components/timepicker/EzTimePicker.ts +540 -0
  85. package/components/toast/EzToast.module.scss +291 -0
  86. package/components/tooltip/EzTooltip.module.scss +124 -0
  87. package/components/tooltip/EzTooltip.ts +153 -0
  88. package/core/EzComponentTypes.ts +693 -0
  89. package/core/EzError.ts +63 -0
  90. package/core/EzModel.ts +268 -0
  91. package/core/EzTypes.ts +328 -0
  92. package/core/eventBus.ts +284 -0
  93. package/core/ez.ts +617 -0
  94. package/core/loader.ts +725 -0
  95. package/core/renderer.ts +1010 -0
  96. package/core/router.ts +490 -0
  97. package/core/services.ts +124 -0
  98. package/core/state.ts +142 -0
  99. package/core/utils.ts +81 -0
  100. package/package.json +51 -0
  101. package/services/RouteUI.js +17 -0
  102. package/services/crypto.js +64 -0
  103. package/services/dialog.js +222 -0
  104. package/services/fetchApi.js +63 -0
  105. package/services/firebase.js +30 -0
  106. package/services/toast.js +214 -0
  107. package/template/doc/EzDocs.js +15 -0
  108. package/template/doc/EzDocs.module.scss +627 -0
  109. package/template/doc/EzDocsController.js +164 -0
  110. package/template/doc/data/activityfeed/EzActivityFeedDoc.js +42 -0
  111. package/template/doc/data/avatar/EzAvatarDoc.js +71 -0
  112. package/template/doc/data/badge/EzBadgeDoc.js +92 -0
  113. package/template/doc/data/button/EzButtonDoc.js +77 -0
  114. package/template/doc/data/buttongroup/EzButtonGroupDoc.js +102 -0
  115. package/template/doc/data/card/EzCardDoc.js +39 -0
  116. package/template/doc/data/chart/EzChartDoc.js +60 -0
  117. package/template/doc/data/checkbox/EzCheckboxDoc.js +67 -0
  118. package/template/doc/data/component/EzComponentDoc.js +34 -0
  119. package/template/doc/data/cssmodules/CSSModulesDoc.js +70 -0
  120. package/template/doc/data/datepicker/EzDatePickerDoc.js +126 -0
  121. package/template/doc/data/dialog/EzDialogDoc.js +217 -0
  122. package/template/doc/data/dropdown/EzDropdownDoc.js +178 -0
  123. package/template/doc/data/form/EzFormDoc.js +90 -0
  124. package/template/doc/data/grid/EzGridDoc.js +99 -0
  125. package/template/doc/data/input/EzInputDoc.js +92 -0
  126. package/template/doc/data/label/EzLabelDoc.js +40 -0
  127. package/template/doc/data/model/EzModelDoc.js +53 -0
  128. package/template/doc/data/outlet/EzOutletDoc.js +63 -0
  129. package/template/doc/data/panel/EzPanelDoc.js +214 -0
  130. package/template/doc/data/radio/EzRadioDoc.js +174 -0
  131. package/template/doc/data/router/EzRouterDoc.js +75 -0
  132. package/template/doc/data/select/EzSelectDoc.js +37 -0
  133. package/template/doc/data/skeleton/EzSkeletonDoc.js +149 -0
  134. package/template/doc/data/switch/EzSwitchDoc.js +82 -0
  135. package/template/doc/data/tabpanel/EzTabPanelDoc.js +44 -0
  136. package/template/doc/data/textarea/EzTextareaDoc.js +131 -0
  137. package/template/doc/data/timepicker/EzTimePickerDoc.js +107 -0
  138. package/template/doc/data/tooltip/EzTooltipDoc.js +193 -0
  139. package/template/doc/data/validators/EzValidatorsDoc.js +37 -0
  140. package/template/doc/sidebar/EzDocsSidebar.js +32 -0
  141. package/template/doc/sidebar/category/EzDocsCategory.js +33 -0
  142. package/template/doc/sidebar/item/EzDocsComponentItem.js +24 -0
  143. package/template/doc/viewer/EzDocsViewer.js +18 -0
  144. package/template/doc/viewer/codepanel/EzDocsCodePanel.js +51 -0
  145. package/template/doc/viewer/content/EzDocsContent.js +315 -0
  146. package/template/doc/viewer/header/EzDocsViewerHeader.js +46 -0
  147. package/template/doc/viewer/showcase/EzDocsShowcase.js +59 -0
  148. package/template/doc/viewer/showcase/EzDocsShowcaseSection.js +25 -0
  149. package/template/doc/viewer/showcase/EzDocsVariantItem.js +29 -0
  150. package/template/doc/welcome/EzDocsWelcome.js +48 -0
  151. package/themes/ez-theme.scss +179 -0
  152. package/themes/nature-fresh.scss +169 -0
  153. package/types/global.d.ts +21 -0
  154. package/utils/cssModules.js +81 -0
@@ -0,0 +1,305 @@
1
+ import { EzBaseComponent, EzBaseComponentConfig } from "./EzBaseComponent.js";
2
+
3
+ declare const ez: {
4
+ _createChildElements(items: unknown[], controller: string | null, parent: unknown, css?: string | null): Promise<Node[]>;
5
+ };
6
+
7
+ type DOMEventHandler<E extends Event = Event> = (e: E) => void;
8
+
9
+ export interface HtmlWrapperConfig extends EzBaseComponentConfig {
10
+ eztype: string;
11
+
12
+ // Content
13
+ text?: string;
14
+ html?: string;
15
+
16
+ // Common HTML attributes
17
+ id?: string;
18
+ src?: string;
19
+ alt?: string;
20
+ title?: string;
21
+ href?: string;
22
+ target?: string;
23
+ rel?: string;
24
+
25
+ // Form element attributes
26
+ value?: string | number;
27
+ type?: string;
28
+ name?: string;
29
+ placeholder?: string;
30
+ checked?: boolean;
31
+ disabled?: boolean;
32
+ readonly?: boolean;
33
+ required?: boolean;
34
+ maxLength?: number;
35
+ minLength?: number;
36
+ min?: number | string;
37
+ max?: number | string;
38
+ step?: number | string;
39
+ pattern?: string;
40
+ autocomplete?: string;
41
+ autofocus?: boolean;
42
+ multiple?: boolean;
43
+ accept?: string;
44
+ rows?: number;
45
+ cols?: number;
46
+
47
+ // Accessibility
48
+ role?: string;
49
+ tabIndex?: number;
50
+ ariaLabel?: string;
51
+ ariaDescribedBy?: string;
52
+ ariaHidden?: boolean;
53
+ ariaExpanded?: boolean;
54
+ ariaSelected?: boolean;
55
+ ariaDisabled?: boolean;
56
+
57
+ // Other attributes
58
+ contentEditable?: boolean | 'true' | 'false';
59
+ draggable?: boolean;
60
+ spellcheck?: boolean;
61
+ hidden?: boolean;
62
+
63
+ // Layout
64
+ flex?: number | string;
65
+ width?: number | string;
66
+ height?: number | string;
67
+
68
+ // Generic attrs for anything not explicitly defined
69
+ attrs?: Record<string, string | number | boolean | null | undefined>;
70
+
71
+ // Mouse events
72
+ onClick?: DOMEventHandler<MouseEvent>;
73
+ onDoubleClick?: DOMEventHandler<MouseEvent>;
74
+ onContextMenu?: DOMEventHandler<MouseEvent>;
75
+ onMouseDown?: DOMEventHandler<MouseEvent>;
76
+ onMouseUp?: DOMEventHandler<MouseEvent>;
77
+ onMouseEnter?: DOMEventHandler<MouseEvent>;
78
+ onMouseLeave?: DOMEventHandler<MouseEvent>;
79
+ onMouseMove?: DOMEventHandler<MouseEvent>;
80
+ onMouseOver?: DOMEventHandler<MouseEvent>;
81
+ onMouseOut?: DOMEventHandler<MouseEvent>;
82
+
83
+ // Keyboard events
84
+ onKeyDown?: DOMEventHandler<KeyboardEvent>;
85
+ onKeyUp?: DOMEventHandler<KeyboardEvent>;
86
+ onKeyPress?: DOMEventHandler<KeyboardEvent>;
87
+
88
+ // Focus events
89
+ onFocus?: DOMEventHandler<FocusEvent>;
90
+ onBlur?: DOMEventHandler<FocusEvent>;
91
+
92
+ // Form events
93
+ onInput?: DOMEventHandler<Event>;
94
+ onDOMChange?: DOMEventHandler<Event>;
95
+ onSubmit?: DOMEventHandler<SubmitEvent>;
96
+ onReset?: DOMEventHandler<Event>;
97
+
98
+ // Scroll/Wheel events
99
+ onScroll?: DOMEventHandler<Event>;
100
+ onWheel?: DOMEventHandler<WheelEvent>;
101
+
102
+ // Drag events
103
+ onDragStart?: DOMEventHandler<DragEvent>;
104
+ onDragEnd?: DOMEventHandler<DragEvent>;
105
+ onDragOver?: DOMEventHandler<DragEvent>;
106
+ onDragEnter?: DOMEventHandler<DragEvent>;
107
+ onDragLeave?: DOMEventHandler<DragEvent>;
108
+ onDrop?: DOMEventHandler<DragEvent>;
109
+
110
+ // Touch events
111
+ onTouchStart?: DOMEventHandler<TouchEvent>;
112
+ onTouchMove?: DOMEventHandler<TouchEvent>;
113
+ onTouchEnd?: DOMEventHandler<TouchEvent>;
114
+ onTouchCancel?: DOMEventHandler<TouchEvent>;
115
+
116
+ // Clipboard events
117
+ onCopy?: DOMEventHandler<ClipboardEvent>;
118
+ onCut?: DOMEventHandler<ClipboardEvent>;
119
+ onPaste?: DOMEventHandler<ClipboardEvent>;
120
+
121
+ // Media events
122
+ onLoad?: DOMEventHandler<Event>;
123
+ onError?: DOMEventHandler<Event>;
124
+
125
+ // Children
126
+ items?: unknown[];
127
+ }
128
+
129
+ export class HtmlWrapper extends EzBaseComponent {
130
+ declare config: HtmlWrapperConfig;
131
+ protected _domListeners: (() => void)[] = [];
132
+
133
+ constructor(cfg: HtmlWrapperConfig) {
134
+ super(cfg);
135
+ this.config = cfg;
136
+ }
137
+
138
+ async render(): Promise<HTMLElement> {
139
+ this._domListeners = [];
140
+
141
+ const cfg = this.config;
142
+ const el = document.createElement(cfg.eztype) as HTMLElement;
143
+
144
+ this.applyCls(el);
145
+ this.applyCommonBindings(el);
146
+ this.applyStyles(el);
147
+
148
+ // Layout
149
+ if (cfg.flex !== undefined) {
150
+ el.style.flex = String(cfg.flex);
151
+ }
152
+ if (cfg.width !== undefined) {
153
+ el.style.width = typeof cfg.width === 'number' ? `${cfg.width}px` : cfg.width;
154
+ }
155
+ if (cfg.height !== undefined) {
156
+ el.style.height = typeof cfg.height === 'number' ? `${cfg.height}px` : cfg.height;
157
+ }
158
+
159
+ // Content
160
+ if (cfg.text) el.textContent = cfg.text;
161
+ if (cfg.html) el.innerHTML = cfg.html;
162
+
163
+ // Standard DOM properties (set as properties, not attributes)
164
+ const domProps = [
165
+ "src", "alt", "title", "href", "target", "rel",
166
+ "value", "type", "name", "placeholder",
167
+ "id", "pattern", "autocomplete", "accept"
168
+ ] as const;
169
+
170
+ for (const key of domProps) {
171
+ const value = cfg[key as keyof HtmlWrapperConfig];
172
+ if (value !== undefined && value !== null) {
173
+ (el as unknown as Record<string, unknown>)[key] = value;
174
+ }
175
+ }
176
+
177
+ // Boolean properties
178
+ const booleanProps = [
179
+ "checked", "disabled", "readonly", "required",
180
+ "autofocus", "multiple", "hidden", "draggable", "spellcheck"
181
+ ] as const;
182
+
183
+ for (const key of booleanProps) {
184
+ const value = cfg[key as keyof HtmlWrapperConfig];
185
+ if (value !== undefined && value !== null) {
186
+ (el as unknown as Record<string, boolean>)[key] = !!value;
187
+ }
188
+ }
189
+
190
+ // Numeric properties
191
+ const numericProps = [
192
+ "maxLength", "minLength", "rows", "cols", "tabIndex"
193
+ ] as const;
194
+
195
+ for (const key of numericProps) {
196
+ const value = cfg[key as keyof HtmlWrapperConfig];
197
+ if (value !== undefined && value !== null) {
198
+ const attrName = key === "maxLength" ? "maxlength"
199
+ : key === "minLength" ? "minlength"
200
+ : key === "tabIndex" ? "tabindex"
201
+ : key;
202
+ el.setAttribute(attrName, String(value));
203
+ }
204
+ }
205
+
206
+ // Min/max/step (can be number or string)
207
+ if (cfg.min !== undefined) el.setAttribute("min", String(cfg.min));
208
+ if (cfg.max !== undefined) el.setAttribute("max", String(cfg.max));
209
+ if (cfg.step !== undefined) el.setAttribute("step", String(cfg.step));
210
+
211
+ // Accessibility attributes
212
+ if (cfg.role) el.setAttribute("role", cfg.role);
213
+ if (cfg.ariaLabel) el.setAttribute("aria-label", cfg.ariaLabel);
214
+ if (cfg.ariaDescribedBy) el.setAttribute("aria-describedby", cfg.ariaDescribedBy);
215
+ if (cfg.ariaHidden !== undefined) el.setAttribute("aria-hidden", String(cfg.ariaHidden));
216
+ if (cfg.ariaExpanded !== undefined) el.setAttribute("aria-expanded", String(cfg.ariaExpanded));
217
+ if (cfg.ariaSelected !== undefined) el.setAttribute("aria-selected", String(cfg.ariaSelected));
218
+ if (cfg.ariaDisabled !== undefined) el.setAttribute("aria-disabled", String(cfg.ariaDisabled));
219
+
220
+ // contentEditable
221
+ if (cfg.contentEditable !== undefined) {
222
+ el.contentEditable = String(cfg.contentEditable);
223
+ }
224
+
225
+ // Generic attrs for anything not explicitly defined
226
+ if (cfg.attrs && typeof cfg.attrs === "object") {
227
+ Object.entries(cfg.attrs).forEach(([k, v]) => {
228
+ if (v === false || v === null || v === undefined) return;
229
+ el.setAttribute(k, String(v));
230
+ });
231
+ }
232
+
233
+ // Event handlers mapping: config key -> DOM event name
234
+ const eventMap: Array<[keyof HtmlWrapperConfig, string]> = [
235
+ // Mouse
236
+ ["onClick", "click"],
237
+ ["onDoubleClick", "dblclick"],
238
+ ["onContextMenu", "contextmenu"],
239
+ ["onMouseDown", "mousedown"],
240
+ ["onMouseUp", "mouseup"],
241
+ ["onMouseEnter", "mouseenter"],
242
+ ["onMouseLeave", "mouseleave"],
243
+ ["onMouseMove", "mousemove"],
244
+ ["onMouseOver", "mouseover"],
245
+ ["onMouseOut", "mouseout"],
246
+ // Keyboard
247
+ ["onKeyDown", "keydown"],
248
+ ["onKeyUp", "keyup"],
249
+ ["onKeyPress", "keypress"],
250
+ // Focus
251
+ ["onFocus", "focus"],
252
+ ["onBlur", "blur"],
253
+ // Form
254
+ ["onInput", "input"],
255
+ ["onDOMChange", "change"],
256
+ ["onSubmit", "submit"],
257
+ ["onReset", "reset"],
258
+ // Scroll/Wheel
259
+ ["onScroll", "scroll"],
260
+ ["onWheel", "wheel"],
261
+ // Drag
262
+ ["onDragStart", "dragstart"],
263
+ ["onDragEnd", "dragend"],
264
+ ["onDragOver", "dragover"],
265
+ ["onDragEnter", "dragenter"],
266
+ ["onDragLeave", "dragleave"],
267
+ ["onDrop", "drop"],
268
+ // Touch
269
+ ["onTouchStart", "touchstart"],
270
+ ["onTouchMove", "touchmove"],
271
+ ["onTouchEnd", "touchend"],
272
+ ["onTouchCancel", "touchcancel"],
273
+ // Clipboard
274
+ ["onCopy", "copy"],
275
+ ["onCut", "cut"],
276
+ ["onPaste", "paste"],
277
+ // Media
278
+ ["onLoad", "load"],
279
+ ["onError", "error"],
280
+ ];
281
+
282
+ for (const [configKey, eventName] of eventMap) {
283
+ const handler = cfg[configKey];
284
+ if (typeof handler === "function") {
285
+ el.addEventListener(eventName, handler as EventListener);
286
+ this._domListeners.push(() =>
287
+ el.removeEventListener(eventName, handler as EventListener)
288
+ );
289
+ }
290
+ }
291
+
292
+ // Children
293
+ if (Array.isArray(cfg.items)) {
294
+ const children = await ez._createChildElements(
295
+ cfg.items,
296
+ cfg.controller || null,
297
+ this,
298
+ (cfg.css as string) || null
299
+ );
300
+ children.forEach(child => el.appendChild(child));
301
+ }
302
+
303
+ return el;
304
+ }
305
+ }
@@ -0,0 +1,200 @@
1
+ // ==========================================================
2
+ // EzAvatar - CSS Module
3
+ // ==========================================================
4
+
5
+ .avatar {
6
+ position: relative;
7
+ display: inline-flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ flex-shrink: 0;
11
+
12
+ width: 40px;
13
+ height: 40px;
14
+
15
+ border-radius: 50%;
16
+ overflow: hidden;
17
+
18
+ background-color: var(--ez-border-primary, #e2e8f0);
19
+ color: var(--ez-text-secondary, #475569);
20
+
21
+ font-weight: 600;
22
+ font-size: 14px;
23
+ text-transform: uppercase;
24
+ user-select: none;
25
+ }
26
+
27
+ // ----------------------------------------------------------
28
+ // Image
29
+ // ----------------------------------------------------------
30
+ .avatarImg {
31
+ width: 100%;
32
+ height: 100%;
33
+ object-fit: cover;
34
+ }
35
+
36
+ // ----------------------------------------------------------
37
+ // Initials (fallback)
38
+ // ----------------------------------------------------------
39
+ .avatarInitials {
40
+ line-height: 1;
41
+ }
42
+
43
+ // ----------------------------------------------------------
44
+ // Status indicator
45
+ // ----------------------------------------------------------
46
+ .avatarStatus {
47
+ position: absolute;
48
+ bottom: 0;
49
+ right: 0;
50
+ width: 12px;
51
+ height: 12px;
52
+ border-radius: 50%;
53
+ border: 2px solid var(--ez-surface-secondary, #ffffff);
54
+ }
55
+
56
+ .statusOnline {
57
+ background-color: var(--ez-success, #22c55e);
58
+ }
59
+
60
+ .statusOffline {
61
+ background-color: var(--ez-text-tertiary, #94a3b8);
62
+ }
63
+
64
+ .statusBusy {
65
+ background-color: var(--ez-danger, #ef4444);
66
+ }
67
+
68
+ .statusAway {
69
+ background-color: var(--ez-warning, #f59e0b);
70
+ }
71
+
72
+ // ----------------------------------------------------------
73
+ // Sizes
74
+ // ----------------------------------------------------------
75
+ .xs {
76
+ width: 24px;
77
+ height: 24px;
78
+ font-size: 10px;
79
+
80
+ .avatarStatus {
81
+ width: 8px;
82
+ height: 8px;
83
+ border-width: 1.5px;
84
+ }
85
+ }
86
+
87
+ .sm {
88
+ width: 32px;
89
+ height: 32px;
90
+ font-size: 12px;
91
+
92
+ .avatarStatus {
93
+ width: 10px;
94
+ height: 10px;
95
+ border-width: 2px;
96
+ }
97
+ }
98
+
99
+ .lg {
100
+ width: 48px;
101
+ height: 48px;
102
+ font-size: 18px;
103
+
104
+ .avatarStatus {
105
+ width: 14px;
106
+ height: 14px;
107
+ }
108
+ }
109
+
110
+ .xl {
111
+ width: 64px;
112
+ height: 64px;
113
+ font-size: 24px;
114
+
115
+ .avatarStatus {
116
+ width: 16px;
117
+ height: 16px;
118
+ }
119
+ }
120
+
121
+ // ----------------------------------------------------------
122
+ // Variants
123
+ // ----------------------------------------------------------
124
+ .square {
125
+ border-radius: var(--ez-radius-lg, 8px);
126
+ }
127
+
128
+ .rounded {
129
+ border-radius: var(--ez-radius-xl, 12px);
130
+ }
131
+
132
+ // ----------------------------------------------------------
133
+ // Color variants (for initials)
134
+ // ----------------------------------------------------------
135
+ .primary {
136
+ background-color: var(--ez-primary-light, #dbeafe);
137
+ color: var(--ez-primary, #2563eb);
138
+ }
139
+
140
+ .success {
141
+ background-color: var(--ez-success-light, #dcfce7);
142
+ color: var(--ez-success, #16a34a);
143
+ }
144
+
145
+ .warning {
146
+ background-color: var(--ez-warning-light, #fef3c7);
147
+ color: var(--ez-warning, #d97706);
148
+ }
149
+
150
+ .danger {
151
+ background-color: var(--ez-danger-light, #fee2e2);
152
+ color: var(--ez-danger, #dc2626);
153
+ }
154
+
155
+ // ----------------------------------------------------------
156
+ // Clickable
157
+ // ----------------------------------------------------------
158
+ .clickable {
159
+ cursor: pointer;
160
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
161
+
162
+ &:hover {
163
+ transform: scale(1.05);
164
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
165
+ }
166
+
167
+ &:active {
168
+ transform: scale(0.98);
169
+ }
170
+ }
171
+
172
+ // ==========================================================
173
+ // Avatar Group
174
+ // ==========================================================
175
+
176
+ .avatarGroup {
177
+ display: inline-flex;
178
+ flex-direction: row-reverse;
179
+
180
+ .avatar {
181
+ border: 2px solid var(--ez-surface-secondary, #ffffff);
182
+ margin-left: -8px;
183
+
184
+ &:last-child {
185
+ margin-left: 0;
186
+ }
187
+ }
188
+ }
189
+
190
+ .avatarGroupSm {
191
+ .avatar {
192
+ margin-left: -6px;
193
+ }
194
+ }
195
+
196
+ .avatarGroupLg {
197
+ .avatar {
198
+ margin-left: -12px;
199
+ }
200
+ }
@@ -0,0 +1,130 @@
1
+ import styles from './EzAvatar.module.scss';
2
+ import { cx } from '../../utils/cssModules.js';
3
+ import { EzBaseComponent, EzBaseComponentConfig } from '../EzBaseComponent.js';
4
+
5
+ const cls = cx(styles);
6
+
7
+ const STATUS_MAP: Record<string, string> = {
8
+ 'online': 'statusOnline',
9
+ 'offline': 'statusOffline',
10
+ 'busy': 'statusBusy',
11
+ 'away': 'statusAway'
12
+ };
13
+
14
+ export interface EzAvatarConfig extends EzBaseComponentConfig {
15
+ size?: number | 'xs' | 'sm' | 'lg' | 'xl';
16
+ width?: number | string;
17
+ height?: number | string;
18
+ variant?: 'square' | 'rounded';
19
+ color?: 'primary' | 'success' | 'warning' | 'danger';
20
+ src?: string;
21
+ alt?: string;
22
+ name?: string;
23
+ initials?: string;
24
+ status?: 'online' | 'offline' | 'busy' | 'away';
25
+ onClick?: (e: MouseEvent, component: EzAvatar) => void;
26
+ }
27
+
28
+ export class EzAvatar extends EzBaseComponent {
29
+ declare config: EzAvatarConfig;
30
+
31
+ constructor(config: EzAvatarConfig = {}) {
32
+ super(config);
33
+ this.config = config;
34
+ }
35
+
36
+ render(): HTMLDivElement {
37
+ const el = document.createElement('div');
38
+
39
+ el.classList.add(cls('avatar'));
40
+
41
+ if (this.config.width || this.config.height) {
42
+ const width = this.config.width || this.config.height;
43
+ const height = this.config.height || this.config.width;
44
+
45
+ el.style.width = typeof width === 'number' ? `${width}px` : String(width);
46
+ el.style.height = typeof height === 'number' ? `${height}px` : String(height);
47
+
48
+ const sizeNum = typeof width === 'number' ? width : parseInt(String(width), 10);
49
+ if (sizeNum) {
50
+ el.style.fontSize = `${Math.round(sizeNum * 0.4)}px`;
51
+ }
52
+ }
53
+ else if (this.config.size) {
54
+ if (typeof this.config.size === 'number') {
55
+ el.style.width = `${this.config.size}px`;
56
+ el.style.height = `${this.config.size}px`;
57
+ el.style.fontSize = `${Math.round(this.config.size * 0.4)}px`;
58
+ }
59
+ else {
60
+ el.classList.add(cls(this.config.size));
61
+ }
62
+ }
63
+
64
+ if (this.config.variant) {
65
+ el.classList.add(cls(this.config.variant));
66
+ }
67
+
68
+ if (this.config.color) {
69
+ el.classList.add(cls(this.config.color));
70
+ }
71
+
72
+ if (this.config.onClick) {
73
+ el.classList.add(cls('clickable'));
74
+ el.addEventListener('click', e => {
75
+ this.config.onClick!(e, this);
76
+ });
77
+ }
78
+
79
+ if (this.config.src) {
80
+ const img = document.createElement('img');
81
+ img.className = cls('avatarImg');
82
+ img.src = this.config.src;
83
+ img.alt = this.config.alt || this.config.name || '';
84
+
85
+ img.onerror = () => {
86
+ img.remove();
87
+ el.appendChild(this._createInitials());
88
+ };
89
+
90
+ el.appendChild(img);
91
+ } else {
92
+ el.appendChild(this._createInitials());
93
+ }
94
+
95
+ if (this.config.status) {
96
+ const status = document.createElement('span');
97
+ const statusClass = STATUS_MAP[this.config.status] || 'statusOffline';
98
+ status.className = cls('avatarStatus', statusClass);
99
+ el.appendChild(status);
100
+ }
101
+
102
+ this.applyStyles(el);
103
+
104
+ return el;
105
+ }
106
+
107
+ private _createInitials(): HTMLSpanElement {
108
+ const initialsEl = document.createElement('span');
109
+ initialsEl.className = cls('avatarInitials');
110
+ initialsEl.textContent = this._getInitials();
111
+ return initialsEl;
112
+ }
113
+
114
+ private _getInitials(): string {
115
+ if (this.config.initials) {
116
+ return this.config.initials.substring(0, 2);
117
+ }
118
+
119
+ const name = this.config.name || '';
120
+ if (!name) return '?';
121
+
122
+ const parts = name.trim().split(/\s+/);
123
+
124
+ if (parts.length === 1) {
125
+ return parts[0].substring(0, 2).toUpperCase();
126
+ }
127
+
128
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
129
+ }
130
+ }