Skip to content

Commit 3a5d7d8

Browse files
authored
feat: S2 ListView HCM (adobe#9760)
* feat: S2 ListView HCM * fix lint * Fix pre-existing issue in Menu * Fix typescript * fix Section headers * fix bad description color * more intuitive control for font color of description * dont' forget menu * fix description in ListView HCM
1 parent fd415a9 commit 3a5d7d8

File tree

9 files changed

+102
-32
lines changed

9 files changed

+102
-32
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ const ComboboxInner = forwardRef(function ComboboxInner(props: ComboBoxProps<any
694694
}],
695695
[TextContext, {
696696
slots: {
697-
'description': {styles: description({size})}
697+
'description': {styles: description({size, isFocused: false, isDisabled: false})}
698698
}
699699
}]
700700
]}>

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

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -289,9 +289,15 @@ const listitem = style<GridListItemRenderProps & {
289289
color: {
290290
default: baseColor('neutral-subdued'),
291291
isSelected: baseColor('neutral'),
292-
isDisabled: {
293-
default: 'disabled',
294-
forcedColors: 'GrayText'
292+
isDisabled: 'disabled',
293+
forcedColors: {
294+
default: 'ButtonText',
295+
selectionStyle: {
296+
highlight: {
297+
isSelected: 'HighlightText'
298+
}
299+
},
300+
isDisabled: 'GrayText'
295301
}
296302
},
297303
position: 'relative',
@@ -346,12 +352,14 @@ const listitem = style<GridListItemRenderProps & {
346352
borderColor: {
347353
default: '--borderColor',
348354
isNextSelected: 'transparent',
349-
isSelected: 'transparent'
355+
isSelected: 'transparent',
356+
forcedColors: 'ButtonBorder'
350357
},
351358
'--radius': {
352359
type: 'borderTopStartRadius',
353360
value: 'default'
354-
}
361+
},
362+
forcedColorAdjust: 'none'
355363
});
356364

357365
const insetBorderRadius = 'calc(var(--radius) - 1px)';
@@ -373,7 +381,8 @@ const listRowBackground = style<GridListItemRenderProps & {
373381
isSelected: '[-1px]',
374382
// Don't overlap focus ring of row above.
375383
isPrevSelected: 0,
376-
isFirstItem: 0
384+
isFirstItem: 0,
385+
forcedColors: 0
377386
},
378387
left: 0,
379388
right: 0,
@@ -412,7 +421,12 @@ const listRowBackground = style<GridListItemRenderProps & {
412421
}
413422
},
414423
forcedColors: {
415-
default: 'Background'
424+
default: 'Background',
425+
selectionStyle: {
426+
highlight: {
427+
isSelected: 'Highlight'
428+
}
429+
}
416430
}
417431
},
418432
borderTopStartRadius: {
@@ -513,9 +527,34 @@ const listRowBackground = style<GridListItemRenderProps & {
513527
}
514528
});
515529

