Skip to content

Commit a3cc172

Browse files
committed
feat(homepage): add remaining content for homepage (no scroll animation)
1 parent 8097b15 commit a3cc172

File tree

20 files changed

+599
-224
lines changed

20 files changed

+599
-224
lines changed

src/components/BlueCard.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface BlueCardProps {
1010
id?: string
1111
small?: boolean
1212
text: string
13-
title: string
13+
title: React.ReactNode
1414
}
1515

1616
export default function BlueCard({
@@ -36,7 +36,7 @@ export default function BlueCard({
3636
}
3737
`}
3838
/>
39-
<h3>{title}</h3>
39+
{typeof title === 'string' ? <h3>{title}</h3> : title}
4040
<div className="title">{text}</div>
4141
{children}
4242
</section>
@@ -59,8 +59,8 @@ const styles = {
5959
}
6060
6161
.title {
62-
max-width: 815px;
63-
margin: 0 auto 30px;
62+
max-width: 700px;
63+
margin: 0 auto 50px;
6464
}
6565
6666
${theme.MIN_DEFAULT_MEDIA_QUERY} {

src/components/Button.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
2121

2222
export default function Button({
2323
children,
24+
className,
2425
extraCss,
2526
loadingCss,
2627
large,
@@ -60,7 +61,10 @@ export default function Button({
6061
classNames.push('btn-link')
6162
}
6263
return (
63-
<button css={extraCss} className={classNames.join(' ')} {...props}>
64+
<button
65+
css={extraCss}
66+
className={`${className} ${classNames.join(' ')}`}
67+
{...props}>
6468
{submitting ? (
6569
<Fragment>
6670
<div style={{ visibility: 'hidden' }}>{children}</div>

src/components/Carousel.tsx

Lines changed: 114 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
import { css } from '@emotion/react'
2-
import React, { PureComponent } from 'react'
31
import * as theme from '../styles/theme'
2+
3+
import React, { PureComponent } from 'react'
4+
45
import Button from './Button'
56
import SVGIcon from './SVGIcon'
7+
import { css } from '@emotion/react'
68

79
interface Props {
810
children: React.ReactNode
9-
keys: string[]
10-
maxWidth: number
11+
keys?: string[]
12+
// Set to 0 to stop
1113
interval?: number
14+
maxWidth: number
15+
numSlides: number
16+
showDesktopButtons?: boolean
1217
}
1318

1419
interface State {
@@ -98,10 +103,6 @@ export default class Carousel extends PureComponent<Props, State> {
98103
this.addListeners()
99104
}
100105

101-
getNumSlides() {
102-
return this.slides.current?.childElementCount || 1
103-
}
104-
105106
getCurrentWidth() {
106107
if (!this.container.current) {
107108
return 0
@@ -111,10 +112,10 @@ export default class Carousel extends PureComponent<Props, State> {
111112

112113
constrainX(x: number) {
113114
const width = this.getCurrentWidth()
115+
const { numSlides } = this.props
114116
const { index } = this.state
115-
const len = this.getNumSlides()
116117
return Math.max(
117-
-width * (index < len - 1 ? index + 1 : index),
118+
-width * (index < numSlides - 1 ? index + 1 : index),
118119
Math.min(-width * (index > 0 ? index - 1 : index), x)
119120
)
120121
}
@@ -151,67 +152,84 @@ export default class Carousel extends PureComponent<Props, State> {
151152
}
152153

153154
next = () => {
155+
const { numSlides } = this.props
154156
const { index } = this.state
155-
const len = this.getNumSlides()
156157
this.setState({
157-
index: (index + 1) % len,
158+
index: (index + 1) % numSlides,
158159
moving: false
159160
})
160161
}
161162

162163
prev = () => {
164+
const { numSlides } = this.props
163165
const { index } = this.state
164-
const len = this.getNumSlides()
165166
this.setState({
166-
index: (index + len - 1) % len,
167+
index: (index + numSlides - 1) % numSlides,
167168
moving: false
168169
})
169170
}
170171

171172
start() {
172-
this.timeout = setInterval(this.next, this.props.interval)
173+
const { interval } = this.props
174+
if (interval! > 0) {
175+
this.timeout = setInterval(this.next, interval)
176+
}
173177
}
174178

175179
stop() {
176180
clearTimeout(this.timeout!)
177181
}
178182

179183
render() {
180-
const { children, keys, maxWidth } = this.props
184+
const {
185+
children,
186+
keys = [],
187+
maxWidth,
188+
numSlides,
189+
showDesktopButtons
190+
} = this.props
181191
const { index, moving, x } = this.state
192+
const buttonStyle = [styles.moveButton]
193+
const arrowStyle = [styles.arrow]
194+
if (showDesktopButtons) {
195+
buttonStyle.push(styles.desktopButton)
196+
arrowStyle.push(styles.desktopArrow)
197+
}
182198

183199
return (
184-
<div ref={this.container} style={{ maxWidth }}>
185-
<div css={styles.buttons}>
186-
{keys.map((key, i) => (
187-
<Button
188-
key={key}
189-
transparent={i !== index}
190-
extraCss={[
191-
styles.button,
192-
css`
193-
${theme.DEFAULT_MEDIA_QUERY} {
194-
:not(:nth-of-type(${index + 1})) {
195-
display: none;
200+
<div css={styles.carousel} ref={this.container} style={{ maxWidth }}>
201+
{!!keys.length && (
202+
<div css={styles.buttons}>
203+
{keys.map((key, i) => (
204+
<Button
205+
key={key}
206+
transparent={i !== index}
207+
extraCss={[
208+
styles.button,
209+
css`
210+
${theme.DEFAULT_MEDIA_QUERY} {
211+
:not(:nth-of-type(${index + 1})) {
212+
display: none;
213+
}
196214
}
215+
`
216+
]}
217+
onClick={() => {
218+
this.stop()
219+
if (
220+
window.matchMedia(`(min-width:${theme.DEFAULT_WIDTH}px)`)
221+
.matches
222+
) {
223+
this.setState({ index: i })
224+
} else {
225+
this.next()
197226
}
198-
`
199-
]}
200-
onClick={() => {
201-
this.stop()
202-
if (
203-
window.matchMedia(`(min-width:${theme.DEFAULT_WIDTH}px)`)
204-
.matches
205-
) {
206-
this.setState({ index: i })
207-
} else {
208-
this.next()
209-
}
210-
}}>
211-
{key}
212-
</Button>
213-
))}
214-
</div>
227+
}}>
228+
{key}
229+
</Button>
230+
))}
231+
</div>
232+
)}
215233
<div css={styles.content}>
216234
<div css={styles.slidesWrap}>
217235
<div
@@ -223,32 +241,34 @@ export default class Carousel extends PureComponent<Props, State> {
223241
style={{
224242
transform: moving
225243
? `translateX(${x}px)`
226-
: `translateX(-${index * 25}%)`,
227-
width: `${keys.length * 100}%`
244+
: `translateX(-${index * (100 / numSlides)}%)`,
245+
width: `${numSlides * 100}%`
228246
}}>
229247
{children}
230248
</div>
231249
</div>
232250
<Button
233-
link
251+
link={!showDesktopButtons}
234252
onClick={() => {
235253
this.stop()
236254
this.prev()
237255
}}
238-
extraCss={[styles.moveButton, styles.prevButton]}>
256+
className="prev-button"
257+
extraCss={buttonStyle}>
239258
<SVGIcon
240-
icon="#arrow-down"
241-
extraCss={[styles.arrow, styles.backArrow]}
259+
icon="#arrow-forward"
260+
extraCss={arrowStyle.concat(styles.backArrow)}
242261
/>
243262
</Button>
244263
<Button
245-
link
264+
link={!showDesktopButtons}
246265
onClick={() => {
247266
this.stop()
248267
this.next()
249268
}}
250-
extraCss={[styles.moveButton, styles.nextButton]}>
251-
<SVGIcon icon="#arrow-down" extraCss={[styles.arrow]} />
269+
className="next-button"
270+
extraCss={buttonStyle}>
271+
<SVGIcon icon="#arrow-forward" extraCss={arrowStyle} />
252272
</Button>
253273
</div>
254274
</div>
@@ -257,6 +277,10 @@ export default class Carousel extends PureComponent<Props, State> {
257277
}
258278

259279
const styles = {
280+
carousel: css`
281+
width: 100%;
282+
cursor: default;
283+
`,
260284
buttons: css`
261285
display: flex;
262286
flex-direction: row;
@@ -291,25 +315,50 @@ const styles = {
291315
`,
292316
moveButton: css`
293317
position: absolute;
294-
top: 50%;
295-
transform: translateY(-50%);
318+
top: -92px;
319+
320+
&.prev-button {
321+
left: 0;
322+
}
323+
324+
&.next-button {
325+
right: 0;
326+
}
296327
297328
${theme.MIN_DEFAULT_MEDIA_QUERY} {
298329
display: none;
299330
}
300331
`,
301-
prevButton: css`
302-
left: -20px;
303-
`,
304-
nextButton: css`
305-
right: -20px;
332+
desktopButton: css`
333+
width: 40px;
334+
height: 40px;
335+
border-radius: 50%;
336+
top: 50%;
337+
transform: translateY(-50%);
338+
339+
${theme.MIN_DEFAULT_MEDIA_QUERY} {
340+
display: flex;
341+
width: 60px;
342+
height: 60px;
343+
344+
&.prev-button {
345+
left: -100px;
346+
}
347+
348+
&.next-button {
349+
right: -100px;
350+
}
351+
}
306352
`,
307353
arrow: css`
308-
width: 14px;
309-
height: 15px;
310-
transform: rotateZ(-90deg);
354+
width: 32px;
355+
height: 32px;
356+
fill: ${theme.primary};
357+
`,
358+
desktopArrow: css`
359+
fill: white;
311360
`,
312361
backArrow: css`
313-
transform: rotateZ(90deg);
362+
transform: rotateY(180deg);
314363
`
315364
}

src/components/Create.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import * as theme from '../styles/theme'
2+
13
import BlueCard, { BlueCardProps } from './BlueCard'
24

35
import React from 'react'
46
import SVGIcon from './SVGIcon'
57
import { css } from '@emotion/react'
6-
import * as theme from '../styles/theme'
78

89
interface Props extends Partial<Omit<BlueCardProps, 'children'>> {
910
hidePricing?: boolean
@@ -13,8 +14,13 @@ export default function Create({ hidePricing, ...props }: Props) {
1314
return (
1415
<BlueCard
1516
id="create"
16-
title="Create a free Spokestack account"
17-
text={`Access our collection of synthetic voices and private speech recognition, download offline wake words, create your own NLU, and more!`}
17+
title={
18+
<h2 css={styles.title}>
19+
Become a Spokestack Maker and{' '}
20+
<a href="https://twitter.com/hashtag/ownyourvoice">#OwnYourVoice</a>
21+
</h2>
22+
}
23+
text="Access our hosted services for model import, natural language processing, text-to-speech, and wakeword."
1824
{...props}>
1925
<div css={styles.buttons}>
2026
<a href="/account/create" className="btn">
@@ -41,6 +47,9 @@ export default function Create({ hidePricing, ...props }: Props) {
4147
}
4248

4349
const styles = {
50+
title: css`
51+
max-width: 615px;
52+
`,
4453
buttons: css`
4554
display: flex;
4655
flex-direction: column;
@@ -53,8 +62,12 @@ const styles = {
5362
${theme.MIN_DEFAULT_MEDIA_QUERY} {
5463
flex-direction: row;
5564
56-
.btn:first-of-type {
57-
margin: 0 20px 0 0;
65+
.btn {
66+
width: 225px;
67+
68+
&:first-of-type {
69+
margin: 0 20px 0 0;
70+
}
5871
}
5972
}
6073
`,

src/components/Layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ export default function Layout({
2929
if (banner) {
3030
style.push(css`
3131
top: 40px;
32+
${theme.DEFAULT_MEDIA_QUERY} {
33+
top: 80px;
34+
}
3235
`)
3336
const navPushStyle = css`
3437
${theme.DEFAULT_MEDIA_QUERY} {

0 commit comments

Comments
 (0)