diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/components/umb-news-card.element.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/components/umb-news-card.element.ts new file mode 100644 index 000000000000..133f5e53c546 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/components/umb-news-card.element.ts @@ -0,0 +1,132 @@ +import { css, customElement, html, nothing, property, unsafeHTML, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { NewsDashboardItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; + +@customElement('umb-news-card') +export class UmbNewsCardElement extends UmbLitElement { + @property({ type: Object }) + item!: NewsDashboardItemResponseModel; + + @property({ type: Number }) + priority: number = 3; + + #renderHeading(priority: number, text: string) { + if (priority <= 2) return html`

${text}

`; + return html`

${text}

`; + } + + override render() { + if (!this.item) return nothing; + + const isLastRow = this.priority === 3; + + const showImage = this.priority <= 2 && !!this.item.imageUrl; + + const content = html` + ${when( + showImage, + () => + this.item.imageUrl + ? html`${this.item.imageAltText` + : html``, + () => nothing, + )} +
+ ${this.#renderHeading(this.priority, this.item.header)} + ${this.item.body ? html`
${unsafeHTML(this.item.body)}
` : nothing} + ${!isLastRow && this.item.url + ? html`
+ + ${this.item.buttonText || 'Open'} + +
` + : nothing} +
+ `; + + // Last row: whole card is a link + return isLastRow + ? this.item.url + ? html` + + ${content} + + ` + : html`
${content}
` + : html`
${content}
`; + } + + static override styles = css` + :host { + display: block; + height: 100%; + } + + .card { + background: var(--uui-color-surface); + border-radius: var(--uui-border-radius, 8px); + box-shadow: var( + --uui-box-box-shadow, + var(--uui-shadow-depth-1, 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)) + ); + overflow: hidden; + display: flex; + flex-direction: column; + height: 100%; + } + + .card-img { + width: 100%; + object-fit: cover; + display: block; + } + + .card-img.placeholder { + height: 8px; + } + + .card-body { + display: flex; + flex-direction: column; + padding: var(--uui-size-space-5); + flex: 1 1 auto; + justify-content: space-between; + gap: var(--uui-size-space-3, 9px); + } + + .card-title { + margin: 0; + } + + .card-text > p { + margin: 0; + } + + .normal-priority { + display: block; + border: 1px solid var(--uui-color-divider); + border-radius: var(--uui-border-radius, 8px); + text-decoration: none; + color: inherit; + overflow: hidden; + + .card-body { + gap: 0; + } + } + .normal-priority:hover { + color: var(--uui-color-interactive-emphasis); + } + .card-actions { + align-self: end; + } + `; +} + +export default UmbNewsCardElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-news-card': UmbNewsCardElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/components/umb-news-container.element.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/components/umb-news-container.element.ts new file mode 100644 index 000000000000..1ca905f829dc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/components/umb-news-container.element.ts @@ -0,0 +1,110 @@ +import { css, customElement, html, nothing, property, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { NewsDashboardItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; + +import './umb-news-card.element.js'; +import { sanitizeHTML } from '@umbraco-cms/backoffice/utils'; + +@customElement('umb-news-container') +export class UmbNewsContainerElement extends UmbLitElement { + @property({ type: Array }) + items: Array = []; + + #groupItemsByPriority(items: NewsDashboardItemResponseModel[]) { + const sanitizedItems = items.map((i) => ({ + ...i, + body: i.body ? sanitizeHTML(i.body) : '', + })); + + // Separate items by priority. + const priority1 = sanitizedItems.filter((item) => item.priority === 'High'); + const priority2 = sanitizedItems.filter((item) => item.priority === 'Medium'); + const priority3 = sanitizedItems.filter((item) => item.priority === 'Normal'); + + // Group 1: First 4 items from priority 1. + const group1Items = priority1.slice(0, 4); + const overflow1 = priority1.slice(4); + + // Group 2: Overflow from priority 1 + priority 2 items (max 4 total). + const group2Items = [...overflow1, ...priority2].slice(0, 4); + const overflow2Count = overflow1.length + priority2.length - 4; + const overflow2 = overflow2Count > 0 ? [...overflow1, ...priority2].slice(4) : []; + + // Group 3: Overflow from groups 1 & 2 + priority 3 items. + const group3Items = [...overflow2, ...priority3]; + + return [ + { priority: 1, items: group1Items }, + { priority: 2, items: group2Items }, + { priority: 3, items: group3Items }, + ]; + } + + override render() { + if (!this.items?.length) return nothing; + + const groups = this.#groupItemsByPriority(this.items); + + return html` + ${repeat( + groups, + (g) => g.priority, + (g) => html` +
+ ${repeat( + g.items, + (i, idx) => i.url || i.header || idx, + (i) => html``, + )} +
+ `, + )} + `; + } + + static override styles = css` + .cards { + --cols: 4; + --gap: var(--uui-size-space-4); + width: 100%; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols)), 1fr)); + gap: var(--gap); + } + + .cards + .cards { + margin-top: var(--uui-size-space-5); + } + + /* For when container-type is not been assigned, not so sure about it???*/ + @media (max-width: 1200px) { + .cards { + grid-template-columns: repeat(auto-fit, minmax(2, 1fr)); + } + } + @media (max-width: 700px) { + .cards { + grid-template-columns: 1fr; + } + } + + @container dashboard (max-width: 1200px) { + .cards { + grid-template-columns: repeat(auto-fit, minmax(2, 1fr)); + } + } + @container dashboard (max-width: 700px) { + .cards { + grid-template-columns: 1fr; + } + } + `; +} + +export default UmbNewsContainerElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-news-container': UmbNewsContainerElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.element.ts b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.element.ts index ba1ef0e957a7..bad9fc44977a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/umbraco-news-dashboard.element.ts @@ -1,32 +1,16 @@ import { UmbNewsDashboardRepository } from './repository/index.js'; -import { - css, - customElement, - html, - nothing, - repeat, - state, - unsafeHTML, - when, -} from '@umbraco-cms/backoffice/external/lit'; -import { sanitizeHTML } from '@umbraco-cms/backoffice/utils'; +import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { NewsDashboardItemResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -interface UmbNewsDashboardGroupedItems { - priority: number; - items: Array; -} +import './components/umb-news-container.element.js'; @customElement('umb-umbraco-news-dashboard') export class UmbUmbracoNewsDashboardElement extends UmbLitElement { @state() private _items: Array = []; - @state() - private _groupedItems: Array = []; - @state() private _loaded: boolean = false; @@ -35,40 +19,9 @@ export class UmbUmbracoNewsDashboardElement extends UmbLitElement { override async firstUpdated() { const res = await this.#repo.getNewsDashboard(); this._items = res.data?.items ?? []; - this._groupedItems = this.#groupItemsByPriority(); this._loaded = true; } - #groupItemsByPriority(): Array { - const sanitizedItems = this._items.map((i) => ({ - ...i, - body: i.body ? sanitizeHTML(i.body) : '', - })); - - // Separate items by priority. - const priority1 = sanitizedItems.filter((item) => item.priority === 'High'); - const priority2 = sanitizedItems.filter((item) => item.priority === 'Medium'); - const priority3 = sanitizedItems.filter((item) => item.priority === 'Normal'); - - // Group 1: First 4 items from priority 1. - const group1Items = priority1.slice(0, 4); - const overflow1 = priority1.slice(4); - - // Group 2: Overflow from priority 1 + priority 2 items (max 4 total). - const group2Items = [...overflow1, ...priority2].slice(0, 4); - const overflow2Count = overflow1.length + priority2.length - 4; - const overflow2 = overflow2Count > 0 ? [...overflow1, ...priority2].slice(4) : []; - - // Group 3: Overflow from groups 1 & 2 + priority 3 items. - const group3Items = [...overflow2, ...priority3]; - - return [ - { priority: 1, items: group1Items }, - { priority: 2, items: group2Items }, - { priority: 3, items: group3Items }, - ]; - } - override render() { if (!this._loaded) { return html`
`; @@ -78,58 +31,7 @@ export class UmbUmbracoNewsDashboardElement extends UmbLitElement { return this.#renderDefaultContent(); } - return html` - ${repeat( - this._groupedItems, - (g) => g.priority, - (g) => html` -
- ${repeat( - g.items, - (i, idx) => i.url || i.header || idx, - (i) => { - const isLastRow = g.priority === 3; - - const content = html` - ${when( - g.priority <= 2, - () => - html`${i.imageUrl - ? html`${i.imageAltText` - : html``}`, - () => nothing, - )} -
- ${g.priority <= 2 - ? html`

${i.header}

` - : html`

${i.header}

`} - ${i.body ? html`
${unsafeHTML(i.body)}
` : null} - ${!isLastRow && i.url - ? html`
- - ${i.buttonText || 'Open'} - -
` - : nothing} -
- `; - - // LAST ROW: whole card is a link - return isLastRow - ? i.url - ? html` - - ${content} - - ` - : html`
${content}
` - : html`
${content}
`; - }, - )} -
- `, - )} - `; + return html` `; } #renderDefaultContent() { @@ -234,92 +136,6 @@ export class UmbUmbracoNewsDashboardElement extends UmbLitElement { margin-top: 0; margin-bottom: 0; } - - /* Grid */ - .cards { - --cols: 4; - --gap: var(--uui-size-space-4); - width: 100%; - display: grid; - grid-template-columns: repeat( - auto-fit, - minmax(calc((100% - (var(--cols) - 1) * var(--gap)) / var(--cols)), 1fr) - ); - gap: var(--gap); - } - - .cards + .cards { - margin-top: var(--uui-size-space-5); - } - - @container (max-width: 1200px) { - .cards { - grid-template-columns: repeat(auto-fit, minmax(2, 1fr)); - } - } - @container (max-width: 700px) { - .cards { - grid-template-columns: 1fr; - } - } - - /* Card */ - .card { - background: var(--uui-color-surface); - border-radius: var(--uui-border-radius, 8px); - box-shadow: var( - --uui-box-box-shadow, - var(--uui-shadow-depth-1, 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)) - ); - overflow: hidden; - display: flex; - flex-direction: column; - height: 100%; - } - - .card-img { - width: 100%; - object-fit: cover; - display: block; - } - - .card-img.placeholder { - height: 8px; - } - - .card-body { - display: flex; - flex-direction: column; - padding: var(--uui-size-space-5); - flex: 1 1 auto; - justify-content: space-between; - gap: var(--uui-size-space-3, 9px); - } - - .card-title { - margin: 0; - } - - .card-text > p { - margin: 0; - } - - .normal-priority { - display: block; - border: 1px solid var(--uui-color-divider); - border-radius: var(--uui-border-radius, 8px); - text-decoration: none; - color: inherit; - overflow: hidden; - - .card-body { - gap: 0; - } - } - - .card-actions { - align-self: end; - } `, ]; }