Skip to content

Commit 494e01c

Browse files
authored
fix: Tabs from testing (#7463)
* fix: Tabs from testing * fix horizontal spacing for icon only * more spacing fixes * make mobile testing easier * rename prop isIconOnly
1 parent 5757c3a commit 494e01c

File tree

4 files changed

+63
-33
lines changed

4 files changed

+63
-33
lines changed

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default meta;
3131
export const Example = (args: any) => (
3232
<Tabs {...args} styles={style({width: 450, height: 256})}>
3333
<TabList aria-label="History of Ancient Rome">
34-
<Tab id="FoR"><Edit /><Text>Founding of Rome</Text></Tab>
34+
<Tab id="FoR">Founding of Rome</Tab>
3535
<Tab id="MaR">Monarchy and Republic</Tab>
3636
<Tab id="Emp">Empire</Tab>
3737
</TabList>
@@ -57,9 +57,9 @@ export const Example = (args: any) => (
5757
export const Disabled = (args: any) => (
5858
<Tabs {...args} styles={style({width: 450, height: 144})} disabledKeys={['FoR', 'MaR', 'Emp']}>
5959
<TabList aria-label="History of Ancient Rome">
60-
<Tab id="FoR"><Edit /><Text>Founding of Rome</Text></Tab>
61-
<Tab id="MaR">Monarchy and Republic</Tab>
62-
<Tab id="Emp">Empire</Tab>
60+
<Tab id="FoR" aria-label="Edit"><Edit /><Text>Edit</Text></Tab>
61+
<Tab id="MaR" aria-label="Notifications"><Bell /><Text>Notifications</Text></Tab>
62+
<Tab id="Emp" aria-label="Likes"><Heart /><Text>Likes</Text></Tab>
6363
</TabList>
6464
<TabPanel id="FoR">
6565
Arma virumque cano, Troiae qui primus ab oris.
@@ -74,7 +74,7 @@ export const Disabled = (args: any) => (
7474
);
7575

7676
export const Icons = (args: any) => (
77-
<Tabs {...args} styles={style({width: 208, height: 144})} iconOnly>
77+
<Tabs {...args} styles={style({width: 208, height: 144})} isIconOnly>
7878
<TabList aria-label="History of Ancient Rome">
7979
<Tab id="FoR" aria-label="Edit"><Edit /><Text>Edit</Text></Tab>
8080
<Tab id="MaR" aria-label="Notifications"><Bell /><Text>Notifications</Text></Tab>

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

+24-13
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
import {centerBaseline} from './CenterBaseline';
3030
import {Collection, DOMRef, DOMRefValue, FocusableRef, FocusableRefValue, Key, Node, Orientation, RefObject} from '@react-types/shared';
3131
import {createContext, forwardRef, Fragment, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
32-
import {focusRing, style} from '../style' with {type: 'macro'};
32+
import {focusRing, size, style} from '../style' with {type: 'macro'};
3333
import {getAllowedOverrides, StyleProps, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
3434
import {IconContext} from './Icon';
3535
// @ts-ignore
@@ -55,7 +55,7 @@ export interface TabsProps extends Omit<AriaTabsProps, 'className' | 'style' | '
5555
/**
5656
* If the tabs should only display icons and no text.
5757
*/
58-
iconOnly?: boolean
58+
isIconOnly?: boolean
5959
}
6060

6161
export interface TabProps extends Omit<AriaTabProps, 'children' | 'style' | 'className'>, StyleProps {
@@ -96,7 +96,7 @@ export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef<HTMLD
9696
isDisabled,
9797
disabledKeys,
9898
orientation = 'horizontal',
99-
iconOnly = false
99+
isIconOnly = false
100100
} = props;
101101
let domRef = useDOMRef(ref);
102102
let [value, setValue] = useControlledState(props.selectedKey, props.defaultSelectedKey ?? null!, props.onSelectionChange);
@@ -112,7 +112,7 @@ export const Tabs = forwardRef(function Tabs(props: TabsProps, ref: DOMRef<HTMLD
112112
disabledKeys,
113113
selectedKey: value,
114114
onSelectionChange: setValue,
115-
iconOnly,
115+
isIconOnly,
116116
onFocus: () => pickerRef.current?.focus(),
117117
pickerRef
118118
}]
@@ -170,7 +170,7 @@ const tablist = style({
170170
});
171171

172172
export function TabList<T extends object>(props: TabListProps<T>) {
173-
let {density, isDisabled, disabledKeys, orientation, iconOnly, onFocus} = useContext(InternalTabsContext) ?? {};
173+
let {density, isDisabled, disabledKeys, orientation, isIconOnly, onFocus} = useContext(InternalTabsContext) ?? {};
174174
let {showItems} = useContext(CollapseContext) ?? {};
175175
let state = useContext(TabListStateContext);
176176
let [selectedTab, setSelectedTab] = useState<HTMLElement | undefined>(undefined);
@@ -208,7 +208,7 @@ export function TabList<T extends object>(props: TabListProps<T>) {
208208
<RACTabList
209209
{...props}
210210
ref={tablistRef}
211-
className={renderProps => tablist({...renderProps, isIconOnly: iconOnly, density})} />
211+
className={renderProps => tablist({...renderProps, isIconOnly, density})} />
212212
{orientation === 'horizontal' &&
213213
<TabLine showItems={showItems} disabledKeys={disabledKeys} isDisabled={isDisabled} selectedTab={selectedTab} orientation={orientation} density={density} />}
214214
</div>
@@ -255,7 +255,7 @@ const selectedIndicator = style({
255255
transitionTimingFunction: 'in-out'
256256
});
257257

258-
function TabLine(props: TabLineProps) {
258+
function TabLine(props: TabLineProps & {isIconOnly?: boolean}) {
259259
let {
260260
disabledKeys,
261261
isDisabled: isTabsDisabled,
@@ -301,7 +301,14 @@ function TabLine(props: TabLineProps) {
301301

302302
useLayoutEffect(() => {
303303
onResize();
304-
}, [onResize, state?.selectedItem?.key, direction, orientation, density]);
304+
}, [onResize, state?.selectedItem?.key, density]);
305+
306+
let ref = useRef<HTMLElement | undefined>(selectedTab);
307+
// assign ref before the useResizeObserver useEffect runs
308+
useLayoutEffect(() => {
309+
ref.current = selectedTab;
310+
});
311+
useResizeObserver({ref, onResize});
305312

306313
return (
307314
<div style={{...style}} className={selectedIndicator({isDisabled, orientation})} />
@@ -333,7 +340,10 @@ const tab = style({
333340
position: 'relative',
334341
cursor: 'default',
335342
flexShrink: 0,
336-
transition: 'default'
343+
transition: 'default',
344+
paddingX: {
345+
isIconOnly: size(6)
346+
}
337347
}, getAllowedOverrides());
338348

339349
const icon = style({
@@ -346,15 +356,15 @@ const icon = style({
346356
});
347357

348358
export function Tab(props: TabProps) {
349-
let {density, iconOnly} = useContext(InternalTabsContext) ?? {};
359+
let {density, isIconOnly} = useContext(InternalTabsContext) ?? {};
350360

351361
return (
352362
<RACTab
353363
{...props}
354364
// @ts-ignore
355365
originalProps={props}
356366
style={props.UNSAFE_style}
357-
className={renderProps => (props.UNSAFE_className || '') + tab({...renderProps, density}, props.styles)}>
367+
className={renderProps => (props.UNSAFE_className || '') + tab({...renderProps, density, isIconOnly}, props.styles)}>
358368
{({
359369
// @ts-ignore
360370
isMenu
@@ -372,7 +382,7 @@ export function Tab(props: TabProps) {
372382
display: {
373383
isIconOnly: 'none'
374384
}
375-
})({isIconOnly: iconOnly})
385+
})({isIconOnly})
376386
}],
377387
[IconContext, {
378388
render: centerBaseline({slot: 'icon', styles: style({order: 0})}),
@@ -469,7 +479,7 @@ let HiddenTabs = function (props: {
469479
let TabsMenu = (props: {items: Array<Node<any>>, onSelectionChange: TabsProps['onSelectionChange']}) => {
470480
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
471481
let {items} = props;
472-
let {density, onSelectionChange, selectedKey, isDisabled, disabledKeys, pickerRef} = useContext(InternalTabsContext);
482+
let {density, onSelectionChange, selectedKey, isDisabled, disabledKeys, pickerRef, isIconOnly} = useContext(InternalTabsContext);
473483
let state = useContext(TabListStateContext);
474484
let allKeysDisabled = useMemo(() => {
475485
return isAllTabsDisabled(state?.collection, disabledKeys ? new Set(disabledKeys) : new Set());
@@ -491,6 +501,7 @@ let TabsMenu = (props: {items: Array<Node<any>>, onSelectionChange: TabsProps['o
491501
ref={pickerRef ? pickerRef : undefined}
492502
isDisabled={isDisabled || allKeysDisabled}
493503
density={density!}
504+
isIconOnly={isIconOnly}
494505
items={items}
495506
disabledKeys={disabledKeys}
496507
selectedKey={selectedKey}

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

+22-7
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,14 @@ import {
2929
checkmark,
3030
description,
3131
icon,
32-
iconCenterWrapper,
3332
label,
3433
menuitem,
3534
sectionHeader,
3635
sectionHeading
3736
} from './Menu';
3837
import CheckmarkIcon from '../ui-icons/Checkmark';
3938
import ChevronIcon from '../ui-icons/Chevron';
40-
import {edgeToText, focusRing, style} from '../style' with {type: 'macro'};
39+
import {edgeToText, focusRing, size, style} from '../style' with {type: 'macro'};
4140
import {fieldInput, StyleProps} from './style-utils' with {type: 'macro'};
4241
import {
4342
FieldLabel
@@ -86,7 +85,11 @@ export interface PickerProps<T extends object> extends
8685
/** Width of the menu. By default, matches width of the trigger. Note that the minimum width of the dropdown is always equal to the trigger's width. */
8786
menuWidth?: number,
8887
/** Density of the tabs, affects the height of the picker. */
89-
density: 'compact' | 'regular'
88+
density: 'compact' | 'regular',
89+
/**
90+
* If the tab picker should only display icon and no text for the button label.
91+
*/
92+
isIconOnly?: boolean
9093
}
9194

9295
export const PickerContext = createContext<ContextValue<Partial<PickerProps<any>>, FocusableRefValue<HTMLButtonElement>>>(null);
@@ -155,6 +158,14 @@ const iconStyles = style({
155158
}
156159
});
157160

161+
const iconCenterWrapper = style({
162+
display: 'flex',
163+
gridArea: 'icon',
164+
paddingStart: {
165+
isIconOnly: size(6)
166+
}
167+
});
168+
158169
let InsideSelectValueContext = createContext(false);
159170

160171
function Picker<T extends object>(props: PickerProps<T>, ref: FocusableRef<HTMLButtonElement>) {
@@ -170,6 +181,7 @@ function Picker<T extends object>(props: PickerProps<T>, ref: FocusableRef<HTMLB
170181
items,
171182
placeholder = stringFormatter.format('picker.placeholder'),
172183
density,
184+
isIconOnly,
173185
...pickerProps
174186
} = props;
175187
let isQuiet = true;
@@ -205,7 +217,7 @@ function Picker<T extends object>(props: PickerProps<T>, ref: FocusableRef<HTMLB
205217
[IconContext, {
206218
slots: {
207219
icon: {
208-
render: centerBaseline({slot: 'icon', styles: iconCenterWrapper}),
220+
render: centerBaseline({slot: 'icon', styles: iconCenterWrapper({isIconOnly})}),
209221
styles: icon
210222
}
211223
}
@@ -214,10 +226,13 @@ function Picker<T extends object>(props: PickerProps<T>, ref: FocusableRef<HTMLB
214226
slots: {
215227
// Default slot is useful when converting other collections to PickerItems.
216228
[DEFAULT_SLOT]: {styles: style({
217-
display: 'block',
229+
display: {
230+
default: 'block',
231+
isIconOnly: 'none'
232+
},
218233
flexGrow: 1,
219234
truncate: true
220-
})}
235+
})({isIconOnly})}
221236
}
222237
}],
223238
[InsideSelectValueContext, true]
@@ -291,7 +306,7 @@ export function PickerItem(props: PickerItemProps) {
291306
<DefaultProvider
292307
context={IconContext}
293308
value={{slots: {
294-
icon: {render: centerBaseline({slot: 'icon', styles: iconCenterWrapper}), styles: icon}
309+
icon: {render: centerBaseline({slot: 'icon', styles: iconCenterWrapper({})}), styles: icon}
295310
}}}>
296311
<DefaultProvider
297312
context={TextContext}

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

+12-8
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ const meta: Meta<typeof Tabs> = {
3030
export default meta;
3131

3232
export const Example = (args: any) => (
33-
<div className={style({width: 700, height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
33+
<div className={style({width: 700, maxWidth: '[calc(100vw - 60px)]', height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
3434
<Tabs {...args} styles={style({width: 'full'})}>
3535
<TabList aria-label="History of Ancient Rome">
36-
<Tab id="FoR"><Edit /><Text>Founding of Rome</Text></Tab>
36+
<Tab id="FoR">Founding of Rome</Tab>
3737
<Tab id="MaR">Monarchy and Republic</Tab>
3838
<Tab id="Emp">Empire</Tab>
3939
</TabList>
@@ -58,10 +58,10 @@ export const Example = (args: any) => (
5858
);
5959

6060
export const Disabled = (args: any) => (
61-
<div className={style({width: 700, height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
61+
<div className={style({width: 700, maxWidth: '[calc(100vw - 60px)]', height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
6262
<Tabs {...args} styles={style({width: 'full'})} disabledKeys={['FoR', 'MaR', 'Emp']}>
6363
<TabList aria-label="History of Ancient Rome">
64-
<Tab id="FoR"><Edit /><Text>Founding of Rome</Text></Tab>
64+
<Tab id="FoR">Founding of Rome</Tab>
6565
<Tab id="MaR">Monarchy and Republic</Tab>
6666
<Tab id="Emp">Empire</Tab>
6767
</TabList>
@@ -78,9 +78,9 @@ export const Disabled = (args: any) => (
7878
</div>
7979
);
8080

81-
export const Icons = (args: any) => (
82-
<div className={style({width: 700, height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
83-
<Tabs {...args} styles={style({width: 'full'})} iconOnly>
81+
const IconsRender = (props) => (
82+
<div className={style({width: 700, maxWidth: '[calc(100vw - 60px)]', height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
83+
<Tabs {...props} styles={style({width: 'full'})}>
8484
<TabList aria-label="History of Ancient Rome">
8585
<Tab id="FoR" aria-label="Edit"><Edit /><Text>Founding of Rome</Text></Tab>
8686
<Tab id="MaR" aria-label="Notifications"><Bell /><Text>Monarchy and Republic</Text></Tab>
@@ -99,6 +99,10 @@ export const Icons = (args: any) => (
9999
</div>
100100
);
101101

102+
export const Icons = {
103+
render: (args) => <IconsRender {...args} />
104+
};
105+
102106
interface Item {
103107
id: number,
104108
title: string,
@@ -111,7 +115,7 @@ let items: Item[] = [
111115
];
112116

113117
export const Dynamic = (args: any) => (
114-
<div className={style({width: 700, height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
118+
<div className={style({width: 700, maxWidth: '[calc(100vw - 60px)]', height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
115119
<Tabs {...args} styles={style({width: 'full'})} disabledKeys={new Set([2])}>
116120
<TabList aria-label="History of Ancient Rome" items={items}>
117121
{item => <Tab>{item.title}</Tab>}

0 commit comments

Comments
 (0)