-
-
Notifications
You must be signed in to change notification settings - Fork 829
/
HeaderList.tsx
87 lines (73 loc) · 3.07 KB
/
HeaderList.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import type { ComponentAttrs } from '../../common/Component';
import Component from '../../common/Component';
import type ItemList from '../../common/utils/ItemList';
import type Mithril from 'mithril';
import classList from '../../common/utils/classList';
import LoadingIndicator from '../../common/components/LoadingIndicator';
export interface IHeaderListAttrs extends ComponentAttrs {
title: string;
controls?: ItemList<Mithril.Children>;
hasItems: boolean;
loading?: boolean;
emptyText: string;
loadMore?: () => void;
}
export default class HeaderList<CustomAttrs extends IHeaderListAttrs = IHeaderListAttrs> extends Component<CustomAttrs> {
$content: JQuery<any> | null = null;
$scrollParent: JQuery<any> | null = null;
boundScrollHandler: (() => void) | null = null;
view(vnode: Mithril.Vnode<CustomAttrs, this>) {
const { title, controls, hasItems, loading = false, emptyText, className, ...attrs } = vnode.attrs;
return (
<div className={classList('HeaderList', className)} {...attrs}>
<div className="HeaderList-header">
<h4 className="App-titleControl App-titleControl--text">{title}</h4>
<div className="App-primaryControl">{controls?.toArray()}</div>
</div>
<div className="HeaderList-content">
{loading ? (
<LoadingIndicator className="LoadingIndicator--block" />
) : hasItems ? (
vnode.children
) : (
<div className="HeaderList-empty">{emptyText}</div>
)}
</div>
</div>
);
}
oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
super.oncreate(vnode);
if (this.attrs.loadMore) {
this.$content = this.$('.HeaderList-content');
// If we are on the notifications page, the window will be scrolling and not the $notifications element.
this.$scrollParent = this.inPanel() ? this.$content : $(window);
this.boundScrollHandler = this.scrollHandler.bind(this);
this.$scrollParent.on('scroll', this.boundScrollHandler);
}
}
onremove(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
super.onremove(vnode);
if (this.attrs.loadMore) {
this.$scrollParent!.off('scroll', this.boundScrollHandler!);
}
}
scrollHandler() {
// Whole-page scroll events are listened to on `window`, but we need to get the actual
// scrollHeight, scrollTop, and clientHeight from the document element.
const scrollParent = this.inPanel() ? this.$scrollParent![0] : document.documentElement;
// On very short screens, the scrollHeight + scrollTop might not reach the clientHeight
// by a fraction of a pixel, so we compensate for that.
const atBottom = Math.abs(scrollParent.scrollHeight - scrollParent.scrollTop - scrollParent.clientHeight) <= 1;
if (atBottom) {
this.attrs.loadMore?.();
}
}
/**
* If the NotificationList component isn't in a panel (e.g. on NotificationPage when mobile),
* we need to listen to scroll events on the window, and get scroll state from the body.
*/
inPanel() {
return this.$content!.css('overflow') === 'auto';
}
}