Skip to content

Commit

Permalink
More work on the TV App example
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesplease committed Jun 18, 2021
1 parent 4230f75 commit 4bea1a6
Show file tree
Hide file tree
Showing 16 changed files with 333 additions and 31 deletions.
7 changes: 6 additions & 1 deletion examples/advanced/tv-app/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# TV App (WIP)

This WIP example shows how you can use this library to build a TV app.
This example shows how you can use this library to build a TV app. Keep in mind
that there are an infinite number of ways that you can architect any given app.
I don't think this is the best possible architecture, nor do I think that you
should necessarily copy it verbatim.

It only exists to serve as a reference of the LRUD library API.

### Running the Example

Expand Down
2 changes: 2 additions & 0 deletions examples/advanced/tv-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"@types/jest": "26.0.23",
"@types/react": "17.0.11",
"@types/react-dom": "17.0.7",
"classnames": "2.3.1",
"framer-motion": "4.1.17",
"lodash": "4.17.21",
"modern-normalize": "1.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
Expand Down
2 changes: 2 additions & 0 deletions examples/advanced/tv-app/src/app.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
.app {
position: relative;
width: var(--appWidth);
height: var(--appHeight);
background: black;
color: white;
overflow: hidden;
}

.block-container {
Expand Down
29 changes: 5 additions & 24 deletions examples/advanced/tv-app/src/app.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useState } from 'react';
import { FocusNode } from '@please/lrud';
import { motion, AnimatePresence } from 'framer-motion';
import { AnimatePresence } from 'framer-motion';
import './app.css';
import Profiles from './profiles/profiles';
import Home from './home/home';
import Nav from './nav/nav';

export default function App() {
const [selectedProfile, setSelectedProfile] = useState(null);
Expand All @@ -16,30 +18,9 @@ export default function App() {
)}
</AnimatePresence>
<AnimatePresence>
{hasSelectedProfile && (
<FocusNode
elementType={motion.div}
initial={{
scale: 0.8,
opacity: 0,
}}
animate={{
scale: 1,
opacity: 1,
}}
exit={{
scale: 1.15,
opacity: 0,
}}
transition={{
duration: 0.25,
ease: 'easeOut',
}}
className="home_placeholder">
You have chosen {selectedProfile.name}
</FocusNode>
)}
{hasSelectedProfile && <Home profile={selectedProfile} />}
</AnimatePresence>
{selectedProfile && <Nav selectedProfile={selectedProfile} />}
</FocusNode>
);
}
3 changes: 3 additions & 0 deletions examples/advanced/tv-app/src/home/home.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.home {
padding: 50px 0 0 180px;
}
86 changes: 86 additions & 0 deletions examples/advanced/tv-app/src/home/home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useState } from 'react';
import './home.css';
import chunk from 'lodash/chunk';
import { FocusNode, useSetFocus } from '@please/lrud';
import { motion } from 'framer-motion';
import Row from './row';

// I just manually typed this in after rendering out a row...
// In a real app you would be a bit more diligent about making sure that the height
// used to animate and the actual height of the row are one and the same.
const ROW_HEIGHT = 280;

// A list of "titles" to display
const titles = Array.from({
length: 30,
});

// Group these titles into rows
const titlesByRow = chunk(titles, 7);

// Add some more information for each row
const rows = titlesByRow.map((titles) => {
return {
name: 'Row Title',
titles,
};
});

export default function Home({ selectedProfile }) {
const [gridPosition, setGridPosition] = useState({
rowIndex: 0,
columnIndex: 0,
});
const setFocus = useSetFocus();

return (
<FocusNode
className="home page"
isGrid
focusId="home"
defaultFocusColumn={gridPosition.columnIndex}
defaultFocusRow={gridPosition.rowIndex}
onLeft={(e) => {
if (gridPosition.columnIndex === 0) {
e.preventDefault();
setFocus('nav');
}
}}
onGridMove={(e) => {
setGridPosition({
rowIndex: e.nextRowIndex,
columnIndex: e.nextColumnIndex,
});
}}
elementType={motion.div}
initial={{
scale: 0.8,
opacity: 0,
y: 0,
}}
animate={{
scale: 1,
opacity: 1,
y: -ROW_HEIGHT * gridPosition.rowIndex,
}}
exit={{
scale: 1.15,
opacity: 0,
}}
transition={{
duration: 0.25,
ease: 'easeOut',
}}>
{rows.map((row, rowIndex) => {
return (
<Row
row={row}
key={rowIndex}
rowIndex={rowIndex}
gridPosition={gridPosition}
/>
);
})}
</FocusNode>
);
}
20 changes: 20 additions & 0 deletions examples/advanced/tv-app/src/home/row.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.row {
margin-bottom: 35px;
transition: opacity 250ms ease-out;
}

