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 df62f7fb9..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,79 +31,6 @@ gap: 16px; } -.empty-state { - display: flex; - flex-direction: column; - align-items: center; -} - -.empty-state__actions { - display: flex; - gap: 12px; -} - -.widgets-hint { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 20px !important; - text-align: center; - width: clamp(300px, 75vw, 800px); -} - -.widgets-hint__title { - display: inline-block; - font-size: 1.25em; - margin-bottom: 4px; -} - -.widgets-hint__text { - color: rgba(0, 0, 0, 0.64); -} - -@media (prefers-color-scheme: dark) { - .widgets-hint__text { - color: rgba(255, 255, 255, 0.64); - } -} - -.widget-examples { - 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); -} - -.widget-example { - display: flex; - flex-direction: column; - border: 1px solid rgba(0,0,0,0.12); - border-radius: 4px; - padding: 16px 16px 0; -} - -@media (prefers-color-scheme: dark) { - .widget-example { - border-color: rgba(255,255,255,0.12); - background-color: var(--surface-dark-color); - } -} - -.widget-example_row-2 { - grid-row: 2; -} - -.widget-example_row-1-2 { - grid-row: span 2; -} - -.examples-arrow { - color: var(--color-accentedPalette-500); - margin: 0 auto 12px; -} - .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 f8a0dd141..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,123 +73,12 @@ -
- -

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

+ - - -
-
- - - arrow_downward - - -
- -
- - - arrow_downward - - -
- -
- - - arrow_downward - - -
- -
- - - arrow_downward - - -
-
- -
- - - -
+
\ 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..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 @@ -21,14 +21,9 @@ 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'; +import { WidgetsEmptyStateComponent } from './widgets-empty-state/widgets-empty-state.component'; @Component({ selector: 'app-db-table-widgets', @@ -46,13 +41,8 @@ import { WidgetDeleteDialogComponent } from './widget-delete-dialog/widget-delet AlertComponent, PlaceholderTableWidgetsComponent, BreadcrumbsComponent, - PasswordEditComponent, - ImageEditComponent, - CodeEditComponent, WidgetComponent, - TextEditComponent, - LongTextEditComponent, - SelectEditComponent, + WidgetsEmptyStateComponent, Angulartics2OnModule, ], }) @@ -68,6 +58,7 @@ export class DbTableWidgetsComponent implements OnInit { public submitting: boolean = false; public widgetsWithSettings: string[]; public codeEditorTheme: 'vs' | 'vs-dark' = 'vs-dark'; + public paramsEditorOptions = { minimap: { enabled: false }, lineNumbersMinChars: 3, @@ -76,10 +67,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: @@ -438,4 +425,5 @@ export class DbTableWidgetsComponent implements OnInit { }, ); } + } 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 = []; + } +}