Add packages/client/src/crumbs

This commit is contained in:
Jerko Steiner 2019-03-25 18:27:32 +08:00
parent 9b5809a1d5
commit d3f294a57c
11 changed files with 156 additions and 22 deletions

View File

@ -1 +0,0 @@
export * from './Breadcrumbs'

View File

@ -2,7 +2,7 @@ import React from 'react'
import {Breadcrumb, BreadcrumbItem} from 'bloomer' import {Breadcrumb, BreadcrumbItem} from 'bloomer'
import {Link} from 'react-router-dom' import {Link} from 'react-router-dom'
export interface IBreadcrumbsProps { export interface ICrumbProps {
links: Array<{ links: Array<{
name: string name: string
to: string to: string
@ -10,17 +10,22 @@ export interface IBreadcrumbsProps {
current: string current: string
} }
export class Breadcrumbs extends React.PureComponent<IBreadcrumbsProps> { export class Crumb extends React.PureComponent<ICrumbProps> {
render() { render() {
return ( return (
<Breadcrumb> <Breadcrumb>
<ul> <ul>
<BreadcrumbItem>
<Link to='/'>Home</Link>
</BreadcrumbItem>
{this.props.links.map((link, i) => ( {this.props.links.map((link, i) => (
<BreadcrumbItem key={i}> <BreadcrumbItem key={i}>
<Link to={link.to}>{link.name}</Link> <Link to={link.to}>{link.name}</Link>
</BreadcrumbItem> </BreadcrumbItem>
))} ))}
<BreadcrumbItem>{this.props.current}</BreadcrumbItem> <BreadcrumbItem>
<a>{this.props.current}</a>
</BreadcrumbItem>
</ul> </ul>
</Breadcrumb> </Breadcrumb>
) )

View File

@ -1,17 +1,17 @@
import React from 'react' import React from 'react'
import {Breadcrumbs} from './Breadcrumbs' import {Crumb} from './Crumb'
import {TestUtils} from '../test-utils' import {TestUtils} from '../test-utils'
import {MemoryRouter} from 'react-router-dom' import {MemoryRouter} from 'react-router-dom'
const t = new TestUtils() const t = new TestUtils()
describe('Breadcrumbs', () => { describe('Crumb', () => {
describe('render', () => { describe('render', () => {
it('renders', () => { it('renders', () => {
const {node} = t.render( const {node} = t.render(
<MemoryRouter> <MemoryRouter>
<Breadcrumbs <Crumb
links={[{ links={[{
name: 'one', name: 'one',
to: '/one', to: '/one',

View File

@ -0,0 +1,25 @@
import {GetAction, IResolvedAction} from '../actions'
export interface ICrumbLink {
name: string
to: string
}
export interface ICrumbs {
links: ICrumbLink[]
current: string
}
export type CrumbsActionType =
IResolvedAction<ICrumbs, 'BREADCRUMBS_SET'>
type Action<T extends string> = GetAction<CrumbsActionType, T>
export class CrumbsActions {
setCrumbs(breadcrumbs: ICrumbs): Action<'BREADCRUMBS_SET'> {
return {
payload: breadcrumbs,
type: 'BREADCRUMBS_SET',
}
}
}

View File

@ -0,0 +1,52 @@
import * as Feature from './'
import React from 'react'
import {MemoryRouter} from 'react-router-dom'
import {TestUtils} from '../test-utils'
const t = new TestUtils()
describe('CrumbsConnector', () => {
const createTestCase = () => t.withProvider({
reducers: {Crumbs: Feature.Crumbs},
select: s => s.Crumbs,
})
.withState({
Crumbs: {
links: [{
name: 'One',
to: '/one',
}, {
name: 'Two',
to: '/two',
}],
current: 'Three',
},
})
.withComponent(select => new Feature.CrumbsConnector().connect(select))
.withJSX(Component => <MemoryRouter><Component /></MemoryRouter>)
describe('render', () => {
it('renders', () => {
const {node} = createTestCase().render({})
expect(node.innerHTML).toMatch(/href="\/one"/)
expect(node.innerHTML).toMatch(/href="\/two"/)
expect(node.innerHTML).toMatch(/Three/)
})
})
describe('BREADCRUMBS_SET', () => {
it('updates breadcrumbs', () => {
const {render, store} = createTestCase()
const actions = new Feature.CrumbsActions()
store.dispatch(actions.setCrumbs({
links: [],
current: 'Crumbtest',
}))
const {node} = render({})
expect(node.innerHTML).toMatch(/Crumbtest/)
})
})
})

View File

@ -0,0 +1,17 @@
import {Crumb} from './Crumb'
import {Connector, IStateSelector} from '../redux'
import {ICrumbsState} from './CrumbsReducer'
import {CrumbsActions} from './CrumbsActions'
export class CrumbsConnector extends Connector<ICrumbsState> {
protected readonly breadcrumbsActions = new CrumbsActions()
connect<State>(getLocalState: IStateSelector<State, ICrumbsState>) {
return this.wrap(
getLocalState,
state => state,
dispatch => ({}),
Crumb,
)
}
}

View File

@ -0,0 +1,22 @@
import {ICrumbs, CrumbsActionType} from './CrumbsActions'
export interface ICrumbsState extends ICrumbs {
}
const defaultState: ICrumbsState = {
links: [],
current: 'Home',
}
export function Crumbs(state = defaultState, action: CrumbsActionType)
: ICrumbsState {
switch (action.type) {
case 'BREADCRUMBS_SET':
return {
links: action.payload.links,
current: action.payload.current,
}
default:
return state
}
}

View File

@ -0,0 +1,4 @@
export * from './Crumb'
export * from './CrumbsActions'
export * from './CrumbsConnector'
export * from './CrumbsReducer'

View File

@ -1,5 +1,6 @@
export * from './actions' export * from './actions'
export * from './components' export * from './components'
export * from './crumbs'
export * from './csrf' export * from './csrf'
export * from './http' export * from './http'
export * from './login' export * from './login'

View File

@ -1,11 +1,11 @@
import React from 'react' import React from 'react'
import {ITeam, IUserInTeam, ReadonlyRecord} from '@rondo/common' import {ITeam, IUserInTeam, ReadonlyRecord} from '@rondo/common'
import {Panel, PanelBlock, PanelHeading} from 'bloomer'
import {Route, Switch} from 'react-router-dom' import {Route, Switch} from 'react-router-dom'
import {TeamActions} from './TeamActions' import {TeamActions} from './TeamActions'
import {TeamEditor} from './TeamEditor' import {TeamEditor} from './TeamEditor'
import {TeamList} from './TeamList' import {TeamList} from './TeamList'
import {TeamUserList} from './TeamUserList' import {TeamUserList} from './TeamUserList'
import {Panel, PanelBlock, PanelHeading} from 'bloomer'
export interface ITeamManagerProps { export interface ITeamManagerProps {
createTeam: TeamActions['createTeam'] createTeam: TeamActions['createTeam']
@ -36,20 +36,22 @@ export class TeamManager extends React.PureComponent<ITeamManagerProps> {
<div className='team-manager'> <div className='team-manager'>
<Switch> <Switch>
<Route exact path='/teams' render={() => <Route exact path='/teams' render={() =>
<TeamList <>
teamsById={teamsById} <TeamList
teamIds={this.props.teamIds} teamsById={teamsById}
onAddTeam={this.props.createTeam} teamIds={this.props.teamIds}
onRemoveTeam={this.props.removeTeam} onAddTeam={this.props.createTeam}
onUpdateTeam={this.props.updateTeam} onRemoveTeam={this.props.removeTeam}
/> onUpdateTeam={this.props.updateTeam}
/>
</>
}/> }/>
<Route exact path='/teams/:teamId/users' render={({match}) => { <Route exact path='/teams/:teamId/users' render={({match}) => {
const {teamId: teamIdParam} = match.params const {teamId: teamIdParam} = match.params
const teamId = teamIdParam ? Number(teamIdParam) : undefined const teamId = teamIdParam ? Number(teamIdParam) : undefined
const team = teamId ? teamsById[teamId] : undefined const team = teamId ? teamsById[teamId] : undefined
return ( return (
<React.Fragment> <>
<Panel> <Panel>
<PanelHeading>Edit Team: {team && team.name}</PanelHeading> <PanelHeading>Edit Team: {team && team.name}</PanelHeading>
<PanelBlock isDisplay='block'> <PanelBlock isDisplay='block'>
@ -70,7 +72,7 @@ export class TeamManager extends React.PureComponent<ITeamManagerProps> {
userKeysByTeamId={this.props.userKeysByTeamId} userKeysByTeamId={this.props.userKeysByTeamId}
usersByKey={this.props.usersByKey} usersByKey={this.props.usersByKey}
/>} />}
</React.Fragment> </>
) )
}}/> }}/>
</Switch> </Switch>

View File

@ -15,7 +15,6 @@ import {
interface IRenderParams<State, LocalState> { interface IRenderParams<State, LocalState> {
reducers: ReducersMapObject<State, any> reducers: ReducersMapObject<State, any>
state?: DeepPartial<State>
select: IStateSelector<State, LocalState> select: IStateSelector<State, LocalState>
// getComponent: ( // getComponent: (
// select: IStateSelector<State, LocalState>) => React.ComponentType<Props>, // select: IStateSelector<State, LocalState>) => React.ComponentType<Props>,
@ -51,11 +50,19 @@ export class TestUtils {
withProvider<State, LocalState, A extends Action<any> = AnyAction>( withProvider<State, LocalState, A extends Action<any> = AnyAction>(
params: IRenderParams<State, LocalState>, params: IRenderParams<State, LocalState>,
) { ) {
const {reducers, state, select} = params const {reducers, select} = params
const store = this.createStore({ let store = this.createStore({
reducer: this.combineReducers(reducers), reducer: this.combineReducers(reducers),
})(state) })()
const withState = (state: DeepPartial<State>) => {
store = this.createStore({
reducer: this.combineReducers(reducers),
})(state)
return {withComponent}
}
const withComponent = <Props extends {}>( const withComponent = <Props extends {}>(
getComponent: (select: IStateSelector<State, LocalState>) => getComponent: (select: IStateSelector<State, LocalState>) =>
@ -96,6 +103,6 @@ export class TestUtils {
return self return self
} }
return {withComponent} return {withState, withComponent}
} }
} }