ezfw-core 1.0.21 → 1.0.23

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 (43) hide show
  1. package/components/EzBaseComponent.ts +100 -5
  2. package/components/EzComponent.ts +3 -3
  3. package/components/EzLabel.ts +12 -3
  4. package/components/avatar/EzAvatar.ts +84 -54
  5. package/components/badge/EzBadge.ts +43 -24
  6. package/components/button/EzButton.ts +5 -3
  7. package/components/button/EzButtonGroup.ts +7 -10
  8. package/components/card/EzCard.ts +2 -1
  9. package/components/chart/EzChart.ts +20 -15
  10. package/components/checkbox/EzCheckbox.ts +47 -43
  11. package/components/dataview/EzDataView.ts +14 -29
  12. package/components/dataview/modes/EzDataViewCards.ts +51 -41
  13. package/components/dataview/modes/EzDataViewGrid.ts +5 -2
  14. package/components/datepicker/EzDatePicker.ts +2 -2
  15. package/components/dialog/EzDialog.ts +84 -67
  16. package/components/dropdown/EzDropdown.ts +72 -58
  17. package/components/form/EzForm.ts +45 -37
  18. package/components/kanban/EzKanban.module.scss +221 -0
  19. package/components/kanban/EzKanban.ts +222 -0
  20. package/components/kanban/EzKanbanTypes.ts +166 -0
  21. package/components/kanban/board/EzKanbanBoard.ts +117 -0
  22. package/components/kanban/card/EzKanbanCard.module.scss +173 -0
  23. package/components/kanban/card/EzKanbanCard.ts +275 -0
  24. package/components/kanban/card/EzKanbanCardEditor.ts +209 -0
  25. package/components/kanban/column/EzKanbanColumn.ts +253 -0
  26. package/components/kanban/state/EzKanbanController.ts +373 -0
  27. package/components/kanban/state/EzKanbanDragDrop.ts +226 -0
  28. package/components/panel/EzPanel.ts +59 -68
  29. package/components/picker/EzPicker.module.scss +14 -0
  30. package/components/picker/EzPicker.ts +118 -0
  31. package/components/radio/EzRadio.ts +55 -47
  32. package/components/select/EzSelect.ts +48 -44
  33. package/components/skeleton/EzSkeleton.ts +31 -26
  34. package/components/switch/EzSwitch.ts +52 -44
  35. package/components/tabs/EzTabPanel.ts +52 -48
  36. package/components/textarea/EzTextarea.ts +69 -54
  37. package/components/timepicker/EzTimePicker.ts +2 -2
  38. package/components/tooltip/EzTooltip.ts +20 -33
  39. package/core/ez.ts +7 -0
  40. package/core/loader.ts +2 -0
  41. package/core/renderer.ts +80 -4
  42. package/core/styleShortcuts.ts +418 -0
  43. package/package.json +1 -1
