Skip to content

Commit a73ac97

Browse files
authored
fix: various S2 bug fixes from docs testing (#9272)
* remove allowsEmptycollection from our pickers since we dont support that behavior * fix focusing DateFields on mobile tap * fix SelectBoxGroup overflow when horizontal and only show checkbox in multiple selection * fix taggroup remove button * update link button to support genai and premium variants * clear the context at the collapse collection level instead * review comments * chromatic fix * make sure unavaliable dates are never marked as selected * fix test case * get rid of act
1 parent a49b4aa commit a73ac97

File tree

13 files changed

+344
-45
lines changed

13 files changed

+344
-45
lines changed

packages/@react-aria/calendar/src/useCalendarCell.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
101101
);
102102

103103
if (isInvalid) {
104-
isSelected = true;
104+
// don't mark unavaliable + invalid dates in range calendars as selected, that case only comes up via allowsNoncontiguousRanges
105+
// and thus those unavailable dates shouldn't be selected. For single select calendars, we mark unavailble + invalid days as selected for styling reasons
106+
if (!('highlightedRange' in state) || !isUnavailable) {
107+
isSelected = true;
108+
}
105109
}
106110

107111
// For performance, reuse the same date object as before if the new date prop is the same.

packages/@react-spectrum/s2/chromatic/Button.stories.tsx

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,34 @@ let states = [
4040

4141
let combinations = generatePowerset(states);
4242

43-
const Template = (args: ButtonProps): ReactNode => {
44-
let {children, ...otherArgs} = args;
43+
let premiumStates = [
44+
{isDisabled: true},
45+
{size: ['S', 'M', 'L', 'XL']}
46+
];
47+
48+
let premiumCombinations = generatePowerset(premiumStates);
49+
50+
let genaiStates = [
51+
{isDisabled: true},
52+
{size: ['S', 'M', 'L', 'XL']}
53+
];
54+
55+
let genaiCombinations = generatePowerset(genaiStates);
56+
57+
const Template = (args: ButtonProps & {combos?: any[]}): ReactNode => {
58+
let {children, combos = combinations, variant, ...otherArgs} = args;
4559
return (
4660
<div className={style({display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 250px))', gridAutoFlow: 'row', alignItems: 'center', justifyItems: 'start', gap: 24, width: '100vw'})}>
47-
{combinations.map(c => {
61+
{combos.map(c => {
4862
let fullComboName = Object.keys(c).map(k => `${k}: ${c[k]}`).join(' ');
4963
let key = Object.keys(c).map(k => shortName(k, c[k])).join(' ');
5064
if (!key) {
5165
key = 'default';
5266
}
5367

54-
let button = <Button data-testid={fullComboName} key={key} {...otherArgs} {...c}>{children ? children : key}</Button>;
68+
let finalVariant = c.variant ?? variant;
69+
let buttonProps = {...otherArgs, ...c, ...(finalVariant && {variant: finalVariant})};
70+
let button = <Button data-testid={fullComboName} key={key} {...buttonProps}>{children ? children : key}</Button>;
5571
if (c.staticColor != null) {
5672
return (
5773
<StaticColorProvider staticColor={c.staticColor}>
@@ -84,4 +100,52 @@ export const IconOnly: StoryObj<typeof Button> = {
84100
}
85101
};
86102

103+
export const Premium: StoryObj<typeof Button> = {
104+
render: (args) => <Template {...args} combos={premiumCombinations} />,
105+
args: {
106+
children: 'Press me',
107+
variant: 'premium'
108+
}
109+
};
110+
111+
export const PremiumWithIcon: StoryObj<typeof Button> = {
112+
render: (args) => <Template {...args} combos={premiumCombinations} />,
113+
args: {
114+
children: <><NewIcon /><Text>Press me</Text></>,
115+
variant: 'premium'
116+
}
117+
};
118+
119+
export const PremiumIconOnly: StoryObj<typeof Button> = {
120+
render: (args) => <Template {...args} combos={premiumCombinations} />,
121+
args: {
122+
children: <NewIcon />,
123+
variant: 'premium'
124+
}
125+
};
126+
127+
export const GenAI: StoryObj<typeof Button> = {
128+
render: (args) => <Template {...args} combos={genaiCombinations} />,
129+
args: {
130+
children: 'Press me',
131+
variant: 'genai'
132+
}
133+
};
134+
135+
export const GenAIWithIcon: StoryObj<typeof Button> = {
136+
render: (args) => <Template {...args} combos={genaiCombinations} />,
137+
args: {
138+
children: <><NewIcon /><Text>Press me</Text></>,
139+
variant: 'genai'
140+
}
141+
};
142+
143+
export const GenAIIconOnly: StoryObj<typeof Button> = {
144+
render: (args) => <Template {...args} combos={genaiCombinations} />,
145+
args: {
146+
children: <NewIcon />,
147+
variant: 'genai'
148+
}
149+
};
150+
87151
export {WithWrapping};

packages/@react-spectrum/s2/chromatic/LinkButton.stories.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,19 @@ export const Example: Story = {
4848
target: '_blank'
4949
}
5050
};
51+
52+
export const Premium: Story = {
53+
...Example,
54+
args: {
55+
...Example.args,
56+
variant: 'premium'
57+
}
58+
};
59+
60+
export const GenAI: Story = {
61+
...Example,
62+
args: {
63+
...Example.args,
64+
variant: 'genai'
65+
}
66+
};

packages/@react-spectrum/s2/chromatic/SelectBoxGroup.stories.tsx

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,30 @@ export const VerticalOrientation: Story = {
3333
<div style={{width: 600}}>
3434
<SelectBoxGroup
3535
aria-label="Vertical"
36-
orientation="vertical"
36+
orientation="vertical"
37+
onSelectionChange={action('onSelectionChange')}>
38+
<SelectBox id="text-only" textValue="V: Text Only">
39+
<Text slot="label">V: Text Only</Text>
40+
</SelectBox>
41+
<SelectBox id="illustration-text" textValue="V: Illustration + Text">
42+
<Server />
43+
<Text slot="label">V: Illustration + Text</Text>
44+
</SelectBox>
45+
<SelectBox id="illustration-desc" textValue="Send">
46+
<PaperAirplane />
47+
</SelectBox>
48+
</SelectBoxGroup>
49+
</div>
50+
)
51+
};
52+
53+
export const VerticalOrientationMultiSelect: Story = {
54+
render: () => (
55+
<div style={{width: 600}}>
56+
<SelectBoxGroup
57+
selectionMode="multiple"
58+
aria-label="Vertical"
59+
orientation="vertical"
3760
onSelectionChange={action('onSelectionChange')}>
3861
<SelectBox id="text-only" textValue="V: Text Only">
3962
<Text slot="label">V: Text Only</Text>
@@ -55,7 +78,36 @@ export const HorizontalOrientation: Story = {
5578
<div style={{width: 800}}>
5679
<SelectBoxGroup
5780
aria-label="Horizontal"
58-
orientation="horizontal"
81+
orientation="horizontal"
82+
onSelectionChange={action('onSelectionChange')}>
83+
<SelectBox id="text-only" textValue="Title Only">
84+
<Text slot="label">Title Only</Text>
85+
</SelectBox>
86+
<SelectBox id="illustration-text" textValue="Illustration + Title">
87+
<Server />
88+
<Text slot="label">Illustration + Title</Text>
89+
</SelectBox>
90+
<SelectBox id="text-desc" textValue="Title + Description">
91+
<Text slot="label">Title + Description</Text>
92+
<Text slot="description">Additional description</Text>
93+
</SelectBox>
94+
<SelectBox id="h-all" textValue="Illustration + Title + Description">
95+
<Server />
96+
<Text slot="label">Illustration + Title + Description</Text>
97+
<Text slot="description">Full horizontal layout with all elements</Text>
98+
</SelectBox>
99+
</SelectBoxGroup>
100+
</div>
101+
)
102+
};
103+
104+
export const HorizontalOrientationMultSelect: Story = {
105+
render: () => (
106+
<div style={{width: 800}}>
107+
<SelectBoxGroup
108+
selectionMode="multiple"
109+
aria-label="Horizontal"
110+
orientation="horizontal"
59111
onSelectionChange={action('onSelectionChange')}>
60112
<SelectBox id="text-only" textValue="Title Only">
61113
<Text slot="label">Title Only</Text>

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

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,14 @@ export const LinkButton = forwardRef(function LinkButton(props: LinkButtonProps,
430430
props = useFormProps(props);
431431
let domRef = useFocusableRef(ref);
432432
let overlayTriggerState = useContext(OverlayTriggerStateContext);
433+
let {
434+
fillStyle = 'fill',
435+
size = 'M',
436+
variant = 'primary',
437+
staticColor,
438+
styles,
439+
children
440+
} = props;
433441

434442
return (
435443
<Link
@@ -440,28 +448,41 @@ export const LinkButton = forwardRef(function LinkButton(props: LinkButtonProps,
440448
...renderProps,
441449
// Retain hover styles when an overlay is open.
442450
isHovered: renderProps.isHovered || overlayTriggerState?.isOpen || false,
443-
variant: props.variant || 'primary',
444-
fillStyle: props.fillStyle || 'fill',
445-
size: props.size || 'M',
446-
staticColor: props.staticColor,
447-
isStaticColor: !!props.staticColor,
451+
variant,
452+
fillStyle,
453+
size,
454+
staticColor,
455+
isStaticColor: !!staticColor,
448456
isPending: false
449-
}, props.styles)}>
450-
<Provider
451-
values={[
452-
[SkeletonContext, null],
453-
[TextContext, {
454-
styles: style({paddingY: '--labelPadding', order: 1}),
455-
// @ts-ignore data-attributes allowed on all JSX elements, but adding to DOMProps has been problematic in the past
456-
'data-rsp-slot': 'text'
457-
}],
458-
[IconContext, {
459-
render: centerBaseline({slot: 'icon', styles: style({order: 0})}),
460-
styles: style({size: fontRelative(20), marginStart: '--iconMargin', flexShrink: 0})
461-
}]
462-
]}>
463-
{typeof props.children === 'string' ? <Text>{props.children}</Text> : props.children}
464-
</Provider>
457+
}, styles)}>
458+
{(renderProps) => (<>
459+
{variant === 'genai' || variant === 'premium'
460+
? (
461+
<span
462+
className={gradient({
463+
...renderProps,
464+
// Retain hover styles when an overlay is open.
465+
isHovered: renderProps.isHovered || overlayTriggerState?.isOpen || false,
466+
variant
467+
})} />
468+
)
469+
: null}
470+
<Provider
471+
values={[
472+
[SkeletonContext, null],
473+
[TextContext, {
474+
styles: style({paddingY: '--labelPadding', order: 1}),
475+
// @ts-ignore data-attributes allowed on all JSX elements, but adding to DOMProps has been problematic in the past
476+
'data-rsp-slot': 'text'
477+
}],
478+
[IconContext, {
479+
render: centerBaseline({slot: 'icon', styles: style({order: 0})}),
480+
styles: style({size: fontRelative(20), marginStart: '--iconMargin', flexShrink: 0})
481+
}]
482+
]}>
483+
{typeof children === 'string' ? <Text>{children}</Text> : children}
484+
</Provider>
485+
</>)}
465486
</Link>
466487
);
467488
});

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ export const FieldGroup = forwardRef(function FieldGroup(props: FieldGroupProps,
202202
}
203203
}}
204204
onTouchEnd={e => {
205-
if (!(e.target as Element).closest('button,input,textarea,[role="button"]')) {
205+
let target = e.target as HTMLElement;
206+
if (!target.isContentEditable && !target.closest('button,input,textarea,[role="button"]')) {
206207
e.preventDefault();
207208
(e.currentTarget.querySelector('input, textarea') as HTMLElement)?.focus();
208209
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export interface PickerStyleProps {
9898

9999
type SelectionMode = 'single' | 'multiple';
100100
export interface PickerProps<T extends object, M extends SelectionMode = 'single'> extends
101-
Omit<AriaSelectProps<T, M>, 'children' | 'style' | 'className' | keyof GlobalDOMAttributes>,
101+
Omit<AriaSelectProps<T, M>, 'children' | 'style' | 'className' | 'allowsEmptyCollection' | keyof GlobalDOMAttributes>,
102102
PickerStyleProps,
103103
StyleProps,
104104
SpectrumLabelableProps,

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,16 @@ const selectBoxStyles = style({
105105
horizontal: 188
106106
}
107107
},
108-
maxWidth: {
109-
default: 170,
110-
orientation: {
111-
horizontal: 480
108+
'--select-box-max-width': {
109+
type: 'width',
110+
value: {
111+
default: 170,
112+
orientation: {
113+
horizontal: 480
114+
}
112115
}
113116
},
117+
maxWidth: 'min(100%, var(--select-box-max-width))',
114118
minHeight: {
115119
default: 144,
116120
orientation: {
@@ -272,7 +276,7 @@ const gridStyles = style<{orientation?: Orientation}>({
272276
gridAutoRows: '1fr',
273277
gap: 24,
274278
justifyContent: 'center',
275-
'--size': {
279+
'--select-box-group-width': {
276280
type: 'width',
277281
value: {
278282
orientation: {
@@ -281,10 +285,19 @@ const gridStyles = style<{orientation?: Orientation}>({
281285
}
282286
}
283287
},
288+
'--select-box-group-min-width': {
289+
type: 'width',
290+
value: {
291+
orientation: {
292+
horizontal: 188,
293+
vertical: 144
294+
}
295+
}
296+
},
284297
gridTemplateColumns: {
285298
orientation: {
286-
horizontal: 'repeat(auto-fit, var(--size))',
287-
vertical: 'repeat(auto-fit, var(--size))'
299+
horizontal: 'repeat(auto-fit, minmax(var(--select-box-group-min-width), min(var(--select-box-group-width), 100%)))',
300+
vertical: 'repeat(auto-fit, minmax(var(--select-box-group-min-width), min(var(--select-box-group-width), 100%)))'
288301
}
289302
}
290303
}, getAllowedOverrides());
@@ -315,7 +328,7 @@ export function SelectBox(props: SelectBoxProps): ReactNode {
315328
}, styles)}
316329
style={pressScale(ref, UNSAFE_style)}
317330
{...otherProps}>
318-
{({isSelected, isDisabled, isHovered}) => {
331+
{({isSelected, isDisabled, isHovered, selectionMode}) => {
319332
return (
320333
<>
321334
<div
@@ -326,7 +339,7 @@ export function SelectBox(props: SelectBoxProps): ReactNode {
326339
pointerEvents: 'none'
327340
})}
328341
aria-hidden="true">
329-
{!isDisabled && (
342+
{!isDisabled && selectionMode === 'multiple' && (
330343
<div
331344
className={box({
332345
isSelected,

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ function TagGroupInner<T>({
297297
<Provider
298298
values={[
299299
[RACTextContext, undefined],
300+
[RACButtonContext, undefined],
300301
[TagGroupContext, {size, isEmphasized}]
301302
]}>
302303
{/* invisible collection for measuring */}
@@ -525,11 +526,9 @@ export const Tag = /*#__PURE__*/ (forwardRef as forwardRefType)(function Tag({ch
525526

526527
function TagWrapper({children, isDisabled, allowsRemoving, isInRealDOM, isEmphasized, isSelected}) {
527528
let {size = 'M'} = useSlottedContext(TagGroupContext) ?? {};
529+
528530
return (
529-
<Provider
530-
values={[
531-
[RACButtonContext, null]
532-
]}>
531+
<>
533532
{isInRealDOM && (
534533
<div
535534
className={style({
@@ -574,6 +573,6 @@ function TagWrapper({children, isDisabled, allowsRemoving, isInRealDOM, isEmphas
574573
isStaticColor={isEmphasized && isSelected}
575574
isDisabled={isDisabled} />
576575
)}
577-
</Provider>
576+
</>
578577
);
579578
}

0 commit comments

Comments
 (0)