Skip to content

Commit

Permalink
feat(demos): add todomvc demo (#190)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrpelz authored and clebert committed Feb 27, 2019
1 parent f86b292 commit f48b9c9
Show file tree
Hide file tree
Showing 27 changed files with 2,312 additions and 151 deletions.
12 changes: 12 additions & 0 deletions packages/demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,17 @@ Demonstrates:
yarn watch:demo server-side-rendering
```

### [TodoMVC](src/todomvc)

Demonstrates:

- how to compose the [TodoMVC][todomvc] application using three Feature Apps and
a Feature Service

```sh
yarn watch:demo todomvc
```

---

Copyright (c) 2018-2019 SinnerSchrader Deutschland GmbH. Released under the
Expand All @@ -85,3 +96,4 @@ terms of the [MIT License][license].
[website]: https://feature-hub.io/
[website-badge]:
https://img.shields.io/badge/Website-feature--hub.io-%23500dc5.svg
[todomvc]: http://todomvc.com
11 changes: 10 additions & 1 deletion packages/demos/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,35 @@
"@feature-hub/react": "^1.1.0",
"@feature-hub/serialized-state-manager": "^1.1.0",
"@feature-hub/server-request": "^1.1.0",
"@types/copy-webpack-plugin": "^4.4.3",
"@types/express": "^4.16.0",
"@types/get-port": "^4.0.0",
"@types/history": "^4.7.2",
"@types/styled-components": "4.1.8",
"@types/webpack": "^4.4.20",
"@types/webpack-dev-middleware": "^2.0.2",
"@types/webpack-merge": "^4.1.3",
"copy-webpack-plugin": "^5.0.0",
"css-loader": "^2.0.0",
"express": "^4.16.4",
"get-port": "^4.0.0",
"history": "^4.7.2",
"lit-html": "^1.0.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.5.0",
"puppeteer": "^1.11.0",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-media": "^1.9.2",
"style-loader": "^0.23.1",
"styled-components": "^4.1.3",
"ts-loader": "^5.3.1",
"ts-node": "^8.0.0",
"tsconfig-paths": "^3.7.0",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"url-loader": "^1.1.2",
"webpack": "^4.27.0",
"webpack-dev-middleware": "^3.4.0"
"webpack-dev-middleware": "^3.4.0",
"webpack-merge": "^4.2.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {FeatureAppDefinition} from '@feature-hub/core';
import {ReactFeatureApp} from '@feature-hub/react';
import * as React from 'react';

export default {
const featureAppDefinition: FeatureAppDefinition<ReactFeatureApp> = {
id: 'test:hello-world-inner',

dependencies: {
Expand All @@ -16,4 +16,6 @@ export default {
create: () => ({
render: () => <Label>Hello, World!</Label>
})
} as FeatureAppDefinition<ReactFeatureApp>;
};

export default featureAppDefinition;
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {Card} from '@blueprintjs/core';
import {FeatureAppDefinition} from '@feature-hub/core';
import {FeatureAppContainer, ReactFeatureApp} from '@feature-hub/react';
import * as React from 'react';
import featureAppDefinition from './feature-app-inner';
import innerFeatureAppDefinition from './feature-app-inner';

const id = 'test:hello-world-outer';

export default {
const featureAppDefinition: FeatureAppDefinition<ReactFeatureApp> = {
id,

dependencies: {
Expand All @@ -20,10 +20,12 @@ export default {
render: () => (
<Card style={{margin: '20px'}}>
<FeatureAppContainer
featureAppDefinition={featureAppDefinition}
featureAppDefinition={innerFeatureAppDefinition}
idSpecifier={id}
/>
</Card>
)
})
} as FeatureAppDefinition<ReactFeatureApp>;
};

export default featureAppDefinition;
6 changes: 4 additions & 2 deletions packages/demos/src/integrator-dom/feature-app.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {FeatureAppDefinition} from '@feature-hub/core';
import {DomFeatureApp} from '@feature-hub/dom';

export default {
const featureAppDefinition: FeatureAppDefinition<DomFeatureApp> = {
id: 'test:hello-world',

create: () => ({
attachTo(element: HTMLElement): void {
element.innerHTML = 'Hello, World!';
}
})
} as FeatureAppDefinition<DomFeatureApp>;
};

export default featureAppDefinition;
6 changes: 4 additions & 2 deletions packages/demos/src/module-loader-amd/feature-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {FeatureAppDefinition} from '@feature-hub/core';
import {ReactFeatureApp} from '@feature-hub/react';
import * as React from 'react';

export default {
const featureAppDefinition: FeatureAppDefinition<ReactFeatureApp> = {
id: 'test:hello-world',

dependencies: {
Expand All @@ -19,4 +19,6 @@ export default {
</Card>
)
})
} as FeatureAppDefinition<ReactFeatureApp>;
};

export default featureAppDefinition;
5 changes: 4 additions & 1 deletion packages/demos/src/module-loader-commonjs/feature-app.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {Card, Label} from '@blueprintjs/core';
import {FeatureAppDefinition} from '@feature-hub/core';
import {ReactFeatureApp} from '@feature-hub/react';
import * as React from 'react';

export default {
const featureAppDefinition: FeatureAppDefinition<ReactFeatureApp> = {
id: 'test:hello-world',

create(): ReactFeatureApp {
Expand All @@ -15,3 +16,5 @@ export default {
};
}
};

export default featureAppDefinition;
34 changes: 34 additions & 0 deletions packages/demos/src/todomvc/footer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {FeatureAppDefinition} from '@feature-hub/core';
import {ReactFeatureApp} from '@feature-hub/react';
import * as React from 'react';
import {TodoManagerV1} from '../todo-manager';
import {TodoMvcFooter} from './todomvc-footer';

export interface FooterFeatureServices {
readonly 'test:todomvc-todo-manager': TodoManagerV1;
}

const featureAppDefinition: FeatureAppDefinition<
ReactFeatureApp,
undefined,
undefined,
FooterFeatureServices
> = {
id: 'test:todomvc-footer',

dependencies: {
featureServices: {
'test:todomvc-todo-manager': '^1.0.0'
}
},

create: ({featureServices}) => {
const todoManager = featureServices['test:todomvc-todo-manager'];

return {
render: () => <TodoMvcFooter todoManager={todoManager} />
};
}
};

export default featureAppDefinition;
42 changes: 42 additions & 0 deletions packages/demos/src/todomvc/footer/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;

&:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
}

.todoCount {
float: left;
text-align: left;

& strong {
font-weight: 300;
}
}

.clearCompleted,
html .clearCompleted:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}

.clearCompleted:hover {
text-decoration: underline;
}
59 changes: 59 additions & 0 deletions packages/demos/src/todomvc/footer/todomvc-footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as React from 'react';
import {Todo, TodoManagerV1} from '../todo-manager';
import styles from './styles.css';

export interface TodoMvcFooterProps {
readonly todoManager: TodoManagerV1;
}

export interface TodoMvcFooterState {
readonly todos: Todo[];
}

export class TodoMvcFooter extends React.Component<
TodoMvcFooterProps,
TodoMvcFooterState
> {
public readonly state = {todos: this.props.todoManager.getTodos()};

public componentDidMount(): void {
this.props.todoManager.subscribe(() => {
this.setState({todos: this.props.todoManager.getTodos()});
});
}

public render(): JSX.Element | null {
const {todos} = this.state;

if (todos.length === 0) {
return null;
}

const itemsLeft = todos.filter(todo => !todo.completed).length;
const hasCompletedTodos = todos.length - itemsLeft > 0;

return (
<footer className={styles.footer}>
<span className={styles.todoCount}>
<strong>{itemsLeft}</strong> {itemsLeft === 1 ? 'item' : 'items'} left
</span>
{hasCompletedTodos && (
<button
className={styles.clearCompleted}
onClick={this.handleClearCompletedClick}
>
Clear completed
</button>
)}
</footer>
);
}

private readonly handleClearCompletedClick = () => {
for (const todo of this.state.todos) {
if (todo && todo.completed) {
this.props.todoManager.remove(todo.id);
}
}
};
}
17 changes: 17 additions & 0 deletions packages/demos/src/todomvc/header/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.new-todo {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: none;
color: inherit;
padding: 16px 16px 16px 60px;
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
box-sizing: border-box;
background: rgba(0, 0, 0, 0.003);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
31 changes: 31 additions & 0 deletions packages/demos/src/todomvc/header/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {FeatureAppDefinition} from '@feature-hub/core';
import {DomFeatureApp} from '@feature-hub/react';
import {TodoManagerV1} from '../todo-manager';
import {TodoMvcHeader} from './todomvc-header';

export interface HeaderFeatureServices {
readonly 'test:todomvc-todo-manager': TodoManagerV1;
}

const featureAppDefinition: FeatureAppDefinition<
DomFeatureApp,
undefined,
undefined,
HeaderFeatureServices
> = {
id: 'test:todomvc-header',

dependencies: {
featureServices: {
'test:todomvc-todo-manager': '^1.0.0'
}
},

create: ({featureServices}) => {
const todoManager = featureServices['test:todomvc-todo-manager'];

return new TodoMvcHeader(todoManager);
}
};

export default featureAppDefinition;
36 changes: 36 additions & 0 deletions packages/demos/src/todomvc/header/todomvc-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {DomFeatureApp} from '@feature-hub/react';
import {html, render} from 'lit-html';
import {TodoManagerV1} from '../todo-manager';

export class TodoMvcHeader implements DomFeatureApp {
public readonly kind = 'react-container';

public constructor(private readonly todoManager: TodoManagerV1) {}

public attachTo(container: Element): void {
const header = html`
<header class="header">
<h1>todos</h1>
<input
class="new-todo"
placeholder="What needs to be done?"
@keypress="${this.handleKeypress}"
autofocus
/>
</header>
`;

render(header, container);
}

private readonly handleKeypress = (event: KeyboardEvent): void => {
if (event.key === 'Enter') {
const input = event.target as HTMLInputElement;
const title = input.value.trim();

this.todoManager.add(title);

input.value = '';
}
};
}
Loading

0 comments on commit f48b9c9

Please sign in to comment.