website: use global featured-slider component (#10254)

* website: use global featured-slider component

* website: delete unused local featured-slider
This commit is contained in:
Zachary Shilton
2021-03-29 11:37:48 -04:00
committed by GitHub
parent 9dc47fd8b7
commit 5ca4acff06
12 changed files with 48 additions and 486 deletions

View File

@@ -1,12 +0,0 @@
import FeaturedSlider from '../featured-slider'
export default function FeaturedSliderSection({ heading, features }) {
return (
<section className="g-featured-slider-section">
<div className="g-grid-container">
<h2 className="g-type-display-2">{heading}</h2>
<FeaturedSlider theme="dark" brand="nomad" features={features} />
</div>
</section>
)
}

View File

@@ -1,12 +0,0 @@
.g-featured-slider-section {
background: var(--black);
padding-top: 128px;
padding-bottom: 128px;
color: var(--white);
& h2 {
margin-top: 0;
color: var(--white);
text-align: center;
}
}

View File

@@ -1,16 +0,0 @@
import React from 'react'
export default function StatusBar({ theme, active, timing, brand }) {
return (
<div className={`progress-bar ${theme}`}>
<span
className={`${active ? ' active' : ''} ${brand ? brand : ''}`}
style={
active
? { animationDuration: `${timing}s` }
: { animationDuration: '0s' }
}
/>
</div>
)
}

View File

@@ -1,221 +0,0 @@
import React, { Component } from 'react'
import StatusBar from './StatusBar'
import marked from 'marked'
import Button from '@hashicorp/react-button'
import Image from '@hashicorp/react-image'
class FeaturedSlider extends Component {
constructor(props) {
super(props)
const timing = this.props.timing ? parseInt(this.props.timing) : 10
this.state = {
active: 0,
timing: timing,
numFrames: this.props.features.length,
measure: true,
containerWidth: 0,
}
this.frames = []
this.handleClick = this.handleClick.bind(this)
this.throttledResize = this.throttledResize.bind(this)
this.measureFrameSize = this.measureFrameSize.bind(this)
this.resetTimer = this.resetTimer.bind(this)
this.resizeTimeout = null
}
componentDidMount() {
if (this.state.numFrames > 1) {
this.timer = setInterval(() => this.tick(), this.state.timing * 1000)
this.measureFrameSize()
}
window.addEventListener('resize', this.throttledResize, false)
}
componentWillUnmount() {
clearInterval(this.timer)
window.removeEventListener('resize', this.throttledResize)
}
componentDidUpdate(prevProps, prevState) {
if (this.props.features !== prevProps.features) {
if (this.props.features.length != prevState.numFrames) {
this.setState(
{
numFrames: this.props.features.length,
measure: true,
},
() => {
if (this.props.features.length === 1) {
clearInterval(this.timer)
window.removeEventListener('resize', this.throttledResize)
}
}
)
}
if (prevState.active > this.props.features.length - 1) {
this.setState({ active: 0 })
}
}
if (this.props.timing && parseInt(this.props.timing) != prevState.timing) {
this.setState(
{
timing: parseInt(this.props.timing),
active: 0,
},
this.resetTimer
)
}
// If we're measuring on this update get the width
if (!prevState.measure && this.state.measure && this.state.numFrames > 1) {
this.measureFrameSize()
}
}
resetTimer() {
clearInterval(this.timer)
this.timer = setInterval(() => this.tick(), this.state.timing * 1000)
}
throttledResize() {
this.resizeTimeout && clearTimeout(this.resizeTimeout)
this.resizeTimeout = setTimeout(() => {
this.resizeTimeout = null
this.setState({ measure: true })
}, 250)
}
tick() {
const nextSlide =
this.state.active === this.state.numFrames - 1 ? 0 : this.state.active + 1
this.setState({ active: nextSlide })
}
handleClick(i) {
if (i === this.state.active) return
this.setState({ active: i }, this.resetTimer)
}
measureFrameSize() {
// All frames are the same size, so we measure the first one
if (this.frames[0]) {
const { width } = this.frames[0].getBoundingClientRect()
this.setState({
frameSize: width,
containerWidth: width * this.state.numFrames,
measure: false,
})
}
}
render() {
// Clear our frames array so we don't keep old refs around
this.frames = []
const { theme, brand, features } = this.props
const { measure, active, timing, numFrames, containerWidth } = this.state
const single = numFrames === 1
// Create inline styling for slide container
// If we're measuring, or have a single slide then no inline styles should be applied
const containerStyle =
measure || single
? {}
: {
width: `${containerWidth}px`,
transform: `translateX(-${(containerWidth / numFrames) * active}px`,
}
// Create inline styling to apply to each frame
// If we're measuring or have a single slide then no inline styles should be applied
const frameStyle =
measure || single ? {} : { float: 'left', width: `${100 / numFrames}%` }
return (
<div className="g-featured-slider">
{!single && (
<div
className={`logo-bar-container${numFrames === 2 ? ' double' : ''}`}
>
{features.map((feature, i) => (
<div
className="logo-bar"
onClick={() => this.handleClick(i)}
key={feature.logo.url}
>
<div className="logo-container">
<Image url={feature.logo.url} alt={feature.logo.alt} />
</div>
<StatusBar
theme={theme}
active={active === i}
timing={timing}
brand={brand}
/>
</div>
))}
</div>
)}
<div className="feature-container">
<div className="slider-container" style={containerStyle}>
{/* React pushes a null ref the first time, so we're filtering those out. */}
{/* see https://reactjs.org/docs/refs-and-the-dom.html#caveats-with-callback-refs */}
{features.map((feature) => (
<div
className={`slider-frame${single ? ' single' : ''}`}
style={frameStyle}
ref={(el) => el && this.frames.push(el)}
key={feature.heading}
>
<div className="feature">
<div className="feature-image">
<a href={feature.link.url}>
<Image
url={feature.image.url}
alt={feature.image.alt}
aspectRatio={single ? [16, 10, 500] : [16, 9, 500]}
/>
</a>
</div>
<div className="feature-content g-type-body">
{single && (
<div className="single-logo">
<Image url={feature.logo.url} alt={feature.logo.alt} />
</div>
)}
<h3
className="g-type-display-4"
dangerouslySetInnerHTML={{
__html: marked.inlineLexer(feature.heading, []),
}}
/>
<p
className="g-type-body"
dangerouslySetInnerHTML={{
__html: marked.inlineLexer(feature.content, []),
}}
/>
<Button
theme={{
brand,
variant: 'secondary',
background: theme,
}}
linkType={feature.link.type}
title={feature.link.text}
url={feature.link.url}
/>
</div>
</div>
</div>
))}
</div>
</div>
</div>
)
}
}
export default FeaturedSlider

View File

@@ -1,41 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import FeaturedSlider from './dist/index.js'
function FeaturedSliderProps(props) {
return <FeaturedSlider {...props} />
}
FeaturedSliderProps.propTypes = {
theme: PropTypes.oneOf(['light', 'dark']),
brand: PropTypes.oneOf([
'hashicorp',
'terraform',
'vault',
'consul',
'nomad',
'packer',
'vagrant',
]),
features: PropTypes.arrayOf(
PropTypes.shape({
logo: PropTypes.shape({
url: PropTypes.string,
alt: PropTypes.string,
}),
image: PropTypes.shape({
url: PropTypes.string,
alt: PropTypes.string,
}),
heading: PropTypes.string,
content: PropTypes.string,
link: PropTypes.shape({
text: PropTypes.string,
url: PropTypes.string,
type: PropTypes.oneOf(['anchor', 'inbound', 'outbound']),
}),
})
),
}
export default FeaturedSliderProps

View File

@@ -1,176 +0,0 @@
.g-featured-slider {
& .logo-bar-container {
display: flex;
padding: 32px 0;
align-items: center;
justify-content: center;
& .logo-bar {
flex-basis: 33.333%;
cursor: pointer;
position: relative;
transition: transform 0.2s ease;
margin-right: 32px;
&:last-child {
margin-right: 0;
}
& .logo-container {
height: 84px;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 24px;
& picture,
& img {
object-fit: contain;
width: 100%;
height: 100%;
}
@media (min-width: 768px) {
height: 92px;
padding: 32px;
}
}
& .progress-bar {
width: 100%;
height: 2px;
display: block;
background-color: var(--DEPRECATED-gray-9);
&.dark {
background-color: var(--DEPRECATED-gray-3);
}
& span {
width: 0;
background-color: var(--brand);
height: 100%;
display: block;
animation-duration: 10s;
&.nomad {
background-color: var(--nomad);
}
&.consul {
background-color: var(--consul);
}
&.terraform {
background-color: var(--terraform);
}
&.active {
animation-name: case-study-bar;
animation-timing-function: linear;
}
}
}
&:hover {
transform: translateY(-4px);
}
}
/* When there are two case studies */
&.double {
& .logo-bar {
flex-basis: 50%;
}
}
@media (min-width: 768px) {
padding: 0 0 48px;
}
}
& .feature-container {
overflow: hidden;
& .slider-container {
transition: transform 400ms ease-out;
& .slider-frame {
& .feature {
& .feature-image {
margin-bottom: 2rem;
& img,
& picture {
width: 100%;
height: auto;
}
}
& .feature-content {
text-align: center;
& h3 {
margin: 0 0 8px;
}
& .single-logo {
margin-bottom: 32px;
width: 100%;
height: 65px;
& picture,
& img {
height: 100%;
width: auto;
}
}
& .g-btn {
margin-top: 32px;
}
}
@media (min-width: 768px) {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
& .feature-image {
margin-bottom: 0;
flex-basis: 60%;
margin-right: 64px;
}
& .feature-content {
flex-basis: 40%;
text-align: left;
& p {
margin: 0;
& + p {
margin-top: 1em;
}
}
}
}
}
&.single {
& .case-study {
align-items: flex-start;
}
}
}
}
}
}
@keyframes case-study-bar {
0% {
width: 0;
}
100% {
width: 100%;
}
}

View File

@@ -1556,6 +1556,40 @@
"@hashicorp/js-utils": "^1.0.10"
}
},
"@hashicorp/react-featured-slider": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@hashicorp/react-featured-slider/-/react-featured-slider-2.0.0.tgz",
"integrity": "sha512-JShdDxe3+3xGh2d4VsfautZxih/qG6QAmSVbt2TtaCaEVm7lVdJ1e4uo0QQ5zTlrlrpBNacD23ZXWIgMJYZY9g==",
"requires": {
"@hashicorp/react-button": "^2.2.6",
"@hashicorp/react-image": "^2.0.3"
},
"dependencies": {
"@hashicorp/react-button": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@hashicorp/react-button/-/react-button-2.3.0.tgz",
"integrity": "sha512-1C6V8OxadkdDAkwgItDfNxx7ns9EB6znK39V94RBiPvElmsNLCEG7zujcQX71V2n+HMSv1JPgDvXp4WLICzK+Q==",
"requires": {
"@hashicorp/react-inline-svg": "^1.0.0",
"slugify": "^1.3.6"
}
},
"@hashicorp/react-image": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@hashicorp/react-image/-/react-image-2.0.4.tgz",
"integrity": "sha512-rJCx74lxQE9l9LpFhlxSjQ0yjrzjce5uzEGmMgPvMsNiQtgetjNyeg1p5N8k7xRGYXNapt8uY2kZiE69OyL9cQ==",
"requires": {
"object-assign": "^4.1.1",
"query-string": "5.1.1"
}
},
"@hashicorp/react-inline-svg": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@hashicorp/react-inline-svg/-/react-inline-svg-1.0.2.tgz",
"integrity": "sha512-AAFnBslSTgnEr++dTbMn3sybAqvn7myIj88ijGigF6u11eSRiV64zqEcyYLQKWTV6dF4AvYoxiYC6GSOgiM0Yw=="
}
}
},
"@hashicorp/react-global-styles": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/@hashicorp/react-global-styles/-/react-global-styles-4.6.1.tgz",

