Skip to content

Commit 488bb6b

Browse files
bsmiley1snowystingerLFDanLu
authored
fix: Vertical TabList Wrapping (#8201)
* fix(XI-7052): Support text wrapping in vertical TabList * Add storybook * fix focus ring size and make sure it works in S2 --------- Co-authored-by: Robert Snow <[email protected]> Co-authored-by: Robert Snow <[email protected]> Co-authored-by: Daniel Lu <[email protected]>
1 parent 5672c86 commit 488bb6b

File tree

6 files changed

+165
-13
lines changed

6 files changed

+165
-13
lines changed

packages/@adobe/spectrum-css-temp/components/tabs/index.css

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,11 @@ governing permissions and limitations under the License.
9191
&::before {
9292
content: '';
9393
position: absolute;
94-
top: 50%;
94+
top: 0;
95+
bottom: 0;
9596

9697
box-sizing: border-box;
9798

98-
block-size: var(--spectrum-tabs-focus-ring-height);
99-
margin-block-start: calc(calc(var(--spectrum-tabs-focus-ring-height) / -2) + calc(var(--spectrum-tabs-rule-height) / 2));
10099
inset-inline-start: calc(var(--spectrum-tabs-focus-ring-padding-x) * -1);
101100
inset-inline-end: calc(var(--spectrum-tabs-focus-ring-padding-x) * -1);
102101
border: var(--spectrum-tabs-focus-ring-size) solid transparent;
@@ -163,6 +162,13 @@ governing permissions and limitations under the License.
163162
& + *:not(.spectrum-Tabs-selectionIndicator) {
164163
margin-inline-start: var(--spectrum-tabs-item-gap);
165164
}
165+
166+
&::before {
167+
top: 50%;
168+
bottom: unset;
169+
block-size: var(--spectrum-tabs-focus-ring-height);
170+
margin-block-start: calc(calc(var(--spectrum-tabs-focus-ring-height) / -2) + calc(var(--spectrum-tabs-rule-height) / 2));
171+
}
166172
}
167173

168174
.spectrum-Tabs-selectionIndicator {
@@ -234,10 +240,22 @@ governing permissions and limitations under the License.
234240
/* padding is included in click area of tab items, so only need to offset by the size of the focus ring's border */
235241
inset-inline-start: calc(var(--spectrum-tabs-focus-ring-size) * -1);
236242
inset-inline-end: calc(var(--spectrum-tabs-focus-ring-size) * -1);
237-
margin-block-start: calc(calc(var(--spectrum-tabs-focus-ring-height) / -2));
238243
}
239244
}
240245

246+
/*
247+
Allow tab labels to wrap when TabList component has minWidth set.
248+
*/
249+
&.spectrum-Tabs--verticalWrap:not(.spectrum-Tabs--compact) .spectrum-Tabs-item {
250+
display: flex;
251+
align-items: center;
252+
white-space: normal;
253+
line-height: normal;
254+
word-break: break-word;
255+
block-size: auto;
256+
min-height: var(--spectrum-tabs-vertical-item-height);
257+
}
258+
241259
&.spectrum-Tabs--compact {
242260
.spectrum-Tabs-item {
243261
block-size: var(--spectrum-tabs-compact-vertical-item-height);

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ const selectedIndicator = style<{isDisabled: boolean, orientation?: Orientation}
275275
borderRadius: 'full'
276276
});
277277

