Skip to content

Commit eb6127e

Browse files
authored
refactor: tabs vanilla extract (#5723)
1 parent 4020830 commit eb6127e

File tree

8 files changed

+314
-2236
lines changed

8 files changed

+314
-2236
lines changed

.changeset/lovely-flies-flash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@ultraviolet/ui": minor
3+
---
4+
5+
Refactor component `Tabs` to use vanilla extract instead of Emotion
Lines changed: 23 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use client'
22

3-
import styled from '@emotion/styled'
43
import type {
54
ComponentProps,
65
ElementType,
@@ -15,96 +14,14 @@ import { Badge } from '../Badge'
1514
import { Stack } from '../Stack'
1615
import { Text } from '../Text'
1716
import { Tooltip } from '../Tooltip'
17+
import {
18+
tabsBadge,
19+
tabsBadgeContainer,
20+
tabsButton,
21+
tabsTextSelected,
22+
} from './styles.css'
1823
import { useTabsContext } from './TabsContext'
1924

20-
const StyledBadge = styled(Badge)`
21-
padding: 0 ${({ theme }) => theme.space['1']};
22-
margin-left: ${({ theme }) => theme.space['1']};
23-
`
24-
25-
const StyledText = styled(Text)``
26-
27-
const StyledTooltip = styled(Tooltip)``
28-
29-
const BadgeContainer = styled.span`
30-
margin-left: ${({ theme }) => theme.space['1']};
31-
display: flex;
32-
`
33-
34-
export const StyledTabButton = styled('button')`
35-
display: flex;
36-
flex-direction: row;
37-
padding: ${({ theme }) => `${theme.space['1']} ${theme.space['2']}`};
38-
cursor: pointer;
39-
justify-content: center;
40-
align-items: baseline;
41-
white-space: nowrap;
42-
color: ${({ theme }) => theme.colors.neutral.text};
43-
text-decoration: none;
44-
user-select: none;
45-
touch-action: manipulation;
46-
transition: color 0.2s;
47-
border: none;
48-
background: none;
49-
border-bottom-width: 2px;
50-
border-bottom-style: solid;
51-
border-bottom-color: ${({ theme }) => theme.colors.neutral.border};
52-
outline: none;
53-
54-
font-size: ${({ theme }) => theme.typography.bodyStrong.fontSize};
55-
font-family: ${({ theme }) => theme.typography.bodyStrong.fontFamily};
56-
font-weight: ${({ theme }) => theme.typography.bodyStrong.weight};
57-
letter-spacing: ${({ theme }) => theme.typography.bodyStrong.letterSpacing};
58-
line-height: ${({ theme }) => theme.typography.bodyStrong.lineHeight};
59-
60-
&:hover,
61-
&:active,
62-
&:focus {
63-
text-decoration: none;
64-
outline: none;
65-
}
66-
67-
&:focus-visible {
68-
outline: auto;
69-
}
70-
71-
&[aria-selected='true'] {
72-
color: ${({ theme }) => theme.colors.primary.text};
73-
border-bottom-color: ${({ theme }) => theme.colors.primary.border};
74-
75-
${StyledText} {
76-
color: ${({ theme }) => theme.colors.primary.text};
77-
}
78-
}
79-
80-
&[aria-disabled='false']:not(:disabled) {
81-
&:hover,
82-
&:focus,
83-
&:active {
84-
outline: none;
85-
color: ${({ theme }) => theme.colors.primary.text};
86-
border-bottom-color: ${({ theme }) => theme.colors.primary.border};
87-
88-
&[data-is-selected='false'] {
89-
${StyledBadge} {
90-
background-color: ${({ theme }) => theme.colors.primary.background};
91-
border-color: ${({ theme }) => theme.colors.primary.background};
92-
color: ${({ theme }) => theme.colors.primary.text};
93-
}
94-
${StyledText} {
95-
color: ${({ theme }) => theme.colors.primary.text};
96-
}
97-
}
98-
}
99-
}
100-
101-
&[aria-disabled='true'],
102-
&:disabled {
103-
cursor: not-allowed;
104-
filter: grayscale(1) opacity(50%);
105-
}
106-
`
107-
10825
type TabProps<T extends ElementType = 'button'> = {
10926
as?: T
11027
badge?: ReactNode
@@ -150,19 +67,19 @@ export const Tab = forwardRef(
15067
) => {
15168
const { selected, onChange } = useTabsContext()
15269
const computedAs = as ?? 'button'
70+
const ComputedComponent = as ?? 'button'
15371
const isSelected = useMemo(
15472
() => value !== undefined && selected === value,
15573
[value, selected],
15674
)
15775

15876
return (
159-
<StyledTooltip text={tooltip}>
160-
<StyledTabButton
77+
<Tooltip text={tooltip}>
78+
<ComputedComponent
16179
aria-disabled={disabled}
16280
aria-label={value ? `${value}` : undefined}
16381
aria-selected={isSelected}
164-
as={computedAs}
165-
className={className}
82+
className={`${className ? `${className} ` : ''}${tabsButton}`}
16683
data-is-selected={isSelected}
16784
disabled={computedAs === 'button' ? disabled : undefined}
16885
onClick={event => {
@@ -186,31 +103,37 @@ export const Tab = forwardRef(
186103
<Stack alignItems="center" direction="row">
187104
{children}
188105
{typeof counter === 'number' || typeof counter === 'string' ? (
189-
<StyledBadge
106+
<Badge
107+
className={tabsBadge}
190108
prominence={isSelected ? 'strong' : 'default'}
191109
sentiment={isSelected ? 'primary' : 'neutral'}
192110
size="medium"
193111
>
194112
{counter}
195-
</StyledBadge>
113+
</Badge>
114+
) : null}
115+
{badge ? (
116+
<span className={tabsBadgeContainer}>{badge}</span>
196117
) : null}
197-
{badge ? <BadgeContainer>{badge}</BadgeContainer> : null}
198118
</Stack>
199119
{subtitle ? (
200120
<Stack direction="row">
201-
<StyledText
121+
<Text
202122
as="span"
123+
className={
124+
tabsTextSelected[isSelected ? 'selected' : 'default']
125+
}
203126
prominence="weak"
204127
sentiment="neutral"
205128
variant="bodySmall"
206129
>
207130
{subtitle}
208-
</StyledText>
131+
</Text>
209132
</Stack>
210133
) : null}
211134
</Stack>
212-
</StyledTabButton>
213-
</StyledTooltip>
135+
</ComputedComponent>
136+
</Tooltip>
214137
)
215138
},
216139
)
Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use client'
22

3-
import styled from '@emotion/styled'
43
import { ArrowDownIcon } from '@ultraviolet/icons'
54
import type {
65
ButtonHTMLAttributes,
@@ -10,29 +9,7 @@ import type {
109
} from 'react'
1110
import { forwardRef } from 'react'
1211
import { Menu } from '../Menu'
13-
import { StyledTabButton } from './Tab'
14-
15-
const ArrowIcon = styled(ArrowDownIcon)``
16-
const StyledMenu = styled(StyledTabButton)`
17-
${ArrowIcon} {
18-
color: inherit;
19-
margin-left: ${({ theme }) => theme.space['1']};
20-
transition: 300ms transform ease-out;
21-
}
22-
23-
&[aria-expanded='true'] ${ArrowIcon} {
24-
transform: rotate(-180deg);
25-
}
26-
`
27-
28-
// This will wrap and give the positioning to the popup div that is added onto the disclosure
29-
const StyledPositioningWrapper = styled.div`
30-
display: flex;
31-
position: sticky;
32-
top: 0;
33-
bottom: 0;
34-
right: 0;
35-
`
12+
import { tabsArrowIcon, tabsButton, tabsMenuWrapper } from './styles.css'
3613

3714
type TabMenuProps = {
3815
children: ReactNode
@@ -55,22 +32,22 @@ export const TabMenu = forwardRef(
5532
}: TabMenuProps,
5633
ref: Ref<HTMLButtonElement>,
5734
) => (
58-
<StyledPositioningWrapper>
35+
<div className={tabsMenuWrapper}>
5936
<Menu
6037
disclosure={
61-
<StyledMenu
38+
<button
6239
aria-disabled={disabled ?? 'false'}
6340
aria-haspopup="menu"
6441
aria-selected={ariaSelected}
65-
className={className}
42+
className={`${className ? `${className} ` : ''}${tabsButton}`}
6643
disabled={disabled}
6744
role="tab"
6845
type="button"
6946
{...props}
7047
>
7148
{disclosure}
72-
<ArrowIcon />
73-
</StyledMenu>
49+
<ArrowDownIcon className={tabsArrowIcon} />
50+
</button>
7451
}
7552
id={id}
7653
portalTarget={document.body}
@@ -79,6 +56,6 @@ export const TabMenu = forwardRef(
7956
>
8057
{children}
8158
</Menu>
82-
</StyledPositioningWrapper>
59+
</div>
8360
),
8461
)
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
'use client'
22

3-
import styled from '@emotion/styled'
43
import type { ComponentProps } from 'react'
54
import { useMemo } from 'react'
65
import { Menu } from '../Menu'
6+
import { tabsTextSelected } from './styles.css'
77
import { useTabsContext } from './TabsContext'
88

9-
const StyledMenuItem = styled(Menu.Item)`
10-
&[aria-selected='true'] {
11-
color: ${({ theme }) => theme.colors.primary.text};
12-
}
13-
`
14-
159
type TabMenuItemProps = {
1610
value?: string | number
17-
} & ComponentProps<typeof StyledMenuItem>
11+
} & ComponentProps<typeof Menu.Item>
1812

1913
export const TabMenuItem = ({
2014
value,
@@ -30,8 +24,9 @@ export const TabMenuItem = ({
3024
)
3125

3226
return (
33-
<StyledMenuItem
27+
<Menu.Item
3428
aria-selected={isSelected}
29+
className={tabsTextSelected[isSelected ? 'selected' : 'default']}
3530
onClick={event => {
3631
if (value !== undefined) {
3732
onChange(value)
@@ -41,6 +36,6 @@ export const TabMenuItem = ({
4136
{...props}
4237
>
4338
{children}
44-
</StyledMenuItem>
39+
</Menu.Item>
4540
)
4641
}

0 commit comments

Comments
 (0)