Skip to content

Commit

Permalink
feat(list): list component
Browse files Browse the repository at this point in the history
  • Loading branch information
webyom committed Sep 14, 2020
1 parent 1c0c26f commit ba4e049
Show file tree
Hide file tree
Showing 7 changed files with 334 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/_site/scripts/root-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { FormRouteComponent } from '../../form/demo';
import { ImgRouteComponent } from '../../img/demo';
import { LayoutRouteComponent } from '../../col/demo';
import { LazyloadRouteComponent } from '../../lazyload/demo';
import { ListRouteComponent } from '../../list/demo';
import { LoadingRouteComponent } from '../../loading/demo';
import { NotifyRouteComponent } from '../../notify/demo';
import { NumberKeyboardRouteComponent } from '../../number-keyboard/demo';
Expand Down Expand Up @@ -59,6 +60,7 @@ export class RootComponent extends preact.Component {
<ImgRouteComponent path="/img/" />
<LayoutRouteComponent path="/layout/" />
<LazyloadRouteComponent path="/lazyload/" />
<ListRouteComponent path="/list/" />
<LoadingRouteComponent path="/loading/" />
<NotifyRouteComponent path="/notify/" />
<NumberKeyboardRouteComponent path="/number-keyboard" />
Expand Down
3 changes: 3 additions & 0 deletions src/_site/scripts/routes/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ export class HomeRouteComponent extends preact.Component {
<Link href="/lazyload/">
Lazyload <Arrow />
</Link>
<Link href="/list/">
List <Arrow />
</Link>
<Link href="/skeleton/">
Skeleton <Arrow />
</Link>
Expand Down
10 changes: 10 additions & 0 deletions src/list/demo/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@import '../../styles/var.scss';

.demo-list {
padding: 0 0 $padding-md 0;
background-color: $gray-1;
min-height: calc(100vh - 56px);
.pant-cell {
text-align: center;
}
}
121 changes: 121 additions & 0 deletions src/list/demo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as preact from 'preact';
import { Cell } from '../../cell';
import { Tabs, Tab } from '../../tab';
import { PullRefresh } from '../../pull-refresh';
import { List, ListLoadResult } from '../../list';
import { createBEM } from '../../utils/bem';
import { NavBar } from '../../_site/scripts/components/nav-bar';
import './index.scss';

const bem = createBEM('demo-list');

export class ListRouteComponent extends preact.Component<any, { list1: number[]; list2: number[]; list3: number[] }> {
private list3Ref = preact.createRef<List>();

constructor(props: any) {
super(props);
this.state = {
list1: [],
list2: [],
list3: [],
};
}

private genList(start: number, end: number): number[] {
const res: number[] = [];
for (let i = start; i < end; i++) {
res.push(i + 1);
}
return res;
}

render(): preact.JSX.Element {
return (
<preact.Fragment>
<NavBar title="List" type="list" />
<div className={bem()}>
<Tabs scrollable>
<Tab title="Basic Usage" lazyRender>
<List
finishedText="Finished"
onLoad={(): Promise<ListLoadResult> => {
return new Promise(resolve => {
setTimeout(() => {
const list1 = this.state.list1;
this.setState({ list1: list1.concat(this.genList(list1.length, list1.length + 10)) }, () => {
resolve({
finished: this.state.list1.length >= 40,
});
});
}, 2000);
});
}}
>
{this.state.list1.map(i => (
<Cell title={i}></Cell>
))}
</List>
</Tab>
<Tab title="Error Info" lazyRender>
<List
errorText="Request failed. Click to reload"
onLoad={(): Promise<ListLoadResult> => {
return new Promise(resolve => {
setTimeout(() => {
const list2 = this.state.list2;
this.setState({ list2: list2.concat(this.genList(list2.length, list2.length + 10)) }, () => {
resolve({
error: this.state.list2.length === 10,
finished: this.state.list2.length >= 40,
});
});
}, 2000);
});
}}
>
{this.state.list2.map(i => (
<Cell title={i}></Cell>
))}
</List>
</Tab>
<Tab title="PullRefresh" lazyRender>
<PullRefresh
onRefresh={(): Promise<void> => {
return new Promise(resolve => {
setTimeout(() => {
this.setState({ list3: this.genList(0, 10) }, () => {
this.list3Ref.current.check();
});
resolve();
}, 2000);
});
}}
>
<List
ref={this.list3Ref}
finishedText="Finished"
onLoad={(): Promise<ListLoadResult> => {
return new Promise(resolve => {
setTimeout(() => {
const list3 = this.state.list3;
this.setState({ list3: list3.concat(this.genList(list3.length, list3.length + 10)) }, () => {
resolve({
finished: this.state.list3.length >= 40,
});
});
}, 2000);
});
}}
>
{this.state.list3.map(i => (
<Cell title={i}></Cell>
))}
</List>
</PullRefresh>
</Tab>
</Tabs>
</div>
</preact.Fragment>
);
}
}
17 changes: 17 additions & 0 deletions src/list/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@import '../styles/var.scss';

.pant-list {
&__loading,
&__finished-text,
&__error-text {
color: $list-text-color;
font-size: $list-text-font-size;
line-height: $list-text-line-height;
text-align: center;
}

&__placeholder {
height: 0;
pointer-events: none;
}
}
175 changes: 175 additions & 0 deletions src/list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import * as preact from 'preact';
import { on, off } from '../utils/event';
import { isHidden } from '../utils/dom';
import { createBEM } from '../utils/bem';
import { getScroller, ScrollElement } from '../utils/scroll';
import { Loading } from '../loading';
import { i18n } from '../locale';
import './index.scss';

type ListDirection = 'up' | 'down';

export type ListLoadResult = {
finished?: boolean;
error?: boolean;
errorText?: string;
errorNode?: preact.VNode;
};

export type ListProps = {
direction?: ListDirection;
offset?: number | string;
loadingText?: string;
finishedText?: string;
errorText?: string;
loadingNode?: preact.VNode;
finishedNode?: preact.VNode;
errorNode?: preact.VNode;
onLoad(error?: boolean): Promise<ListLoadResult | void>;
};

type ListState = {
loading: boolean;
loadResult: ListLoadResult;
};

const bem = createBEM('pant-list');

export class List extends preact.Component<ListProps, ListState> {
private containerRef = preact.createRef<HTMLDivElement>();
private placeholderRef = preact.createRef<HTMLDivElement>();
private scroller: ScrollElement;

constructor(props: ListProps) {
super(props);
this.state = {
loading: false,
loadResult: {},
};
this.check = this.check.bind(this);
this.onClickError = this.onClickError.bind(this);
}

componentDidMount(): void {
this.scroller = getScroller(this.containerRef.current);
on(this.scroller, 'scroll', this.check);
this.check();
}

componentWillUnmount(): void {
off(this.scroller, 'scroll', this.check);
this.scroller = null;
}

check(): void {
const { loading, loadResult } = this.state;
if (loading || loadResult.finished || loadResult.error) {
return;
}

const scroller = this.scroller as any;
const { offset, direction } = this.props;
let scrollerRect;

if (scroller.getBoundingClientRect) {
scrollerRect = scroller.getBoundingClientRect();
} else {
scrollerRect = {
top: 0,
bottom: scroller.innerHeight,
};
}

const scrollerHeight = scrollerRect.bottom - scrollerRect.top;

if (!scrollerHeight || isHidden(this.containerRef.current)) {
return;
}

let isReachEdge = false;
const placeholderRect = this.placeholderRef.current.getBoundingClientRect();

if (direction === 'up') {
isReachEdge = scrollerRect.top - placeholderRect.top <= offset;
} else {
isReachEdge = placeholderRect.bottom - scrollerRect.bottom <= offset;
}

if (isReachEdge) {
this.load();
}
}

private load(error?: boolean): void {
this.setState({ loading: true, loadResult: {} }, () => {
this.props.onLoad(error).then(res => {
this.setState({ loading: false, loadResult: res || {} }, () => {
this.check();
});
});
});
}

private genLoading(): preact.JSX.Element {
const { loading, loadResult } = this.state;
const { loadingText, loadingNode } = this.props;
if (loading && !loadResult.finished) {
return (
<div class={bem('loading')} key="loading">
{loadingNode || <Loading size="16">{loadingText || i18n().loading}</Loading>}
</div>
);
}
}

private genFinishedText(): preact.JSX.Element {
const { loadResult } = this.state;
const { finishedText, finishedNode } = this.props;
if (loadResult.finished) {
const text = finishedNode || finishedText;

if (text) {
return <div class={bem('finished-text')}>{text}</div>;
}
}
}

private genErrorText(): preact.JSX.Element {
const { loadResult } = this.state;
const { errorText, errorNode } = this.props;
if (loadResult.error) {
const text = loadResult.errorNode || loadResult.errorText || errorNode || errorText;

if (text) {
return (
<div onClick={this.onClickError} class={bem('error-text')}>
{text}
</div>
);
}
}
}

private onClickError(): void {
this.load(true);
}

render(): preact.JSX.Element {
const { direction, children } = this.props;
const Placeholder = <div ref={this.placeholderRef} class={bem('placeholder')} />;
return (
<div ref={this.containerRef} class={bem()} role="feed" aria-busy={this.state.loading}>
{direction === 'down' ? children : Placeholder}
{this.genLoading()}
{this.genFinishedText()}
{this.genErrorText()}
{direction === 'up' ? children : Placeholder}
</div>
);
}
}

List.defaultProps = {
direction: 'down',
offset: 300,
};
6 changes: 6 additions & 0 deletions src/styles/var.scss
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,12 @@ $info-dot-color: $red;
$info-dot-size: 8px;
$info-font-family: PingFang SC, Helvetica Neue, Arial, sans-serif;

// List
$list-icon-margin-right: 5px;
$list-text-color: $gray-6;
$list-text-font-size: $font-size-md;
$list-text-line-height: 50px;

// Loading
$loading-text-color: $gray-6;
$loading-text-font-size: $font-size-md;
Expand Down

0 comments on commit ba4e049

Please sign in to comment.