Skip to content

Commit 075f2ed

Browse files
authored
docs: docs testing followups (#9175)
* strip markdown from blog post card descriptions * fix release notes link * fix icon search overflow and focus visible style * add color swatch * translations * fix styling/layout and add background * add background to submenu animation * make videos full width * make release and blog list full width since no ToC exists
1 parent 2e36b00 commit 075f2ed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+412
-85
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2025 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
'use client';
14+
import {ColorPicker} from 'react-aria-components';
15+
import {ColorArea, ColorSlider, ColorField, ColorSwatch, Picker, PickerItem} from '@react-spectrum/s2';
16+
import {style} from '@react-spectrum/s2/style' with {type: 'macro'};
17+
import {getColorChannels} from '@react-stately/color';
18+
import {useState} from 'react';
19+
import type {ColorSpace} from 'react-aria-components';
20+
// @ts-ignore
21+
import intlMessages from './intl/*.json';
22+
import {useLocalizedStringFormatter} from '@react-aria/i18n';
23+
24+
interface ColorEditorProps {
25+
hideAlphaChannel?: boolean;
26+
}
27+
28+
function ColorEditor({hideAlphaChannel = false}: ColorEditorProps) {
29+
let [format, setFormat] = useState<ColorSpace | 'hex'>('hex');
30+
let formatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/color');
31+
32+
return (
33+
<div className={style({display: 'flex', flexDirection: 'column', gap: 4, minWidth: 380})}>
34+
<div className={style({display: 'flex', gap: 12})}>
35+
<ColorArea colorSpace="hsb" xChannel="saturation" yChannel="brightness" />
36+
<ColorSlider colorSpace="hsb" channel="hue" orientation="vertical" />
37+
{!hideAlphaChannel && (
38+
<ColorSlider channel="alpha" orientation="vertical" />
39+
)}
40+
</div>
41+
<div className={style({display: 'flex', gap: 4})}>
42+
<Picker
43+
aria-label={formatter.format('colorFormat')}
44+
isQuiet
45+
styles={style({width: 70})}
46+
value={format}
47+
onChange={(key) => setFormat(key as typeof format)}>
48+
<PickerItem id="hex">{formatter.format('hex')}</PickerItem>
49+
<PickerItem id="rgb">{formatter.format('rgb')}</PickerItem>
50+
<PickerItem id="hsl">{formatter.format('hsl')}</PickerItem>
51+
<PickerItem id="hsb">{formatter.format('hsb')}</PickerItem>
52+
</Picker>
53+
{format === 'hex'
54+
? <ColorField styles={style({width: 120})} aria-label={formatter.format('hex')} />
55+
: getColorChannels(format).map(channel => (
56+
<ColorField styles={style({width: 70})} key={channel} colorSpace={format} channel={channel} />
57+
))}
58+
{!hideAlphaChannel && (
59+
<ColorField styles={style({width: 70})} channel="alpha" />
60+
)}
61+
</div>
62+
</div>
63+
);
64+
}
65+
66+
export function ColorEditorExample() {
67+
return (
68+
<div
69+
role="group"
70+
aria-label="Example"
71+
className={style({
72+
backgroundColor: 'layer-1',
73+
borderRadius: 'xl',
74+
marginY: 32,
75+
padding: {
76+
default: 12,
77+
lg: 24
78+
}
79+
})}>
80+
<ColorPicker defaultValue="#5100FF">
81+
{({color}) => (
82+
<div className={style({display: 'flex', flexWrap: 'wrap', gap: 24})}>
83+
<ColorEditor />
84+
<div className={style({display: 'flex', flexDirection: 'column', gap: 8, marginTop: 16})}>
85+
<ColorSwatch color={color} size="L" />
86+
<span className={style({font: 'body'})}>{color.getColorName(navigator.language || 'en-US')}</span>
87+
</div>
88+
</div>
89+
)}
90+
</ColorPicker>
91+
</div>
92+
);
93+
}

packages/dev/s2-docs/pages/react-aria/blog/SubmenuAnimation.tsx

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,21 +114,33 @@ export function SubmenuAnimation(): JSX.Element {
114114
}, []);
115115

