All you need to build a Web Experience for your Threekit Configurator.
The Threekit React Dev-Kit is a feature-rich React boilerplate containing an extensive library of components, hooks, tools, functionality and deployment workflows needed to build an intuitive and engaging web experience for a Threekit Configurator. We have everything you need to get started!
Useful Links
- Threekit Docs
- Component Library
- Threekit Website
- Threekit Platform - Preview Environment
- Threekit Platform - Admin-FTS Environment
- Threekit React Dev-Kit
-
Click the
Use this template
button and create a new repo for your project. -
Clone the new project repo using the following command (replacing PROJECT_REPO with the name of your new project repository).
git clone https://github.com/Threekit/PROJECT_REPO.git
cd ./PROJECT_REPO
- Install all the dependencies with the following command:
yarn install
- Rename the
.env.sample
file to.env
and replace all the credentials and constants in the file - Start up the local development server with the command
yarn start-react
. Your project will be available locally onhttp://localhost:3000
.
It is strongly recommended that you avoid making any changed to the code in the /threekit folder. To update the React Dev-Kit for projects in active development we recommend replacing the threekit folder with the updated version. This is only manageable if there is no project specific code in the threekit folder.
The Dev-Kit follows a 'Provider' Pattern where all the React code that interacts with the Threekit API is placed inside the ThreekitProvider. Within the ThreekitProvider context all Threekit components and hooks have complete flexibility in where and how they're used while still fully connected to the Threekit API, the 3D Player, and each other.
The Dev-Kit's provides 3 main family of features: Expanded functionality, including react-hooks, that provide direct ways to implement higher-level features, components built using that functionality to provide UI presentational elements for the configurator and many other features, and tools which allow us to add new and custom interactivity to the 3D.
- Input Components -> Components that can connect and control Attributes. (i.e. Buttons, Swatch)
- Widgets -> Components for Integration Features. (i.e. Snapshot, Zoom)
- Forms -> Component for an entire configurator, built up of Input Components. (i.e. Form)
- Layouts -> Organizational components for adding structure and visual heirarchy. (i.e. Modal, Accordion)
- Display -> Displays information sourced from Threekit. (i.e. Attribute Title, Price)
- Wrappers -> Components that wrap a block of express code. (i.e. PortalToElement, AwaitPlayerLoad)
- Controller API -> An extension of the Threekit JS API that adds a variety of higher level functions. (i.e. saveConfiguration, takeSnapshot)
- Hooks -> React Redux hooks to build Threekit powered and connected components. (i.e. useAttribute, usePrice).
Tools are extensions for the Threekit Player that allow us to add new functionality to interactions with the 3D, including click, drag, hover, etc. These interactions also have full access to our Redux store and work seemsly between the 3D space and and any browser space UI.
All scripts are expected to be run from the project root directory. These include:
Starts up the React local development server on localhost:3000
.
Starts up the Express.js local development server on localhost:5000
. In development mode this server will not be responsible for serving the React development server.
Starts up the local Storybook server on localhost:6006
, where you can explore and play around with our UI component library.
We use Storybook to build, test, and showcase our components. You can also use Storybook to support any further development using the boilerplate by adding stories for your own components.
Bundles React in production mode into the directory /build_react
.
Bundles the Express Server in production mode into the directory /build_server
.
A single command to build the entire App (React + Express). It executes both the build-server
and build-react
scripts as described above.
This command is used by our Dockerfile as part of the build process.
The <ThreekitProvider />
should be wrapped around the portion of the React app where the Threekit API is being used. It initializes the Threekit Player and sets up a store to power all the hooks and connected components that will be used to build the UI to interact with the Player.
import React from 'react';
import ReactDOM from 'react-dom';
import { ThreekitProvider } from 'threekit';
import { tooltip } from 'threekit/tools';
import App from './App';
const config = {
// While the project credentials should ideally be defined
// in a .env file, they can also be passed-in or overwritten
// in this config object
authToken: '3fb4asd5d-ea38-4a05-a1c3-6cf9d8dd3d48',
assetId: 'a9a66218-bkid-4106-96fe-a0709fdc3dc1',
orgId: '20df501b-1ef8-4bh0-sfda-2b99426624de',
threekitEnv: 'admin-fts.threekit.com',
// Any additional parameters to pass to the player initialization
// can also be added here. For example setting the showShare
// property
showShare: true,
// This is where we will setup out additionalTools...
additionalTools: [tooltip()],
// We can pass overwrites to the default theme
theme: { primaryColor: '#54AA54' },
};
ReactDOM.render(
<ThreekitProvider config={config}>
<App /> // All Threekit related code goes here
</ThreekitProvider>,
document.getElementById('root')
);
The <Player />
component renders the Threekit Player.
import { Player } from 'threekit/components';
const ThreekitApp = () => {
return (
<div>
<Player />
</div>
);
};
The Player Component also comes with widget containers that can be used to position widgets or any component in pre-defined locations around the player.
import { Player } from 'threekit/components';
// We can seperate out the widget container component
// or use the component directly from the Player component
const { TopRightWidgets } = Player;
const ThreekitApp = () => {
return (
<div>
<Player>
<TopRightWidgets>
<div>This will show up in the top-right of the player</div>
</TopRightWidgets>
<Player.BottomRightWidgets>
<div>This will show up in the bottom-right of the player</div>
</Player.BottomRightWidgets>
</Player>
</div>
);
};
Forms can be used to render out complete configurators as a single component.
The <Form>
component is used to render out an item's entire configurator. By default this is the configurator for the initialized item, however, it can also be for an item nested as a selection in an Attribute.
import { Form } from 'threekit/components';
const App = () => {
return (
<div>
<Form />
</div>
);
};
The Form can take a prop of attributeComponents
that allow us to specify, which component to use for an attribute or if we want to hide any attributes.
By default the Form will not render reserved attributes. This can be controlled/over-written by using the prop includeReservedAttributes=false}
import { Form } from 'threekit/components';
const App = () => {
const attributeComponents = {
// This will render out 'Attribute 1' using the Buttons component
'Attribute 1': 'buttons',
// This will prevent 'Attribute 2' from being rendered.
'Attribute 2': undefined,
};
return (
<div>
<Form attributeComponents={attributeComponents} />
</div>
);
};
The useAttribute
hook allows us to connect a component to an Attribute in our configurator.
It takes the name of the attribute that you want to interact with and returns an array where the first element is the data for that attribute, as returned by the getDisplayAttributes()
function, and the second element is a function that can be used to update the value of that attribute, by prepping and passing the value on to setConfiguration()
.
import { useAttribute } from 'threekit/hooks';
const AttributeComponent = () => {
const [attribute, setAttribute] = useAttribute('Attribute Name');
const selected = attribute.value;
return (
<div>
<div>{attribute.name}</div>
<div>
{attribute.values.map((option, i) => (
<div key={i} onClick={setAttribute(option.assetId)}>
{option.label}
</div>
))}
</div>
</div>
);
};
The useAttributes
hook allows us to connect to all our attributes in the intialized item's configurator.
The hook returns an array of two items. The first item is almost identical to the value returned by getDisplayAttributes()
. The second item is a change handler function that passes the value passed in straight through to setConfiguration()
.
import { useAttributes } from 'threekit/hooks';
const AttributeComponent = () => {
const [attributes, setConfiguration] = useAttributes();
return <div>Attributes Component example</div>;
};
The useCamera
hook allows us to use the reserved metadata camera attribute.
The hook will return an array of two items. The first is the camera attributes data, which includes the options available and selected. The second item is the change handler, which allows us to change the active camera.
import { useCamera } from 'threekit/hooks';
const Component = () => {
const [cameraAttribute, handleCameraSelect] = useCamera();
return (
<div>
{cameraAttribute.values.map(el => (
<button onClick={() => handleCameraSelect(el.value)}>{el.labe}</button>}
))}
</div>
);
};
The useCameraToggle
hook allows us to toggle through a list of camera options using the reserved camera attribute.
The hook will return an array of two items. The first is the camera attributes data, which includes the options available and selected. This list can be filtered by providing a list of allowed cameras as a property in the hook's config object - { cameras: ['Camera 1', 'Camera 2']}
.
The second item is the step change handler where calling the function will update the camera to the next one in the list, looping around if its at the end.
import { useCameraToggle } from 'threekit/hooks';
const Component = () => {
const [cameraAttribute, handleCameraSelect] = useCameraToggle();
return (
<div>
{cameraAttribute.values.map(el => (
<button onClick={() => handleCameraSelect(el.value)}>{el.labe}</button>}
))}
</div>
);
};
The useHistory
hook allows us to step forward and backward in our configuration history
The hook returns a function that takes the value we want to step in our history, where negative numbers step us backward (undo) and positive numbers step us forward (redo) assuming we have already stepped back in the history.
import { useHistory } from 'threekit/hooks';
const Component = () => {
const stepHistory = useHistory();
const handleUndo = () => {
stepHistory(-1);
};
const handleRedo = () => {
stepHistory(1);
};
return (
<div>
<button onClick={handleUndo}>Click to Undo</button>
<button onClick={handleRedo}>Click to Redo</button>
</div>
);
};
The useLanguages
hook allows us to select and update the language we want our Threekit sourced data (i.e the configurator) to be displayed in.
The hook returns an array that includes, respectively, the selected language, an array of language options (strings) and a change handler for updating the language.
It is used to build the LanguageSelector Widget
Note: The languages have to be defined and populated in your org on the Threekit Platform.
import { useLanguages } from 'threekit/hooks';
const LanguageSelector = () => {
const [selected, options, handleChange] = useLanguages();
return <div>// Any custom component to use languages</div>;
};
The useMetadata
hook provides the metadata present on the item used to initialize the player.
The hook returns an object with the metadata.
It is used to build the Description Display component
import { useMetadata } from 'threekit/hooks';
const MetadataComponent = () => {
const metadata = useMetadata();
return (
<div>
{Object.entries(metadata).map(([key, value], i) => (
<div>
{key}: {value}
</div>
))}
</div>
);
};
The useName
hook provides the name of the item used to initialize the player.
The hook a single string value.
It is used to build the Title Display component
import { useName } from 'threekit/hooks';
const TitleComponent = () => {
const name = useName();
return <div>{name}</div>;
};
The useNestedAttribute
hook allows us to connect a component to an Attribute in the active nested configurator. Except for targetting the nested configurator, it works identically to the useAttribute() hook.
import { useNestedAttribute } from 'threekit/hooks';
const AttributeComponent = () => {
const [attribute, setAttribute] = useNestedAttribute('Attribute Name');
const selected = attribute.value;
return (
<div>
<div>{attribute.name}</div>
<div>
{attribute.values.map((option, i) => (
<div key={i} onClick={setAttribute(option.assetId)}>
{option.label}
</div>
))}
</div>
</div>
);
};
The useNestedConfigurator
allows us to interact with the configurator of an Item that is nested as the selection on an attribute. For example, if the there is a part-reference type attribute where each of the values (items) have their own configurators, the useNestedConfigurator() hook can be used to target the configurator of the selected value (item).
The hook returns an array of four items, in order:
attributes
- The Attributes found in the nested configuratoraddress
- The attributes address of the nested configurator.handleSelectAttribute
- To update the address for which attribute's selection to targethandleSetConfiguration
- A change handler to update the value of an attribute in the nested configurator. This functions identically to the change handler on the useAttribute() hook.
import { useNestedConfigurator } from 'threekit/hooks';
const AttributeComponent = () => {
const [
attributes,
address,
handleSelectAttribute,
handleSetConfiguration,
] = useNestedConfigurator('Attribute Name');
const selected = attribute.value;
return <div>Nested Configurator</div>;
};
The useZoom
hook provides functionality to control the zoom property in the Threekit Player.
The hook returns an array of 2 functions: zoom-in and zoom-out. Both functions accept a single argument: the zoom increment step.
The default increment value is 1
.
It is used to build the Zoom Widget.
import { useZoom } from 'threekit/hooks';
const ZoomComponent = () => {
const [zoomIn, zoomOut] = useZoom();
return (
<div>
// Changes zoom by +1
<button onClick={zoomIn}>Zoom In</button>
// Changes zoom -3
<button onClick={() => zoomOut(3)}>Zoom Out</button>
</div>
);
};
import { Buttons, RadioButtons, Dropdown, Swatch } from 'threekit/components';
const App = () => {
return(
<Buttons attribute="Attribute Name">
)
}
To explore our Input Components library on Storybook, click here.
The <CameraToggle />
widget is a single button that allows the user to cycle through the cameras defined using the attribute reserverd camera attribute.
The component is built using the useCameraToggle hook`.
import { CameraToggle } from 'threekit/components';
const Component = () => {
return (
<div>
<CameraToggle />
</div>
);
};
The <LanguageSelector />
widget allows the user to view, select and update the language the Threekit Configuratos is presented in.
The component is built using the useLanguages hook. It renders out a simple dropdown to toggle through all the language options.
import { LanguageSelector } from 'threekit/components';
const Component = () => {
return (
<div>
<LanguageSelector />
</div>
);
};
The <Share />
widget renders a button, which when clicked copies a resumable link to the current configuration.
The share feature uses the Configuration Service for the relevant org on the Threekit Platform.
import { Share } from 'threekit/components';
const Component = () => {
return (
<div>
<Share />
</div>
);
};
The <Snapshot />
widget renders a button, which when clicked downloads the specified snapshots to the user's device.
The component can be given specifications for the output image, including format : 'png'(default) | 'jpeg'
, filename: string
and size.
The component can be provided with a list of Cameras to use for the snapshots as a prop - cameras={['snapshot-camera', 'birdsey-camera']}
.
Note: These cameras must be setup to the configurator using the reserved camera attribute.
import { Snapshot } from 'threekit/components';
const Component = () => {
return (
<div>
<Snapshot cameras={['snapshot-camera', 'birdsey-camera']} format="jpeg" />
</div>
);
};
The <Undo />
widget allows the user to Undo or step backward in their configuration history.
The component is built using the useHistory hook. It renders a button that allows us to step our configuration backward.
import { Undo } from 'threekit/components';
const Component = () => {
return (
<div>
<Undo />
</div>
);
};
The <Redo />
widget allows the user to Redo or step forward in their configuration history, assuming they have already stepped backward.
The component is built using the useHistory hook. It renders a button that allows us to step our configuration backward.
import { Redo } from 'threekit/components';
const Component = () => {
return (
<div>
// Any React content
<Redo />
</div>
);
};
The <Zoom />
widget allows the user to control the zoom property of the Threekit Player.
The component is built using the useZoom hook. It provides a simple pair of +
and -
buttons the user can click to update the zoom.
It defaults to single increments changes but also accepts increment values to use instead.
import { Zoom } from 'threekit/components';
const Component = () => {
return (
<div>
<Zoom />
</div>
);
};
Display components can be used to display specific information anywhere in the UI.
The <Title />
component will display the value of the metadata key _title
on the Catalog Item used to initialize the Player.
The title can also be overwritten by passing in your own title in as a prop.
import { Title } from 'threekit/components';
const Component = () => {
return (
<div>
<Title />
// With a custom title
<Title title="Custom Title" />
</div>
);
};
The <Description />
component will display the value of the metadata key _description
on the Catalog Item used to initialize the Player.
The description can also be overwritten by passing in your own description in as a prop.
It is built using the useMetadata() hook.
import { Description } from 'threekit/components';
const Component = () => {
return (
<div>
<Description />
// With a custom description
<Description description="This is a custom description." />
</div>
);
};
The <AttributeTitle>
will display the translated attribute name of an attribute.
It is built using the useName() hook.
import { AttributeTitle } from 'threekit/components';
const Component = () => {
return (
<div>
<AttributeTitle attribute="Attribute Name" />
</div>
);
};
The <AttributeValue>
will display the translated selected value of an attribute.
import { AttributeValue } from 'threekit/components';
const Component = () => {
return (
<div>
<AttributeValue attribute="Attribute Name">
</div>
);
};
The <Price>
component will display the total price of your configuration. It is calculated using the first Pricebook in the Org, and the first Curreny in that Pricebook.
It is built using the usePrice() hook.
Support for multiple Pricebooks and Currencies coming soon
import { Price } from 'threekit/components';
const Component = () => {
return (
<div>
<Price />
</div>
);
};
The <AwaitPlayerLoad>
wrapper, is used to wrap any content that we don't want to render until the Threekit Player initialization process is complete.
import { AwaitPlayerLoad } from 'threekit/components';
const Component = () => {
return (
<div>
<div>Content here will be rendered as normal</div>
<AwaitPlayerLoad>
This content will only be rendered after the Threekit Player
intialization is complete
</AwaitPlayerLoad>
</div>
);
};
The <PortalToElement>
wrapper can be used to place its content in any div anywhere in the DOM. This is can be very useful when embedding the app in an existing eCommerce page or Website, where you can split the app up into sections to be rendered independetly within the page's exisitng html structure.
The wrapper requries an elementId
prop to specify the id of the div to render its contents into. It also optionally takes a strict
prop, which determines the behavior if the element to render the content into is not found. strict={true}
will only render the contents in the HTML element matching the provided element ID, while strict={false} (default)
will render out its contents as part of the regular React flow, if the specified element is not found.
import { AwaitPlayerLoad } from 'threekit/components';
const Component = () => {
return (
<div>
<div>Content here will be rendered as normal</div>
<PortalToElement elementId="form-container">
This content will only be rendered in a div with the id 'form-container'
</PortalToElement>
</div>
);
};
Layouts are organizational and design components that we can use to introduce visual heirarhcy and structure to our configurator and application. Most Layout components have no inherent connection to the Threekit API and can be used freely outside of the ThreekitProvider if needed.
import { Accordion } from 'threekit/components';
const { AccordionItem } = Accordion;
const App = () => {
return (
<Accordion>
<AccordionItem label="Section 1 Heading">Section 1 content</AccordionItem>
<AccordionItem label="Section 2 Heading">Section 2 content</AccordionItem>
</Accordion>
);
};
import { Tabs } from 'threekit/components';
const { TabPane } = Tabs;
const App = () => {
return (
<Tabs>
<TabPane label="Section 1 Heading">Section 1 content</TabPane>
<TabPane label="Section 2 Heading">Section 2 content</TabPane>
</Tabs>
);
};
import { Steps } from 'threekit/components';
const { StepPane } = Steps;
const App = () => {
return (
<Steps>
<StepPane label="Section 1 Heading">Section 1 content</StepPane>
<StepPane label="Section 2 Heading">Section 2 content</StepPane>
</Steps>
);
};
A Modal is used to present an actionable pop-up to the user.
import { useState } from 'react';
import { Modal } from 'threekit/components';
const App = () => {
const [showModal, setShowModal] = useState(false);
const handleClose = () => setShowModal(false);
return (
<Modal show={showModal} handleClose={handleClose}>
<div>
Content to be placed in the modal is added as an HTML child element.
</div>
</Modal>
);
};
A Drawer can be used to present an actionable slide-out drawer to the user.
import { useState } from 'react';
import { Drawer } from 'threekit/components';
const App = () => {
const [showDrawer, setShowDrawer] = useState(false);
const handleClose = () => setShowDrawer(false);
return (
<Drawer show={showDrawer} handleClose={handleClose}>
<div>
Content to be placed in the drawer is added as an HTML child element.
</div>
</Drawer>
);
};
A PopOver renders out a button, which when clicken pops out a floating card aligned with one of its corners. The content of the floating card will be the content/children passed into the PopOver component.
import { useState } from 'react';
import { PopOver } from 'threekit/components';
const App = () => {
return (
<PopOver label="Click me to open" position="bottom-left">
<div>This content will be in the PopOver's floating card.</div>
</PopOver>
);
};
Tools add functionality to the 3D-player beyond the out-of-the-box features such as scroll to zoom, drag to rotate, and right-click to pan the camera. To implement a tool it needs to be added to the array of additionalTools
that is passed to the <ThreekitProvider>
as part of it config object.
import { exampleTool } from 'threekit/tools';
const config = {
additionalTools: [exampleTool()],
};
const ThreekitApp = () => {
return (
<ThreekitProvider config={config}>
<App />
</ThreekitProvider>
);
};
The tooltip
tool displays a tooltip in the Threekit player when hovering over a model which has a tooltip value defined in it's model's Catalog Item Metadata.
The reserved metadata key for a tooltip is _tooltip
. This metadata field can be overwritten by passing in an override as part of its configuration object.
import { tooltip } from 'threekit/tools';
const config = {
additionalTools: [tooltip({ metadataField: 'productName' })],
};
const ThreekitApp = () => {
return (
<ThreekitProvider config={config}>
<App />
</ThreekitProvider>
);
};
The animateItem
tool applies a transform (translation, rotation, re-scale) to an model over a specified duration.
To use the animateItem tool, the name of the attribute where the Catalog Item, for the Model to animate, is selected must match the name of the null/node that model is being applied to. The properties of the transform, including duration, are stored in the Metadata of the Catalog Item of the Model we intend to animate.
The format for the tranform in the Catalog Item's Metadata is key=value
, where multiple key-value pairs are seperated by commas. For example x=1.2, duration=1000
.
The reserved metadata keys for storing animation transforms are:
- Translate ->
_translate
- Rotate ->
_rotate
import { animateItem } from 'threekit/tools';
const config = {
additionalTools: [animateItem()],
};
const ThreekitApp = () => {
return (
<ThreekitProvider config={config}>
<App />
</ThreekitProvider>
);
};
const { player, configurator, controller } = window.threekit;
The player
and configurator
API are the standard API interfaces returned when initializing the Threekit Player.
For documentation on the Player API, click here.
For documentation on the Configurator API, click here.
The controller API has all the higher-level functionality to interact with the 3D.
const { controller } = window.threekit;
Use this method to save a configuration on the Threekit platform, along with any additional metadata or thumbnail URLs related to that configuration.
const { controller } = window.threekit;
// optional
const saveData = {
metadata: {
color: 'blue',
sku: 'ABCD-1',
},
productVersion: 'v5', // defaults to v1
thumbnail:
'https://www.threekit.com/hubfs/Logos%20and%20Trademarks/threekit_logo_black.svg',
};
const response = await controller.saveConfiguration(saveData);
Use this method to resume a configuration saved on the Threekit platform by passing it the saved configuration's ID.
const { controller } = window.threekit;
await controller.resumeConfiguration('20df501b-1ef8-4bh0-sfda-2b99426624de');