-
{{ item.name }}
-
v{{ item.version }}
+
+
{{ item.name }}
+
+ person
+ {{ item.creatorDisplayName }}
+ ·
+ download
+ {{ item.totalInstallCount | shortNumber }}
- @if (item.type === ItemType.WIDGET && getSubtypeLabel(); as subtypeLabel) {
-
{{ subtypeLabel }}
+
+ @if (isInstalled()) {
+ @if (isSameVersion()) {
+
{{ 'iot-hub.installed' | translate }}
+ } @else {
+
}
+ } @else {
+
}
- @if (item.description) {
-
{{ item.description }}
- }
- @if (getNodes(); as nodes) {
- @if (nodes.length) {
-
- @for (node of nodes | slice:0:maxVisibleNodes; track node.name) {
- {{ node.name }}
- }
- @if (getNodeCount() > maxVisibleNodes) {
- +{{ getNodeCount() - maxVisibleNodes }}
- }
+
+} @else {
+
+
+
+ @if (getPreviewUrl(); as previewUrl) {
+
![]()
+ } @else {
+
+ {{ getPlaceholderIcon() }}
}
- }
- @if (getCategoryLabels().length || getFirstUseCaseLabel()) {
-
- @if (getCategoryLabels(); as labels) {
- @if (labels.length) {
- {{ labels[0] }}
- }
- }
- @if (getFirstUseCaseLabel(); as useCaseLabel) {
- {{ useCaseLabel }}
- }
-
- }
-
+
+
{{ item.name }}
+
-
+}
diff --git a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss
index 2fc96ecd8d..04747e566c 100644
--- a/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss
+++ b/ui-ngx/src/app/modules/home/pages/iot-hub/iot-hub-item-card.component.scss
@@ -19,9 +19,10 @@
height: 100%;
}
+// Card — Figma: white bg, border rgba(0,0,0,0.12), rounded-8, overflow-clip, flex-col, gap-16, pb-16
.tb-iot-hub-item-card {
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
+ border: 1px solid rgba(0, 0, 0, 0.12);
+ border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: box-shadow 0.2s, transform 0.2s;
@@ -29,41 +30,33 @@
height: 100%;
display: flex;
flex-direction: column;
+ gap: 16px;
&:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
- &.tb-iot-hub-card-compact {
- border-color: #e5e7eb;
-
- .tb-iot-hub-card-content {
- padding: 16px 18px;
- }
- }
}
-// Preview area
+// Preview area — Figma: aspect ~305:172, bg rgba(0,0,0,0.04), overflow-clip, p-12, dot pattern bg
.tb-iot-hub-card-preview {
- height: 160px;
+ aspect-ratio: 305 / 172;
+ background: rgba(0, 0, 0, 0.04);
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
-
- &.preview-widget, &.preview-dashboard { background: #f5f7fa; border-bottom: 1px solid #e5e7eb; }
- &.preview-calculated-field { background: linear-gradient(135deg, #e3f2fd, #bbdefb); }
- &.preview-rule-chain { background: linear-gradient(135deg, #f3e5f5, #e1bee7); }
- &.preview-device { background: linear-gradient(135deg, #eceff1, #cfd8dc); }
+ padding: 12px;
+ background-image: radial-gradient(circle, rgba(0, 0, 0, 0.06) 1px, transparent 1px);
+ background-size: 20px 20px;
}
.tb-iot-hub-preview-image {
- max-width: 100%;
- max-height: 100%;
+ width: 100%;
+ height: 100%;
object-fit: contain;
- padding: 8px;
}
.tb-iot-hub-preview-placeholder {
@@ -73,17 +66,14 @@
display: flex;
align-items: center;
justify-content: center;
+ background: rgba(0, 0, 0, 0.06);
tb-icon {
font-size: 36px;
width: 36px;
height: 36px;
+ color: rgba(0, 0, 0, 0.38);
}
-
- .preview-widget &, .preview-dashboard & { background: rgba(107, 114, 128, 0.1); tb-icon { color: #6b7280; } }
- .preview-calculated-field & { background: rgba(25, 118, 210, 0.15); tb-icon { color: #1565c0; } }
- .preview-rule-chain & { background: rgba(123, 31, 162, 0.15); tb-icon { color: #7b1fa2; } }
- .preview-device & { background: rgba(69, 90, 100, 0.15); tb-icon { color: #455a64; } }
}
.tb-iot-hub-pe-badge {
@@ -98,283 +88,253 @@
font-weight: 600;
}
-// Content area
+// Content area — Figma: px-16, gap-8, flex-col
.tb-iot-hub-card-content {
- padding: 12px 16px 12px;
+ padding: 0 16px 16px;
flex: 1;
display: flex;
flex-direction: column;
+ gap: 8px;
}
-// Compact header (CF / RC)
-.tb-iot-hub-card-cf-header {
+// Compact card (CF / RC) — Figma: horizontal flex, gap-16, p-16, rounded-8
+.tb-iot-hub-card-compact {
display: flex;
- align-items: flex-start;
- gap: 12px;
- margin-bottom: 10px;
+ align-items: center;
+ gap: 16px;
+ padding: 16px;
+ background: white;
+ border: 1px solid rgba(0, 0, 0, 0.12);
+ border-radius: 8px;
+ cursor: pointer;
+ height: 100%;
+ transition: box-shadow 0.2s, transform 0.2s;
+
+ &:hover {
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+ transform: translateY(-2px);
+ }
}
-.tb-iot-hub-card-cf-icon {
- width: 40px;
- height: 40px;
- border-radius: 10px;
+// Compact icon — Figma: 72x72, rounded-6, colored bg, white icon 36px centered
+.tb-iot-hub-compact-icon {
+ width: 72px;
+ height: 72px;
+ border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
tb-icon {
- font-size: 22px;
- width: 22px;
- height: 22px;
+ font-size: 36px;
+ width: 36px;
+ height: 36px;
color: #fff;
}
}
-.tb-iot-hub-card-cf-title-area {
+// Compact content — Figma: flex-1, flex-col, gap-8
+.tb-iot-hub-compact-content {
flex: 1;
min-width: 0;
-
- .tb-iot-hub-card-name-row {
- margin-bottom: 2px;
- }
-}
-
-.tb-iot-hub-card-cf-type {
- font-size: 11px;
- font-weight: 500;
- color: #6b7280;
-}
-
-.tb-iot-hub-pe-badge-inline {
- position: static;
- flex-shrink: 0;
- align-self: flex-start;
-}
-
-// Name row
-.tb-iot-hub-card-name-row {
display: flex;
- align-items: baseline;
+ flex-direction: column;
gap: 8px;
- margin-bottom: 4px;
}
-.tb-iot-hub-card-name {
+// Compact name — Figma: 16px Medium, line-height 24, tracking 0.25, ellipsis
+.tb-iot-hub-compact-name {
font-size: 16px;
- font-weight: 700;
+ font-weight: 500;
+ line-height: 24px;
+ letter-spacing: 0.25px;
+ color: rgba(0, 0, 0, 0.87);
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- color: rgba(0, 0, 0, 0.87);
-}
-
-.tb-iot-hub-card-version {
- font-size: 13px;
- color: rgba(0, 0, 0, 0.4);
- flex-shrink: 0;
-}
-
-.tb-iot-hub-card-widget-type-label {
- font-size: 11px;
- font-weight: 500;
- color: #6b7280;
- margin-bottom: 4px;
}
-// Description
-.tb-iot-hub-card-description {
- font-size: 13px;
- line-height: 1.4;
- color: rgba(0, 0, 0, 0.55);
- margin: 0 0 10px;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
+// Compact meta row — Figma: flex row, gap-8, items-center
+.tb-iot-hub-compact-meta {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 12px;
+ line-height: 16px;
+ letter-spacing: 0.25px;
+ color: rgba(0, 0, 0, 0.54);
}
-// Node chips (Rule Chain)
-.tb-iot-hub-card-nodes {
- display: flex;
- gap: 6px;
- flex-wrap: nowrap;
- overflow: hidden;
- margin-bottom: 8px;
+.tb-iot-hub-compact-author-icon {
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
}
-.tb-iot-hub-card-node-chip {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 6px;
- font-size: 11px;
- line-height: 16px;
- height: 20px;
+.tb-iot-hub-compact-creator {
+ cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
- min-width: 0;
- max-width: 120px;
-
- &.node-enrichment { background: #f5fcd5; color: #4a7a00; }
- &.node-filter { background: #fcfadb; color: #7a7200; }
- &.node-transformation { background: #ddf2fc; color: #1a6d9e; }
- &.node-action { background: #fde3e2; color: #9e2a27; }
- &.node-analytics { background: #d5f6f4; color: #0e7a74; }
- &.node-external { background: #fef1d8; color: #8a5d00; }
- &.node-flow { background: #f3edfc; color: #5b3a9e; }
- &.node-unknown { background: #f5f5f5; color: rgba(0, 0, 0, 0.54); }
+
+ &:hover {
+ color: #00695c;
+ text-decoration: underline;
+ }
}
-.tb-iot-hub-card-node-more {
- font-size: 12px;
- font-weight: 500;
- color: rgba(0, 0, 0, 0.45);
- padding: 3px 4px;
+.tb-iot-hub-compact-dot {
+ margin: 0 4px;
flex-shrink: 0;
}
-// Category / use case tags
-.tb-iot-hub-card-categories {
- display: flex;
- gap: 4px;
- flex-wrap: wrap;
- margin-bottom: 12px;
+.tb-iot-hub-compact-download-icon {
+ font-size: 12px;
+ width: 12px;
+ height: 12px;
+ flex-shrink: 0;
}
-.tb-iot-hub-card-category-chip {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 4px;
- font-size: 11px;
+.tb-iot-hub-compact-count {
font-weight: 500;
- background: #eff6ff;
- color: #1e6bb8;
- white-space: nowrap;
+ text-transform: uppercase;
}
-.tb-iot-hub-card-use-case-chip {
- display: inline-block;
- padding: 2px 8px;
- border-radius: 4px;
- font-size: 11px;
+// Name — Figma: 16px Medium, line-height 24, tracking 0.25, ellipsis
+.tb-iot-hub-card-name {
+ font-size: 16px;
font-weight: 500;
- background: #f0fdf4;
- color: #16a34a;
+ line-height: 24px;
+ letter-spacing: 0.25px;
+ margin: 0;
white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ color: rgba(0, 0, 0, 0.87);
}
-// Footer
+
+// Footer — Figma: flex, justify-between, no border-top, inline with content
.tb-iot-hub-card-footer {
display: flex;
align-items: center;
font-size: 12px;
- color: rgba(0, 0, 0, 0.45);
+ line-height: 16px;
+ letter-spacing: 0.25px;
+ color: rgba(0, 0, 0, 0.54);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: auto;
- margin-left: -16px;
- margin-right: -16px;
- margin-bottom: -12px;
- padding: 12px 16px;
- border-top: 1px solid rgba(0, 0, 0, 0.08);
+ // Author icon — Figma: person icon, 16px
.tb-iot-hub-card-author-icon {
- font-size: 14px;
- width: 14px;
- height: 14px;
+ font-size: 16px;
+ width: 16px;
+ height: 16px;
flex-shrink: 0;
margin-right: 4px;
}
.tb-iot-hub-card-creator {
- color: #1976d2;
- cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
+ cursor: pointer;
&:hover {
+ color: #00695c;
text-decoration: underline;
}
}
+ // Dot separator — Figma: 3px circle, rgba(0,0,0,0.38)
.tb-iot-hub-card-dot {
- margin: 0 6px;
+ margin: 0 8px;
flex-shrink: 0;
}
.tb-iot-hub-card-published {
- flex-shrink: 0;
+ display: none;
}
.tb-iot-hub-card-stats {
display: flex;
align-items: center;
- gap: 10px;
+ gap: 8px;
margin-left: auto;
flex-shrink: 0;
}
+ // Download stat — Figma: download icon 12px, count 12px Medium uppercase
.tb-iot-hub-card-stat {
display: flex;
align-items: center;
- gap: 3px;
- font-size: 11px;
- color: #9ca3af;
+ gap: 2px;
+ font-size: 12px;
+ font-weight: 500;
+ color: rgba(0, 0, 0, 0.54);
+ text-transform: uppercase;
tb-icon {
- font-size: 14px;
- width: 14px;
- height: 14px;
+ font-size: 12px;
+ width: 12px;
+ height: 12px;
}
}
- .tb-iot-hub-card-install-btn {
- padding: 5px 14px;
- border-radius: 6px;
- border: 1px solid #305680;
- background: transparent;
- color: #305680;
- font-size: 12px;
- font-weight: 600;
- cursor: pointer;
- white-space: nowrap;
- transition: all 0.15s;
+}
- &:hover {
- background: #305680;
- color: #fff;
- }
- }
+// Install button — Figma: border rgba(0,0,0,0.12), rounded-4, px-16 py-6, text #00695c 14px Medium
+.tb-iot-hub-card-install-btn {
+ padding: 6px 16px;
+ border-radius: 4px;
+ border: 1px solid rgba(0, 0, 0, 0.12);
+ background: transparent;
+ color: #305680;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 20px;
+ letter-spacing: 0.25px;
+ cursor: pointer;
+ white-space: nowrap;
+ flex-shrink: 0;
+ transition: background 0.15s;
- .tb-iot-hub-card-installed-label {
- padding: 5px 14px;
- border-radius: 6px;
- background: #e8f5e9;
- color: #2e7d32;
- font-size: 12px;
- font-weight: 600;
- white-space: nowrap;
+ &:hover {
+ background: rgba(48, 86, 128, 0.04);
}
+}
- .tb-iot-hub-card-update-btn {
- padding: 5px 14px;
- border-radius: 6px;
- border: 1px solid #f57c00;
- background: transparent;
- color: #f57c00;
- font-size: 12px;
- font-weight: 600;
- cursor: pointer;
- white-space: nowrap;
- transition: all 0.15s;
+.tb-iot-hub-card-installed-label {
+ padding: 6px 16px;
+ border-radius: 4px;
+ background: #e8f5e9;
+ color: #2e7d32;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 20px;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
- &:hover {
- background: #f57c00;
- color: #fff;
- }
- }
+.tb-iot-hub-card-update-btn {
+ padding: 6px 16px;
+ border-radius: 4px;
+ border: 1px solid #f57c00;
+ background: transparent;
+ color: #f57c00;
+ font-size: 14px;
+ font-weight: 500;
+ line-height: 20px;
+ cursor: pointer;
+ white-space: nowrap;
+ flex-shrink: 0;
+ transition: background 0.15s;
+ &:hover {
+ background: rgba(245, 124, 0, 0.04);
+ }
}
diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg
index 471177f37b..85f1a39345 100644
--- a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg
+++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-1.svg
@@ -1,13 +1,13 @@
diff --git a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg
index 3e453e4b17..ff17afc8fb 100644
--- a/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg
+++ b/ui-ngx/src/assets/iot-hub/hero-dashboard-icon-2.svg
@@ -1,11 +1,11 @@