Skip to content

Commit 25caaac

Browse files
devongovettGitHub Enterprise
authored andcommitted
Fix overlay trigger press scaling and menu description color (#196)
1 parent f4c37b7 commit 25caaac

File tree

7 files changed

+70
-27
lines changed

7 files changed

+70
-27
lines changed

packages/@react-spectrum/s2/src/ActionButton.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {ButtonProps, ButtonRenderProps, Button as RACButton, Text, Provider} from 'react-aria-components';
13+
import {ButtonProps, ButtonRenderProps, Button as RACButton, Text, Provider, OverlayTriggerStateContext} from 'react-aria-components';
1414
import {baseColor, fontRelative, style} from '../style/spectrum-theme' with { type: 'macro' };
1515
import {pressScale} from './pressScale';
1616
import {StyleProps, focusRing, getAllowedOverrides} from './style-utils' with { type: 'macro' };
1717
import {FocusableRef} from '@react-types/shared';
1818
import {useFocusableRef} from '@react-spectrum/utils';
19-
import {ReactNode, forwardRef} from 'react';
19+
import {ReactNode, forwardRef, useContext} from 'react';
2020
import {IconContext} from './Icon';
2121
import {centerBaseline} from './CenterBaseline';
2222
import {TextContext} from './Content';
@@ -173,6 +173,7 @@ export const btnStyles = style<ButtonRenderProps & ActionButtonStyleProps & Togg
173173

174174
function ActionButton(props: ActionButtonProps, ref: FocusableRef<HTMLButtonElement>) {
175175
let domRef = useFocusableRef(ref);
176+
let overlayTriggerState = useContext(OverlayTriggerStateContext);
176177

177178
return (
178179
<RACButton
@@ -181,6 +182,8 @@ function ActionButton(props: ActionButtonProps, ref: FocusableRef<HTMLButtonElem
181182
style={pressScale(domRef, props.UNSAFE_style)}
182183
className={renderProps => (props.UNSAFE_className || '') + btnStyles({
183184
...renderProps,
185+
// Retain hover styles when an overlay is open.
186+
isHovered: renderProps.isHovered || overlayTriggerState?.isOpen || false,
184187
staticColor: props.staticColor,
185188
size: props.size || 'M',
186189
isQuiet: props.isQuiet

packages/@react-spectrum/s2/src/Button.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {ButtonRenderProps, Button as RACButton, ButtonProps as RACButtonProps, Provider, Link, LinkProps} from 'react-aria-components';
13+
import {ButtonRenderProps, Button as RACButton, ButtonProps as RACButtonProps, Provider, Link, LinkProps, OverlayTriggerStateContext} from 'react-aria-components';
1414
import {FocusableRef} from '@react-types/shared';
1515
import {style, baseColor, fontRelative} from '../style/spectrum-theme' with {type: 'macro'};
1616
import {StyleProps, centerPadding, focusRing, getAllowedOverrides} from './style-utils' with {type: 'macro'};
@@ -279,6 +279,7 @@ function Button(props: ButtonProps, ref: FocusableRef<HTMLButtonElement>) {
279279
let domRef = useFocusableRef(ref);
280280
let ctx = useContext(ButtonContext);
281281
props = mergeProps(ctx, props);
282+
let overlayTriggerState = useContext(OverlayTriggerStateContext);
282283

283284
return (
284285
<RACButton
@@ -287,6 +288,8 @@ function Button(props: ButtonProps, ref: FocusableRef<HTMLButtonElement>) {
287288
style={pressScale(domRef, props.UNSAFE_style)}
288289
className={renderProps => (props.UNSAFE_className || '') + button({
289290
...renderProps,
291+
// Retain hover styles when an overlay is open.
292+
isHovered: renderProps.isHovered || overlayTriggerState?.isOpen || false,
290293
variant: props.variant || 'primary',
291294
fillStyle: props.fillStyle || 'fill',
292295
size: props.size || 'M',
@@ -318,6 +321,7 @@ function LinkButton(props: LinkButtonProps, ref: FocusableRef<HTMLAnchorElement>
318321
let domRef = useFocusableRef(ref);
319322
let ctx = useContext(ButtonContext);
320323
props = mergeProps(ctx, props);
324+
let overlayTriggerState = useContext(OverlayTriggerStateContext);
321325

322326
return (
323327
<Link
@@ -326,6 +330,8 @@ function LinkButton(props: LinkButtonProps, ref: FocusableRef<HTMLAnchorElement>
326330
style={pressScale(domRef, props.UNSAFE_style)}
327331
className={renderProps => (props.UNSAFE_className || '') + button({
328332
...renderProps,
333+
// Retain hover styles when an overlay is open.
334+
isHovered: renderProps.isHovered || overlayTriggerState?.isOpen || false,
329335
variant: props.variant || 'primary',
330336
fillStyle: props.fillStyle || 'fill',
331337
size: props.size || 'M',

packages/@react-spectrum/s2/src/ComboBox.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ export function ComboBoxItem(props: ComboBoxItemProps) {
357357
[TextContext, {
358358
slots: {
359359
label: {className: label},
360-
description: {className: description({size})}
360+
description: {className: description({...renderProps, size})}
361361
}
362362
}]
363363
]}>

packages/@react-spectrum/s2/src/DialogTrigger.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import {DialogTrigger as AriaDialogTrigger, DialogTriggerProps as AriaDialogTriggerProps, PopoverProps as AriaPopoverProps} from 'react-aria-components';
1414
import {DialogContext} from './Dialog';
15+
import {PressResponder} from '@react-aria/interactions';
1516

1617
export interface DialogTriggerProps extends AriaDialogTriggerProps, Pick<AriaPopoverProps, 'placement' | 'shouldFlip' | 'isKeyboardDismissDisabled' | 'containerPadding' | 'offset' | 'crossOffset' > {
1718
/**
@@ -50,7 +51,11 @@ export function DialogTrigger(props: DialogTriggerProps) {
5051
offset: props.offset,
5152
crossOffset: props.crossOffset
5253
}}>
53-
{props.children}
54+
{/* RAC sets isPressed via PressResponder when the dialog is open.
55+
We don't want press scaling to appear to get "stuck", so override this. */}
56+
<PressResponder isPressed={false}>
57+
{props.children}
58+
</PressResponder>
5459
</DialogContext.Provider>
5560
</AriaDialogTrigger>
5661
);

packages/@react-spectrum/s2/src/Menu.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
SubmenuTriggerProps as AriaSubmenuTriggerProps,
2626
SeparatorProps
2727
} from 'react-aria-components';
28+
import {PressResponder} from '@react-aria/interactions';
2829
import {box, iconStyles} from './Checkbox';
2930
import {TextContext, HeadingContext, HeaderContext, Text, ImageContext, KeyboardContext} from './Content';
3031
import {StyleProps, centerPadding, focusRing, getAllowedOverrides} from './style-utils' with {type: 'macro'};
@@ -112,7 +113,7 @@ export let section = style({
112113
});
113114

114115
export let sectionHeader = style<{size?: 'S' | 'M' | 'L' | 'XL'}>({
115-
color: 'gray-800',
116+
color: 'neutral',
116117
gridColumnStart: 2,
117118
gridColumnEnd: -2,
118119
boxSizing: 'border-box',
@@ -244,6 +245,14 @@ export let label = style({
244245
export let description = style({
245246
gridArea: 'description',
246247
fontFamily: 'sans',
248+
color: {
249+
default: 'neutral-subdued',
250+
// Ideally this would use the same token as hover, but we don't have access to that here.
251+
// TODO: should we always consider isHovered and isFocused to be the same thing?
252+
isFocused: 'gray-800',
253+
isDisabled: 'disabled'
254+
},
255+
transition: 'default',
247256
fontSize: {
248257
default: 'ui-sm',
249258
size: {
@@ -432,7 +441,7 @@ export function MenuItem(props: MenuItemProps) {
432441
[TextContext, {
433442
slots: {
434443
label: {className: label},
435-
description: {className: description({size})},
444+
description: {className: description({...renderProps, size})},
436445
value: {className: value}
437446
}
438447
}],
@@ -468,7 +477,13 @@ function MenuTrigger(props: MenuTriggerProps) {
468477
direction: props.direction,
469478
shouldFlip: props.shouldFlip
470479
}}>
471-
<AriaMenuTrigger {...props} />
480+
<AriaMenuTrigger {...props}>
481+
{/* RAC sets isPressed via PressResponder when the menu is open.
482+
We don't want press scaling to appear to get "stuck", so override this. */}
483+
<PressResponder isPressed={false}>
484+
{props.children}
485+
</PressResponder>
486+
</AriaMenuTrigger>
472487
</InternalMenuTriggerContext.Provider>
473488
);
474489
}

packages/@react-spectrum/s2/src/Picker.tsx

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
menuitem,
4747
description,
4848
icon,
49-
label as labelStyles,
49+
label,
5050
section,
5151
sectionHeader,
5252
sectionHeading,
@@ -310,18 +310,7 @@ function Picker<T extends object>(props: PickerProps<T>, ref: FocusableRef<HTMLB
310310
<Provider
311311
values={[
312312
[HeaderContext, {className: sectionHeader({size})}],
313-
[HeadingContext, {className: sectionHeading}],
314-
[IconContext, {
315-
slots: {
316-
icon: {render: centerBaseline({slot: 'icon', className: iconCenterWrapper}), styles: icon}
317-
}
318-
}],
319-
[TextContext, {
320-
slots: {
321-
label: {className: labelStyles},
322-
'description': {className: description({size})}
323-
}
324-
}]
313+
[HeadingContext, {className: sectionHeading}]
325314
]}>
326315
<ListBox
327316
items={items}
@@ -361,16 +350,40 @@ export function PickerItem(props: PickerItemProps) {
361350
{(renderProps) => {
362351
let {children} = props;
363352
return (
364-
<>
365-
{!isLink && <CheckmarkIcon size={({S: 'S', M: 'L', L: 'XL', XL: 'XXL'} as const)[size]} className={checkmark({...renderProps, size})} />}
366-
{typeof children === 'string' ? <Text slot="label">{children}</Text> : children}
367-
</>
353+
<DefaultProvider
354+
context={IconContext}
355+
value={{slots: {
356+
icon: {render: centerBaseline({slot: 'icon', className: iconCenterWrapper}), styles: icon}
357+
}}}>
358+
<DefaultProvider
359+
context={TextContext}
360+
value={{
361+
slots: {
362+
label: {className: label},
363+
description: {className: description({...renderProps, size})}
364+
}
365+
}}>
366+
{!isLink && <CheckmarkIcon size={({S: 'S', M: 'L', L: 'XL', XL: 'XXL'} as const)[size]} className={checkmark({...renderProps, size})} />}
367+
{typeof children === 'string' ? <Text slot="label">{children}</Text> : children}
368+
</DefaultProvider>
369+
</DefaultProvider>
368370
);
369371
}}
370372
</ListBoxItem>
371373
);
372374
}
373375

376+
// A Context.Provider that only sets a value if one is not already set higher in the tree.
377+
// This is so the slots can be overridden inside of SelectValue.
378+
function DefaultProvider({context, value, children}: {context: React.Context<any>, value: any, children: any}) {
379+
let cur = useContext(context);
380+
if (Object.keys(cur).length) {
381+
return children;
382+
}
383+
384+
return <context.Provider value={value}>{children}</context.Provider>;
385+
}
386+
374387
export interface PickerSectionProps<T extends object> extends SectionProps<T> {}
375388
export function PickerSection<T extends object>(props: PickerSectionProps<T>) {
376389
return (

packages/@react-spectrum/s2/stories/Menu.stories.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import ClockPendingIcon from '../s2wf-icons/assets/svg/S2_Icon_ClockPending_20_N
2020
import CommunityIcon from '../s2wf-icons/assets/svg/S2_Icon_Community_20_N.svg';
2121
import DeviceTabletIcon from '../s2wf-icons/assets/svg/S2_Icon_DeviceTablet_20_N.svg';
2222
import DeviceDesktopIcon from '../s2wf-icons/assets/svg/S2_Icon_DeviceDesktop_20_N.svg';
23+
import More from '../s2wf-icons/assets/svg/S2_Icon_More_20_N.svg';
2324

2425
import type {Meta, StoryObj} from '@storybook/react';
2526
import {categorizeArgTypes} from './utils';
@@ -160,7 +161,7 @@ export const PublishAndExport: Story = {
160161
};
161162
return (
162163
<MenuTrigger {...triggerProps}>
163-
<Button aria-label="Share menu"><NewIcon /></Button>
164+
<Button variant="accent">Publish</Button>
164165
<Menu {...menuProps}>
165166
<MenuSection>
166167
<Header>
@@ -392,7 +393,7 @@ export const DynamicExample: Story = {
392393
};
393394
return (
394395
<MenuTrigger {...triggerProps}>
395-
<Button aria-label="Actions"><NewIcon /></Button>
396+
<Button aria-label="Actions"><More /></Button>
396397
<Menu {...menuProps}>
397398
{function renderItem(arg) {
398399
let item = arg as IExampleItem;

0 commit comments

Comments
 (0)