116116
return (
117-
<div ref={ref} role="img" aria-label="Animation showing a submenu closing when the cursor leaves the trigger item to go to the submenu">
118-
<svg
119-
ref={mouseRef}
120-
viewBox="0 0 12 19"
121-
width={mouseWidth}
122-
height={19}
123-
aria-hidden="true"
124-
style={{position: 'absolute', filter: 'drop-shadow(0 1px 1px #aaa)', transform: 'translate(-1000px, -1000px)'}}>
125-
<g transform="matrix(1, 0, 0, 1, -150, -63.406998)">
126-
<path d="M150 79.422V63.407l11.591 11.619h-6.781l-.411.124Z" fill="#fff" fillRule="evenodd" />
127-
<path d="m159.084 80.1-3.6 1.535-4.684-11.093 3.686-1.553Z" fill="#fff" fillRule="evenodd" />
128-
<path d="m157.751 79.416-1.844.774-3.1-7.374 1.841-.775Z" fillRule="evenodd" />
129-
<path d="M151 65.814V77l2.969-2.866.431-.134h4.768Z" fillRule="evenodd" />
130-
</g>
131-
</svg>
117+
<div
118+
role="group"
119+
aria-label="Example"
120+
className={style({
121+
backgroundColor: 'layer-1',
122+
borderRadius: 'xl',
123+
marginY: 32,
124+
padding: {
125+
default: 12,
126+
lg: 24
127+
}
128+
})}>
129+
<div ref={ref} role="img" aria-label="Animation showing a submenu closing when the cursor leaves the trigger item to go to the submenu">
130+
<svg
131+
ref={mouseRef}
132+
viewBox="0 0 12 19"
133+
width={mouseWidth}
134+
height={19}
135+
aria-hidden="true"
136+
style={{position: 'absolute', filter: 'drop-shadow(0 1px 1px #aaa)', transform: 'translate(-1000px, -1000px)'}}>
137+
<g transform="matrix(1, 0, 0, 1, -150, -63.406998)">
138+
<path d="M150 79.422V63.407l11.591 11.619h-6.781l-.411.124Z" fill="#fff" fillRule="evenodd" />
139+
<path d="m159.084 80.1-3.6 1.535-4.684-11.093 3.686-1.553Z" fill="#fff" fillRule="evenodd" />
140+
<path d="m157.751 79.416-1.844.774-3.1-7.374 1.841-.775Z" fillRule="evenodd" />
141+
<path d="M151 65.814V77l2.969-2.866.431-.134h4.768Z" fillRule="evenodd" />
142+
</g>
143+
</svg>
132144

133145
<svg
134146
xmlns="http://www.w3.org/2000/svg"
@@ -352,6 +364,7 @@ export function SubmenuAnimation(): JSX.Element {
352364
data-name="Option 2 arrow" />
353365
</g>
354366
</svg>
367+
</div>
355368
</div>
356369
);
357370
}

packages/dev/s2-docs/pages/react-aria/blog/accessible-color-descriptions.mdx

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default Layout;
1717
import docs from 'docs:@react-spectrum/s2';
1818
import React from 'react';
1919
import {Byline} from '../../../src/BlogList';
20+
import {ColorEditorExample} from './ColorEditorExample';
2021

2122
export const tags = ['color picker', 'color', 'internationalization', 'localization', 'components', 'accessibility', 'react spectrum', 'react'];
2223
export const description = 'Recently, we released a suite of color picker components in React Aria and React Spectrum. Since colors are inherently visual, ensuring these components are accessible to users with visual impairments presented a significant challenge. In this post, we\'ll discuss how we developed an algorithm that generates clear color descriptions for screen readers in multiple languages, while minimizing bundle size.';
@@ -37,7 +38,7 @@ Accessibility is at the core of all of our work on the React Spectrum team, and
3738

