Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
class="card__remove"
design="Default"
icon="decline"
[accessibleName]="'Remove card: ' + (card().label || card().component)"
[accessibleName]="
'Remove card: ' + (card().label || card().component)
"
(click)="removeCard.emit()"
/>
</div>
<ui5-icon
class="card__resize-indicator"
name="resize-corner"
accessible-name="Resizable"
aria-hidden="true"
/>
}
<div
class="component-host"
[style.pointer-events]="editMode() ? 'none' : 'auto'"
>
<div #elementHost></div>
@if (editMode()) {
<ui5-icon
class="card__resize-indicator"
name="resize-corner"
accessible-name="Resizable"
aria-hidden="true"
/>
}
</div>
</div>
} @else {
Expand All @@ -35,4 +35,4 @@
<ng-content />
</div>
</div>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
padding-inline-start: var(--mfp_cardContainerPadding, 10px);
padding-inline-end: var(--mfp_cardContainerPadding, 10px);

&--editing {
cursor: grab;
}

&--editing:hover {
--sapTile_BorderColor: var(--sapHighlightColor, #0070f2);
--sapGroup_ContentBorderColor: var(--sapHighlightColor, #0070f2);
Expand All @@ -44,14 +48,37 @@
}
}

// Gridstack adds .ui-draggable-dragging to the <gridstack-item> wrapping this
// card while a drag is active. Override the open-palm `grab` cursor with the
// closed-fist `grabbing` cursor for the duration of the drag.
:host-context(.ui-draggable-dragging) .component-card--editing {
cursor: grabbing !important;
}

// Keep the edit-mode highlight (blue border + resize-corner icon) visible on
// the dragged card. The browser stops firing :hover events during a drag, so
// we re-apply the same styles using the same Gridstack hook used for cursor.
:host-context(.ui-draggable-dragging) .component-card--editing {
--sapTile_BorderColor: var(--sapHighlightColor, #0070f2);
--sapGroup_ContentBorderColor: var(--sapHighlightColor, #0070f2);
}

:host-context(.ui-draggable-dragging) .component-card--editing .card__resize-indicator {
opacity: 1;
}

.component-host {
position: relative;
height: 100%;
}

.card__resize-indicator {
position: absolute;
right: 4px;
// Anchored to the .component-card edges. The card has horizontal padding
Comment thread
gkrajniak marked this conversation as resolved.
// (--mfp_cardContainerPadding, default 10px), so we pull the chevron back
// by that much to land inside the visible card surface rather than the
// padding gutter.
right: calc(var(--mfp_cardContainerPadding, 10px) + 4px);
bottom: 4px;
width: 1rem;
height: 1rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
</div>
@if (config().description) {
<ui5-title level="H5" size="H5" wrapping-type="Normal">
<span class="mfp-dashboard__description" [innerHTML]="safeDescription()"></span>
<span
class="mfp-dashboard__description"
[innerHTML]="safeDescription()"
></span>
</ui5-title>
}
</div>
Expand All @@ -33,7 +36,11 @@
<ui5-button
#editCardsBtn
id="edit-cards-btn"
[accessibleName]="editCardsButton().text || editCardsButton().tooltip || 'Edit Cards'"
[accessibleName]="
editCardsButton().text ||
editCardsButton().tooltip ||
'Edit Cards'
"
[design]="editCardsButton().design"
[icon]="editCardsButton().icon"
[tooltip]="editCardsButton().tooltip"
Expand Down Expand Up @@ -90,7 +97,9 @@
} @else {
@if (config().editButtonFirst && config().editable) {
<ui5-button
[accessibleName]="editViewButton().text || editViewButton().tooltip || 'Edit View'"
[accessibleName]="
editViewButton().text || editViewButton().tooltip || 'Edit View'
"
[design]="editViewButton().design"
[icon]="editViewButton().icon"
[tooltip]="editViewButton().tooltip"
Expand All @@ -113,7 +122,9 @@
}
@if (!config().editButtonFirst && config().editable) {
<ui5-button
[accessibleName]="editViewButton().text || editViewButton().tooltip || 'Edit View'"
[accessibleName]="
editViewButton().text || editViewButton().tooltip || 'Edit View'
"
[design]="editViewButton().design"
[icon]="editViewButton().icon"
[tooltip]="editViewButton().tooltip"
Expand All @@ -134,7 +145,6 @@
@for (section of sections(); track section.id) {
<mfp-dashboard-section
[cards]="sectionCards()(section.id)"
[columns]="12"
[editMode]="editMode()"
[section]="section"
(removeCard)="removeCard($event)"
Expand All @@ -143,12 +153,15 @@
}
</div>

<gridstack #grid [options]="gridOptions()" (addedCB)="onGridChange($event)" (changeCB)="onGridChange($event)" (removedCB)="onGridChange($event)">
<gridstack
#grid
[options]="gridOptions()"
(addedCB)="onGridChange($event)"
(changeCB)="onGridChange($event)"
(removedCB)="onGridChange($event)"
>
@for (card of looseCards(); track card.id) {
<gridstack-item
[options]="card"
[style.cursor]="editMode() ? 'pointer' : 'auto'"
>
<gridstack-item [options]="card">
<mfp-dashboard-card
[card]="card"
[editMode]="editMode()"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use '../models/breakpoints' as bp;
@import 'gridstack/dist/gridstack.min.css';

mfp-dashboard,
Expand All @@ -16,22 +17,24 @@ mfp-wc-dashboard {

.mfp-dashboard {
padding: calc(var(--sapShell_Space_XL, 3rem) - 10px);
// Acts as the inline-size query container for everything below. The
// `.mfp-sections-container` grid then reacts to *this element's* width
// instead of the viewport, which matches Gridstack (whose breakpoints also
// measure the grid element by default — see DashboardConfig.allowedWidths
// discussion / GridStackOptions.columnOpts.breakpointForWindow).
container-type: inline-size;
container-name: mfp-dashboard;
Comment thread
gkrajniak marked this conversation as resolved.
}

.mfp-sections-container {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-auto-rows: auto;
align-content: start;
gap: 1rem;

@media (max-width: 726px) {
grid-template-columns: 1fr;
}

@media (min-width: 727px) and (max-width: 1200px) {
grid-template-columns: repeat(8, 1fr);
}
// Column track count comes from ../models/_breakpoints.scss — same source
// of truth used by .section__grid below and by Gridstack's columnOpts.
@include bp.dashboard-grid-columns;
}

.mfp-dashboard__topbar {
Expand All @@ -50,7 +53,7 @@ mfp-wc-dashboard {
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
padding: 0px 10px;
padding: 0 10px;
}

.mfp-dashboard__header {
Expand Down Expand Up @@ -123,7 +126,7 @@ mfp-wc-dashboard {
padding: 7px 16px;
background: var(--sapPageFooter_Background, white);
border-radius: var(--sapElement_BorderCornerRadius, 12px);
box-shadow: 0px 0px 0px 1px rgba(34, 53, 72, 0.48), 0px 2px 8px 0px rgba(34, 53, 72, 0.3);
box-shadow: 0 0 0 1px rgba(34, 53, 72, 0.48), 0 2px 8px 0 rgba(34, 53, 72, 0.3);
box-sizing: border-box;
overflow: hidden;
}
Expand All @@ -145,3 +148,24 @@ mfp-wc-dashboard {
padding: calc(var(--sapShell_Space_L, 2rem) - 10px);
}
}

// While Gridstack is dragging an item, force the grabbing (closed-fist) cursor
// across the whole grid. Gridstack 12 sets `pointer-events: none` on the drag
// helper (see node_modules/gridstack/dist/dd-draggable.js:316) which prevents
// it from setting its own cursor — the maintainer's TODO comment on the next
// line confirms this is a known browser limitation. As a substitute, Gridstack
// adds `.grid-stack-dragging` to the grid host for the duration of the drag,
// which is the official hook for global drag styling.
.grid-stack.grid-stack-dragging,
.grid-stack.grid-stack-dragging * {
cursor: grabbing !important;
}

// Suppress the small grey diagonal-arrow SVG Gridstack draws inside its corner
// resize handles (see node_modules/gridstack/dist/gridstack.min.css — the
// .ui-resizable-{ne,nw,se,sw} rules embed an inline data: URI). The handle
// hit area stays interactive; we surface our own .card__resize-indicator
// inside the card as the edit-mode hint instead.
.grid-stack-item > .ui-resizable-handle {
background-image: none !important;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ButtonSettings } from '../../models/ui-definition';
import { ButtonSettings } from '../../models';
import { DashboardCard } from '../card/dashboard-card.component';
import { addComponentToRegistry } from '../card/utils/dashboard-card-registry';
import { addComponentToRegistry } from '../card/utils';
import { DiscardChangesDialog } from '../discard-changes-dialog/discard-changes-dialog.component';
import { EditCardsDialog } from '../edit-cards-dialog/edit-cards-dialog.component';
import { UnsavedChangesDialog } from '../unsaved-changes-dialog/unsaved-changes-dialog.component';
import { CardConfig, DashboardConfig, SectionConfig } from '../models';
import {
CardConfig,
DASHBOARD_BREAKPOINTS,
DashboardConfig,
SectionConfig,
} from '../models';
import { CELL_HEIGHT, COMPACT_BREAKPOINT } from '../models/constants';
import { DashboardSection } from '../section/dashboard-section.component';
import { UnsavedChangesDialog } from '../unsaved-changes-dialog/unsaved-changes-dialog.component';
import {
Component,
ElementRef,
Expand Down Expand Up @@ -139,18 +144,16 @@ export class Dashboard implements OnInit, OnDestroy {
protected gridOptions = computed(
(): GridStackOptions => ({
cellHeight: CELL_HEIGHT,
sizeToContent: true,
disableResize: !this.editMode(),
disableDrag: !this.editMode(),
marginBottom: 0,
marginLeft: 0,
marginRight: 0,
columnOpts: {
breakpointForWindow: true,
breakpoints: [
{ w: 1440, c: 12, layout: 'none' },
{ w: 1024, c: 8, layout: 'compact' },
{ w: 600, c: 1, layout: 'list' },
],
// Source of truth: ../models/breakpoints.ts (paired with
// ../models/_breakpoints.scss for the section grid's container queries).
breakpoints: [...DASHBOARD_BREAKPOINTS],
},
}),
);
Expand Down
58 changes: 58 additions & 0 deletions projects/ngx/declarative-ui/dashboard/models/_breakpoints.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// ─────────────────────────────────────────────────────────────────────────────
// MUST MATCH: ./breakpoints.ts
//
// SCSS half of the shared dashboard breakpoint table. Consumed by
// dashboard.component.scss for @container queries on .mfp-sections-container.
// Keep these values identical to DASHBOARD_BREAKPOINTS in breakpoints.ts.
// ─────────────────────────────────────────────────────────────────────────────

// Each breakpoint's max-width (in px) and the corresponding column count
// applied at that breakpoint. Ordered ascending by `w` so it's easy to read
// off as a series of (max-width, columns) pairs.
$dashboard-breakpoints: (
(599, 4),
(1023, 8),
(1439, 12),
// Above 1439: 14 columns. Implemented as the default (no @container rule);
// the 4000 ceiling exists in the TS table for Gridstack's benefit only.
);

/// Convenience accessors for the four breakpoint widths used by the grid.
$dashboard-bp-sm: 599px;
$dashboard-bp-md: 1023px;
$dashboard-bp-lg: 1439px;

/// Column counts at each breakpoint.
$dashboard-cols-sm: 4;
$dashboard-cols-md: 8;
$dashboard-cols-lg: 12;
$dashboard-cols-xl: 14;

/// Emit `grid-template-columns: repeat(N, 1fr)` rules wrapped in @container
/// queries against the `mfp-dashboard` named container. Every dashboard grid
/// (the section-laying-out one and each section's card-laying-out one) calls
/// this so the column counts stay aligned.
///
/// `$override` lets a caller substitute a CSS variable in front of each value
/// (used by .section__grid to honour an explicit `--cols` override before
/// falling back to the responsive default).
@mixin dashboard-grid-columns($override: null) {
$sm: if($override, var($override, #{$dashboard-cols-sm}), $dashboard-cols-sm);
$md: if($override, var($override, #{$dashboard-cols-md}), $dashboard-cols-md);
$lg: if($override, var($override, #{$dashboard-cols-lg}), $dashboard-cols-lg);
$xl: if($override, var($override, #{$dashboard-cols-xl}), $dashboard-cols-xl);

grid-template-columns: repeat(#{$xl}, 1fr);

@container mfp-dashboard (max-width: #{$dashboard-bp-sm}) {
grid-template-columns: repeat(#{$sm}, 1fr);
}

@container mfp-dashboard (min-width: #{$dashboard-bp-sm + 1px}) and (max-width: #{$dashboard-bp-md}) {
grid-template-columns: repeat(#{$md}, 1fr);
}

@container mfp-dashboard (min-width: #{$dashboard-bp-md + 1px}) and (max-width: #{$dashboard-bp-lg}) {
grid-template-columns: repeat(#{$lg}, 1fr);
}
}
37 changes: 37 additions & 0 deletions projects/ngx/declarative-ui/dashboard/models/breakpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// ─────────────────────────────────────────────────────────────────────────────
// MUST MATCH: ./_breakpoints.scss
//
// These breakpoints are consumed by two systems that can't share a single
// source of truth at compile time:
//
// 1. dashboard.component.ts — passed to Gridstack as
// `gridOptions.columnOpts.breakpoints`. Gridstack picks the active
// breakpoint by `w` (max-width). At each breakpoint, every grid item is
// laid out using `c` columns.
//
// 2. dashboard.component.scss — used in a `@container` query so the
// `.mfp-sections-container` CSS grid mirrors the same column count as
// Gridstack at the same width.
//
// If you change either set of values, change BOTH files in the same commit.
// The two are kept in sync by hand (no codegen). A SCSS-from-TS generator
// would remove this hazard but is out of scope here.
// ─────────────────────────────────────────────────────────────────────────────

import type { Breakpoint } from 'gridstack';

/**
* Layout strategy applied at each breakpoint when Gridstack changes column
* count. See ColumnOptions in gridstack/dist/types.d.ts:27 for the full set.
*/
type LayoutStrategy = 'compact' | 'list' | 'none';

/** Single source of truth for grid + section breakpoints (TypeScript half). */
export const DASHBOARD_BREAKPOINTS: ReadonlyArray<
Readonly<Required<Pick<Breakpoint, 'w' | 'c'>> & { layout: LayoutStrategy }>
> = [
Comment thread
gkrajniak marked this conversation as resolved.
{ w: 4000, c: 14, layout: 'compact' },
{ w: 1439, c: 12, layout: 'compact' },
{ w: 1023, c: 8, layout: 'compact' },
{ w: 599, c: 4, layout: 'list' },
] as const;
1 change: 1 addition & 0 deletions projects/ngx/declarative-ui/dashboard/models/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './dashboard.model';
export * from './breakpoints';
Loading