Skip to content

feat: create reusable Hero component #4215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: 2.x
Choose a base branch
from
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
15 changes: 2 additions & 13 deletions extensions/messages/js/src/forum/components/MessagesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -110,18 +110,7 @@ export default class MessagesPage<CustomAttrs extends IMessagesPageAttrs = IMess
}

hero(): Mithril.Children {
return (
<header className="Hero MessagesPageHero">
<div className="container">
<div className="containerNarrow">
<h1 className="Hero-title">
<Icon name="fas fa-envelope" /> {app.translator.trans('flarum-messages.forum.messages_page.hero.title')}
</h1>
<div className="Hero-subtitle">{app.translator.trans('flarum-messages.forum.messages_page.hero.subtitle')}</div>
</div>
</div>
</header>
);
return <MessagesPageHero />;
}

contentItems() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CustomAttrs extends IMessagesPageHeroAttrs = IMessagesPageHeroAttrs> extends Hero<CustomAttrs> {
className(): string {
return 'MessagesPageHero';
}

bodyItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

items.add('content', <div className="containerNarrow">{this.contentItems().toArray()}</div>, 80);

return items;
}

contentItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

items.add(
'title',
<h1 className="Hero-title">
<Icon name="fas fa-envelope" /> {app.translator.trans('flarum-messages.forum.messages_page.hero.title')}
</h1>,
100
);

items.add('subtitle', <div className="Hero-subtitle">{app.translator.trans('flarum-messages.forum.messages_page.hero.subtitle')}</div>, 90);

return items;
}
}
53 changes: 0 additions & 53 deletions extensions/tags/js/src/forum/components/TagHero.js

This file was deleted.

56 changes: 56 additions & 0 deletions extensions/tags/js/src/forum/components/TagHero.tsx
Original file line number Diff line number Diff line change
@@ -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<CustomAttrs extends ITagHeroAttrs = ITagHeroAttrs> extends Hero<CustomAttrs> {
className(): string {
const tag = this.attrs.model;
const color = tag.color();

return classList('TagHero', {
'TagHero--colored': color,
[textContrastClass(color)]: color,
});
}

style(): Record<string, string> | undefined {
const tag = this.attrs.model;
const color = tag.color();

return color ? { '--hero-bg': color } : undefined;
}

bodyItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

items.add('content', <div className="containerNarrow">{this.contentItems().toArray()}</div>, 80);

return items;
}

contentItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();
const tag = this.attrs.model;

items.add(
'tag-title',
<h1 className="Hero-title">
{tag.icon() && tagIcon(tag, {}, { useColor: false })} {tag.name()}
</h1>,
100
);

items.add('tag-subtitle', <div className="Hero-subtitle">{tag.description()}</div>, 90);

return items;
}
}
41 changes: 0 additions & 41 deletions framework/core/js/src/forum/components/DiscussionHero.js

This file was deleted.

39 changes: 39 additions & 0 deletions framework/core/js/src/forum/components/DiscussionHero.tsx
Original file line number Diff line number Diff line change
@@ -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<CustomAttrs extends IDiscussionHeroAttrs = IDiscussionHeroAttrs> extends Hero<CustomAttrs> {
className(): string {
return 'DiscussionHero';
}

bodyItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

items.add('items', <ul className="DiscussionHero-items">{listItems(this.items().toArray())}</ul>, 100);

return items;
}

items(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

const discussion = this.attrs.discussion;
const badges = discussion.badges().toArray();

if (badges.length) {
items.add('badges', <ul className="DiscussionHero-badges badges">{listItems(badges)}</ul>, 10);
}

items.add('title', <h1 className="DiscussionHero-title">{discussion.title()}</h1>);

return items;
}
}
71 changes: 71 additions & 0 deletions framework/core/js/src/forum/components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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<CustomAttrs extends IHeroAttrs = IHeroAttrs> extends Component<CustomAttrs> {
/**
* 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 '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<Mithril.Children> {
* const items = new ItemList<Mithril.Children>();
* items.add('title', <h1>Welcome!</h1>);
* return items;
* }
* ```
*/
abstract bodyItems(): ItemList<Mithril.Children>;

/**
* Defines inline CSS styles for the hero component's root element.
* Subclasses can override this method to provide custom styles.
*
* @example
* ```ts
* style(): Record<string, string> {
* return {
* backgroundColor: '#e7672e',
* };
* }
* ```
*/
style(): Record<string, string> | undefined {
return undefined;
}

view(): Mithril.Vnode | null {
return (
<header className={classList('Hero', this.className())} style={this.style() ?? undefined}>
{this.viewItems().toArray()}
</header>
);
}

viewItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

items.add('container', <div className="container">{this.bodyItems().toArray()}</div>, 100);

return items;
}
}
24 changes: 8 additions & 16 deletions framework/core/js/src/forum/components/WelcomeHero.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
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';

/**
* The `WelcomeHero` component displays a hero that welcomes the user to the
* forum.
*/
export default class WelcomeHero extends Component<IWelcomeHeroAttrs> {
oninit(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>) {
super.oninit(vnode);
export default class WelcomeHero<CustomAttrs extends IWelcomeHeroAttrs = IWelcomeHeroAttrs> extends Hero<CustomAttrs> {
className(): string {
return 'WelcomeHero';
}

view(vnode: Mithril.Vnode<IWelcomeHeroAttrs, this>) {
view() {
if (this.isHidden()) return null;

const slideUp = () => {
this.$().slideUp(this.hide.bind(this));
};

return (
<header className="Hero WelcomeHero">
<div className="container">{this.viewItems().toArray()}</div>
</header>
);
return super.view();
}

viewItems(): ItemList<Mithril.Children> {
bodyItems(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

const slideUp = () => {
Expand Down
Loading