516-
let listRowFocusRing = style({
530+
let listRowFocusRing = style<GridListItemRenderProps & {
531+
selectionStyle?: 'highlight' | 'checkbox',
532+
isFirstItem?: boolean,
533+
isPrevSelected?: boolean,
534+
isPrevNotSelected?: boolean,
535+
isNextSelected?: boolean,
536+
isNextNotSelected?: boolean,
537+
isLastItem?: boolean,
538+
isQuiet?: boolean
539+
}>({
517540
...focusRing(),
518-
outlineOffset: -2,
541+
outlineOffset: {
542+
default: -2,
543+
forcedColors: -3
544+
},
545+
outlineWidth: {
546+
default: 2,
547+
forcedColors: '[3px]'
548+
},
549+
outlineColor: {
550+
default: 'focus-ring',
551+
forcedColors: {
552+
default: 'Highlight',
553+
selectionStyle: {
554+
highlight: 'ButtonBorder'
555+
}
556+
}
557+
},
519558
position: 'absolute',
520559
inset: 0,
521560
top: {
@@ -567,7 +606,8 @@ export let description = style({
567606
font: 'ui-sm',
568607
color: {
569608
default: baseColor('neutral-subdued'),
570-
isDisabled: 'disabled'
609+
isDisabled: 'disabled',
610+
forcedColors: 'inherit'
571611
},
572612
transition: 'default'
573613
});
@@ -770,7 +810,7 @@ export function ListViewItem(props: ListViewItemProps): ReactNode {
770810
isLastItem: isLastItem(id, state)
771811
})
772812
} />
773-
{renderProps.isFocusVisible &&
813+
{renderProps.isFocusVisible &&
774814
<div
775815
className={listRowFocusRing({
776816
...renderProps,

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -169,13 +169,11 @@ export let menuitem = style<Omit<MenuItemRenderProps, 'hasSubmenu' | 'isOpen'> &
169169
},
170170
color: {
171171
default: baseColor('neutral'),
172+
isDisabled: 'disabled',
172173
forcedColors: {
173174
default: 'ButtonText',
174-
isFocused: 'HighlightText'
175-
},
176-
isDisabled: {
177-
default: 'disabled',
178-
forcedColors: 'GrayText'
175+
isFocused: 'HighlightText',
176+
isDisabled: 'GrayText'
179177
}
180178
},
181179
position: 'relative',
@@ -278,7 +276,7 @@ export let label = style<{size: string}>({
278276
marginTop: '--labelPadding'
279277
});
280278

281-
export let description = style({
279+
export let description = style<{size: 'S' | 'M' | 'L' | 'XL', isFocused: boolean, isDisabled: boolean}>({
282280
gridArea: 'description',
283281
font: {
284282
default: 'ui-sm',
@@ -294,7 +292,10 @@ export let description = style({
294292
// Ideally this would use the same token as hover, but we don't have access to that here.
295293
// TODO: should we always consider isHovered and isFocused to be the same thing?
296294
isFocused: 'gray-800',
297-
isDisabled: 'disabled'
295+
isDisabled: 'disabled',
296+
forcedColors: {
297+
default: 'inherit'
298+
}
298299
},
299300
transition: 'default'
300301
});
@@ -304,7 +305,7 @@ let value = style({
304305
marginStart: 8
305306
});
306307

307-
let keyboard = style<{size: 'S' | 'M' | 'L' | 'XL', isDisabled: boolean}>({
308+
let keyboard = style<{size: 'S' | 'M' | 'L' | 'XL', isDisabled: boolean, isFocused: boolean}>({
308309
gridArea: 'keyboard',
309310
marginStart: 8,
310311
font: 'ui',
@@ -313,7 +314,7 @@ let keyboard = style<{size: 'S' | 'M' | 'L' | 'XL', isDisabled: boolean}>({
313314
default: 'gray-600',
314315
isDisabled: 'disabled',
315316
forcedColors: {
316-
isDisabled: 'GrayText'
317+
default: 'inherit'
317318
}
318319
},
319320
unicodeBidi: 'plaintext'
@@ -386,7 +387,7 @@ export const Menu = /*#__PURE__*/ (forwardRef as forwardRefType)(function Menu<T
386387
}],
387388
[TextContext, {
388389
slots: {
389-
'description': {styles: description({size})}
390+
'description': {styles: description({size, isFocused: false, isDisabled: false})}
390391
}
391392
}],
392393
[InPopoverContext, false]
@@ -526,6 +527,7 @@ export function MenuItem(props: MenuItemProps): ReactNode {
526527
{(renderProps) => {
527528
let {children} = props;
528529
let checkboxRenderProps = {...renderProps, size, isFocused: false, isFocusVisible: false, isIndeterminate: false, isReadOnly: false, isInvalid: false, isRequired: false};
530+
let isFocused = (renderProps.hasSubmenu && renderProps.isOpen) || renderProps.isFocused;
529531
return (
530532
<>
531533
<Provider
@@ -540,11 +542,11 @@ export function MenuItem(props: MenuItemProps): ReactNode {
540542
slots: {
541543
[DEFAULT_SLOT]: {styles: label({size})},
542544
label: {styles: label({size})},
543-
description: {styles: description({...renderProps, size})},
545+
description: {styles: description({...renderProps, size, isFocused})},
544546
value: {styles: value}
545547
}
546548
}],
547-
[KeyboardContext, {styles: keyboard({size, isDisabled: renderProps.isDisabled})}],
549+
[KeyboardContext, {styles: keyboard({...renderProps, size, isFocused})}],
548550
[ImageContext, {styles: image({size})}]
549551
]}>
550552
{renderProps.selectionMode === 'single' && !renderProps.hasSubmenu && <CheckmarkIcon size={checkmarkIconSize[size]} className={checkmark({...renderProps, size})} />}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ export const Picker = /*#__PURE__*/ (forwardRef as forwardRefType)(function Pick
448448
}],
449449
[TextContext, {
450450
slots: {
451-
description: {styles: description({size})}
451+
'description': {styles: description({size, isFocused: false, isDisabled: false})}
452452
}
453453
}]
454454
]}>

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,10 @@ const cellFocus = {
476476
},
477477
outlineOffset: -2,
478478
outlineWidth: 2,
479-
outlineColor: 'focus-ring',
479+
outlineColor: {
480+
default: 'focus-ring',
481+
forcedColors: 'Highlight'
482+
},
480483
borderRadius: '[6px]'
481484
} as const;
482485

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ function Picker<T extends object>(props: PickerProps<T>, ref: FocusableRef<HTMLB
271271
}],
272272
[TextContext, {
273273
slots: {
274-
description: {styles: description({size})}
274+
'description': {styles: description({size, isFocused: false, isDisabled: false})}
275275
}
276276
}]
277277
]}>

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ const meta: Meta<typeof ListView> = {
3333
},
3434
tags: ['autodocs'],
3535
argTypes: {
36-
...categorizeArgTypes('Events', ['onSelectionChange'])
36+
...categorizeArgTypes('Events', ['onSelectionChange']),
37+
children: {table: {disable: true}}
3738
},
3839
title: 'ListView',
3940
args: {

packages/@react-spectrum/s2/style/__tests__/style-macro.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,23 @@ describe('style-macro', () => {
366366
expect(js({isSelected: true})).toMatchInlineSnapshot('" ple12 -macro-dynamic-37zkvn"');
367367
});
368368

369+
it('inherits parent default when nested branch has no default key', () => {
370+
let {css, js} = testStyle({
371+
color: {
372+
forcedColors: {
373+
default: 'ButtonText',
374+
variant: {
375+
highlight: {isSelected: 'HighlightText'}
376+
}
377+
}
378+
}
379+
});
380+
// forcedColors.default should apply when variant=highlight but !isSelected
381+
expect(css).toContain('ButtonText');
382+
expect(js({variant: 'highlight'})).toMatchInlineSnapshot('" plb12 -macro-dynamic-1owjb9s"');
383+
expect(js({variant: 'highlight', isSelected: true})).toMatchInlineSnapshot('" ple12 -macro-dynamic-37zkvn"');
384+
});
385+
369386
it('should expand shorthand properties to longhands', () => {
370387
let {js, css} = testStyle({
371388
padding: 24

packages/@react-spectrum/s2/style/style-macro.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
223223
// We also check if this is globalThis, which happens in non-strict mode bundles.
224224
// Also allow style to be called as a normal function in tests.
225225
// @ts-ignore
226-
226+
227227
if ((this == null || this === globalThis) && process.env.NODE_ENV !== 'test') {
228228
throw new Error('The style macro must be imported with {type: "macro"}.');
229229
}
@@ -478,7 +478,14 @@ export function createTheme<T extends Theme>(theme: T): StyleFunction<ThemePrope
478478
priority = Math.max(priority, subPriority);
479479
} else if (val && typeof val === 'object' && !Array.isArray(val)) {
480480
for (let key in val) {
481-
let [subPriority, subRules] = conditionalToRules(val[key], rulePriority, currentConditions, subSkipConditions, fn);
481+
let branchValue = val[key];
482+
// If this branch has no default, inherit the parent's default so e.g. forcedColors.default
483+
// applies when selectionStyle.highlight doesn't define its own default.
484+
// eslint-disable-next-line max-depth
485+
if (value.default !== undefined && branchValue && typeof branchValue === 'object' && !Array.isArray(branchValue) && !('default' in branchValue)) {
486+
branchValue = {default: value.default, ...branchValue};
487+
}
488+
let [subPriority, subRules] = conditionalToRules(branchValue, rulePriority, currentConditions, subSkipConditions, fn);
482489
rules.push(...compileCondition(currentConditions, `${condition} === ${JSON.stringify(key)}`, priority, subRules));
483490
priority = Math.max(priority, subPriority);
484491
}
@@ -868,7 +875,7 @@ export function raw(this: MacroContext | void, css: string, layer = '_.a'): stri
868875
// We also check if this is globalThis, which happens in non-strict mode bundles.
869876
// Also allow style to be called as a normal function in tests.
870877
// @ts-ignore
871-
878+
872879
if ((this == null || this === globalThis) && process.env.NODE_ENV !== 'test') {
873880
throw new Error('The raw macro must be imported with {type: "macro"}.');
874881
}
@@ -898,7 +905,7 @@ export function keyframes(this: MacroContext | void, css: string): string {
898905
// We also check if this is globalThis, which happens in non-strict mode bundles.
899906
// Also allow style to be called as a normal function in tests.
900907
// @ts-ignore
901-
908+
902909
if ((this == null || this === globalThis) && process.env.NODE_ENV !== 'test') {
903910
throw new Error('The keyframes macro must be imported with {type: "macro"}.');
904911
}

0 commit comments

Comments
 (0)