mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 10:25:42 +03:00
website: use global featured-slider component (#10254)
* website: use global featured-slider component * website: delete unused local featured-slider
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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%;
|
||||
}
|
||||
}
|
||||
34
website/package-lock.json
generated
34
website/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user