View File

@@ -14,6 +14,7 @@
"@hashicorp/react-call-to-action": "1.0.3",
"@hashicorp/react-content": "6.2.0",
"@hashicorp/react-docs-page": "10.6.0",
"@hashicorp/react-featured-slider": "^2.0.0",
"@hashicorp/react-hashi-stack-menu": "^1.1.0",
"@hashicorp/react-hero": "4.1.0",
"@hashicorp/react-image": "3.0.3",

View File

@@ -20,6 +20,7 @@
@import '~@hashicorp/react-docs-page/style.css';
@import '~@hashicorp/react-docs-sidenav/style.css';
@import '~@hashicorp/react-enterprise-alert/style.css';
@import '~@hashicorp/react-featured-slider/style.css';
@import '~@hashicorp/react-hero/style.css';
@import '~@hashicorp/react-product-downloader/dist/style.css';
@import '~@hashicorp/react-search/style.css';
@@ -37,8 +38,6 @@
@import '../components/enterprise-info/style.css';
@import '../components/mini-cta/style.css';
@import '../components/homepage-hero/style.css';
@import '../components/featured-slider/style.css';
@import '../components/featured-slider-section/style.css';
@import '../components/learn-nomad/style.css';
@import '../components/basic-hero/style.css';
@import '../components/footer/style.css';

