Skip to content

Commit c80e3d1

Browse files
authored
chore: Make home roller title more responsive (#9317)
* chore: responsive home title spinner * remove dead code * fix mobile * review feedback * enable rolling on mobile * fix typescript * fix TS
1 parent 1a6f8c3 commit c80e3d1

File tree

2 files changed

+114
-58
lines changed

2 files changed

+114
-58
lines changed

packages/dev/s2-docs/pages/s2/home/Home.tsx

Lines changed: 113 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ import bg from 'data-url:./bg.svg';
4848
import { keyframes } from "../../../../../@react-spectrum/s2/style/style-macro" with {type: 'macro'};
4949
// @ts-ignore
5050
import { getBaseUrl } from "../../../src/pageUtils";
51+
// @ts-ignore
52+
import { fontSizeToken } from "../../../../../@react-spectrum/s2/style/tokens" with {type: 'macro'};
53+
// @ts-ignore
54+
import { letters } from "../../../src/textWidth";
5155

5256
const container = style({
5357
backgroundColor: 'layer-2/80',
@@ -67,78 +71,90 @@ const container = style({
6771
const swapWrapper = style({
6872
display: 'inline-block',
6973
position: 'relative',
70-
height: '[1em]',
74+
height: '[1.2em]',
7175
overflow: 'hidden',
7276
verticalAlign: 'baseline',
7377
whiteSpace: 'nowrap',
74-
lineHeight: '[1em]',
75-
marginEnd: 12
78+
lineHeight: '[1.2]'
7679
});
7780

7881
// Track that scrolls vertically through all the items.
79-
const slideTrack = keyframes(`
82+
// Use 3D to ensure crisp text rendering.
83+
// With 10x hold time vs transition time:
84+
// Total units: (6 holds × 10) + (6 transitions × 1) = 60 + 6 = 66 units
85+
// Each transition = 100/66 = 1.515%
86+
// Each hold = 10 × 1.515% = 15.152%
87+
const slideTrack: string = keyframes(`
8088
0% {
81-
transform: translateY(0%);
89+
transform: translate3d(0, 0, 0);
8290
}
83-
15% {
84-
transform: translateY(0%);
91+
15.15% {
92+
transform: translate3d(0, 0, 0);
8593
}
86-
16% {
87-
transform: translateY(-16.666%);
94+
16.67% {
95+
transform: translate3d(0, -1.2em, 0);
8896
}
89-
31% {
90-
transform: translateY(-16.666%);
97+
31.82% {
98+
transform: translate3d(0, -1.2em, 0);
9199
}
92-
32% {
93-
transform: translateY(-33.333%);
100+
33.33% {
101+
transform: translate3d(0, -2.4em, 0);
94102
}
95-
47% {
96-
transform: translateY(-33.333%);
103+
48.48% {
104+
transform: translate3d(0, -2.4em, 0);
97105
}
98-
48% {
99-
transform: translateY(-50%);
106+
50% {
107+
transform: translate3d(0, -3.6em, 0);
100108
}
101-
63% {
102-
transform: translateY(-50%);
109+
65.15% {
110+
transform: translate3d(0, -3.6em, 0);
103111
}
104-
64% {
105-
transform: translateY(-66.666%);
112+
66.67% {
113+
transform: translate3d(0, -4.8em, 0);
106114
}
107-
79% {
108-
transform: translateY(-66.666%);
115+
81.82% {
116+
transform: translate3d(0, -4.8em, 0);
109117
}
110-
80% {
111-
transform: translateY(-83.333%);
118+
83.33% {
119+
transform: translate3d(0, -6em, 0);
112120
}
113-
95% {
114-
transform: translateY(-83.333%);
121+
98.48% {
122+
transform: translate3d(0, -6em, 0);
115123
}
116124
100% {
117-
transform: translateY(0%);
125+
transform: translate3d(0, -7.2em, 0);
118126
}
119127
`);
120128

121129
const swapTrack = style({
122-
animation: slideTrack,
123-
animationDuration: 15000,
124-
animationTimingFunction: 'linear',
125-
animationIterationCount: 'infinite',
126-
display: 'flex',
130+
position: 'relative',
131+
display: {
132+
default: 'flex',
133+
'@media (prefers-reduced-motion: reduce)': 'none'
134+
},
127135
flexDirection: 'column',
128-
whiteSpace: 'nowrap'
136+
whiteSpace: 'nowrap',
137+
height: '[1.2em]',
138+
overflow: 'hidden',
139+
fontSize: '[1em]',
140+
willChange: 'transform'
129141
});
130142

131-
// for measuring longest word
132143
const swapSizer = style({
133-
opacity: 0,
144+
display: {
145+
default: 'none',
146+
'@media (prefers-reduced-motion: reduce)': 'block'
147+
},
134148
whiteSpace: 'nowrap'
135149
});
136150

137-
138151
const swapRow = style({
139-
display: 'block',
140-
height: '[1em]',
141-
lineHeight: '[1em]'
152+
animation: slideTrack,
153+
animationDuration: 10000,
154+
animationTimingFunction: 'linear',
155+
animationIterationCount: 'infinite',
156+
lineHeight: '[1.2]',
157+
height: '[1.2em]'
142158
});
143159

144160
export function Home() {
@@ -197,21 +213,21 @@ export function Home() {
197213
</div>
198214
</nav>
199215
<header aria-labelledby={headingId} className={style({marginX: 'auto', paddingX: {default: 16, sm: 40}, paddingY: 96, maxWidth: 1024})}>
200-
<h1 id={headingId} className={style({font: 'heading-3xl', marginY: 0, color: 'white'})}>
201-
<span className={swapWrapper} >Build apps </span>
216+
<HomeH1 id={headingId}>
217+
<span className={swapWrapper}>Build apps with&nbsp;</span>
202218
<span className={swapWrapper}>
203-
{/* @ts-ignore */}
204-
<span className={swapTrack}>
205-
<span className={swapRow}>with polish</span>
206-
<span className={swapRow}>with speed</span>
207-
<span className={swapRow}>with ease</span>
208-
<span className={swapRow}>with accessibility</span>
209-
<span className={swapRow}>with consistency</span>
210-
<span className={swapRow}>with React Spectrum</span>
211-
</span>
212-
<span className={swapSizer} aria-hidden>with React Spectrum</span>
219+
<div className={swapTrack}>
220+
<span className={swapRow}>polish</span>
221+
<span className={swapRow}>speed</span>
222+
<span className={swapRow}>ease</span>
223+
<span className={swapRow}>accessibility</span>
224+
<span className={swapRow}>consistency</span>
225+
<span className={swapRow}>React Spectrum</span>
226+
<span className={swapRow} aria-hidden>polish</span>
227+
</div>
228+
<span className={swapSizer} aria-hidden>React Spectrum</span>
213229
</span>
214-
</h1>
230+
</HomeH1>
215231
<p className={style({font: 'body-3xl', marginY: 0, color: 'white'})}>React Spectrum gives you the power to build high quality, accessible UI with the cohesive look and feel of Adobe. </p>
216232
<div className={style({display: 'flex', gap: 16, flexDirection: {default: 'column', sm: 'row'}, marginTop: 32, marginBottom: 56})}>
217233
<LinkButton size="XL" staticColor="white" href="getting-started">Get started</LinkButton>
@@ -386,7 +402,7 @@ export function Home() {
386402
<h4 className={style({font: 'title', marginTop: 0})}>Button.tsx</h4>
387403
<Pre><Code lang="tsx">{`import {style, focusRing} from '@react-spectrum/s2/style' with {type: 'macro'};
388404
import {hstack} from './style-utils' with {type: 'macro'};
389-
405+
390406
const buttonStyle = style({
391407
...focusRing(),
392408
...hstack(4)
@@ -439,21 +455,21 @@ const buttonStyle = style({
439455
description="Comprehensive markdown docs, llms.txt, and an agent-friendly MCP server."
440456
illustration={<Sparkles />}
441457
styles={style({gridColumnStart: {default: 'span 6', lg: 'span 2'}})}>
442-
458+
443459
</Feature>
444460
<Feature
445461
title="SSR"
446462
description="Server-side rendering and React Server Components support, maximizing Core Web Vitals with zero layout thrashing."
447463
illustration={<Server />}
448464
styles={style({gridColumnStart: {default: 'span 6', lg: 'span 2'}})}>
449-
465+
450466
</Feature>
451467
<Feature
452468
title="Small bundle"
453469
description="Aggressive tree-shaking and atomic CSS resulting in reduced bundle sizes and faster runtime performance."
454470
illustration={<SpeedFast />}
455471
styles={style({gridColumnStart: {default: 'span 6', lg: 'span 2'}})}>
456-
472+
457473
</Feature>
458474
</Section>
459475
</main>
@@ -488,6 +504,46 @@ const buttonStyle = style({
488504
);
489505
}
490506

507+
function getTitleTextWidth(text: string) {
508+
let width = 0;
509+
for (let c of text) {
510+
let w = letters[c];
511+
if (w != null) {
512+
width += w;
513+
}
514+
}
515+
516+
return width;
517+
}
518+
519+
function HomeH1(props) {
520+
let {children, ...otherProps} = props;
521+
return (
522+
<h1
523+
{...otherProps}
524+
style={{'--width-per-em': getTitleTextWidth('React Spectrum')} as any}
525+
className={style({
526+
font: 'heading-3xl',
527+
// This variable is used to calculate the line height.
528+
// Normally it is set by the fontSize, but the custom clamp prevents this.
529+
'--fs': {
530+
type: 'opacity',
531+
value: 'pow(1.125, 10)' // heading-2xl
532+
},
533+
'--headingFontSize': {
534+
type: 'width',
535+
value: `[round(pow(1.125, ${fontSizeToken('heading-size-xxxl')}) * var(--s2-font-size-base, 14) / 16 * 1rem, 1px)]`
536+
},
537+
// On mobile, adjust heading to fit in the viewport, and clamp between a min and max font size.
538+
fontSize: `clamp(${35 / 16}rem, (100vw - 40px) / var(--width-per-em), var(--headingFontSize))`,
539+
marginY: 0,
540+
color: 'white'
541+
})}>
542+
{children}
543+
</h1>
544+
)
545+
}
546+
491547
function Section({title, description, children}: any) {
492548
let headingId = useId();
493549
return (

packages/dev/s2-docs/src/textWidth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Map of characters to width per em, generated from Adobe Clean Spectrum VF.
22
// Multiply by the font size to get the pixel width.
3-
let letters: Record<string, number> = {
3+
export let letters: Record<string, number> = {
44
'0': 0.5467998461797813,
55
'1': 0.33863281002940315,
66
'2': 0.4949392889121938,

0 commit comments

Comments
 (0)