@iyulab/data-components 0.1.0 → 0.1.1

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.
@@ -1,9 +1,8 @@
1
1
  import { html } from 'lit';
2
2
  import { property, state } from 'lit/decorators.js';
3
- import { styleMap } from 'lit/directives/style-map.js';
4
- import { BaseElement } from '@iyulab/components/dist/components/BaseElement.js';
5
- import { UIconButton } from '@iyulab/components/dist/components/icon-button/UIconButton.component.js';
6
- import { USkeleton } from '@iyulab/components/dist/components/skeleton/USkeleton.component.js';
3
+ import { UElement } from '@iyulab/components/dist/components/UElement.js';
4
+ import { UIcon } from '@iyulab/components/dist/components/icon/UIcon.component.js';
5
+ import { UButton } from '@iyulab/components/dist/components/button/UButton.component.js';
7
6
  import { styles } from './UDataView.styles.js';
8
7
 
9
8
  var __defProp = Object.defineProperty;
@@ -15,287 +14,213 @@ var __decorateClass = (decorators, target, key, kind) => {
15
14
  if (result) __defProp(target, key, result);
16
15
  return result;
17
16
  };
18
- const IMAGE_SIZE_LIST = 128;
19
- const IMAGE_SIZE_TABLE = 64;
20
- const IMAGE_SIZE_GRID = 128;
21
- class UDataView extends BaseElement {
17
+ class UDataView extends UElement {
22
18
  constructor() {
23
19
  super(...arguments);
24
- this.data = [];
25
- this.layout = "grid";
26
- this.imageField = "image";
27
- this.itemMargin = "1rem";
28
- this.minItemWidth = "250px";
29
- this.selectedItem = null;
30
- this.currentLayout = "grid";
31
- this.loading = true;
32
- this.imageLoadErrors = /* @__PURE__ */ new Set();
33
- this.randomColors = /* @__PURE__ */ new Map();
20
+ this.items = [];
21
+ this.mode = "grid";
22
+ this.gridMinWidth = "200px";
23
+ this.gap = "1rem";
24
+ this.selectedIndex = null;
34
25
  }
35
26
  static {
36
27
  this.styles = [super.styles, styles];
37
28
  }
38
29
  static {
39
30
  this.dependencies = {
40
- "u-icon-button": UIconButton,
41
- "u-skeleton": USkeleton
31
+ "u-icon": UIcon,
32
+ "u-button": UButton
42
33
  };
43
34
  }
44
- connectedCallback() {
45
- super.connectedCallback();
46
- setTimeout(() => {
47
- this.loading = false;
48
- this.requestUpdate();
49
- }, 2e3);
35
+ render() {
36
+ return html`
37
+ <div class="data-view">
38
+ ${this.renderToolbar()}
39
+ ${this.renderContent()}
40
+ </div>
41
+ `;
50
42
  }
51
- handleImageError(item) {
52
- this.imageLoadErrors.add(item[this.imageField]);
53
- this.requestUpdate();
43
+ renderToolbar() {
44
+ return html`
45
+ <div class="toolbar">
46
+ <div class="view-toggles">
47
+ ${this.renderViewButton("grid", "grid-3x3-gap", "Grid")}
48
+ ${this.renderViewButton("list", "list-ul", "List")}
49
+ ${this.renderViewButton("table", "table", "Table")}
50
+ </div>
51
+ <div class="info">
52
+ ${this.items.length} items
53
+ </div>
54
+ </div>
55
+ `;
54
56
  }
55
- renderTable() {
56
- const columns = this.columns || this.getDefaultColumns();
57
+ renderViewButton(mode, icon, label) {
57
58
  return html`
58
- <table class="default-table">
59
- <thead>
60
- <tr>
61
- <th>Image</th>
62
- ${columns.map((column) => html`<th>${this.getDisplayName(column)}</th>`)}
63
- </tr>
64
- </thead>
65
- <tbody>
66
- ${this.loading ? Array(5).fill(0).map(() => html`
67
- <tr>
68
- <td><u-skeleton effect="shimmer" width="64px" height="64px"></u-skeleton></td>
69
- ${columns.map(() => html`
70
- <td><u-skeleton effect="shimmer" width="80%" height="1em"></u-skeleton></td>
71
- `)}
72
- </tr>
73
- `) : this.data?.map((item) => html`
74
- <tr class="default-item ${this.selectedItem === item ? "selected" : ""}"
75
- @click=${() => this.handleItemClick(item)}
76
- @dblclick=${() => this.handleItemDoubleClick(item)}>
77
- <td class="image-cell">
78
- ${this.defaultRenderImage(item, "table")}
79
- </td>
80
- ${columns.map((column) => html`<td>${this.formatValue(item[column.name])}</td>`)}
81
- </tr>
82
- `)}
83
- </tbody>
84
- </table>
59
+ <u-button
60
+ ?active=${this.mode === mode}
61
+ @click=${() => this.switchMode(mode)}
62
+ title=${label}
63
+ >
64
+ <u-icon lib="bootstrap" name=${icon}></u-icon>
65
+ </u-button>
85
66
  `;
86
67
  }
87
- getDefaultColumns() {
88
- if (this.data && this.data.length > 0) {
89
- return Object.keys(this.data[0]).filter((key) => key !== this.imageField).map((key) => ({ name: key }));
68
+ renderContent() {
69
+ if (!this.items?.length) {
70
+ return html`<div class="empty">No data available</div>`;
90
71
  }
91
- return [];
92
- }
93
- getDisplayName(column) {
94
- return column.display || this.toPascalCase(column.name);
95
- }
96
- toPascalCase(str) {
97
- const words = str.split(/(?=[A-Z])|\s+|[-_]+/);
98
- return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
99
- }
100
- formatValue(value) {
101
- if (value instanceof Date) {
102
- const pad = (num) => num.toString().padStart(2, "0");
103
- return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad(value.getHours())}:${pad(value.getMinutes())}`;
72
+ switch (this.mode) {
73
+ case "grid":
74
+ return this.renderGrid();
75
+ case "list":
76
+ return this.renderList();
77
+ case "table":
78
+ return this.renderTable();
104
79
  }
105
- return value;
106
80
  }
107
- defaultRenderFields(item) {
108
- const columns = this.columns || this.getDefaultColumns();
81
+ renderGrid() {
109
82
  return html`
110
- <div class="default-fields">
111
- ${columns.map((column) => html`
112
- <div class="field">
113
- ${this.loading ? html`
114
- <u-skeleton effect="shimmer" width="30%" height="1em" class="field-title"></u-skeleton>
115
- <u-skeleton effect="shimmer" width="60%" height="1em" class="field-value"></u-skeleton>
116
- ` : html`
117
- <span class="field-title">${this.getDisplayName(column)}:</span>
118
- <span class="field-value">${this.formatValue(item[column.name])}</span>
119
- `}
120
- </div>
121
- `)}
83
+ <div class="grid" style="--min-width: ${this.gridMinWidth}; --gap: ${this.gap};">
84
+ ${this.items.map((item, index) => this.renderGridItem(item, index))}
122
85
  </div>
123
86
  `;
124
87
  }
125
- getRandomColor(key) {
126
- if (!this.randomColors.has(key)) {
127
- const hue = Math.floor(Math.random() * 360);
128
- const saturation = 70 + Math.floor(Math.random() * 30);
129
- const lightness = 60 + Math.floor(Math.random() * 20);
130
- this.randomColors.set(key, `hsl(${hue}, ${saturation}%, ${lightness}%)`);
131
- }
132
- return this.randomColors.get(key);
133
- }
134
- defaultRenderImage(item, viewType = "grid") {
135
- if (this.loading) {
136
- const skeletonSize = viewType === "table" ? `${IMAGE_SIZE_TABLE}px` : viewType === "list" ? `${IMAGE_SIZE_LIST}px` : `${IMAGE_SIZE_GRID}px`;
137
- return html`<u-skeleton effect="shimmer" width=${skeletonSize} height=${skeletonSize} style="margin-bottom: 1em;"></u-skeleton>`;
138
- }
139
- const imageSrc = item[this.imageField];
140
- const imageClass = `default-image ${viewType}-image`;
141
- if (imageSrc && !this.imageLoadErrors.has(imageSrc)) {
142
- return html`
143
- <img
144
- src=${imageSrc}
145
- alt="Item image"
146
- class=${imageClass}
147
- @error=${() => this.handleImageError(item)}
148
- style=${viewType !== "grid" ? "" : "margin-bottom: 1em;"}
149
- />
150
- `;
151
- } else {
152
- const backgroundColor = this.getRandomColor(item.id || JSON.stringify(item));
153
- const textColor = this.getContrastColor(backgroundColor);
154
- const styles2 = {
155
- backgroundColor,
156
- color: textColor,
157
- display: "flex",
158
- alignItems: "center",
159
- justifyContent: "center",
160
- fontSize: viewType === "table" ? "1.5rem" : "2rem",
161
- fontWeight: "bold",
162
- width: viewType === "table" ? `${IMAGE_SIZE_TABLE}px` : viewType === "list" ? `${IMAGE_SIZE_LIST}px` : "100%",
163
- height: viewType === "table" ? `${IMAGE_SIZE_TABLE}px` : viewType === "list" ? `${IMAGE_SIZE_LIST}px` : `${IMAGE_SIZE_GRID}px`
164
- };
165
- return html`
166
- <div class=${`${imageClass} placeholder-image`} style=${styleMap(styles2)}>
167
- ${item.name ? item.name.charAt(0).toUpperCase() : "N/A"}
168
- </div>
169
- `;
170
- }
88
+ renderList() {
89
+ return html`
90
+ <div class="list" style="--gap: ${this.gap};">
91
+ ${this.items.map((item, index) => this.renderListItem(item, index))}
92
+ </div>
93
+ `;
171
94
  }
172
- getContrastColor(backgroundColor) {
173
- const rgb = this.hexToRgb(backgroundColor);
174
- const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1e3;
175
- return brightness > 128 ? "#000000" : "#FFFFFF";
95
+ renderTable() {
96
+ const cols = this.getColumns();
97
+ return html`
98
+ <div class="table-wrapper">
99
+ <table>
100
+ <thead>
101
+ <tr>
102
+ ${cols.map((col) => html`
103
+ <th style=${col.width ? `width: ${col.width}` : ""}>
104
+ ${col.label || this.formatLabel(col.key)}
105
+ </th>
106
+ `)}
107
+ </tr>
108
+ </thead>
109
+ <tbody>
110
+ ${this.items.map((item, index) => html`
111
+ <tr
112
+ class=${this.selectedIndex === index ? "selected" : ""}
113
+ @click=${() => this.selectItem(index)}
114
+ >
115
+ ${cols.map((col) => html`
116
+ <td>${this.getCellContent(item, col, index)}</td>
117
+ `)}
118
+ </tr>
119
+ `)}
120
+ </tbody>
121
+ </table>
122
+ </div>
123
+ `;
176
124
  }
177
- hexToRgb(hex) {
178
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
179
- return result ? {
180
- r: parseInt(result[1], 16),
181
- g: parseInt(result[2], 16),
182
- b: parseInt(result[3], 16)
183
- } : { r: 0, g: 0, b: 0 };
125
+ renderGridItem(item, index) {
126
+ const content = this.renderCard ? this.renderCard(item, index) : this.renderDefaultCard(item);
127
+ return html`
128
+ <div
129
+ class="card ${this.selectedIndex === index ? "selected" : ""}"
130
+ @click=${() => this.selectItem(index)}
131
+ >
132
+ ${content}
133
+ </div>
134
+ `;
184
135
  }
185
- render() {
136
+ renderListItem(item, index) {
137
+ const content = this.renderCard ? this.renderCard(item, index) : this.renderDefaultCard(item);
186
138
  return html`
187
- <div class="u-data-view-container">
188
- <div class="layout-selector">
189
- <u-icon-button lib="internal" name="grid"
190
- @click=${() => this.setLayout("grid")}
191
- ></u-icon-button>
192
- <u-icon-button lib="internal" name="list-ul"
193
- @click=${() => this.setLayout("list")}
194
- ></u-icon-button>
195
- <u-icon-button lib="internal" name="table"
196
- @click=${() => this.setLayout("table")}
197
- ></u-icon-button>
198
- </div>
199
- <div class="u-data-view ${this.currentLayout}" style="--item-margin: ${this.itemMargin}; --min-item-width: ${this.minItemWidth};">
200
- ${this.currentLayout === "table" ? this.renderTable() : this.renderItems()}
201
- </div>
139
+ <div
140
+ class="card list-card ${this.selectedIndex === index ? "selected" : ""}"
141
+ @click=${() => this.selectItem(index)}
142
+ >
143
+ ${content}
202
144
  </div>
203
145
  `;
204
146
  }
205
- renderItems() {
206
- if (this.loading) {
207
- return Array(5).fill(0).map(() => html`
208
- <div class="default-item" style="padding: 1em; border: 1px solid #eee; border-radius: 4px;">
209
- <u-skeleton effect="shimmer" width="100%" height="${IMAGE_SIZE_GRID}px" style="margin-bottom: 1em;"></u-skeleton>
210
- <div class="default-fields">
211
- ${Array(3).fill(0).map(() => html`
212
- <div class="field" style="margin-bottom: 0.5em;">
213
- <u-skeleton effect="shimmer" width="30%" height="1em" style="margin-right: 0.5em;"></u-skeleton>
214
- <u-skeleton effect="shimmer" width="60%" height="1em"></u-skeleton>
215
- </div>
216
- `)}
217
- </div>
218
- </div>
219
- `);
220
- }
221
- return this.data?.map(
222
- (item) => this.renderItem ? this.renderItem(item) : html`
223
- <div class="default-item ${this.selectedItem === item ? "selected" : ""}"
224
- @click=${() => this.handleItemClick(item)}
225
- @dblclick=${() => this.handleItemDoubleClick(item)}>
226
- ${this.renderImage ? this.renderImage(item) : this.defaultRenderImage(item, this.currentLayout)}
227
- ${this.renderFields ? this.renderFields(item) : this.defaultRenderFields(item)}
147
+ renderDefaultCard(item) {
148
+ const cols = this.getColumns();
149
+ return html`
150
+ <div class="card-content">
151
+ ${cols.slice(0, 5).map((col) => {
152
+ const value = item[col.key];
153
+ return html`
154
+ <div class="card-field">
155
+ <span class="label">${col.label || this.formatLabel(col.key)}:</span>
156
+ <span class="value">${this.formatValue(value)}</span>
228
157
  </div>
229
- `
230
- );
231
- }
232
- setLayout(newLayout) {
233
- this.currentLayout = newLayout;
234
- this.requestUpdate();
158
+ `;
159
+ })}
160
+ </div>
161
+ `;
235
162
  }
236
- handleItemClick(item) {
237
- this.selectedItem = item;
238
- if (this.onItemSelect) {
239
- this.onItemSelect(item);
163
+ getCellContent(item, column, index) {
164
+ if (this.renderCell) {
165
+ return this.renderCell(item, column, index);
240
166
  }
241
- this.requestUpdate();
167
+ return this.formatValue(item[column.key]);
242
168
  }
243
- handleItemDoubleClick(item) {
244
- if (this.onItemDoubleClick) {
245
- this.onItemDoubleClick(item);
169
+ getColumns() {
170
+ if (this.columns?.length) {
171
+ return this.columns;
246
172
  }
173
+ if (this.items.length > 0) {
174
+ const firstItem = this.items[0];
175
+ return Object.keys(firstItem).map((key) => ({ key }));
176
+ }
177
+ return [];
178
+ }
179
+ formatLabel(key) {
180
+ return key.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
181
+ }
182
+ formatValue(value) {
183
+ if (value == null) return "—";
184
+ if (typeof value === "boolean") return value ? "✓" : "✗";
185
+ if (value instanceof Date) return value.toLocaleString();
186
+ if (typeof value === "object") return JSON.stringify(value);
187
+ return String(value);
188
+ }
189
+ switchMode(mode) {
190
+ this.mode = mode;
191
+ this.emit("mode-change", { mode });
192
+ }
193
+ selectItem(index) {
194
+ this.selectedIndex = index;
195
+ this.emit("select", {
196
+ item: this.items[index],
197
+ index
198
+ });
247
199
  }
248
200
  }
249
201
  __decorateClass([
250
202
  property({ type: Array })
251
- ], UDataView.prototype, "data");
252
- __decorateClass([
253
- property({ type: String })
254
- ], UDataView.prototype, "layout");
255
- __decorateClass([
256
- property({ type: Object })
257
- ], UDataView.prototype, "renderItem");
258
- __decorateClass([
259
- property({ type: Object })
260
- ], UDataView.prototype, "renderImage");
261
- __decorateClass([
262
- property({ type: Object })
263
- ], UDataView.prototype, "renderFields");
264
- __decorateClass([
265
- property({ type: Number })
266
- ], UDataView.prototype, "itemsPerPage");
267
- __decorateClass([
268
- property({ type: String })
269
- ], UDataView.prototype, "imageField");
270
- __decorateClass([
271
- property({ type: String })
272
- ], UDataView.prototype, "itemMargin");
203
+ ], UDataView.prototype, "items");
273
204
  __decorateClass([
274
205
  property({ type: String })
275
- ], UDataView.prototype, "minItemWidth");
206
+ ], UDataView.prototype, "mode");
276
207
  __decorateClass([
277
208
  property({ type: Array })
278
209
  ], UDataView.prototype, "columns");
279
210
  __decorateClass([
280
- property({ type: Object })
281
- ], UDataView.prototype, "onItemSelect");
282
- __decorateClass([
283
- property({ type: Object })
284
- ], UDataView.prototype, "onItemDoubleClick");
285
- __decorateClass([
286
- state()
287
- ], UDataView.prototype, "selectedItem");
211
+ property({ type: String })
212
+ ], UDataView.prototype, "gridMinWidth");
288
213
  __decorateClass([
289
- state()
290
- ], UDataView.prototype, "currentLayout");
214
+ property({ type: String })
215
+ ], UDataView.prototype, "gap");
291
216
  __decorateClass([
292
- state()
293
- ], UDataView.prototype, "loading");
217
+ property({ attribute: false })
218
+ ], UDataView.prototype, "renderCard");
294
219
  __decorateClass([
295
- state()
296
- ], UDataView.prototype, "imageLoadErrors");
220
+ property({ attribute: false })
221
+ ], UDataView.prototype, "renderCell");
297
222
  __decorateClass([
298
223
  state()
299
- ], UDataView.prototype, "randomColors");
224
+ ], UDataView.prototype, "selectedIndex");
300
225
 
301
226
  export { UDataView };
@@ -5,4 +5,4 @@ declare global {
5
5
  }
6
6
  }
7
7
  export { UDataView };
8
- export type { DataViewColumnDefinition } from './UDataView.component.js';
8
+ export type { Column, ViewMode } from './UDataView.component.js';