@@ -0,0 +1,222 @@
1
+ import styles from './EzKanban.module.scss';
2
+ import { cx } from '../../utils/cssModules.js';
3
+ import { EzBaseComponent } from '../EzBaseComponent.js';
4
+ import { EzKanbanController } from './state/EzKanbanController.js';
5
+ import { EzKanbanDragDrop } from './state/EzKanbanDragDrop.js';
6
+ import type {
7
+ EzKanbanConfig,
8
+ KanbanCard,
9
+ KanbanColumn,
10
+ CardMoveEvent,
11
+ CardCreateEvent,
12
+ CardUpdateEvent,
13
+ CardDeleteEvent
14
+ } from './EzKanbanTypes.js';
15
+
16
+ import './board/EzKanbanBoard.js';
17
+ import './column/EzKanbanColumn.js';
18
+ import './card/EzKanbanCard.js';
19
+ import './card/EzKanbanCardEditor.js';
20
+
21
+ const cls = cx(styles);
22
+
23
+ declare const ez: {
24
+ _createElement(config: unknown): Promise<HTMLElement>;
25
+ define(name: string, cls: unknown): void;
26
+ };
27
+
28
+ export class EzKanban extends EzBaseComponent {
29
+ declare config: EzKanbanConfig;
30
+
31
+ private _controller: EzKanbanController;
32
+ private _dragDrop: EzKanbanDragDrop;
33
+ private _el: HTMLElement | null = null;
34
+ private _unsubscribers: Array<() => void> = [];
35
+
36
+ constructor(config: EzKanbanConfig = {}) {
37
+ super(config);
38
+ this._controller = new EzKanbanController(config);
39
+ this._dragDrop = new EzKanbanDragDrop(this._controller);
40
+
41
+ this._setupEventForwarding();
42
+ }
43
+
44
+ get controller(): EzKanbanController {
45
+ return this._controller;
46
+ }
47
+
48
+ async render(): Promise<HTMLElement> {
49
+ const variant = this.config.variant || 'elevated';
50
+ const items: unknown[] = [];
51
+
52
+ // Toolbar
53
+ if (this.config.filterable || this.config.searchable) {
54
+ items.push(this._buildToolbarConfig());
55
+ }
56
+
57
+ // Board
58
+ items.push({
59
+ eztype: 'EzKanbanBoard',
60
+ kanbanController: this._controller,
61
+ kanbanDragDrop: this._dragDrop,
62
+ kanbanConfig: this.config
63
+ });
64
+
65
+ const el = await ez._createElement({
66
+ eztype: 'div',
67
+ cls: cls('kanban', variant),
68
+ items
69
+ }) as HTMLElement;
70
+
71
+ this._el = el;
72
+
73
+ if (this.config.autoLoad !== false && this.config.remote) {
74
+ queueMicrotask(() => this._controller.load());
75
+ }
76
+
77
+ return el;
78
+ }
79
+
80
+ private _buildToolbarConfig(): unknown {
81
+ const toolbarItems: unknown[] = [];
82
+
83
+ if (this.config.searchable) {
84
+ toolbarItems.push({
85
+ eztype: 'EzInput',
86
+ placeholder: 'Search cards...',
87
+ icon: 'search',
88
+ style: { width: '240px' },
89
+ onInput: (value: string) => {
90
+ this._controller.setFilters({ search: value });
91
+ }
92
+ });
93
+ }
94
+
95
+ if (this.config.filterable) {
96
+ toolbarItems.push({
97
+ eztype: 'div',
98
+ style: { flex: '1' }
99
+ });
100
+
101
+ if (this.config.availableAssignees?.length) {
102
+ toolbarItems.push({
103
+ eztype: 'button',
104
+ cls: 'ez-kanban-filter-btn',
105
+ items: [
106
+ { eztype: 'i', cls: 'fa-solid fa-filter' },
107
+ { eztype: 'span', text: ' Filters' }
108
+ ]
109
+ });
110
+ }
111
+ }
112
+
113
+ return {
114
+ eztype: 'div',
115
+ cls: 'ez-kanban-toolbar',
116
+ items: toolbarItems
117
+ };
118
+ }
119
+
120
+ private _setupEventForwarding(): void {
121
+ this._unsubscribers.push(
122
+ this._controller.on('cardmove', (payload) => {
123
+ this.config.onCardMove?.(payload as CardMoveEvent);
124
+ })
125
+ );
126
+
127
+ this._unsubscribers.push(
128
+ this._controller.on('cardcreate', (payload) => {
129
+ this.config.onCardCreate?.(payload as CardCreateEvent);
130
+ })
131
+ );
132
+
133
+ this._unsubscribers.push(
134
+ this._controller.on('cardupdate', (payload) => {
135
+ this.config.onCardUpdate?.(payload as CardUpdateEvent);
136
+ })
137
+ );
138
+
139
+ this._unsubscribers.push(
140
+ this._controller.on('carddelete', (payload) => {
141
+ this.config.onCardDelete?.(payload as CardDeleteEvent);
142
+ })
143
+ );
144
+
145
+ this._unsubscribers.push(
146
+ this._controller.on('columncreate', (payload) => {
147
+ this.config.onColumnCreate?.(payload as KanbanColumn);
148
+ })
149
+ );
150
+
151
+ this._unsubscribers.push(
152
+ this._controller.on('columnupdate', (payload) => {
153
+ this.config.onColumnUpdate?.(payload as KanbanColumn);
154
+ })
155
+ );
156
+
157
+ this._unsubscribers.push(
158
+ this._controller.on('columndelete', (payload) => {
159
+ this.config.onColumnDelete?.(payload as KanbanColumn);
160
+ })
161
+ );
162
+ }
163
+
164
+ async load(): Promise<void> {
165
+ return this._controller.load();
166
+ }
167
+
168
+ async reload(): Promise<void> {
169
+ return this._controller.reload();
170
+ }
171
+
172
+ setData(columns: KanbanColumn[], cards: KanbanCard[]): void {
173
+ this._controller.setData(columns, cards);
174
+ }
175
+
176
+ addCard(columnId: string | number, cardData: Partial<KanbanCard>): KanbanCard {
177
+ return this._controller.addCard(columnId, cardData);
178
+ }
179
+
180
+ updateCard(cardId: string | number, updates: Partial<KanbanCard>): KanbanCard | null {
181
+ return this._controller.updateCard(cardId, updates);
182
+ }
183
+
184
+ deleteCard(cardId: string | number): KanbanCard | null {
185
+ return this._controller.deleteCard(cardId);
186
+ }
187
+
188
+ moveCard(cardId: string | number, toColumnId: string | number, toIndex: number): void {
189
+ this._controller.moveCard(cardId, toColumnId, toIndex);
190
+ }
191
+
192
+ addColumn(columnData: Partial<KanbanColumn>): void {
193
+ this._controller.addColumn(columnData);
194
+ }
195
+
196
+ updateColumn(columnId: string | number, updates: Partial<KanbanColumn>): void {
197
+ this._controller.updateColumn(columnId, updates);
198
+ }
199
+
200
+ deleteColumn(columnId: string | number): void {
201
+ this._controller.deleteColumn(columnId);
202
+ }
203
+
204
+ setFilters(filters: { search?: string; assignees?: (string | number)[]; labels?: (string | number)[] }): void {
205
+ this._controller.setFilters(filters);
206
+ }
207
+
208
+ clearFilters(): void {
209
+ this._controller.clearFilters();
210
+ }
211
+
212
+ findCard(cardId: string | number): KanbanCard | null {
213
+ return this._controller.findCard(cardId);
214
+ }
215
+
216
+ destroy(): void {
217
+ this._unsubscribers.forEach(fn => fn());
218
+ this._unsubscribers = [];
219
+ }
220
+ }
221
+
222
+ ez.define('EzKanban', EzKanban);
@@ -0,0 +1,166 @@
1
+ export type KanbanPriority = 'critical' | 'high' | 'medium' | 'low' | 'none';
2
+
3
+ export interface KanbanLabel {
4
+ id: string | number;
5
+ text: string;
6
+ color: string;
7
+ }
8
+
9
+ export interface KanbanAssignee {
10
+ id: string | number;
11
+ name: string;
12
+ avatar?: string;
13
+ email?: string;
14
+ }
15
+
16
+ export interface KanbanCard {
17
+ id: string | number;
18
+ columnId: string | number;
19
+ title: string;
20
+ description?: string;
21
+ priority?: KanbanPriority;
22
+ labels?: KanbanLabel[];
23
+ assignee?: KanbanAssignee | null;
24
+ assignees?: KanbanAssignee[];
25
+ dueDate?: string | Date | null;
26
+ order?: number;
27
+ coverImage?: string;
28
+ attachments?: number;
29
+ comments?: number;
30
+ checklist?: { total: number; completed: number };
31
+ createdAt?: string | Date;
32
+ updatedAt?: string | Date;
33
+ [key: string]: unknown;
34
+ }
35
+
36
+ export interface KanbanColumn {
37
+ id: string | number;
38
+ title: string;
39
+ order?: number;
40
+ color?: string;
41
+ limit?: number;
42
+ collapsed?: boolean;
43
+ locked?: boolean;
44
+ }
45
+
46
+ export interface NormalizedColumn extends KanbanColumn {
47
+ _id: string;
48
+ cards: KanbanCard[];
49
+ }
50
+
51
+ export interface DragContext {
52
+ type: 'card' | 'column';
53
+ item: KanbanCard | NormalizedColumn;
54
+ sourceColumnId: string | null;
55
+ sourceIndex: number;
56
+ isDragging: boolean;
57
+ }
58
+
59
+ export interface DropTarget {
60
+ columnId: string;
61
+ index: number;
62
+ position: 'before' | 'after';
63
+ }
64
+
65
+ export interface KanbanFilterConfig {
66
+ search?: string;
67
+ assignees?: (string | number)[];
68
+ labels?: (string | number)[];
69
+ priorities?: KanbanPriority[];
70
+ dueDateRange?: { from?: Date; to?: Date };
71
+ }
72
+
73
+ export interface KanbanRemoteConfig {
74
+ api: string;
75
+ source?: {
76
+ columnsPath?: string;
77
+ cardsPath?: string;
78
+ };
79
+ }
80
+
81
+ export interface CardMoveEvent {
82
+ card: KanbanCard;
83
+ fromColumnId: string | number;
84
+ toColumnId: string | number;
85
+ fromIndex: number;
86
+ toIndex: number;
87
+ }
88
+
89
+ export interface CardCreateEvent {
90
+ card: KanbanCard;
91
+ columnId: string | number;
92
+ }
93
+
94
+ export interface CardUpdateEvent {
95
+ card: KanbanCard;
96
+ changes: Partial<KanbanCard>;
97
+ }
98
+
99
+ export interface CardDeleteEvent {
100
+ card: KanbanCard;
101
+ columnId: string | number;
102
+ }
103
+
104
+ export interface EzKanbanConfig {
105
+ columns?: KanbanColumn[];
106
+ cards?: KanbanCard[];
107
+ remote?: KanbanRemoteConfig;
108
+ autoLoad?: boolean;
109
+
110
+ variant?: 'elevated' | 'flat' | 'outlined';
111
+ columnWidth?: number;
112
+ columnMinWidth?: number;
113
+ columnMaxWidth?: number;
114
+ cardGap?: number;
115
+
116
+ columnsReorderable?: boolean;
117
+ cardsReorderable?: boolean;
118
+ columnsDeletable?: boolean;
119
+ columnsEditable?: boolean;
120
+ cardsEditable?: boolean;
121
+ cardsDeletable?: boolean;
122
+ addColumnEnabled?: boolean;
123
+ addCardEnabled?: boolean;
124
+
125
+ showLabels?: boolean;
126
+ showAssignee?: boolean;
127
+ showDueDate?: boolean;
128
+ showPriority?: boolean;
129
+ showChecklist?: boolean;
130
+ showAttachments?: boolean;
131
+ showComments?: boolean;
132
+ showCoverImage?: boolean;
133
+
134
+ filterable?: boolean;
135
+ searchable?: boolean;
136
+ filters?: KanbanFilterConfig;
137
+
138
+ cardRender?: (card: KanbanCard, column: NormalizedColumn) => unknown;
139
+
140
+ onCardMove?: (event: CardMoveEvent) => void | Promise<void>;
141
+ onCardCreate?: (event: CardCreateEvent) => void | Promise<void>;
142
+ onCardUpdate?: (event: CardUpdateEvent) => void | Promise<void>;
143
+ onCardDelete?: (event: CardDeleteEvent) => void | Promise<void>;
144
+ onCardClick?: (card: KanbanCard) => void;
145
+ onCardDoubleClick?: (card: KanbanCard) => void;
146
+ onColumnCreate?: (column: KanbanColumn) => void | Promise<void>;
147
+ onColumnUpdate?: (column: KanbanColumn) => void | Promise<void>;
148
+ onColumnDelete?: (column: KanbanColumn) => void | Promise<void>;
149
+
150
+ availableLabels?: KanbanLabel[];
151
+ availableAssignees?: KanbanAssignee[];
152
+
153
+ id?: string;
154
+ cls?: string | string[];
155
+ style?: Partial<CSSStyleDeclaration>;
156
+ flex?: number;
157
+ [key: string]: unknown;
158
+ }
159
+
160
+ export interface KanbanControllerState {
161
+ columns: NormalizedColumn[];
162
+ loading: boolean;
163
+ error: Error | null;
164
+ filters: KanbanFilterConfig;
165
+ dragContext: DragContext | null;
166
+ }
@@ -0,0 +1,117 @@
1
+ import { EzBaseComponent } from '../../EzBaseComponent.js';
2
+ import type { EzKanbanController } from '../state/EzKanbanController.js';
3
+ import type { EzKanbanDragDrop } from '../state/EzKanbanDragDrop.js';
4
+ import type { EzKanbanConfig, NormalizedColumn } from '../EzKanbanTypes.js';
5
+
6
+ declare const ez: {
7
+ _createElement(config: unknown): Promise<HTMLElement>;
8
+ define(name: string, cls: unknown): void;
9
+ dialog: {
10
+ prompt(opts: { title: string; placeholder: string }): Promise<string | null>;
11
+ };
12
+ };
13
+
14
+ interface EzKanbanBoardConfig {
15
+ kanbanController: EzKanbanController;
16
+ kanbanDragDrop: EzKanbanDragDrop;
17
+ kanbanConfig: EzKanbanConfig;
18
+ [key: string]: unknown;
19
+ }
20
+
21
+ export class EzKanbanBoard extends EzBaseComponent {
22
+ declare config: EzKanbanBoardConfig;
23
+
24
+ private _el: HTMLElement | null = null;
25
+ private _unsubscribers: Array<() => void> = [];
26
+
27
+ async render(): Promise<HTMLElement> {
28
+ const { kanbanController, kanbanDragDrop, kanbanConfig } = this.config;
29
+
30
+ const items: unknown[] = [];
31
+
32
+ // Add columns
33
+ const columns = kanbanController.state.columns;
34
+ for (const column of columns) {
35
+ items.push({
36
+ eztype: 'EzKanbanColumn',
37
+ column,
38
+ kanbanController,
39
+ kanbanDragDrop,
40
+ kanbanConfig
41
+ });
42
+ }
43
+
44
+ // Add column button
45
+ if (kanbanConfig.addColumnEnabled !== false) {
46
+ items.push({
47
+ eztype: 'button',
48
+ cls: 'ez-kanban-add-column',
49
+ items: [
50
+ { eztype: 'i', cls: 'fa-solid fa-plus' },
51
+ { eztype: 'span', text: 'Add Column' }
52
+ ],
53
+ onClick: () => this._handleAddColumn()
54
+ });
55
+ }
56
+
57
+ const el = await ez._createElement({
58
+ eztype: 'div',
59
+ cls: 'ez-kanban-board',
60
+ items
61
+ }) as HTMLElement;
62
+
63
+ this._el = el;
64
+ kanbanDragDrop.setBoardElement(el);
65
+
66
+ const unsubscribe = kanbanController.on('datachange', () => {
67
+ this._renderColumns();
68
+ });
69
+ this._unsubscribers.push(unsubscribe);
70
+
71
+ return el;
72
+ }
73
+
74
+ private async _handleAddColumn(): Promise<void> {
75
+ const { kanbanController } = this.config;
76
+ const name = await ez.dialog.prompt({
77
+ title: 'New Column',
78
+ placeholder: 'Column name'
79
+ });
80
+ if (name) {
81
+ kanbanController.addColumn({ title: name });
82
+ }
83
+ }
84
+
85
+ private async _renderColumns(): Promise<void> {
86
+ if (!this._el) return;
87
+
88
+ const { kanbanController, kanbanDragDrop, kanbanConfig } = this.config;
89
+ const columns = kanbanController.state.columns;
90
+
91
+ const existingAddBtn = this._el.querySelector('.ez-kanban-add-column');
92
+ const existingColumns = this._el.querySelectorAll('.ez-kanban-column');
93
+ existingColumns.forEach(col => col.remove());
94
+
95
+ for (const column of columns) {
96
+ const columnEl = await ez._createElement({
97
+ eztype: 'EzKanbanColumn',
98
+ column,
99
+ kanbanController,
100
+ kanbanDragDrop,
101
+ kanbanConfig
102
+ });
103
+ if (existingAddBtn) {
104
+ this._el.insertBefore(columnEl, existingAddBtn);
105
+ } else {
106
+ this._el.appendChild(columnEl);
107
+ }
108
+ }
109
+ }
110
+
111
+ destroy(): void {
112
+ this._unsubscribers.forEach(fn => fn());
113
+ this._unsubscribers = [];
114
+ }
115
+ }
116
+
117
+ ez.define('EzKanbanBoard', EzKanbanBoard);
@@ -0,0 +1,173 @@
1
+ .card {
2
+ position: relative;
3
+ background: var(--ez-kanban-card-bg, #ffffff);
4
+ border: 1px solid var(--ez-kanban-border, #e2e8f0);
5
+ border-radius: 6px;
6
+ padding: 12px;
7
+ cursor: pointer;
8
+ transition: box-shadow 0.15s, transform 0.1s, opacity 0.15s;
9
+
10
+ &:hover {
11
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
12
+ }
13
+
14
+ &:global(.is-dragging) {
15
+ opacity: 0.6;
16
+ transform: rotate(2deg);
17
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
18
+ }
19
+
20
+ &:global(.drop-above) {
21
+ margin-top: 40px;
22
+
23
+ &::before {
24
+ content: '';
25
+ position: absolute;
26
+ top: -24px;
27
+ left: 0;
28
+ right: 0;
29
+ height: 3px;
30
+ background: var(--ez-kanban-primary, #005871);
31
+ border-radius: 2px;
32
+ }
33
+ }
34
+
35
+ &:global(.drop-below) {
36
+ margin-bottom: 40px;
37
+
38
+ &::after {
39
+ content: '';
40
+ position: absolute;
41
+ bottom: -24px;
42
+ left: 0;
43
+ right: 0;
44
+ height: 3px;
45
+ background: var(--ez-kanban-primary, #005871);
46
+ border-radius: 2px;
47
+ }
48
+ }
49
+ }
50
+
51
+ .critical {
52
+ border-left: 3px solid var(--ez-kanban-priority-critical, #dc2626);
53
+ }
54
+
55
+ .high {
56
+ border-left: 3px solid var(--ez-kanban-priority-high, #ea580c);
57
+ }
58
+
59
+ .medium {
60
+ border-left: 3px solid var(--ez-kanban-priority-medium, #ca8a04);
61
+ }
62
+
63
+ .low {
64
+ border-left: 3px solid var(--ez-kanban-priority-low, #16a34a);
65
+ }
66
+
67
+ .cover {
68
+ width: calc(100% + 24px);
69
+ margin: -12px -12px 12px;
70
+ border-radius: 6px 6px 0 0;
71
+ height: 120px;
72
+ object-fit: cover;
73
+ }
74
+
75
+ .labels {
76
+ display: flex;
77
+ flex-wrap: wrap;
78
+ gap: 4px;
79
+ margin-bottom: 8px;
80
+ }
81
+
82
+ .label {
83
+ padding: 2px 8px;
84
+ border-radius: 4px;
85
+ font-size: 11px;
86
+ font-weight: 500;
87
+ color: #fff;
88
+ }
89
+
90
+ .title {
91
+ font-size: 14px;
92
+ font-weight: 500;
93
+ color: var(--ez-kanban-text, #1e293b);
94
+ line-height: 1.4;
95
+ margin-bottom: 4px;
96
+ word-break: break-word;
97
+ }
98
+
99
+ .description {
100
+ font-size: 12px;
101
+ color: var(--ez-kanban-text-secondary, #475569);
102
+ line-height: 1.5;
103
+ margin-bottom: 8px;
104
+ }
105
+
106
+ .footer {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: 12px;
110
+ margin-top: 8px;
111
+ font-size: 12px;
112
+ color: var(--ez-kanban-text-muted, #94a3b8);
113
+ }
114
+
115
+ .dueDate {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 4px;
119
+
120
+ i {
121
+ font-size: 11px;
122
+ }
123
+ }
124
+
125
+ .overdue {
126
+ color: var(--ez-kanban-priority-critical, #dc2626);
127
+ }
128
+
129
+ .meta {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: 4px;
133
+
134
+ i {
135
+ font-size: 11px;
136
+ }
137
+ }
138
+
139
+ .assignees {
140
+ display: flex;
141
+ margin-left: auto;
142
+ }
143
+
144
+ .avatar {
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ width: 24px;
149
+ height: 24px;
150
+ border-radius: 50%;
151
+ background: var(--ez-kanban-primary, #005871);
152
+ color: white;
153
+ font-size: 10px;
154
+ font-weight: 600;
155
+ margin-left: -6px;
156
+ border: 2px solid var(--ez-kanban-card-bg, #ffffff);
157
+ overflow: hidden;
158
+
159
+ &:first-child {
160
+ margin-left: 0;
161
+ }
162
+
163
+ img {
164
+ width: 100%;
165
+ height: 100%;
166
+ object-fit: cover;
167
+ }
168
+ }
169
+
170
+ .avatarMore {
171
+ background: var(--ez-surface-tertiary, #f1f5f9);
172
+ color: var(--ez-kanban-text-secondary, #475569);
173
+ }