View File

@@ -1,6 +1,6 @@
import UseCasesLayout from 'components/use-case-page'
import TextSplitWithImage from '@hashicorp/react-text-split-with-image'
import FeaturedSliderSection from 'components/featured-slider-section'
import FeaturedSlider from '@hashicorp/react-featured-slider'
export default function AutomatedServiceNetworkingWithConsulPage() {
return (
@@ -71,8 +71,10 @@ export default function AutomatedServiceNetworkingWithConsulPage() {
}}
/>
<FeaturedSliderSection
<FeaturedSlider
heading="Case Studies"
theme="dark"
product="nomad"
features={[
{
logo: {

View File

@@ -1,6 +1,6 @@
import UseCasesLayout from 'components/use-case-page'
import TextSplitWithImage from '@hashicorp/react-text-split-with-image'
import FeaturedSliderSection from 'components/featured-slider-section'
import FeaturedSlider from '@hashicorp/react-featured-slider'
export default function NonContainerizedApplicationOrchestrationPage() {
return (
@@ -98,8 +98,10 @@ export default function NonContainerizedApplicationOrchestrationPage() {
/>
</div>
<FeaturedSliderSection
<FeaturedSlider
heading="Case Study"
product="nomad"
theme="dark"
features={[
{
logo: {

View File

@@ -1,7 +1,7 @@
import UseCasesLayout from 'components/use-case-page'
import TextSplitWithCode from '@hashicorp/react-text-split-with-code'
import TextSplitWithImage from '@hashicorp/react-text-split-with-image'
import FeaturedSliderSection from 'components/featured-slider-section'
import FeaturedSlider from '@hashicorp/react-featured-slider'
// Imports below are used in getStaticProps only
import highlightData from '@hashicorp/nextjs-scripts/prism/highlight-data'
@@ -198,8 +198,10 @@ export default function SimpleContainerOrchestrationPage({ codeBlocks }) {
}}
/>
<FeaturedSliderSection
<FeaturedSlider
heading="Case Studies"
product="nomad"
theme="dark"
features={[
{
logo: {