In the last post, I showed you how to design a table with the Data Table package in Framer X. Have you tried it yet? If not, do it now and come back. I'll wait for you.
...
Alright, now that your table looks good and it's even filled with real data. The next step is to put it inside a prototype to do some user testing, or present it to the customer or executives. How would you usually make a table prototype? Stitch together some mock pictures? Wait for the developer to implement it?
Let's roll our sleeves and build one ourselves! What about a real, fully interactive table? Prototyping is the forte of Framer X. It's what Data Table does well, too.
In this post, I'll show you some examples of what we could build. I hope these would give you inspiration and motivation to try more. There are so many possibilities with the power of code in your hands!
Here are some GIFs for your viewing pleasure:
To take full advantage of this post, you should know overrides and how overrides communicate.
Overrides are a powerful idea in Framer X. It gives you the best of both worlds: design visually on the canvas, add logic and process data with code.
If you are not familiar with overrides (or even coding), take a look at this course I prepared for you.
Guess what the override below will give us?
export function TableHoverActions(): Override {
return {
showDefaultColumns: false,
columns: [
{ accessor: 'symbol' },
{ accessor: 'market' },
{ accessor: 'price' },
{
accessor: 'actions',
Cell: () => (
<div style={{ width: 80 }}>
<div
style={{
width: '100%',
display: 'flex',
justifyContent: 'space-between',
cursor: 'pointer',
}}
>
<span>🗑</span>
<span>✋</span>
<span>💾</span>
</div>
</div>
),
},
],
}
}
You should be able to work it out if you've read the last post, right? It'd be a 4-column table. The last column would include custom cells for each row:
How would we make it interactive? How would we only show the actions for the row that the user is hovering the mouse on?
In another word, we need to know which row the mouse is currently on. We'd use that to determine whether to show the actions or not.
We could use rowProps
to add the onMouseEnter
event handler to the row div:
export function TableHoverActions(): Override {
return {
...
rowProps: ({ index }) => ({
onMouseEnter: () => { console.log(index) }
})
}
}
Then, save the row index into a Data
object:
import { Override, Data } from "framer"
const hoverData = Data({ hoveredIndex: -1 })
export function TableHoverActions(): Override {
return {
...
rowProps: ({ index }) => ({
onMouseEnter: () => (hoverData.hoveredIndex = index)
})
}
}
Finally, we'd be able to determine whether to show the actions by comparing the indices:
export function TableHoverActions(): Override {
return {
columns: [
...
{
accessor: "actions",
Cell: ({ row: { index } }) => (
<div style={{ width: 80 }}>
{index === hoverData.hoveredIndex && (
<div
style={{
width: "100%",
display: "flex",
justifyContent: "space-between",
cursor: "pointer",
}}
>
<span>🗑</span>
<span>✋</span>
<span>💾</span>
</div>
)}
</div>
),
},
],
...
}
}
The full source code can be found here.
Note: as an alternative, we could use React state for this prototype, instead of Data
.
To update the rows in the table, we'd use the data
prop of the component. The rest is some usual Override/JS fu.
Note: the data
prop takes precedence over the "Data" property on the properties panel.
const appState = Data({
rows: [{ symbol: 'MSFT', market: 'NASDAQ', price: 180 }],
})
export function Table(props): Override {
return {
data: appState.rows,
}
}
export function Add(props): Override {
return {
onTap() {
appState.rows = [
...appState.rows,
{ symbol: 'TSLA', market: 'NASDAQ', price: 870 },
]
},
}
}
export function Remove(props): Override {
return {
onTap() {
appState.rows = appState.rows.slice(0, appState.rows.length - 1)
},
}
}
The full source code can be found here.
To support pagination, we need to override two props: pageSize
and pageIndex
.
export function TablePaged(props): Override {
return {
pageSize: 10,
pageIndex: 2,
}
}
How would we add buttons to flip pages? The answer is Data
:
const data = Data({
pageSize: 10,
pageIndex: 0,
})
export function TablePaged(props): Override {
return {
pageSize: data.pageSize,
pageIndex: data.pageIndex,
}
}
Then, we can change data
from the buttons:
export function NextPage(props): Override {
return {
onTap: () => {
data.pageIndex = data.pageIndex + 1
},
}
}
export function PreviousPage(props): Override {
return {
onTap: () => {
data.pageIndex = data.pageIndex - 1
},
}
}
export function PageSize(props): Override {
return {
onChange: (v) => {
data.pageSize = Number.parseInt(v)
},
}
}
In addition, the onPageChange
prop allows us to extract key information about the page, such as pageCount
.
export function TablePaged(props): Override {
return {
...
onPageChange: ({ pageCount }) => {
data.pageCount = pageCount
}
}
}
With onPageChange
, we can display page information like so:
export function PageInfo(props): Override {
return {
text: `Page ${data.pageIndex + 1} of ${data.pageCount}`,
}
}
The full source code can be found here.
Typically we'd have a column to let user expand or collapse a row. We could use row.getExpandedToggleProps()
to make an element clickable. Behind the scene, it adds event handlers and styles to the element and toggles the internal state of the row.
export function Table(props): Override {
return {
columns: [
...
{
accessor: "expander",
Header: "",
Cell: ({ row }) => (
<div {...row.getExpandedToggleProps()}>
+
</div>
),
},
],
...
}
}
Next, we use row.isExpanded
to retrieve the internal state of the row, and display accordingly.
...
Cell: ({ row }) => (
<div {...row.getExpandedToggleProps()}>
{row.isExpanded ? "-" : "+"}
</div>
),
...
Finally, we override the subRow
prop to show the content of the expanded row:
export function Table(props): Override {
return {
...
subRow: row => <div>Detailed info for {row.original.symbol}</div>,
}
}
Under the hood, Data Table is powered by the wonderful react-table package (Thanks Tanner!). Since react-table is a fully featured library for production usage, we can see a lot of exciting possibilities for future improvement. What about filtering, sorting, re-ordering columns, or resizable columns? Tell me what you want to build, and I'll see if I can add it in!
Alright! This post is all about creating interactive data tables with a little bit of code. With what's covered in the last post, I hope you find Data Table a useful tool in your arsenal.
Here a few key techniques used in this post:
columns
/Cell
and rowProps
to achieve hover actionsdata
prop to customize the rows in the tablepageSize
, pageIndex
and onPageChange
props to achieve paginationrow.getExpandedToggleProps
, row.isExpanded
and subRow
prop to achieve expandable rowsData
as the glue to combine different items on the canvas and achieve interactivityAs I mentioned, these are just some example of what's possible. If you know how to harness the power of code, even just a little bit of code, you'll go far and beyond. I can't wait to see what you'll create!
I hope you find this article useful!
One of my 2021 goals is to write more posts that are useful, interactive and entertaining. Want to receive early previews of future posts? Sign up below. No spam, unsubscribe anytime.