278-
const tab = style<TabRenderProps & {density?: 'compact' | 'regular', labelBehavior?: 'show' | 'hide'}>({
278+
const tab = style<TabRenderProps & {density?: 'compact' | 'regular', labelBehavior?: 'show' | 'hide', orientation?: Orientation}>({
279279
...focusRing(),
280280
display: 'flex',
281281
color: {
@@ -290,9 +290,23 @@ const tab = style<TabRenderProps & {density?: 'compact' | 'regular', labelBehavi
290290
borderRadius: 'sm',
291291
gap: 'text-to-visual',
292292
height: {
293-
density: {
294-
compact: 32,
295-
regular: 48
293+
orientation: {
294+
horizontal: {
295+
density: {
296+
compact: 32,
297+
regular: 48
298+
}
299+
}
300+
}
301+
},
302+
minHeight: {
303+
orientation: {
304+
vertical: {
305+
density: {
306+
compact: 32,
307+
regular: 48
308+
}
309+
}
296310
}
297311
},
298312
alignItems: 'center',
@@ -330,7 +344,7 @@ export function Tab(props: TabProps): ReactNode {
330344
originalProps={props}
331345
aria-labelledby={`${labelBehavior === 'hide' ? contentId : ''} ${ariaLabelledBy}`}
332346
style={props.UNSAFE_style}
333-
className={renderProps => (props.UNSAFE_className || '') + tab({...renderProps, density, labelBehavior}, props.styles)}>
347+
className={renderProps => (props.UNSAFE_className || '') + tab({...renderProps, density, labelBehavior, orientation}, props.styles)}>
334348
{({
335349
// @ts-ignore
336350
isMenu,

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ export default meta;
4040
type Story = StoryObj<typeof Tabs>;
4141

4242
const tabs = style({width: 'full', height: 'full'});
43+
const tabList = style({maxWidth: {orientation: {vertical: 150}}});
4344

4445
export const Example: Story = {
4546
render: (args) => (
4647
<div className={style({width: 700, maxWidth: 'calc(100vw - 60px)', height: 256, resize: 'horizontal', overflow: 'hidden', padding: 8})}>
4748
<Tabs {...args} styles={tabs} aria-label="History of Ancient Rome">
48-
<TabList>
49+
<TabList styles={tabList({orientation: args.orientation})}>
4950
<Tab id="FoR">Founding of Rome</Tab>
5051
<Tab id="MaR">Monarchy and Republic</Tab>
5152
<Tab id="Emp">Empire</Tab>

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ function TabLine(props: TabLineProps) {
261261
* A TabList is used within Tabs to group tabs that a user can switch between.
262262
* The keys of the items within the <TabList> must match up with a corresponding item inside the <TabPanels>.
263263
*/
264-
export function TabList<T>(props: SpectrumTabListProps<T>): ReactNode {
264+
export function TabList<T>(props: SpectrumTabListProps<T> & {wrap?: boolean}): ReactNode {
265265
const tabContext = useContext(TabContext)!;
266266
const {refs, tabState, tabProps, tabPanelProps} = tabContext;
267267
const {isQuiet, density, isEmphasized, orientation} = tabProps;
@@ -288,6 +288,8 @@ export function TabList<T>(props: SpectrumTabListProps<T>): ReactNode {
288288

289289
let tabListclassName = classNames(styles, 'spectrum-TabsPanel-tabs');
290290

291+
const verticalWrap = props.wrap && orientation === 'vertical';
292+
291293
const tabContent = (
292294
<div
293295
{...stylePropsFinal}
@@ -301,7 +303,8 @@ export function TabList<T>(props: SpectrumTabListProps<T>): ReactNode {
301303
{
302304
'spectrum-Tabs--quiet': isQuiet,
303305
'spectrum-Tabs--emphasized': isEmphasized,
304-
['spectrum-Tabs--compact']: density === 'compact'
306+
['spectrum-Tabs--compact']: density === 'compact',
307+
'spectrum-Tabs--verticalWrap': verticalWrap
305308
},
306309
orientation === 'vertical' && styleProps.className
307310
)

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ OrientationVertical.story = {
5757
name: 'orientation: vertical'
5858
};
5959

60+
export const OrientationVerticalWrap: TabsStory = () => renderWithVerticalWrap({minWidth: 90, wrap: true});
61+
62+
OrientationVerticalWrap.story = {
63+
name: 'orientation: vertical, wrap: true'
64+
};
65+
6066
export const DensityCompact: TabsStory = () => render({density: 'compact'});
6167

6268
DensityCompact.story = {
@@ -742,6 +748,111 @@ function renderWithFalsyKey(props = {}) {
742748
);
743749
}
744750

751+
function renderWithVerticalWrap(props = {}) {
752+
return (
753+
<Tabs
754+
orientation="vertical"
755+
aria-label="Tab example"
756+
maxWidth={500}
757+
onSelectionChange={action('onSelectionChange')}>
758+
<TabList {...props}>
759+
<Item key="val1">User Profile Settings</Item>
760+
<Item key="val2">Notification Preferences and yabba dabba doo yabba dabba doo yabba dabba doo</Item>
761+
<Item key="val3">Tab 3</Item>
762+
<Item key="val4">Tab 4</Item>
763+
<Item key="val5">Tab 5</Item>
764+
</TabList>
765+
<TabPanels>
766+
<Item key="val1">
767+
<Heading>Tab Body 1</Heading>
768+
<Text>
769+
Dolore ex esse laboris elit magna esse sunt. Pariatur in veniam Lorem est occaecat do
770+
magna nisi mollit ipsum sit adipisicing fugiat ex. Pariatur ullamco exercitation ea qui
771+
adipisicing. Id cupidatat aute id ut excepteur exercitation magna pariatur. Mollit irure
772+
irure reprehenderit pariatur eiusmod proident Lorem deserunt duis cillum mollit. Do
773+
reprehenderit sit cupidatat quis laborum in do culpa nisi ipsum. Velit aliquip commodo
774+
ea ipsum incididunt culpa nostrud deserunt incididunt exercitation. In quis proident sit
775+
ad dolore tempor. Eiusmod pariatur quis commodo labore cupidatat cillum enim eiusmod
776+
voluptate laborum culpa. Laborum cupidatat incididunt velit voluptate incididunt
777+
occaecat quis do. Consequat adipisicing irure Lorem commodo officia sint id. Velit sit
778+
magna aliquip eiusmod non id deserunt. Magna veniam ad consequat dolor cupidatat esse
779+
enim Lorem ullamco. Anim excepteur consectetur id in. Mollit laboris duis labore enim
780+
duis esse reprehenderit.
781+
</Text>
782+
</Item>
783+
<Item key="val2">
784+
<Heading>Tab Body 2</Heading>
785+
<Text>
786+
Dolore ex esse laboris elit magna esse sunt. Pariatur in veniam Lorem est occaecat do
787+
magna nisi mollit ipsum sit adipisicing fugiat ex. Pariatur ullamco exercitation ea qui
788+
adipisicing. Id cupidatat aute id ut excepteur exercitation magna pariatur. Mollit irure
789+
irure reprehenderit pariatur eiusmod proident Lorem deserunt duis cillum mollit. Do
790+
reprehenderit sit cupidatat quis laborum in do culpa nisi ipsum. Velit aliquip commodo
791+
ea ipsum incididunt culpa nostrud deserunt incididunt exercitation. In quis proident sit
792+
ad dolore tempor. Eiusmod pariatur quis commodo labore cupidatat cillum enim eiusmod
793+
voluptate laborum culpa. Laborum cupidatat incididunt velit voluptate incididunt
794+
occaecat quis do. Consequat adipisicing irure Lorem commodo officia sint id. Velit sit
795+
magna aliquip eiusmod non id deserunt. Magna veniam ad consequat dolor cupidatat esse
796+
enim Lorem ullamco. Anim excepteur consectetur id in. Mollit laboris duis labore enim
797+
duis esse reprehenderit.
798+
</Text>
799+
</Item>
800+
<Item key="val3">
801+
<Heading>Tab Body 3</Heading>
802+
<Text>
803+
Dolore ex esse laboris elit magna esse sunt. Pariatur in veniam Lorem est occaecat do
804+
magna nisi mollit ipsum sit adipisicing fugiat ex. Pariatur ullamco exercitation ea qui
805+
adipisicing. Id cupidatat aute id ut excepteur exercitation magna pariatur. Mollit irure
806+
irure reprehenderit pariatur eiusmod proident Lorem deserunt duis cillum mollit. Do
807+
reprehenderit sit cupidatat quis laborum in do culpa nisi ipsum. Velit aliquip commodo
808+
ea ipsum incididunt culpa nostrud deserunt incididunt exercitation. In quis proident sit
809+
ad dolore tempor. Eiusmod pariatur quis commodo labore cupidatat cillum enim eiusmod
810+
voluptate laborum culpa. Laborum cupidatat incididunt velit voluptate incididunt
811+
occaecat quis do. Consequat adipisicing irure Lorem commodo officia sint id. Velit sit
812+
magna aliquip eiusmod non id deserunt. Magna veniam ad consequat dolor cupidatat esse
813+
enim Lorem ullamco. Anim excepteur consectetur id in. Mollit laboris duis labore enim
814+
duis esse reprehenderit.
815+
</Text>
816+
</Item>
817+
<Item key="val4">
818+
<Heading>Tab Body 4</Heading>
819+
<Text>
820+
Dolore ex esse laboris elit magna esse sunt. Pariatur in veniam Lorem est occaecat do
821+
magna nisi mollit ipsum sit adipisicing fugiat ex. Pariatur ullamco exercitation ea qui
822+
adipisicing. Id cupidatat aute id ut excepteur exercitation magna pariatur. Mollit irure
823+
irure reprehenderit pariatur eiusmod proident Lorem deserunt duis cillum mollit. Do
824+
reprehenderit sit cupidatat quis laborum in do culpa nisi ipsum. Velit aliquip commodo
825+
ea ipsum incididunt culpa nostrud deserunt incididunt exercitation. In quis proident sit
826+
ad dolore tempor. Eiusmod pariatur quis commodo labore cupidatat cillum enim eiusmod
827+
voluptate laborum culpa. Laborum cupidatat incididunt velit voluptate incididunt
828+
occaecat quis do. Consequat adipisicing irure Lorem commodo officia sint id. Velit sit
829+
magna aliquip eiusmod non id deserunt. Magna veniam ad consequat dolor cupidatat esse
830+
enim Lorem ullamco. Anim excepteur consectetur id in. Mollit laboris duis labore enim
831+
duis esse reprehenderit.
832+
</Text>
833+
</Item>
834+
<Item key="val5">
835+
<Heading>Tab Body 5</Heading>
836+
<Text>
837+
Dolore ex esse laboris elit magna esse sunt. Pariatur in veniam Lorem est occaecat do
838+
magna nisi mollit ipsum sit adipisicing fugiat ex. Pariatur ullamco exercitation ea qui
839+
adipisicing. Id cupidatat aute id ut excepteur exercitation magna pariatur. Mollit irure
840+
irure reprehenderit pariatur eiusmod proident Lorem deserunt duis cillum mollit. Do
841+
reprehenderit sit cupidatat quis laborum in do culpa nisi ipsum. Velit aliquip commodo
842+
ea ipsum incididunt culpa nostrud deserunt incididunt exercitation. In quis proident sit
843+
ad dolore tempor. Eiusmod pariatur quis commodo labore cupidatat cillum enim eiusmod
844+
voluptate laborum culpa. Laborum cupidatat incididunt velit voluptate incididunt
845+
occaecat quis do. Consequat adipisicing irure Lorem commodo officia sint id. Velit sit
846+
magna aliquip eiusmod non id deserunt. Magna veniam ad consequat dolor cupidatat esse
847+
enim Lorem ullamco. Anim excepteur consectetur id in. Mollit laboris duis labore enim
848+
duis esse reprehenderit.
849+
</Text>
850+
</Item>
851+
</TabPanels>
852+
</Tabs>
853+
);
854+
}
855+
745856
interface DynamicTabItem {
746857
name: string,
747858
children: ReactNode,

packages/@react-types/tabs/src/index.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ export interface SpectrumTabsProps<T> extends AriaTabListBase, Omit<SingleSelect
8181

8282
export interface SpectrumTabListProps<T> extends DOMProps, StyleProps {
8383
/** The tab items to display. Item keys should match the key of the corresponding `<Item>` within the `<TabPanels>` element. */
84-
children: CollectionChildren<T>
84+
children: CollectionChildren<T>,
85+
/**
86+
* When `true`, tab labels will wrap if they exceed the TabList's width. Only supported in vertical orientation with `regular` density. For proper wrapping, set a `minWidth` on TabList.
87+
* @default false
88+
*/
89+
wrap?: boolean
8590
}
8691

8792
export interface SpectrumTabPanelsProps<T> extends DOMProps, StyleProps {

0 commit comments

Comments
 (0)