-
Notifications
You must be signed in to change notification settings - Fork 221
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Refactor Tabs component to use new styling utilities and tokens #3119
base: prerelease/major
Are you sure you want to change the base?
Changes from all commits
087ef54
80eea64
ce56729
4c81677
d3598d9
e2ae6d3
ae71af6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,17 @@ | ||
import * as React from 'react'; | ||
|
||
import {colors, space, type, borderRadius, iconColors} from '@workday/canvas-kit-react/tokens'; | ||
import { | ||
focusRing, | ||
hideMouseFocus, | ||
styled, | ||
StyledType, | ||
slugify, | ||
createElemPropsHook, | ||
composeHooks, | ||
ExtractProps, | ||
ellipsisStyles, | ||
EllipsisText, | ||
createSubcomponent, | ||
useModalityType, | ||
createComponent, | ||
} from '@workday/canvas-kit-react/common'; | ||
import {Box, FlexProps} from '@workday/canvas-kit-react/layout'; | ||
import {Box, FlexProps, mergeStyles} from '@workday/canvas-kit-react/layout'; | ||
import {OverflowTooltip} from '@workday/canvas-kit-react/tooltip'; | ||
import {SystemIcon, systemIconStencil} from '@workday/canvas-kit-react/icon'; | ||
import { | ||
|
@@ -25,8 +21,11 @@ import { | |
useListItemSelect, | ||
useOverflowListItemMeasure, | ||
} from '@workday/canvas-kit-react/collection'; | ||
import {calc, createStencil} from '@workday/canvas-kit-styling'; | ||
|
||
import {useTabsModel} from './useTabsModel'; | ||
import {buttonStencil} from '@workday/canvas-kit-react/button'; | ||
import {system, brand, base} from '@workday/canvas-tokens-web'; | ||
export interface TabsItemProps | ||
extends ExtractProps<typeof Box, never>, | ||
Partial<Pick<FlexProps, 'gap'>> { | ||
|
@@ -82,79 +81,91 @@ export interface TabsItemProps | |
tabIndex?: number; | ||
} | ||
|
||
export const StyledTabItem = styled(Box.as('button'))<StyledType & Pick<TabsItemProps, 'gap'>>( | ||
({theme}) => ({ | ||
...type.levels.subtext.large, | ||
fontWeight: type.properties.fontWeights.medium, | ||
const tabItemStencil = createStencil({ | ||
base: { | ||
...system.type.subtext.large, | ||
fontWeight: system.fontWeight.medium, | ||
border: 'none', | ||
backgroundColor: 'transparent', | ||
flex: '0 0 auto', | ||
minWidth: system.space.zero, | ||
alignItems: 'center', | ||
padding: `${space.xs} ${space.s}`, | ||
padding: `${system.space.x3} ${system.space.x4}`, | ||
height: 52, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. height prob needs either a calc or |
||
boxSizing: 'border-box', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can remove box-sizing border box since stencils adds it by default |
||
cursor: 'pointer', | ||
color: colors.licorice300, | ||
color: system.color.fg.muted.default, | ||
position: 'relative', | ||
borderRadius: `${borderRadius.m} ${borderRadius.m} 0px 0px`, | ||
borderRadius: `${system.space.x1} ${system.space.x1} ${system.space.zero} ${system.space.zero}`, | ||
transition: 'background 150ms ease, color 150ms ease', | ||
whiteSpace: 'nowrap', | ||
textOverflow: 'ellipsis', | ||
overflow: 'hidden', | ||
[systemIconStencil.vars.color]: system.color.fg.muted.default, | ||
|
||
...hideMouseFocus, | ||
'&:has(span)': { | ||
display: 'flex', | ||
gap: system.space.x2, | ||
}, | ||
|
||
'&:hover, &:focus': { | ||
backgroundColor: colors.soap200, | ||
color: colors.blackPepper400, | ||
'&:hover, &.hover, &:focus-visible, &.focus': { | ||
backgroundColor: base.soap200, | ||
color: base.blackPepper400, | ||
|
||
[systemIconStencil.vars.color]: iconColors.hover, | ||
[systemIconStencil.vars.color]: system.color.icon.strong, | ||
}, | ||
|
||
'&:focus': { | ||
'&:focus-visible, &.focus': { | ||
outline: `none`, | ||
...focusRing({inset: 'outer', width: 0, separation: 2}, theme), | ||
...focusRing({inset: 'outer', width: 0, separation: 2}), | ||
[buttonStencil.vars.boxShadowInner]: system.color.border.inverse, | ||
[buttonStencil.vars.boxShadowOuter]: brand.common.focusOutline, | ||
[systemIconStencil.vars.color]: system.color.icon.strong, | ||
}, | ||
|
||
'&:disabled, &[aria-disabled]': { | ||
color: colors.licorice100, | ||
[systemIconStencil.vars.color]: iconColors.disabled, | ||
color: base.licorice100, | ||
[systemIconStencil.vars.color]: system.color.fg.disabled, | ||
'&:hover': { | ||
cursor: 'auto', | ||
backgroundColor: `transparent`, | ||
[systemIconStencil.vars.color]: iconColors.disabled, | ||
backgroundColor: system.color.bg.transparent, | ||
[systemIconStencil.vars.color]: system.color.fg.disabled, | ||
}, | ||
}, | ||
|
||
'&[aria-selected=true]': { | ||
color: theme.canvas.palette.primary.main, | ||
color: brand.primary.base, | ||
cursor: 'default', | ||
[systemIconStencil.vars.color]: theme.canvas.palette.primary.main, | ||
[systemIconStencil.vars.color]: brand.primary.base, | ||
'&:after': { | ||
position: 'absolute', | ||
height: space.xxxs, | ||
borderRadius: `${borderRadius.m} ${borderRadius.m} 0px 0px`, | ||
backgroundColor: theme.canvas.palette.primary.main, | ||
bottom: 0, | ||
height: system.space.x1, | ||
borderRadius: `${system.shape.x1} ${system.shape.x1} ${system.shape.zero} ${system.shape.zero}`, | ||
backgroundColor: brand.primary.base, | ||
bottom: system.space.zero, | ||
content: `''`, | ||
left: 0, | ||
marginTop: '-2px', | ||
left: system.space.zero, | ||
marginTop: `${calc.negate(calc.divide(system.space.x2, system.space.x1))}`, | ||
width: '100%', | ||
}, | ||
'&:hover, &:focus': { | ||
backgroundColor: `transparent`, | ||
color: theme.canvas.palette.primary.main, | ||
'&:hover, &.hover, &:focus-visible, &.focus': { | ||
backgroundColor: system.color.bg.transparent, | ||
color: brand.primary.base, | ||
}, | ||
}, | ||
}), | ||
({children, gap = space.xxs}) => { | ||
if (typeof children === 'string') { | ||
return ellipsisStyles; | ||
} else { | ||
return { | ||
display: 'flex', | ||
gap, | ||
}; | ||
} | ||
} | ||
); | ||
}, | ||
}); | ||
|
||
export const StyledTabItem = createComponent('button')<TabsItemProps>({ | ||
displayName: 'StyledTabItem', | ||
Component: ({children, ...elemProps}, ref, Element) => { | ||
return ( | ||
<Element ref={ref} {...mergeStyles(elemProps, tabItemStencil())}> | ||
{children} | ||
</Element> | ||
); | ||
}, | ||
}); | ||
|
||
export const useTabsItem = composeHooks( | ||
createElemPropsHook(useTabsModel)(({state}, _, elemProps: {'data-id'?: string} = {}) => { | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,24 +1,22 @@ | ||||||
import * as React from 'react'; | ||||||
|
||||||
import {commonColors} from '@workday/canvas-kit-react/tokens'; | ||||||
import { | ||||||
composeHooks, | ||||||
createSubcomponent, | ||||||
createElemPropsHook, | ||||||
ExtractProps, | ||||||
useModalityType, | ||||||
styled, | ||||||
StyledType, | ||||||
filterOutProps, | ||||||
} from '@workday/canvas-kit-react/common'; | ||||||
import {Flex} from '@workday/canvas-kit-react/layout'; | ||||||
import {Flex, mergeStyles} from '@workday/canvas-kit-react/layout'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
import { | ||||||
useOverflowListMeasure, | ||||||
useListRenderItems, | ||||||
useListResetCursorOnBlur, | ||||||
} from '@workday/canvas-kit-react/collection'; | ||||||
|
||||||
import {useTabsModel} from './useTabsModel'; | ||||||
import {createStencil} from '@workday/canvas-kit-styling'; | ||||||
import {system} from '@workday/canvas-tokens-web'; | ||||||
|
||||||
export interface TabListProps<T = any> extends Omit<ExtractProps<typeof Flex, never>, 'children'> { | ||||||
/** | ||||||
|
@@ -46,44 +44,90 @@ export const useTabsList = composeHooks( | |||||
useListResetCursorOnBlur | ||||||
); | ||||||
|
||||||
const StyledStack = styled(Flex, { | ||||||
shouldForwardProp: filterOutProps(['maskImage']), | ||||||
})<StyledType & {maskImage?: string}>(({maskImage}) => ({ | ||||||
maskImage: maskImage, | ||||||
})); | ||||||
const tabsListStencil = createStencil({ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this line can probably be added to the stencil instead; https://github.com/Workday/canvas-kit/pull/3119/files#diff-5e3729c5f87ee54b9d212a45937aac7b46d5314877e6b5b1e8182b7d55a5901dL42 We should remove styles in model hooks so the css makes sense. |
||||||
base: { | ||||||
display: 'flex', | ||||||
position: 'relative', | ||||||
borderBottom: `1px solid ${system.color.border.divider}`, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
gap: system.space.x2, | ||||||
paddingInline: system.space.x6, | ||||||
maskImage: 'none', | ||||||
}, | ||||||
modifiers: { | ||||||
modality: { | ||||||
touch: { | ||||||
paddingInline: system.space.zero, | ||||||
}, | ||||||
mouse: {}, | ||||||
pen: {}, | ||||||
}, | ||||||
isDragging: { | ||||||
true: {}, | ||||||
false: {}, | ||||||
}, | ||||||
direction: { | ||||||
left: {}, | ||||||
right: {}, | ||||||
}, | ||||||
currentScrollPos: { | ||||||
true: {}, | ||||||
false: {}, | ||||||
}, | ||||||
}, | ||||||
compound: [ | ||||||
{ | ||||||
modifiers: { | ||||||
modality: 'touch', | ||||||
isDragging: true, | ||||||
direction: 'left', | ||||||
}, | ||||||
styles: { | ||||||
maskImage: 'linear-gradient(to left, white 80%, transparent)', | ||||||
}, | ||||||
}, | ||||||
{ | ||||||
modifiers: { | ||||||
modality: 'touch', | ||||||
isDragging: true, | ||||||
direction: 'right', | ||||||
}, | ||||||
styles: { | ||||||
maskImage: 'linear-gradient(to right, white 80%, transparent)', | ||||||
}, | ||||||
}, | ||||||
], | ||||||
}); | ||||||
|
||||||
export const TabsList = createSubcomponent('div')({ | ||||||
displayName: 'Tabs.List', | ||||||
modelHook: useTabsModel, | ||||||
elemPropsHook: useTabsList, | ||||||
})<TabListProps>(({children, overflowButton, ...elemProps}, Element, model) => { | ||||||
const modality = useModalityType(); | ||||||
const touchStates = useTouchDirection(); | ||||||
return ( | ||||||
<StyledStack | ||||||
as={Element} | ||||||
position="relative" | ||||||
borderBottom={`1px solid ${commonColors.divider}`} | ||||||
paddingX={modality === 'touch' ? 'zero' : 'm'} | ||||||
gap="xs" | ||||||
maskImage={ | ||||||
modality === 'touch' && touchStates.isDragging | ||||||
? `linear-gradient(${ | ||||||
touchStates.direction === 'left' ? 'to left' : 'to right' | ||||||
}, white 80%, transparent)` | ||||||
: undefined | ||||||
} | ||||||
{...elemProps} | ||||||
> | ||||||
{useListRenderItems(model, children)} | ||||||
{overflowButton} | ||||||
</StyledStack> | ||||||
); | ||||||
}); | ||||||
})<TabListProps & {maskImage?: string}>( | ||||||
({children, overflowButton, ...elemProps}, Element, model) => { | ||||||
const modality = useModalityType(); | ||||||
const touchStates = useTouchDirection(); | ||||||
|
||||||
return ( | ||||||
<Element | ||||||
{...mergeStyles( | ||||||
elemProps, | ||||||
tabsListStencil({ | ||||||
modality: modality, | ||||||
isDragging: touchStates.isDragging, | ||||||
direction: touchStates.direction, | ||||||
}) | ||||||
)} | ||||||
> | ||||||
{useListRenderItems(model, children)} | ||||||
{overflowButton} | ||||||
</Element> | ||||||
); | ||||||
} | ||||||
); | ||||||
|
||||||
export const useTouchDirection = () => { | ||||||
const [isDragging, setIsDragging] = React.useState(false); | ||||||
const [touchDir, setTouchDirection] = React.useState('right'); | ||||||
const [touchDir, setTouchDirection] = React.useState<'right' | 'left' | undefined>('right'); | ||||||
|
||||||
React.useEffect(() => { | ||||||
let prevXPos = window.pageXOffset; | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -13,6 +13,9 @@ import {useOverflowListTarget} from '@workday/canvas-kit-react/collection'; | |||||
import {useMenuTarget} from '@workday/canvas-kit-react/menu'; | ||||||
import {useTabsModel} from './useTabsModel'; | ||||||
import {StyledTabItem} from './TabsItem'; | ||||||
import {createStencil} from '@workday/canvas-kit-styling'; | ||||||
import {system} from '@workday/canvas-tokens-web'; | ||||||
import {mergeStyles} from '../../layout'; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
export interface OverflowButtonProps { | ||||||
/** | ||||||
|
@@ -21,6 +24,12 @@ export interface OverflowButtonProps { | |||||
children: React.ReactNode; | ||||||
} | ||||||
|
||||||
const TabsOverflowButtonStencil = createStencil({ | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
base: { | ||||||
gap: system.space.x1, | ||||||
}, | ||||||
}); | ||||||
|
||||||
export const useTabsOverflowButton = composeHooks( | ||||||
createElemPropsHook(useTabsModel)(() => { | ||||||
return { | ||||||
|
@@ -37,7 +46,11 @@ export const TabsOverflowButton = createSubcomponent('button')({ | |||||
elemPropsHook: useTabsOverflowButton, | ||||||
})<OverflowButtonProps>(({children, ...elemProps}, Element) => { | ||||||
return ( | ||||||
<StyledTabItem type="button" gap="xxxs" as={Element} {...elemProps}> | ||||||
<StyledTabItem | ||||||
type="button" | ||||||
as={Element} | ||||||
{...mergeStyles(elemProps, TabsOverflowButtonStencil())} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
> | ||||||
<span>{children}</span> | ||||||
<SystemIcon icon={chevronDownSmallIcon} /> | ||||||
</StyledTabItem> | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should extend
buttonStencil
.