3839
Our initial implementation followed the typical ARIA patterns such as [slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider/) to implement ColorArea, ColorSlider, and ColorWheel, and [listbox](https://www.w3.org/WAI/ARIA/apg/patterns/listbox/) to implement ColorSwatchPicker. This provided good support for mouse, touch, and keyboard input, but the screen reader experience left something to be desired. Out of the box, screen readers would only announce raw channel values like “Red: 182, Green: 96, Blue: 38”. I don’t know about you, but I can’t imagine what color that is just by hearing those numbers!
3940

40-
<video src={initialVideoUrl} style={{maxWidth: 'min(100%, 700px)', display: 'block', margin: '20px auto'}} controls preload="metadata" />
41+
<video src={initialVideoUrl} style={{width: '100%', display: 'block', margin: '20px auto'}} controls preload="metadata" />
4142

4243
## Improving screen reader announcements
4344

@@ -157,30 +158,14 @@ The order that the hue, chroma, and lightness descriptors are combined varies by
157158

158159
Check out the color picker below to see the results of this algorithm:
159160

160-
```tsx render
161-
'use client';
162-
import {ColorPicker} from 'react-aria-components';
163-
import {ColorSwatch} from '@react-spectrum/s2';
164-
165-
// TODO: No ColorEditor in S2
166-
<ColorPicker label="Fill" defaultValue="#5100FF">
167-
{({color}) =>
168-
<div style={{display: 'flex', flexWrap: 'wrap', gap: 24}}>
169-
<div style={{display: 'flex', flexDirection: 'column', gap: 8}}>
170-
<ColorSwatch size="L" />
171-
<span>{color.getColorName(navigator.language || 'en-US')}</span>
172-
</div>
173-
</div>
174-
}
175-
</ColorPicker>
176-
```
161+
<ColorEditorExample />
177162

178163
## Final result
179164

180165
After developing this algorithm to generate color descriptions, we integrated it into all of our color picker components. Since the same description may be generated for a range of colors, our components also announce the precise numeric value of the channels being modified. For example, a hue slider may announce “260 degrees, blue purple, slider”. Numeric values are useful for fine adjustments, while the color descriptions provide an overall sense of the color, similar to how one would perceive it visually.
181166

182167
The video below shows interacting with a ColorArea with color descriptions. You can also try it yourself with a screen reader in the example above.
183168

184-
<video src={finalVideoUrl} style={{maxWidth: 'min(100%, 700px)', display: 'block', margin: '20px auto'}} controls preload="metadata" />
169+
<video src={finalVideoUrl} style={{width: '100%', display: 'block', margin: '20px auto'}} controls preload="metadata" />
185170

186171
Check out our [ColorPicker](../ColorPicker.html) components in React Aria to build accessible, customizable, and styleable color pickers in your own applications.

packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-1.mdx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Mobile browsers often introduce delays before emulated mouse events like onClick
6363

6464
The CSS `:active` and `:hover` pseudo-classes are also affected by mouse event emulation. For example, when tapping down on a button and dragging your finger off, the active state persists even when your finger is not over it. This makes it appear like lifting your finger will activate the button when it will not. This is not how native buttons behave, so it can feel inconsistent with user expectations.
6565

66-
<video src={draggingVideoUrl} style={{maxWidth: 'min(100%, 700px)', display: 'block', margin: '20px auto'}} controls preload="metadata" />
66+
<video src={draggingVideoUrl} style={{width: '100%', display: 'block', margin: '20px auto'}} controls preload="metadata" />
6767

6868
## Pointer events
6969

@@ -83,7 +83,7 @@ Touch events can also be canceled by scrolling. If you touch a button and then s
8383

8484
Text selection gestures are another case where we need to determine the user's intent. On iOS, for example, a long press begins text selection. However, when pressing a button, you wouldn't usually expect text selection to start.
8585

86-
<video src={textSelectionVideoUrl} style={{maxWidth: 'min(100%, 700px)', display: 'block', margin: '20px auto'}} controls preload="metadata" />
86+
<video src={textSelectionVideoUrl} style={{width: '100%', display: 'block', margin: '20px auto'}} controls preload="metadata" />
8787

8888
It is possible to add the `user-select: none` CSS property to the button to make it non-selectable, but even with that enabled, Safari still tries to select elements nearby. The only way to avoid this is to add `user-select: none` to the entire page. We wouldn't want to do this all the time though, because some elements should allow text selection to occur. React Aria automatically handles adding `user-select: none` to the page on touch start on a pressable element, and removes it after a short delay on press up. The delay is necessary because iOS may begin selecting even after touch up within some threshold.
8989

@@ -107,7 +107,7 @@ Each of these events receive a unified `PressEvent` object rather than the under
107107

108108
With the [usePress](../usePress.html) hook, our buttons handle interactions consistently. Dragging your pointer off the button correctly removes the active state, text selection is canceled, and issues with emulated mouse events are avoided.
109109

110-
<video src={buttonVideoUrl} style={{maxWidth: 'min(100%, 700px)', display: 'block', margin: '20px auto'}} controls preload="metadata" />
110+
<video src={buttonVideoUrl} style={{width: '100%', display: 'block', margin: '20px auto'}} controls preload="metadata" />
111111

112112
Try a live example for yourself in our [Button](../../s2/Button.html) docs!
113113

packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-2.mdx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ The first thing that may come to mind when you think about implementing a hover
4444

4545
On touch devices, `:hover` is emulated for backward compatibility with older apps that weren't designed with touch in mind. Depending on the browser, `:hover` might never match, might match only while the user is touching an element, or may be sticky and act more like focus. On iOS for example, tapping once on an element shows the hover style, and tapping away from the element removes it.
4646

47-
<video src={hoverVideoUrl} loop autoPlay muted style={{maxWidth: 'min(100%, 700px)', display: 'block', margin: '20px auto'}} />
47+
<video src={hoverVideoUrl} loop autoPlay muted style={{width: '100%', display: 'block', margin: '20px auto'}} />
4848

4949
This is not how you'd usually expect a button to behave, but browsers need to do this kind of emulation for apps that may only show or hide content on hover (e.g. navigation menus). If they did not, then perhaps this content would not be accessible at all to touch users. Unfortunately, there is no built-in way of opting out of this behavior, so we need to find another way to apply our hover styles.
5050

@@ -76,7 +76,7 @@ We've wrapped all of this behavior into the [useHover](../useHover.html) hook in
7676

7777
The [Button](../../s2/Button.html) component, and all other components in React Spectrum that support hover states, use the [useHover](../useHover.html) hook to handle interactions, and apply a CSS class when they are hovered. This ensures that hover states are only applied when interacting with a mouse, which avoids unexpected behavior on touch devices.
7878

79-
<video src={hoveriPadVideoUrl} loop autoPlay muted style={{maxWidth: 'min(100%, 700px)', display: 'block', margin: '20px auto'}} />
79+
<video src={hoveriPadVideoUrl} loop autoPlay muted style={{width: '100%', display: 'block', margin: '20px auto'}} />
8080

8181
Try a live example for yourself in our [Button](../../s2/Button.html) docs!
8282

@@ -85,4 +85,3 @@ Try a live example for yourself in our [Button](../../s2/Button.html) docs!
8585
As we've seen, cross-device interactions are difficult to handle across so many different types of devices. Even "simple" components like buttons are much more complicated than they seem at first. If you're building your own button component, I'd recommend checking out the [useButton](https://react-spectrum.adobe.com/react-aria/useButton.html) and [useHover](../useHover.html) hooks, which will help ensure that everything works as expected across a wide variety of devices.
8686

8787
In the [next part](building-a-button-part-3.html) of this series, we'll cover how React Spectrum and React Aria handle focus behavior across devices and browsers.
88-

packages/dev/s2-docs/pages/react-aria/blog/building-a-button-part-3.mdx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ There are many aspects of focus management, and perhaps we will cover more in fu
4444

4545
An important feature for keyboard users is a **focus ring**. This is a visual affordance for the currently focused element, which allows a keyboard user to know which element they are currently on. It may only be visible when navigating with a keyboard, however, so as not to distract mouse and touchscreen users.
4646

47-
<video src={focusRingVideoUrl} loop autoPlay muted style={{maxWidth: 'min(100%, 640px)', display: 'block', margin: '20px auto'}} />
47+
<video src={focusRingVideoUrl} loop autoPlay muted style={{width: '100%', display: 'block', margin: '20px auto'}} />
4848

4949
As you can see in the above video, the focus ring appears around each button when it receives keyboard focus, but when the user interacts with a mouse it does not appear. To implement this, we attach global event listeners for pointer, keyboard, and focus events at the document level and keep track of the most recent input modality that the user was interacting with. If the user most recently interacted with a keyboard or assistive technology, we show the focus ring, otherwise we do not show it.
5050

@@ -79,4 +79,3 @@ This focus normalization behavior is implemented by the [usePress](../usePress.h
7979
## Conclusion
8080

8181
In this series, you've seen how complicated even "simple" components like buttons can be when you consider all of the interactions they can support. React Aria aims to simplify this complexity and provide consistent behavior out of the box, while giving you complete rendering and styling control for your own components. This lets you focus on your unique design requirements, and build high quality components much faster. If you're working on a design system, check it out!
82-

0 commit comments

Comments
 (0)