.row-isBeforeActiveRow {
opacity: 0;
}

.row_header {
height: 35px;
font-size: 21px;
font-weight: 700;
margin-bottom: 10px;
}

.row_titles {
display: flex;
gap: 8px;
}
22 changes: 22 additions & 0 deletions examples/advanced/tv-app/src/home/row.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FocusNode } from '@please/lrud';
import classnames from 'classnames';
import './row.css';
import Title from './title';

export default function Row({ row, rowIndex, gridPosition }) {
const isBeforeActiveRow = rowIndex < gridPosition.rowIndex;

return (
<FocusNode
className={classnames('row', {
'row-isBeforeActiveRow': isBeforeActiveRow,
})}>
<div className="row_header">{row.name}</div>
<div className="row_titles">
{row.titles.map((title, titleIndex) => {
return <Title title={title} key={titleIndex} />;
})}
</div>
</FocusNode>
);
}
37 changes: 37 additions & 0 deletions examples/advanced/tv-app/src/home/title.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
.title {
position: relative;
flex: 0 0 140px;
height: 200px;
display: grid;
place-items: center;
background: #222;
color: #aaa;
font-size: 14px;
font-weight: 500;
border-radius: 6px;
transition: transform 200ms ease-out;
}

.title.isFocused {
background: #666;
transform: scale(1.1);
z-index: 2;
color: #ccc;
}

.title:before {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
box-shadow: 0 0 20px rgba(0, 0, 0, 1);
opacity: 0;
transition: opacity 200ms ease-out;
}

.title.isFocused:before {
opacity: 1;
}
6 changes: 6 additions & 0 deletions examples/advanced/tv-app/src/home/title.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FocusNode } from '@please/lrud';
import './title.css';

export default function Title() {
return <FocusNode className="title">Title</FocusNode>;
}
1 change: 1 addition & 0 deletions examples/advanced/tv-app/src/index.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
@import './styles/variables.css';
@import './styles/cursor.css';
@import './styles/utils.css';

body {
margin: 0;
Expand Down
77 changes: 77 additions & 0 deletions examples/advanced/tv-app/src/nav/nav.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
.nav {
position: absolute;
z-index: 10;
left: 20px;
padding-top: 50px;
height: var(--appHeight);
width: 200px;
color: white;
transition: transform 150ms ease-out;
}

.nav.isFocused {
transform: translateX(40px);
}

.nav_list {
display: flex;
flex-direction: column;
}

.nav_link {
position: relative;
font-size: 20px;
font-weight: 500;
height: 40px;
display: inline-flex;
align-items: center;
padding: 0 10px;
margin-bottom: 5px;
}

.nav_link-isCurrentPage {
color: #ffed00;
}

.nav_linkContents {
position: relative;
}

.nav_linkContents:after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
right: 0;
height: 2px;
background: #ffed00;
transform-origin: center;
transform: scaleX(0);
transition: transform 100ms ease-out;
}

.nav_link.isFocused .nav_linkContents:after {
transform: scaleX(1);
transition-duration: 250ms;
}

.nav_shim {
position: absolute;
z-index: 9;
left: 0;
top: 0;
bottom: 0;
width: 300px;
background: linear-gradient(
to right,
rgba(0, 0, 0, 0.8) 30%,
rgba(0, 0, 0, 0) 100%
);
opacity: 0;
transition: all 150ms ease-out;
}

.nav_shim-isVisible {
opacity: 1;
transform: translateX(120px);
}
Loading

0 comments on commit 4bea1a6

Please sign in to comment.