github.com/uchennaokeke444/nomad@v0.11.8/website/components/featured-slider/index.jsx (about) 1 import React, { Component } from 'react' 2 import StatusBar from './StatusBar' 3 import marked from 'marked' 4 import Button from '@hashicorp/react-button' 5 import Image from '@hashicorp/react-image' 6 7 class FeaturedSlider extends Component { 8 constructor(props) { 9 super(props) 10 const timing = this.props.timing ? parseInt(this.props.timing) : 10 11 this.state = { 12 active: 0, 13 timing: timing, 14 numFrames: this.props.features.length, 15 measure: true, 16 containerWidth: 0 17 } 18 19 this.frames = [] 20 21 this.handleClick = this.handleClick.bind(this) 22 this.throttledResize = this.throttledResize.bind(this) 23 this.measureFrameSize = this.measureFrameSize.bind(this) 24 this.resetTimer = this.resetTimer.bind(this) 25 this.resizeTimeout = null 26 } 27 28 componentDidMount() { 29 if (this.state.numFrames > 1) { 30 this.timer = setInterval(() => this.tick(), this.state.timing * 1000) 31 this.measureFrameSize() 32 } 33 window.addEventListener('resize', this.throttledResize, false) 34 } 35 36 componentWillUnmount() { 37 clearInterval(this.timer) 38 window.removeEventListener('resize', this.throttledResize) 39 } 40 41 componentDidUpdate(prevProps, prevState) { 42 if (this.props.features !== prevProps.features) { 43 if (this.props.features.length != prevState.numFrames) { 44 this.setState( 45 { 46 numFrames: this.props.features.length, 47 measure: true 48 }, 49 () => { 50 if (this.props.features.length === 1) { 51 clearInterval(this.timer) 52 window.removeEventListener('resize', this.throttledResize) 53 } 54 } 55 ) 56 } 57 if (prevState.active > this.props.features.length - 1) { 58 this.setState({ active: 0 }) 59 } 60 } 61 62 if (this.props.timing && parseInt(this.props.timing) != prevState.timing) { 63 this.setState( 64 { 65 timing: parseInt(this.props.timing), 66 active: 0 67 }, 68 this.resetTimer 69 ) 70 } 71 // If we're measuring on this update get the width 72 if (!prevState.measure && this.state.measure && this.state.numFrames > 1) { 73 this.measureFrameSize() 74 } 75 } 76 77 resetTimer() { 78 clearInterval(this.timer) 79 this.timer = setInterval(() => this.tick(), this.state.timing * 1000) 80 } 81 82 throttledResize() { 83 this.resizeTimeout && clearTimeout(this.resizeTimeout) 84 this.resizeTimeout = setTimeout(() => { 85 this.resizeTimeout = null 86 this.setState({ measure: true }) 87 }, 250) 88 } 89 90 tick() { 91 const nextSlide = 92 this.state.active === this.state.numFrames - 1 ? 0 : this.state.active + 1 93 this.setState({ active: nextSlide }) 94 } 95 96 handleClick(i) { 97 if (i === this.state.active) return 98 this.setState({ active: i }, this.resetTimer) 99 } 100 101 measureFrameSize() { 102 // All frames are the same size, so we measure the first one 103 if (this.frames[0]) { 104 const { width } = this.frames[0].getBoundingClientRect() 105 this.setState({ 106 frameSize: width, 107 containerWidth: width * this.state.numFrames, 108 measure: false 109 }) 110 } 111 } 112 113 render() { 114 // Clear our frames array so we don't keep old refs around 115 this.frames = [] 116 const { theme, brand, features } = this.props 117 const { measure, active, timing, numFrames, containerWidth } = this.state 118 119 const single = numFrames === 1 120 121 // Create inline styling for slide container 122 // If we're measuring, or have a single slide then no inline styles should be applied 123 const containerStyle = 124 measure || single 125 ? {} 126 : { 127 width: `${containerWidth}px`, 128 transform: `translateX(-${(containerWidth / numFrames) * active}px` 129 } 130 131 // Create inline styling to apply to each frame 132 // If we're measuring or have a single slide then no inline styles should be applied 133 const frameStyle = 134 measure || single ? {} : { float: 'left', width: `${100 / numFrames}%` } 135 136 return ( 137 <div className="g-featured-slider"> 138 {!single && ( 139 <div 140 className={`logo-bar-container${numFrames === 2 ? ' double' : ''}`} 141 > 142 {features.map((feature, i) => ( 143 <div 144 className="logo-bar" 145 onClick={() => this.handleClick(i)} 146 key={feature.logo.url} 147 > 148 <div className="logo-container"> 149 <Image url={feature.logo.url} alt={feature.logo.alt} /> 150 </div> 151 <StatusBar 152 theme={theme} 153 active={active === i} 154 timing={timing} 155 brand={brand} 156 /> 157 </div> 158 ))} 159 </div> 160 )} 161 <div className="feature-container"> 162 <div className="slider-container" style={containerStyle}> 163 {/* React pushes a null ref the first time, so we're filtering those out. */} 164 {/* see https://reactjs.org/docs/refs-and-the-dom.html#caveats-with-callback-refs */} 165 {features.map(feature => ( 166 <div 167 className={`slider-frame${single ? ' single' : ''}`} 168 style={frameStyle} 169 ref={el => el && this.frames.push(el)} 170 key={feature.heading} 171 > 172 <div className="feature"> 173 <div className="feature-image"> 174 <a href={feature.link.url}> 175 <Image 176 url={feature.image.url} 177 alt={feature.image.alt} 178 aspectRatio={single ? [16, 10, 500] : [16, 9, 500]} 179 /> 180 </a> 181 </div> 182 <div className="feature-content g-type-body"> 183 {single && ( 184 <div className="single-logo"> 185 <Image url={feature.logo.url} alt={feature.logo.alt} /> 186 </div> 187 )} 188 <h3 189 className="g-type-display-4" 190 dangerouslySetInnerHTML={{ 191 __html: marked.inlineLexer(feature.heading, []) 192 }} 193 /> 194 <p 195 className="g-type-body" 196 dangerouslySetInnerHTML={{ 197 __html: marked.inlineLexer(feature.content, []) 198 }} 199 /> 200 <Button 201 theme={{ 202 brand, 203 variant: 'secondary', 204 background: theme 205 }} 206 linkType={feature.link.type} 207 title={feature.link.text} 208 url={feature.link.url} 209 /> 210 </div> 211 </div> 212 </div> 213 ))} 214 </div> 215 </div> 216 </div> 217 ) 218 } 219 } 220 221 export default FeaturedSlider