From f29c529a8895b8cb03a62c751d1aef5fa05e85db Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Wed, 21 May 2025 11:59:25 +0200 Subject: [PATCH 1/8] feat: wip reusable hero component --- .../core/js/src/forum/components/Hero.tsx | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 framework/core/js/src/forum/components/Hero.tsx diff --git a/framework/core/js/src/forum/components/Hero.tsx b/framework/core/js/src/forum/components/Hero.tsx new file mode 100644 index 0000000000..233d094286 --- /dev/null +++ b/framework/core/js/src/forum/components/Hero.tsx @@ -0,0 +1,51 @@ +import app from '../app'; +import Component from '../../common/Component'; +import classList from '../../common/utils/classList'; + +import ItemList from '../../common/utils/ItemList'; +import type Mithril from 'mithril'; + +export interface IHeroAttrs {} + +export default abstract class Hero extends Component { + view(): Mithril.Vnode | null { + return ( +
+ {this.viewItems().toArray()} +
+ ); + } + + viewItems(): ItemList { + const items = new ItemList(); + + items.add('container',
{this.bodyItems().toArray()}
, 100); + + return items; + } + + /** + * @example + * ```ts + * className(): string { + * return 'WelcomeHero'; + * } + */ + abstract className(): string; + + /** + * + * @example + * ```ts + * style(): Record { + * return { + * backgroundColor: '#e7672e', + * }; + * ``` + */ + protected style(): Record | undefined { + return undefined; + } + + abstract bodyItems(): ItemList; +} From dd23e79525c8e62c26d9c7272a92008d947076dc Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Wed, 21 May 2025 12:00:21 +0200 Subject: [PATCH 2/8] refactor: adjust implementation of `DiscussionHero` --- .../js/src/forum/components/DiscussionHero.js | 41 ------------------- .../src/forum/components/DiscussionHero.tsx | 39 ++++++++++++++++++ 2 files changed, 39 insertions(+), 41 deletions(-) delete mode 100644 framework/core/js/src/forum/components/DiscussionHero.js create mode 100644 framework/core/js/src/forum/components/DiscussionHero.tsx diff --git a/framework/core/js/src/forum/components/DiscussionHero.js b/framework/core/js/src/forum/components/DiscussionHero.js deleted file mode 100644 index 5e5f2534a6..0000000000 --- a/framework/core/js/src/forum/components/DiscussionHero.js +++ /dev/null @@ -1,41 +0,0 @@ -import Component from '../../common/Component'; -import ItemList from '../../common/utils/ItemList'; -import listItems from '../../common/helpers/listItems'; - -/** - * The `DiscussionHero` component displays the hero on a discussion page. - * - * ### attrs - * - * - `discussion` - */ -export default class DiscussionHero extends Component { - view() { - return ( -
-
-
    {listItems(this.items().toArray())}
-
-
- ); - } - - /** - * Build an item list for the contents of the discussion hero. - * - * @return {ItemList} - */ - items() { - const items = new ItemList(); - const discussion = this.attrs.discussion; - const badges = discussion.badges().toArray(); - - if (badges.length) { - items.add('badges',
    {listItems(badges)}
, 10); - } - - items.add('title',

{discussion.title()}

); - - return items; - } -} diff --git a/framework/core/js/src/forum/components/DiscussionHero.tsx b/framework/core/js/src/forum/components/DiscussionHero.tsx new file mode 100644 index 0000000000..eb7bd4dbdd --- /dev/null +++ b/framework/core/js/src/forum/components/DiscussionHero.tsx @@ -0,0 +1,39 @@ +import Hero, { IHeroAttrs } from './Hero'; +import ItemList from '../../common/utils/ItemList'; +import listItems from '../../common/helpers/listItems'; + +import type Discussion from '../../common/models/Discussion'; +import type Mithril from 'mithril'; + +export interface IDiscussionHeroAttrs extends IHeroAttrs { + discussion: Discussion; +} + +export default class DiscussionHero extends Hero { + className(): string { + return 'DiscussionHero'; + } + + bodyItems(): ItemList { + const items = new ItemList(); + + items.add('items',
    {listItems(this.items().toArray())}
, 100); + + return items; + } + + items(): ItemList { + const items = new ItemList(); + + const discussion = this.attrs.discussion; + const badges = discussion.badges().toArray(); + + if (badges.length) { + items.add('badges',
    {listItems(badges)}
, 10); + } + + items.add('title',

{discussion.title()}

); + + return items; + } +} From 5f86ee26e11f11646bac2b4627e1737d7b8f43dc Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Wed, 21 May 2025 12:00:55 +0200 Subject: [PATCH 3/8] refactor: adjust implementation of `WelcomeHero` --- .../js/src/forum/components/WelcomeHero.tsx | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/framework/core/js/src/forum/components/WelcomeHero.tsx b/framework/core/js/src/forum/components/WelcomeHero.tsx index fc87b63d89..2d4ab34354 100644 --- a/framework/core/js/src/forum/components/WelcomeHero.tsx +++ b/framework/core/js/src/forum/components/WelcomeHero.tsx @@ -1,10 +1,10 @@ import app from '../app'; -import Component from '../../common/Component'; +import Hero, { IHeroAttrs } from './Hero'; import Button from '../../common/components/Button'; import type Mithril from 'mithril'; import ItemList from '../../common/utils/ItemList'; -export interface IWelcomeHeroAttrs {} +export interface IWelcomeHeroAttrs extends IHeroAttrs {} const LOCAL_STORAGE_KEY = 'welcomeHidden'; @@ -12,26 +12,18 @@ const LOCAL_STORAGE_KEY = 'welcomeHidden'; * The `WelcomeHero` component displays a hero that welcomes the user to the * forum. */ -export default class WelcomeHero extends Component { - oninit(vnode: Mithril.Vnode) { - super.oninit(vnode); +export default class WelcomeHero extends Hero { + className(): string { + return 'WelcomeHero'; } - view(vnode: Mithril.Vnode) { + view() { if (this.isHidden()) return null; - const slideUp = () => { - this.$().slideUp(this.hide.bind(this)); - }; - - return ( -
-
{this.viewItems().toArray()}
-
- ); + return super.view(); } - viewItems(): ItemList { + bodyItems(): ItemList { const items = new ItemList(); const slideUp = () => { From e74a677370248f474a19bf3bcab0a3062ddca3c9 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Wed, 21 May 2025 12:12:42 +0200 Subject: [PATCH 4/8] refactor(tags): use new reusable hero --- .../tags/js/src/forum/components/TagHero.js | 53 ------------------ .../tags/js/src/forum/components/TagHero.tsx | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+), 53 deletions(-) delete mode 100644 extensions/tags/js/src/forum/components/TagHero.js create mode 100644 extensions/tags/js/src/forum/components/TagHero.tsx diff --git a/extensions/tags/js/src/forum/components/TagHero.js b/extensions/tags/js/src/forum/components/TagHero.js deleted file mode 100644 index 5e77ad302d..0000000000 --- a/extensions/tags/js/src/forum/components/TagHero.js +++ /dev/null @@ -1,53 +0,0 @@ -import Component from 'flarum/common/Component'; -import textContrastClass from 'flarum/common/helpers/textContrastClass'; -import tagIcon from '../../common/helpers/tagIcon'; -import classList from 'flarum/common/utils/classList'; -import ItemList from 'flarum/common/utils/ItemList'; -import Mithril from 'mithril'; - -export default class TagHero extends Component { - view() { - const tag = this.attrs.model; - const color = tag.color(); - - return ( -
-
{this.viewItems().toArray()}
-
- ); - } - - /** - * @returns {ItemList} - */ - viewItems() { - const items = new ItemList(); - - items.add('content',
{this.contentItems().toArray()}
, 80); - - return items; - } - - /** - * @returns {ItemList} - */ - contentItems() { - const items = new ItemList(); - const tag = this.attrs.model; - - items.add( - 'tag-title', -

- {tag.icon() && tagIcon(tag, {}, { useColor: false })} {tag.name()} -

, - 100 - ); - - items.add('tag-subtitle',
{tag.description()}
, 90); - - return items; - } -} diff --git a/extensions/tags/js/src/forum/components/TagHero.tsx b/extensions/tags/js/src/forum/components/TagHero.tsx new file mode 100644 index 0000000000..04ebf767a3 --- /dev/null +++ b/extensions/tags/js/src/forum/components/TagHero.tsx @@ -0,0 +1,56 @@ +import Hero, { IHeroAttrs } from 'flarum/forum/components/Hero'; +import textContrastClass from 'flarum/common/helpers/textContrastClass'; +import tagIcon from '../../common/helpers/tagIcon'; +import classList from 'flarum/common/utils/classList'; +import ItemList from 'flarum/common/utils/ItemList'; + +import type Tag from '../../common/models/Tag'; +import type Mithril from 'mithril'; + +export interface ITagHeroAttrs extends IHeroAttrs { + model: Tag; +} + +export default class TagHero extends Hero { + className(): string { + const tag = this.attrs.model; + const color = tag.color(); + + return classList('TagHero', { + 'TagHero--colored': color, + [textContrastClass(color)]: color, + }); + } + + style(): Record | undefined { + const tag = this.attrs.model; + const color = tag.color(); + + return color ? { '--hero-bg': color } : undefined; + } + + bodyItems(): ItemList { + const items = new ItemList(); + + items.add('content',
{this.contentItems().toArray()}
, 80); + + return items; + } + + contentItems(): ItemList { + const items = new ItemList(); + const tag = this.attrs.model; + + items.add( + 'tag-title', +

+ {tag.icon() && tagIcon(tag, {}, { useColor: false })} {tag.name()} +

, + 100 + ); + + items.add('tag-subtitle',
{tag.description()}
, 90); + + return items; + } +} From 562a85787be1674c5eb4a246cc2aa21ecc74e876 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Wed, 21 May 2025 12:25:40 +0200 Subject: [PATCH 5/8] refactor(messages): use new reusable hero --- .../js/src/forum/components/MessagesPage.tsx | 15 +------- .../src/forum/components/MessagesPageHero.tsx | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 extensions/messages/js/src/forum/components/MessagesPageHero.tsx diff --git a/extensions/messages/js/src/forum/components/MessagesPage.tsx b/extensions/messages/js/src/forum/components/MessagesPage.tsx index 8c3ce07232..4abfd0e6ff 100644 --- a/extensions/messages/js/src/forum/components/MessagesPage.tsx +++ b/extensions/messages/js/src/forum/components/MessagesPage.tsx @@ -2,13 +2,13 @@ import app from 'flarum/forum/app'; import Page, { IPageAttrs } from 'flarum/common/components/Page'; import PageStructure from 'flarum/forum/components/PageStructure'; import Mithril from 'mithril'; -import Icon from 'flarum/common/components/Icon'; import DialogList from './DialogList'; import Dialog from '../../common/models/Dialog'; import LoadingIndicator from 'flarum/common/components/LoadingIndicator'; import Stream from 'flarum/common/utils/Stream'; import InfoTile from 'flarum/common/components/InfoTile'; import MessagesSidebar from './MessagesSidebar'; +import MessagesPageHero from './MessagesPageHero'; import DialogSection from './DialogSection'; import listItems from 'flarum/common/helpers/listItems'; import ItemList from 'flarum/common/utils/ItemList'; @@ -110,18 +110,7 @@ export default class MessagesPage -
-
-

- {app.translator.trans('flarum-messages.forum.messages_page.hero.title')} -

-
{app.translator.trans('flarum-messages.forum.messages_page.hero.subtitle')}
-
-
- - ); + return ; } contentItems() { diff --git a/extensions/messages/js/src/forum/components/MessagesPageHero.tsx b/extensions/messages/js/src/forum/components/MessagesPageHero.tsx new file mode 100644 index 0000000000..a8d7d2062e --- /dev/null +++ b/extensions/messages/js/src/forum/components/MessagesPageHero.tsx @@ -0,0 +1,38 @@ +import app from 'flarum/forum/app'; +import Hero, { IHeroAttrs } from 'flarum/forum/components/Hero'; +import Icon from 'flarum/common/components/Icon'; +import ItemList from 'flarum/common/utils/ItemList'; + +import type Mithril from 'mithril'; + +export interface IMessagesPageHeroAttrs extends IHeroAttrs {} + +export default class MessagesPageHero extends Hero { + className(): string { + return 'MessagesPageHero'; + } + + bodyItems(): ItemList { + const items = new ItemList(); + + items.add('content',
{this.contentItems().toArray()}
, 80); + + return items; + } + + contentItems(): ItemList { + const items = new ItemList(); + + items.add( + 'title', +

+ {app.translator.trans('flarum-messages.forum.messages_page.hero.title')} +

, + 100 + ); + + items.add('subtitle',
{app.translator.trans('flarum-messages.forum.messages_page.hero.subtitle')}
, 90); + + return items; + } +} From e6830d6291a633ab1b82f9656c941b8a515e7c14 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Wed, 21 May 2025 14:42:02 +0200 Subject: [PATCH 6/8] chore: make style method public --- framework/core/js/src/forum/components/Hero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/core/js/src/forum/components/Hero.tsx b/framework/core/js/src/forum/components/Hero.tsx index 233d094286..b29c9db256 100644 --- a/framework/core/js/src/forum/components/Hero.tsx +++ b/framework/core/js/src/forum/components/Hero.tsx @@ -43,7 +43,7 @@ export default abstract class Hero * }; * ``` */ - protected style(): Record | undefined { + style(): Record | undefined { return undefined; } From 920245e7b835bfcd57cee56676ecf566cfc77f0d Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Wed, 21 May 2025 15:27:55 +0200 Subject: [PATCH 7/8] chore: reorder, comments --- .../core/js/src/forum/components/Hero.tsx | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/framework/core/js/src/forum/components/Hero.tsx b/framework/core/js/src/forum/components/Hero.tsx index b29c9db256..5338621ff2 100644 --- a/framework/core/js/src/forum/components/Hero.tsx +++ b/framework/core/js/src/forum/components/Hero.tsx @@ -8,32 +8,37 @@ import type Mithril from 'mithril'; export interface IHeroAttrs {} export default abstract class Hero extends Component { - view(): Mithril.Vnode | null { - return ( -
- {this.viewItems().toArray()} -
- ); - } - - viewItems(): ItemList { - const items = new ItemList(); - - items.add('container',
{this.bodyItems().toArray()}
, 100); - - return items; - } - /** + * Defines the primary CSS class name for the hero component's root element. + * Subclasses MUST implement this method to provide a specific class name. + * * @example * ```ts * className(): string { - * return 'WelcomeHero'; - * } + * return 'FoobarHero'; + * } + * ``` */ abstract className(): string; /** + * Defines the child elements that will be rendered within the main container of the hero. + * Subclasses MUST implement this method to define the specific content of the hero. + * + * @example + * ```tsx + * bodyItems(): ItemList { + * const items = new ItemList(); + * items.add('title',

Welcome!

); + * return items; + * } + * ``` + */ + abstract bodyItems(): ItemList; + + /** + * Defines inline CSS styles for the hero component's root element. + * Subclasses can override this method to provide custom styles. * * @example * ```ts @@ -41,11 +46,26 @@ export default abstract class Hero * return { * backgroundColor: '#e7672e', * }; + * } * ``` */ style(): Record | undefined { return undefined; } - abstract bodyItems(): ItemList; + view(): Mithril.Vnode | null { + return ( +
+ {this.viewItems().toArray()} +
+ ); + } + + viewItems(): ItemList { + const items = new ItemList(); + + items.add('container',
{this.bodyItems().toArray()}
, 100); + + return items; + } } From 2e9cd78e8d425733561970974644863fdd309fe6 Mon Sep 17 00:00:00 2001 From: Davide Iadeluca Date: Sun, 9 Nov 2025 00:18:40 +0100 Subject: [PATCH 8/8] chore: remove unused import, extend ComponentAttrs --- framework/core/js/src/forum/components/Hero.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/core/js/src/forum/components/Hero.tsx b/framework/core/js/src/forum/components/Hero.tsx index 5338621ff2..3b085290b4 100644 --- a/framework/core/js/src/forum/components/Hero.tsx +++ b/framework/core/js/src/forum/components/Hero.tsx @@ -1,11 +1,10 @@ -import app from '../app'; -import Component from '../../common/Component'; +import Component, { ComponentAttrs } from '../../common/Component'; import classList from '../../common/utils/classList'; import ItemList from '../../common/utils/ItemList'; import type Mithril from 'mithril'; -export interface IHeroAttrs {} +export interface IHeroAttrs extends ComponentAttrs {} export default abstract class Hero extends Component { /**