Creating a themeable component
In this guide we're going to be using React hooks added in React v16.8. If you're using the old demo-v1 you will have to convert these to lifecycle functions (e.g. componentWillMount
).
In this guide we are going to create a themeable component.
We're going to add a countdown component.
00:00:30 until sale begins
1. Create your component
Create the file client/src/components/Countdown.js
Below is the basics of a countdown component (you can see a working example above).
client/src/components/Countdown.js
import React, { useState, useLayoutEffect } from 'react';
// Change the time from Milliseconds to a useful string
const formatTime = timeInMs => {
let seconds = Math.floor((timeInMs / 1000) % 60),
minutes = Math.floor((timeInMs / (1000 * 60)) % 60),
hours = Math.floor((timeInMs / (1000 * 60 * 60)) % 24);
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds;
}
export const Countdown = ({ seconds }) => {
const millisecond = 1000,
startTime = seconds ? seconds * millisecond : millisecond * 5,
[time, setTime] = useState(startTime),
[isActive, setIsActive] = useState(true);
useLayoutEffect(() => {
let tick = setTimeout(() => {
if (time <= 0) {
setIsActive(false);
} else {
setTime(time - millisecond);
}
}, millisecond);
return () => {
clearTimeout(tick);
}
}, [time]);
return (
<div>
{isActive ?
<p>{`${formatTime(time)} until sale begins`}</p>
:
<p>Sale Started</p>
}
</div>
);
}
2. Export your component
Now you've created your component you will need to export it from the client/src/components/index.js
file.
client/src/components/index.js
...
export * from './Countdown';
This simplifies the process of importing it in the future, by allowing it to be imported from the components folder directly rather than a subfile.
3. Include your component
The next step is to include the component somewhere on the page. We're going to add it to the header. Once you've done this you should see your component appear on the page...it will just be unstyled.
client/src/components/Header/HeaderMenuBar.js
...
import { Countdown } from '..';
export const HeaderMenuBar = props => {
...
return (
<Box>
...
<Countdown seconds={30} />
</Box>
);
};
4. Make your component 'themeable'
Let's head back to your component file client/src/components/Countdown.js
and add our themed
wrapper.
client/src/components/Countdown.js
...
import { Box, themed } from '@deity/falcon-ui';
const CountdownLayout = themed({
tag: Box,
defaultTheme: {
countdownLayout: {
p: 'md',
bg: 'primary',
css: ({ theme }) => ({
textAlign: 'center',
width: '100%',
p: {
color: theme.colors.secondary,
margin: 0,
fontSize: theme.fontSizes.lgTitle,
}
})
}
}
});
...
export const Countdown = ({ seconds }) => {
...
return (
<CountdownLayout>
{isActive ?
<p>{`${formatTime(time)} until sale begins`}</p>
:
<p>Sale Started</p>
}
</CountdownLayout>
);
}
We've now imported both Box
& themed
. We then create a new component CountdownLayout
that is themed
.
We can call this whatever we want but our naming convention suggests it should have a Layout
suffix. e.g. [ComponentName]Layout
.
We can then style this using the variables defined in our client/src/styling/theme.js
.
5. Add translations
One last bit of tidy up here is we've got text that can't be translated.
Using the @deity/falcon-i18n
package we can easily make these string translatable.
client/src/components/Countdown.js
import { T } from '@deity/falcon-i18n';
...
<CountdownLayout>
{isActive ?
<p><T id="countdown.message" time={formatTime(time)} /></p>
:
<p><T id="countdown.started" /></p>
}
</CountdownLayout>
...
client/i18n/en/translations.json
{
...
"countdown": {
"message": "{{time}} until sale starts",
"started": "Sale Started"
}
}
6. Code Cleanup
For extra brownie points it would be great to use our themeable Text
component rather than <p>
tags. This will mean it's both themeable and will adopt default styles already assigned to the <Text>
component.
client/src/components/Countdown.js
...
import { Box, Text, themed } from '@deity/falcon-ui';
...
export const Countdown = ({ seconds }) => {
...
return (
<CountdownLayout>
{isActive ?
<Text><T id="countdown.message" time={formatTime(time)} /></Text>
:
<Text><T id="countdown.started" /></Text>
}
</CountdownLayout>
);
}
Finished
That's it...you've now got a fancy countdown component you can theme using your client/src/styling/theme.js
or modify with our theme editor tool.
Full Code
Here's the complete Countdown.js
file.
import React, { useState, useLayoutEffect } from 'react';
import { Box, Text, themed } from '@deity/falcon-ui';
import { T } from '@deity/falcon-i18n';
const CountdownLayout = themed({
tag: Box,
defaultTheme: {
countdownLayout: {
p: 'md',
bg: 'primary',
css: ({ theme }) => ({
textAlign: 'center',
width: '100%',
p: {
color: theme.colors.secondary,
margin: 0,
fontSize: theme.fontSizes.lgTitle,
}
})
}
}
});
// Change the time from Milliseconds to a useful string
const formatTime = timeInMs => {
let seconds = Math.floor((timeInMs / 1000) % 60),
minutes = Math.floor((timeInMs / (1000 * 60)) % 60),
hours = Math.floor((timeInMs / (1000 * 60 * 60)) % 24);
hours = (hours < 10) ? "0" + hours : hours;
minutes = (minutes < 10) ? "0" + minutes : minutes;
seconds = (seconds < 10) ? "0" + seconds : seconds;
return hours + ":" + minutes + ":" + seconds;
}
export const Countdown = ({ seconds }) => {
const millisecond = 1000,
startTime = seconds ? seconds * millisecond : millisecond * 5,
[time, setTime] = useState(startTime),
[isActive, setIsActive] = useState(true);
useLayoutEffect(() => {
let tick = setTimeout(() => {
if (time <= 0) {
setIsActive(false);
} else {
setTime(time - millisecond);
}
}, 1000);
return () => {
clearTimeout(tick);
}
}, [time]);
return (
<CountdownLayout>
{isActive ?
<Text><T id="countdown.message" time={formatTime(time)} /></Text>
:
<Text><T id="countdown.started" /></Text>
}
</CountdownLayout>
);
}