From f9c38b0718d4bd683901aab5d9f9a0595833d2fc Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Wed, 4 Mar 2026 16:07:11 +0200 Subject: [PATCH 1/3] Redesign widget empty state with two-column layout and animated demo table Replace the old centered empty state with a two-column design: left side shows an animated mini table demonstrating widget transformations (Boolean, Money, URL, Country) with staggered row animations, right side displays a grid of 18 available widget types. Remove unused edit component imports. Co-Authored-By: Claude Opus 4.6 --- .../db-table-widgets.component.css | 310 ++++++++++++++++-- .../db-table-widgets.component.html | 273 +++++++++------ .../db-table-widgets.component.ts | 63 +++- 3 files changed, 488 insertions(+), 158 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css index 501c07724..4a16493ca 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css @@ -32,76 +32,316 @@ } .empty-state { + display: flex; + align-items: stretch; + height: calc(100vh - 200px); + gap: 0; + max-width: 1200px; + margin: 0 auto; +} + +.empty-left { + width: 420px; + flex-shrink: 0; display: flex; flex-direction: column; + justify-content: center; + padding: 32px 32px 32px 0; +} + +.empty-icon-box { + width: 40px; + height: 40px; + border-radius: 10px; + background: rgba(99, 102, 241, 0.1); + display: flex; align-items: center; + justify-content: center; + margin-bottom: 16px; +} + +.empty-icon { + color: var(--color-accentedPalette-500); + font-size: 22px; + width: 22px; + height: 22px; +} + +.empty-title { + font-size: 20px; + font-weight: 600; + margin: 0 0 8px; + line-height: 1.3; +} + +.empty-subtitle { + font-size: 14px; + color: rgba(0, 0, 0, 0.42); + margin: 0 0 24px; + line-height: 1.5; +} + +@media (prefers-color-scheme: dark) { + .empty-subtitle { + color: rgba(255, 255, 255, 0.42); + } +} + +/* Demo animated table */ +.demo-table { + border-radius: 8px; + overflow: hidden; + background: #1e1e1e; + border: 1px solid #2a2a2a; } -.empty-state__actions { +.demo-header { display: flex; - gap: 12px; + padding: 8px 14px; + border-bottom: 1px solid #2a2a2a; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: rgba(255, 255, 255, 0.35); +} + +.demo-header-col { + width: 110px; + flex-shrink: 0; } -.widgets-hint { +.demo-header-val { + flex: 1; +} + +.demo-row { display: flex; - flex-direction: column; align-items: center; - margin-bottom: 20px !important; - text-align: center; - width: clamp(300px, 75vw, 800px); + padding: 10px 14px; + border-bottom: 1px solid #2a2a2a; + position: relative; +} + +.demo-row--last { + border-bottom: none; +} + +.demo-col { + width: 110px; + flex-shrink: 0; + font-family: monospace; + font-size: 12px; + color: rgba(255, 255, 255, 0.5); +} + +.demo-val-wrap { + flex: 1; + position: relative; + height: 20px; +} + +.demo-raw { + position: absolute; + top: 0; + left: 0; + font-family: monospace; + font-size: 12px; + color: rgba(255, 255, 255, 0.4); + white-space: nowrap; + transition: opacity 0.35s, transform 0.35s; + line-height: 20px; +} + +.demo-widget { + position: absolute; + top: 0; + left: 0; + font-size: 13px; + font-weight: 500; + white-space: nowrap; + display: flex; + align-items: center; + gap: 4px; + opacity: 0; + transform: translateY(6px); + transition: opacity 0.35s, transform 0.35s; + line-height: 20px; +} + +.show-widget .demo-raw { + opacity: 0; + transform: translateY(-4px); +} + +.show-widget .demo-widget { + opacity: 1; + transform: translateY(0); +} + +.demo-widget--green { + color: #22c55e; +} + +.demo-widget--amber { + color: #f59e0b; } -.widgets-hint__title { - display: inline-block; - font-size: 1.25em; - margin-bottom: 4px; +.demo-widget--purple { + color: #8b5cf6; } -.widgets-hint__text { - color: rgba(0, 0, 0, 0.64); +.demo-widget--blue { + color: #3b82f6; +} + +.demo-widget-icon { + font-size: 16px; + width: 16px; + height: 16px; +} + +.demo-widget-icon--green { + color: #22c55e; +} + +.demo-widget-icon--purple { + color: #8b5cf6; +} + +.demo-widget-icon--blue { + color: #3b82f6; +} + +.widget-badge { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.06); + padding: 2px 8px; + border-radius: 4px; + opacity: 0; + transition: opacity 0.35s; + flex-shrink: 0; + margin-left: 8px; +} + +.widget-badge--visible { + opacity: 1; +} + +.empty-cta { + margin-top: 24px; + display: flex; + align-items: center; + gap: 16px; +} + +.empty-docs-link { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 13px; + color: var(--color-accentedPalette-500); + text-decoration: none; +} + +.empty-docs-link:hover { + opacity: 0.8; +} + +.empty-docs-icon { + font-size: 14px; + width: 14px; + height: 14px; +} + +.empty-divider { + width: 1px; + background: rgba(0, 0, 0, 0.1); + flex-shrink: 0; + align-self: center; + height: 60%; } @media (prefers-color-scheme: dark) { - .widgets-hint__text { - color: rgba(255, 255, 255, 0.64); + .empty-divider { + background: rgba(255, 255, 255, 0.1); } } -.widget-examples { +.empty-right { + flex: 1; + padding: 32px; + overflow: auto; + display: flex; + flex-direction: column; + justify-content: center; +} + +.widget-grid-label { + text-transform: uppercase; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + color: rgba(0, 0, 0, 0.4); + margin-bottom: 16px; +} + +@media (prefers-color-scheme: dark) { + .widget-grid-label { + color: rgba(255, 255, 255, 0.4); + } +} + +.widget-grid { display: grid; - grid-template-columns: repeat(3, minmax(300px, 1fr)); - grid-template-rows: repeat(2, auto); - gap: 20px; - margin-top: 32px !important; - width: clamp(300px, 80vw, 1200px); + grid-template-columns: repeat(6, auto); + gap: 10px; } -.widget-example { +.widget-card { display: flex; flex-direction: column; - border: 1px solid rgba(0,0,0,0.12); - border-radius: 4px; - padding: 16px 16px 0; + align-items: center; + padding: 10px 2px 8px; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + text-align: center; } @media (prefers-color-scheme: dark) { - .widget-example { - border-color: rgba(255,255,255,0.12); - background-color: #202020; + .widget-card { + border-color: rgba(255, 255, 255, 0.08); } } -.widget-example_row-2 { - grid-row: 2; +.widget-card-icon { + font-size: 24px; + width: 24px; + height: 24px; + color: var(--color-accentedPalette-500); + margin-bottom: 6px; } -.widget-example_row-1-2 { - grid-row: span 2; +.widget-card-name { + font-size: 12px; + font-weight: 500; + line-height: 1.3; } -.examples-arrow { - color: var(--color-accentedPalette-500); - margin: 0 auto 12px; +.widget-card-desc { + font-size: 11px; + color: rgba(0, 0, 0, 0.4); + line-height: 1.3; +} + +@media (prefers-color-scheme: dark) { + .widget-card-desc { + color: rgba(255, 255, 255, 0.4); + } } .widget-settings { diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html index f8a0dd141..37b344487 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html @@ -74,122 +74,185 @@
- -

- Customize how fields look in forms and tables - Nothing will be changed in your data types, just better reflecting the nature of the data. -

+
+
+ tune +
- +

Make your data easier to read and edit

+

By default every column shows raw text. Widgets change how a column is displayed and edited β€” without modifying the database.

-
-
- - - arrow_downward - - +
+
+ Column + Value +
+
+ is_active + + 1 + + check_circle + Yes + + + Boolean +
+
+ price + + 9900 + USD 99.00 + + Money +
+
+ website + + https://rocketadmin.com + + link + rocketadmin.com + + + URL +
+
+ country + + US + + 🇺🇸 United States + + + Country +
-
- - - arrow_downward - - +
+ + + Docs open_in_new +
+
-
- - - arrow_downward - - -
+
-
- - - arrow_downward - - +
+ Available widgets +
+
+ image + Image + Photo preview +
+
+ toggle_on + Boolean + Yes / No +
+
+ payments + Money + Currency format +
+
+ flag + Country + Country picker +
+
+ link + URL + Clickable link +
+
+ list + Select + Dropdown list +
+
+ event + Date + Date picker +
+
+ palette + Color + Color picker +
+
+ phone + Phone + Phone format +
+
+ code + Code + Syntax highlight +
+
+ tag + Number + Numeric display +
+
+ linear_scale + Range + Slider input +
+
+ password + Password + Hidden text +
+
+ notes + Textarea + Multi-line text +
+
+ fingerprint + UUID + Auto-generate +
+
+ cloud_upload + S3 + File storage +
+
+ vpn_key + Foreign key + Linked record +
+
+ text_fields + String + Text validation +
+
-
- +
+ - -
+
\ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts index 16043c049..b7eb2056c 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts @@ -1,5 +1,5 @@ import { CommonModule, Location } from '@angular/common'; -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; @@ -21,12 +21,6 @@ import { UiSettingsService } from 'src/app/services/ui-settings.service'; import { PlaceholderTableWidgetsComponent } from '../../../skeletons/placeholder-table-widgets/placeholder-table-widgets.component'; import { AlertComponent } from '../../../ui-components/alert/alert.component'; import { BreadcrumbsComponent } from '../../../ui-components/breadcrumbs/breadcrumbs.component'; -import { CodeEditComponent } from '../../../ui-components/record-edit-fields/code/code.component'; -import { ImageEditComponent } from '../../../ui-components/record-edit-fields/image/image.component'; -import { LongTextEditComponent } from '../../../ui-components/record-edit-fields/long-text/long-text.component'; -import { PasswordEditComponent } from '../../../ui-components/record-edit-fields/password/password.component'; -import { SelectEditComponent } from '../../../ui-components/record-edit-fields/select/select.component'; -import { TextEditComponent } from '../../../ui-components/record-edit-fields/text/text.component'; import { WidgetComponent } from './widget/widget.component'; import { WidgetDeleteDialogComponent } from './widget-delete-dialog/widget-delete-dialog.component'; @@ -46,17 +40,11 @@ import { WidgetDeleteDialogComponent } from './widget-delete-dialog/widget-delet AlertComponent, PlaceholderTableWidgetsComponent, BreadcrumbsComponent, - PasswordEditComponent, - ImageEditComponent, - CodeEditComponent, WidgetComponent, - TextEditComponent, - LongTextEditComponent, - SelectEditComponent, Angulartics2OnModule, ], }) -export class DbTableWidgetsComponent implements OnInit { +export class DbTableWidgetsComponent implements OnInit, OnDestroy { protected posthog = posthog; public connectionID: string | null = null; public tableName: string | null = null; @@ -68,6 +56,8 @@ export class DbTableWidgetsComponent implements OnInit { public submitting: boolean = false; public widgetsWithSettings: string[]; public codeEditorTheme: 'vs' | 'vs-dark' = 'vs-dark'; + public animatedRows: boolean[] = [false, false, false, false]; + public paramsEditorOptions = { minimap: { enabled: false }, lineNumbersMinChars: 3, @@ -76,10 +66,6 @@ export class DbTableWidgetsComponent implements OnInit { scrollBeyondLastLine: false, wordWrap: 'on', }; - public widgetCodeEample = `

Why UI Customization Matters in Admin Panels

-

- A well-designed admin panel isn’t just about managing data β€” it’s about making that data easier to understand and interact with. By customizing how each field is displayed, you can turn raw database values into meaningful, user-friendly interfaces that save time and reduce errors. -

`; // JSON5-formatted default params public defaultParams = { Boolean: `// Display "Yes/No" buttons with configurable options: @@ -320,6 +306,10 @@ export class DbTableWidgetsComponent implements OnInit { this.codeEditorTheme = this._uiSettings.isDarkMode ? 'vs-dark' : 'vs'; } + ngOnDestroy(): void { + this._clearAnimationTimers(); + } + get currentConnection() { return this._connections.currentConnection; } @@ -413,6 +403,9 @@ export class DbTableWidgetsComponent implements OnInit { if (widget.widget_type === '') widget.widget_type = 'Default'; }); this.widgets = res; + if (this.widgets.length === 0) { + this._startWidgetAnimation(); + } }); } @@ -438,4 +431,38 @@ export class DbTableWidgetsComponent implements OnInit { }, ); } + + private _animationTimers: ReturnType[] = []; + + private _startWidgetAnimation(): void { + this._clearAnimationTimers(); + this._runAnimationCycle(); + } + + private _runAnimationCycle(): void { + for (let i = 0; i < 4; i++) { + this._animationTimers.push( + setTimeout(() => { + this.animatedRows[i] = true; + }, i * 600), + ); + } + + this._animationTimers.push( + setTimeout(() => { + this.animatedRows = [false, false, false, false]; + + this._animationTimers.push( + setTimeout(() => { + this._runAnimationCycle(); + }, 800), + ); + }, 3 * 600 + 2200), + ); + } + + private _clearAnimationTimers(): void { + this._animationTimers.forEach((t) => clearTimeout(t)); + this._animationTimers = []; + } } From 0df3982f1cb297e6b0d6f2f11a74d99bea4ae247 Mon Sep 17 00:00:00 2001 From: Karina Kharchenko Date: Wed, 4 Mar 2026 16:10:44 +0200 Subject: [PATCH 2/3] Update demo table widget styles: white text for Money/Country, monospace font Co-Authored-By: Claude Opus 4.6 --- .../db-table-widgets/db-table-widgets.component.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css index 4a16493ca..474bdc74a 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css @@ -157,7 +157,8 @@ position: absolute; top: 0; left: 0; - font-size: 13px; + font-family: monospace; + font-size: 12px; font-weight: 500; white-space: nowrap; display: flex; @@ -184,7 +185,7 @@ } .demo-widget--amber { - color: #f59e0b; + color: rgba(255, 255, 255, 0.85); } .demo-widget--purple { @@ -192,7 +193,7 @@ } .demo-widget--blue { - color: #3b82f6; + color: rgba(255, 255, 255, 0.85); } .demo-widget-icon { From 0a43bc7c564c467e492e5ceda3242dccef27d91f Mon Sep 17 00:00:00 2001 From: Lyubov Voloshko Date: Thu, 5 Mar 2026 16:07:36 +0000 Subject: [PATCH 3/3] fixes: - scroll; - text contrast; - bottom bar, only back button; - light theme for demo table; - move empty state to separate component. --- .../db-table-widgets.component.css | 314 ---------------- .../db-table-widgets.component.html | 180 +-------- .../db-table-widgets.component.ts | 47 +-- .../widgets-empty-state.component.css | 355 ++++++++++++++++++ .../widgets-empty-state.component.html | 170 +++++++++ .../widgets-empty-state.component.ts | 67 ++++ 6 files changed, 599 insertions(+), 534 deletions(-) create mode 100644 frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css create mode 100644 frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.html create mode 100644 frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.ts diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css index d49034796..0a53f76b2 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.css @@ -31,320 +31,6 @@ gap: 16px; } -.empty-state { - display: flex; - align-items: stretch; - height: calc(100vh - 200px); - gap: 0; - max-width: 1200px; - margin: 0 auto; -} - -.empty-left { - width: 420px; - flex-shrink: 0; - display: flex; - flex-direction: column; - justify-content: center; - padding: 32px 32px 32px 0; -} - -.empty-icon-box { - width: 40px; - height: 40px; - border-radius: 10px; - background: rgba(99, 102, 241, 0.1); - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 16px; -} - -.empty-icon { - color: var(--color-accentedPalette-500); - font-size: 22px; - width: 22px; - height: 22px; -} - -.empty-title { - font-size: 20px; - font-weight: 600; - margin: 0 0 8px; - line-height: 1.3; -} - -.empty-subtitle { - font-size: 14px; - color: rgba(0, 0, 0, 0.42); - margin: 0 0 24px; - line-height: 1.5; -} - -@media (prefers-color-scheme: dark) { - .empty-subtitle { - color: rgba(255, 255, 255, 0.42); - } -} - -/* Demo animated table */ -.demo-table { - border-radius: 8px; - overflow: hidden; - background: #1e1e1e; - border: 1px solid #2a2a2a; -} - -.demo-header { - display: flex; - padding: 8px 14px; - border-bottom: 1px solid #2a2a2a; - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.05em; - color: rgba(255, 255, 255, 0.35); -} - -.demo-header-col { - width: 110px; - flex-shrink: 0; -} - -.demo-header-val { - flex: 1; -} - -.demo-row { - display: flex; - align-items: center; - padding: 10px 14px; - border-bottom: 1px solid #2a2a2a; - position: relative; -} - -.demo-row--last { - border-bottom: none; -} - -.demo-col { - width: 110px; - flex-shrink: 0; - font-family: monospace; - font-size: 12px; - color: rgba(255, 255, 255, 0.5); -} - -.demo-val-wrap { - flex: 1; - position: relative; - height: 20px; -} - -.demo-raw { - position: absolute; - top: 0; - left: 0; - font-family: monospace; - font-size: 12px; - color: rgba(255, 255, 255, 0.4); - white-space: nowrap; - transition: opacity 0.35s, transform 0.35s; - line-height: 20px; -} - -.demo-widget { - position: absolute; - top: 0; - left: 0; - font-family: monospace; - font-size: 12px; - font-weight: 500; - white-space: nowrap; - display: flex; - align-items: center; - gap: 4px; - opacity: 0; - transform: translateY(6px); - transition: opacity 0.35s, transform 0.35s; - line-height: 20px; -} - -.show-widget .demo-raw { - opacity: 0; - transform: translateY(-4px); -} - -.show-widget .demo-widget { - opacity: 1; - transform: translateY(0); -} - -.demo-widget--green { - color: #22c55e; -} - -.demo-widget--amber { - color: rgba(255, 255, 255, 0.85); -} - -.demo-widget--purple { - color: #8b5cf6; -} - -.demo-widget--blue { - color: rgba(255, 255, 255, 0.85); -} - -.demo-widget-icon { - font-size: 16px; - width: 16px; - height: 16px; -} - -.demo-widget-icon--green { - color: #22c55e; -} - -.demo-widget-icon--purple { - color: #8b5cf6; -} - -.demo-widget-icon--blue { - color: #3b82f6; -} - -.widget-badge { - font-size: 10px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.04em; - color: rgba(255, 255, 255, 0.3); - background: rgba(255, 255, 255, 0.06); - padding: 2px 8px; - border-radius: 4px; - opacity: 0; - transition: opacity 0.35s; - flex-shrink: 0; - margin-left: 8px; -} - -.widget-badge--visible { - opacity: 1; -} - -.empty-cta { - margin-top: 24px; - display: flex; - align-items: center; - gap: 16px; -} - -.empty-docs-link { - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 13px; - color: var(--color-accentedPalette-500); - text-decoration: none; -} - -.empty-docs-link:hover { - opacity: 0.8; -} - -.empty-docs-icon { - font-size: 14px; - width: 14px; - height: 14px; -} - -.empty-divider { - width: 1px; - background: rgba(0, 0, 0, 0.1); - flex-shrink: 0; - align-self: center; - height: 60%; -} - -@media (prefers-color-scheme: dark) { - .empty-divider { - background: rgba(255, 255, 255, 0.1); - } -} - -.empty-right { - flex: 1; - padding: 32px; - overflow: auto; - display: flex; - flex-direction: column; - justify-content: center; -} - -.widget-grid-label { - text-transform: uppercase; - font-size: 11px; - font-weight: 600; - letter-spacing: 0.08em; - color: rgba(0, 0, 0, 0.4); - margin-bottom: 16px; -} - -@media (prefers-color-scheme: dark) { - .widget-grid-label { - color: rgba(255, 255, 255, 0.4); - } -} - -.widget-grid { - display: grid; - grid-template-columns: repeat(6, auto); - gap: 10px; -} - -.widget-card { - display: flex; - flex-direction: column; - align-items: center; - padding: 10px 2px 8px; - border: 1px solid rgba(0, 0, 0, 0.08); - border-radius: 8px; - text-align: center; -} - -@media (prefers-color-scheme: dark) { - .widget-card { - border-color: rgba(255, 255, 255, 0.08); - } -} - -.widget-card-icon { - font-size: 24px; - width: 24px; - height: 24px; - color: var(--color-accentedPalette-500); - margin-bottom: 6px; -} - -.widget-card-name { - font-size: 12px; - font-weight: 500; - line-height: 1.3; -} - -.widget-card-desc { - font-size: 11px; - color: rgba(0, 0, 0, 0.4); - line-height: 1.3; -} - -@media (prefers-color-scheme: dark) { - .widget-card-desc { - color: rgba(255, 255, 255, 0.4); - } -} - .widget-settings { display: grid; grid-template-columns: minmax(10%, 130px) 1fr 2fr 1fr 2fr 50px; diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html index 37b344487..ddb9fe69f 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.html @@ -73,186 +73,12 @@ -
-
-
- tune -
- -

Make your data easier to read and edit

-

By default every column shows raw text. Widgets change how a column is displayed and edited β€” without modifying the database.

- -
-
- Column - Value -
-
- is_active - - 1 - - check_circle - Yes - - - Boolean -
-
- price - - 9900 - USD 99.00 - - Money -
-
- website - - https://rocketadmin.com - - link - rocketadmin.com - - - URL -
-
- country - - US - - 🇺🇸 United States - - - Country -
-
- -
- - - Docs open_in_new - -
-
- -
- -
- Available widgets -
-
- image - Image - Photo preview -
-
- toggle_on - Boolean - Yes / No -
-
- payments - Money - Currency format -
-
- flag - Country - Country picker -
-
- link - URL - Clickable link -
-
- list - Select - Dropdown list -
-
- event - Date - Date picker -
-
- palette - Color - Color picker -
-
- phone - Phone - Phone format -
-
- code - Code - Syntax highlight -
-
- tag - Number - Numeric display -
-
- linear_scale - Range - Slider input -
-
- password - Password - Hidden text -
-
- notes - Textarea - Multi-line text -
-
- fingerprint - UUID - Auto-generate -
-
- cloud_upload - S3 - File storage -
-
- vpn_key - Foreign key - Linked record -
-
- text_fields - String - Text validation -
-
-
-
+
\ No newline at end of file diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts index b7eb2056c..5fcd2280b 100644 --- a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/db-table-widgets.component.ts @@ -1,5 +1,5 @@ import { CommonModule, Location } from '@angular/common'; -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; @@ -23,6 +23,7 @@ import { AlertComponent } from '../../../ui-components/alert/alert.component'; import { BreadcrumbsComponent } from '../../../ui-components/breadcrumbs/breadcrumbs.component'; import { WidgetComponent } from './widget/widget.component'; import { WidgetDeleteDialogComponent } from './widget-delete-dialog/widget-delete-dialog.component'; +import { WidgetsEmptyStateComponent } from './widgets-empty-state/widgets-empty-state.component'; @Component({ selector: 'app-db-table-widgets', @@ -41,10 +42,11 @@ import { WidgetDeleteDialogComponent } from './widget-delete-dialog/widget-delet PlaceholderTableWidgetsComponent, BreadcrumbsComponent, WidgetComponent, + WidgetsEmptyStateComponent, Angulartics2OnModule, ], }) -export class DbTableWidgetsComponent implements OnInit, OnDestroy { +export class DbTableWidgetsComponent implements OnInit { protected posthog = posthog; public connectionID: string | null = null; public tableName: string | null = null; @@ -56,7 +58,6 @@ export class DbTableWidgetsComponent implements OnInit, OnDestroy { public submitting: boolean = false; public widgetsWithSettings: string[]; public codeEditorTheme: 'vs' | 'vs-dark' = 'vs-dark'; - public animatedRows: boolean[] = [false, false, false, false]; public paramsEditorOptions = { minimap: { enabled: false }, @@ -306,10 +307,6 @@ export class DbTableWidgetsComponent implements OnInit, OnDestroy { this.codeEditorTheme = this._uiSettings.isDarkMode ? 'vs-dark' : 'vs'; } - ngOnDestroy(): void { - this._clearAnimationTimers(); - } - get currentConnection() { return this._connections.currentConnection; } @@ -403,9 +400,6 @@ export class DbTableWidgetsComponent implements OnInit, OnDestroy { if (widget.widget_type === '') widget.widget_type = 'Default'; }); this.widgets = res; - if (this.widgets.length === 0) { - this._startWidgetAnimation(); - } }); } @@ -432,37 +426,4 @@ export class DbTableWidgetsComponent implements OnInit, OnDestroy { ); } - private _animationTimers: ReturnType[] = []; - - private _startWidgetAnimation(): void { - this._clearAnimationTimers(); - this._runAnimationCycle(); - } - - private _runAnimationCycle(): void { - for (let i = 0; i < 4; i++) { - this._animationTimers.push( - setTimeout(() => { - this.animatedRows[i] = true; - }, i * 600), - ); - } - - this._animationTimers.push( - setTimeout(() => { - this.animatedRows = [false, false, false, false]; - - this._animationTimers.push( - setTimeout(() => { - this._runAnimationCycle(); - }, 800), - ); - }, 3 * 600 + 2200), - ); - } - - private _clearAnimationTimers(): void { - this._animationTimers.forEach((t) => clearTimeout(t)); - this._animationTimers = []; - } } diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css new file mode 100644 index 000000000..7d9dd4dbd --- /dev/null +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.css @@ -0,0 +1,355 @@ +.empty-state { + display: flex; + align-items: stretch; + height: calc(100vh - 200px - 16px); + gap: 0; + max-width: 1200px; + margin: 0 auto; +} + +.empty-left { + width: 420px; + flex-shrink: 0; + display: flex; + flex-direction: column; + justify-content: center; + padding: 32px 32px 32px 0; +} + +.empty-icon-box { + width: 40px; + height: 40px; + border-radius: 10px; + background: rgba(99, 102, 241, 0.1); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; +} + +.empty-icon { + color: var(--color-accentedPalette-500); + font-size: 22px; + width: 22px; + height: 22px; +} + +.empty-title { + font-size: 20px; + font-weight: 600; + margin: 0 0 8px; + line-height: 1.3; +} + +.empty-subtitle { + font-size: 14px; + font-weight: 300; + color: rgba(0, 0, 0, 0.64); + margin: 0 0 24px !important; + line-height: 1.5; +} + +.empty-subtitle strong { + font-weight: 500; +} + +@media (prefers-color-scheme: dark) { + .empty-subtitle { + color: rgba(255, 255, 255, 0.64); + } +} + +/* Demo animated table */ +.demo-table { + border-radius: 8px; + overflow: hidden; + background: #1e1e1e; + border: 1px solid #2a2a2a; +} + +.demo-header { + display: flex; + padding: 8px 14px; + border-bottom: 1px solid #2a2a2a; + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: rgba(255, 255, 255, 0.35); +} + +.demo-header-col { + width: 110px; + flex-shrink: 0; +} + +.demo-header-val { + flex: 1; +} + +.demo-row { + display: flex; + align-items: center; + padding: 10px 14px; + border-bottom: 1px solid #2a2a2a; + position: relative; +} + +.demo-row--last { + border-bottom: none; +} + +.demo-col { + width: 110px; + flex-shrink: 0; + font-family: monospace; + font-size: 12px; + color: rgba(255, 255, 255, 0.5); +} + +.demo-val-wrap { + flex: 1; + position: relative; + height: 20px; +} + +.demo-raw { + position: absolute; + top: 0; + left: 0; + font-family: monospace; + font-size: 12px; + color: rgba(255, 255, 255, 0.4); + white-space: nowrap; + transition: opacity 0.35s, transform 0.35s; + line-height: 20px; +} + +.demo-widget { + position: absolute; + top: 0; + left: 0; + font-family: monospace; + font-size: 12px; + font-weight: 500; + white-space: nowrap; + display: flex; + align-items: center; + gap: 4px; + opacity: 0; + transform: translateY(6px); + transition: opacity 0.35s, transform 0.35s; + line-height: 20px; +} + +.show-widget .demo-raw { + opacity: 0; + transform: translateY(-4px); +} + +.show-widget .demo-widget { + opacity: 1; + transform: translateY(0); +} + +.demo-widget--green { + color: #22c55e; +} + +.demo-widget--amber { + color: rgba(255, 255, 255, 0.85); +} + +.demo-widget--purple { + color: #8b5cf6; +} + +.demo-widget--blue { + color: rgba(255, 255, 255, 0.85); +} + +.demo-widget-icon { + font-size: 16px; + width: 16px; + height: 16px; +} + +.demo-widget-icon--green { + color: #22c55e; +} + +.demo-widget-icon--purple { + color: #8b5cf6; +} + +.demo-widget-icon--blue { + color: #3b82f6; +} + +.widget-badge { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.06); + padding: 2px 8px; + border-radius: 4px; + opacity: 0; + transition: opacity 0.35s; + flex-shrink: 0; + margin-left: 8px; +} + +.widget-badge--visible { + opacity: 1; +} + +@media (prefers-color-scheme: light) { + .demo-table { + background: #f8f8f8; + border-color: rgba(0, 0, 0, 0.1); + } + + .demo-header { + border-bottom-color: rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.4); + } + + .demo-row { + border-bottom-color: rgba(0, 0, 0, 0.08); + } + + .demo-col { + color: rgba(0, 0, 0, 0.5); + } + + .demo-raw { + color: rgba(0, 0, 0, 0.4); + } + + .demo-widget--amber { + color: rgba(0, 0, 0, 0.85); + } + + .demo-widget--blue { + color: rgba(0, 0, 0, 0.85); + } + + .widget-badge { + color: rgba(0, 0, 0, 0.35); + background: rgba(0, 0, 0, 0.06); + } +} + +.empty-cta { + margin-top: 24px; + display: flex; + align-items: center; + gap: 16px; +} + +.empty-docs-link { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 13px; + color: var(--color-accentedPalette-500); + text-decoration: none; +} + +.empty-docs-link:hover { + opacity: 0.8; +} + +.empty-docs-icon { + font-size: 14px; + width: 14px; + height: 14px; +} + +.empty-divider { + width: 1px; + background: rgba(0, 0, 0, 0.1); + flex-shrink: 0; + align-self: center; + height: 60%; +} + +@media (prefers-color-scheme: dark) { + .empty-divider { + background: rgba(255, 255, 255, 0.1); + } +} + +.empty-right { + flex: 1; + padding: 32px; + overflow: auto; + display: flex; + flex-direction: column; + justify-content: center; +} + +.widget-grid-label { + text-transform: uppercase; + font-size: 11px; + /* font-weight: 600; */ + letter-spacing: 0.08em; + color: rgba(0, 0, 0, 0.64); + margin-bottom: 16px; +} + +@media (prefers-color-scheme: dark) { + .widget-grid-label { + color: rgba(255, 255, 255, 0.64); + } +} + +.widget-grid { + display: grid; + grid-template-columns: repeat(6, auto); + gap: 10px; +} + +.widget-card { + display: flex; + flex-direction: column; + align-items: center; + padding: 10px 2px 8px; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + text-align: center; +} + +@media (prefers-color-scheme: dark) { + .widget-card { + border-color: rgba(255, 255, 255, 0.08); + } +} + +.widget-card-icon { + font-size: 24px; + width: 24px; + height: 24px; + color: var(--color-accentedPalette-500); + margin-bottom: 6px; +} + +.widget-card-name { + font-size: 12px; + font-weight: 500; + line-height: 1.3; +} + +.widget-card-desc { + font-size: 11px; + color: rgba(0, 0, 0, 0.54); + line-height: 1.3; +} + +@media (prefers-color-scheme: dark) { + .widget-card-desc { + color: rgba(255, 255, 255, 0.54); + } +} diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.html b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.html new file mode 100644 index 000000000..b32f4e7b5 --- /dev/null +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.html @@ -0,0 +1,170 @@ +
+
+
+ tune +
+ +

Make your data easier to read and edit

+

By default every column shows raw text. Widgets change how a column is displayed and edited β€” without modifying the database.

+ +
+
+ Column + Value +
+
+ is_active + + 1 + + check_circle + Yes + + + Boolean +
+
+ price + + 9900 + USD 99.00 + + Money +
+
+ website + + https://rocketadmin.com + + link + rocketadmin.com + + + URL +
+
+ country + + US + + 🇺🇸 United States + + + Country +
+
+ +
+ + + Docs open_in_new + +
+
+ +
+ +
+ Available widgets +
+
+ image + Image + Photo preview +
+
+ toggle_on + Boolean + Yes / No +
+
+ payments + Money + Currency format +
+
+ flag + Country + Country picker +
+
+ link + URL + Clickable link +
+
+ list + Select + Dropdown list +
+
+ event + Date + Date picker +
+
+ palette + Color + Color picker +
+
+ phone + Phone + Phone format +
+
+ code + Code + Syntax highlight +
+
+ tag + Number + Numeric display +
+
+ linear_scale + Range + Slider input +
+
+ password + Password + Hidden text +
+
+ notes + Textarea + Multi-line text +
+
+ fingerprint + UUID + Auto-generate +
+
+ cloud_upload + S3 + File storage +
+
+ vpn_key + Foreign key + Linked record +
+
+ text_fields + String + Text validation +
+
+
+
diff --git a/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.ts b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.ts new file mode 100644 index 000000000..2540c2da1 --- /dev/null +++ b/frontend/src/app/components/dashboard/db-table-view/db-table-widgets/widgets-empty-state/widgets-empty-state.component.ts @@ -0,0 +1,67 @@ +import { CommonModule } from '@angular/common'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { Angulartics2OnModule } from 'angulartics2'; +import posthog from 'posthog-js'; + +@Component({ + selector: 'app-widgets-empty-state', + templateUrl: './widgets-empty-state.component.html', + styleUrls: ['./widgets-empty-state.component.css'], + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + Angulartics2OnModule, + ], +}) +export class WidgetsEmptyStateComponent implements OnInit, OnDestroy { + protected posthog = posthog; + + @Output() onAddWidget = new EventEmitter(); + + public animatedRows: boolean[] = [false, false, false, false]; + + ngOnInit(): void { + this._startWidgetAnimation(); + } + + ngOnDestroy(): void { + this._clearAnimationTimers(); + } + + private _animationTimers: ReturnType[] = []; + + private _startWidgetAnimation(): void { + this._clearAnimationTimers(); + this._runAnimationCycle(); + } + + private _runAnimationCycle(): void { + for (let i = 0; i < 4; i++) { + this._animationTimers.push( + setTimeout(() => { + this.animatedRows[i] = true; + }, i * 600), + ); + } + + this._animationTimers.push( + setTimeout(() => { + this.animatedRows = [false, false, false, false]; + + this._animationTimers.push( + setTimeout(() => { + this._runAnimationCycle(); + }, 800), + ); + }, 3 * 600 + 2200), + ); + } + + private _clearAnimationTimers(): void { + this._animationTimers.forEach((t) => clearTimeout(t)); + this._animationTimers = []; + } +}