Custom Thumbnail Bar
You can use the <cylindo-viewer> with a custom thumbnail bar.
Set the value of the slot attribute of the custom thumbnail bar to thumbnail-bar and use the methods and properties on the <cylindo-viewer> to control the thumbnail bar.
Simple custom thumbnail bar
Below, you will find a simple example to get you started.
This interactive example demonstrates the concept using React. If you're using a different framework, adapt the core concepts to your preferred technology.
function Example() { const viewerRef = React.useRef(null); const [viewerItemIndex, setViewerItemIndex] = useState(0); const [items, setItems] = useState([]); useEffect(() => { const viewer = viewerRef.current; if (!viewer) return; const onLocalItemsChange = event => { setItems(event.detail); }; const onItemChange = event => { setViewerItemIndex(event.detail.index); }; viewer.addEventListener("items-change", onLocalItemsChange); viewer.addEventListener("item-change", onItemChange); return () => { viewer.removeEventListener("items-change", onLocalItemsChange); viewer.removeEventListener("item-change", onItemChange); }; }, []); return ( <cylindo-viewer ref={viewerRef} customer-id="5098" code="ARMCHAIR-PDP" > <cylindo-studio code="RS-BARILA-A" customer-id="5098" /> <cylindo-360-frame frame="10" /> <cylindo-360-frame frame="16" /> <cylindo-360 frame="3" /> <cylindo-model /> <div slot="thumbnail-bar" className="thumbnail-bar"> {items && items.map((item, index) => { const isSelected = viewerItemIndex === index; { /* Button where you can pass your own design, or you can call the Content API (CAPI) to get content for each item. */ } return ( <button key={index} className={isSelected ? "selected" : ""} onClick={() => (viewerRef.current.item = { viewerItem: item, viewerItemIndex: index, }) } > {item.type} </button> ); })} </div> </cylindo-viewer> ); }
Custom thumbnail bar with remote config
The following example demonstrates how to build a thumbnail bar for remote config.
This interactive example demonstrates the concept using React. If you're using a different framework, adapt the core concepts to your preferred technology.
function Example() { const viewerRef = React.useRef(null); const [viewerItemIndex, setViewerItemIndex] = useState(0); const [localItems, setLocalItems] = useState([]); const [configItems, setConfigItems] = useState([]); useEffect(() => { const viewer = viewerRef.current; if (!viewer) return; const onLocalItemsChange = event => { setLocalItems(event.detail); }; const onRemoteItemsChange = event => { setConfigItems(event.detail.items); }; const onItemChange = event => { setViewerItemIndex(event.detail.index); }; viewer.addEventListener("items-change", onLocalItemsChange); viewer.addEventListener("config-change", onRemoteItemsChange); viewer.addEventListener("item-change", onItemChange); return () => { viewer.removeEventListener("items-change", onLocalItemsChange); viewer.removeEventListener("config-change", onRemoteItemsChange); viewer.removeEventListener("item-change", onItemChange); }; }, []); const items = [...configItems, ...localItems]; return ( <cylindo-viewer ref={viewerRef} customer-id="5098" code="SALSIE FF" remote-config="k2hctc08" > <div slot="thumbnail-bar" className="thumbnail-bar"> {items && items.map((item, index) => { const isSelected = viewerItemIndex === index; const viewer = viewerRef.current; const features = item.features || (viewer ? viewer.features : {}); { /* Button where you can pass your own design, or you can call the Content API (CAPI) to get content for each item. */ } return ( <button key={index} className={isSelected ? "selected" : ""} onClick={() => (viewer.item = { viewerItem: item, viewerItemIndex: index, }) } > {item.type} </button> ); })} </div> </cylindo-viewer> ); }
Custom thumbnail bar with different materials' variations
The example below shows a possible approach to creating a custom thumbnail bar with local items with different materials' variations. Clicking on these items will swap the materials' variations and change the current viewer item.
This interactive example demonstrates the concept using React. If you're using a different framework, adapt the core concepts to your preferred technology.
function Example() { const viewerRef = React.useRef(null); const [viewerItemIndex, setViewerItemIndex] = useState(0); const [localItems, setLocalItems] = useState([]); const [configItems, setConfigItems] = useState([]); const [viewerFeatures, setViewerFeatures] = useState({}); useEffect(() => { const viewer = viewerRef.current; if (!viewer) return; const onLocalItemsChange = event => { setLocalItems(event.detail); }; const onRemoteItemsChange = event => { setConfigItems(event.detail.items); }; const onItemChange = event => { setViewerItemIndex(event.detail.index); }; const onFeaturesChange = event => { setViewerFeatures(event.detail); }; viewer.addEventListener("items-change", onLocalItemsChange); viewer.addEventListener("config-change", onRemoteItemsChange); viewer.addEventListener("item-change", onItemChange); viewer.addEventListener("features-change", onFeaturesChange); return () => { viewer.removeEventListener("items-change", onLocalItemsChange); viewer.removeEventListener("config-change", onRemoteItemsChange); viewer.removeEventListener("item-change", onItemChange); viewer.removeEventListener("features-change", onFeaturesChange); }; }, []); const items = [ ...configItems, ...localItems, // Append the custom items with different materials { type: "360", custom: true, features: { UPHOLSTERY: "MONTREAL SAND" }, frame: 1, }, { type: "360", custom: true, features: { UPHOLSTERY: "VICTORY TEAL" }, frame: 16, }, { type: "360StaticFrame", custom: true, features: { UPHOLSTERY: "ELEMENT EMERALD" }, frame: 1, } ]; const customerId = "5098"; const code = "WHISTLER SOFA BED"; return ( <cylindo-viewer ref={viewerRef} customer-id={customerId} code={code} remote-config="k2hctc08" > <div slot="thumbnail-bar" className="thumbnail-bar"> {items.map((item, index) => { const isSelected = viewerItemIndex === index; const features = item.features || viewerFeatures; return ( <button key={index} className={`thumb ${isSelected ? "selected" : ""}`} onClick={() => { const viewer = viewerRef.current; // For our appended items if (item.custom) { // Find the corresponding item const _item = items.find(i => i.type === item.type); // Append the new feature (Material variation) viewer.features = { ...features, ...item.features, }; // Set the new item viewer.item = { viewerItem: _item, viewerItemIndex: index, }; if (item.frame) { viewer.frame = item.frame; } return; } viewer.item = { viewerItem: item, viewerItemIndex: index, }; }} > <img className="thumb-image" src={getThumbnailImgSrc({ customerId, code, item, features, })} alt={`Thumbnail bar item - ${item.type}`} draggable="false" /> </button> ); })} </div> </cylindo-viewer> ); // Get the thumbs images from the Content API function getThumbnailImgSrc({ item, customerId, code, features }) { const baseUrl = `https://content.cylindo.com/api/v2/${customerId}/products/`; // For the sake of this example, we take only the first feature available UPHOLSTERY. const featureOption = features["UPHOLSTERY"]; const featuresParams = featureOption ? `&feature=UPHOLSTERY:${encodeURIComponent(featureOption)}` : ""; switch (item.type) { case "studio": return `${baseUrl}${item.code}/frames/1/${item.code}.webp?size=105${featuresParams}`; case "360StaticFrame": return `${baseUrl}${code}/frames/${item.frame}/${code}.webp?size=105${featuresParams}`; case "360": return `${baseUrl}${code}/frames/${ item.frame || 1 }/${code}.webp?size=105${featuresParams}`; case "model": return `${baseUrl}${code}/frames/1/${code}.webp?size=105${featuresParams}`; case "dimensionShot": return `${baseUrl}${code}/dimensions/${code}.webp?dimensionCode=${item.dimensionCode}&dimensionLabelUnit=${item.dimensionLabelUnit}&size=105${featuresParams}`; case "swatch": return `${baseUrl}${code}/material/${code}.webp?crop=(32,32,64,64)&size=105&size=105&feature=UPHOLSTERY:${encodeURIComponent( featureOption || item.defaultOptionCode )}`; default: throw Error(`Unhandled item type: ${item.type}`); } } }
The following code shows the styles used for all the examples above.
::part(thumbnail-bar-fullscreen-container) {
display: flex;
justify-content: center;
align-items: center;
padding: 4px 8px;
}
.thumbnail-bar {
display: flex;
gap: 4px;
display: flex;
column-gap: 10px;
margin: 10px 0;
align-items: center;
padding: 0 1em;
}
.thumb {
cursor: pointer;
padding: 0;
background-color: #abafad26;
border: 1px solid transparent;
border-radius: 8px;
padding: 4px;
box-sizing: content-box;
width: 64px;
height: 48px;
flex-shrink: 0;
transition: border 500ms;
}
.selected {
border: 1px solid #9bb0be;
}
.thumb-image {
display: flex;
justify-content: center;
align-items: center;
object-fit: cover;
width: 100%;
height: 100%;
border-radius: 4px;
}