Skip to content
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

Open
wants to merge 7 commits into
base: prerelease/major
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion modules/docs/mdx/13.0-UPGRADE-GUIDE.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ yarn remove @workday/canvas-kit-codemod

### Styling API and CSS Tokens

**PRs:** [#3101](https://github.com/Workday/canvas-kit/pull/3101), [#3088](https://github.com/Workday/canvas-kit/pull/3088), [#3114](https://github.com/Workday/canvas-kit/pull/3114)
**PRs:** [#3101](https://github.com/Workday/canvas-kit/pull/3101), [#3088](https://github.com/Workday/canvas-kit/pull/3088), [#3114](https://github.com/Workday/canvas-kit/pull/3114), [#3119](https://github.com/Workday/canvas-kit/pull/3119)

Several components have been refactored to use our
[Canvas Tokens](https://workday.github.io/canvas-tokens/?path=/docs/docs-getting-started--docs) and
Expand All @@ -108,6 +108,7 @@ The following components are affected:
- `ExternalHyperlink`
- `Menu`
- `Skeleton`
- `Tabs`

## External Hyperlink

Expand Down
105 changes: 58 additions & 47 deletions modules/react/tabs/lib/TabsItem.tsx
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 {
Expand All @@ -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'>> {
Expand Down Expand Up @@ -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({
Copy link
Contributor

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.

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

height prob needs either a calc or px2rem

boxSizing: 'border-box',
Copy link
Contributor

Choose a reason for hiding this comment

The 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} = {}) => {
Expand Down
114 changes: 79 additions & 35 deletions modules/react/tabs/lib/TabsList.tsx
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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import {Flex, mergeStyles} from '@workday/canvas-kit-react/layout';
import {mergeStyles} from '@workday/canvas-kit-react/layout';

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'> {
/**
Expand Down Expand Up @@ -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({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const tabsListStencil = createStencil({
export const tabsListStencil = createStencil({

Copy link
Contributor

Choose a reason for hiding this comment

The 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}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
borderBottom: `1px solid ${system.color.border.divider}`,
borderBottom: `px2rem(1) solid ${system.color.border.divider}`,

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;
Expand Down
15 changes: 14 additions & 1 deletion modules/react/tabs/lib/TabsOverflowButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import {mergeStyles} from '../../layout';
import {mergeStyles} from '@workday/canvas-kit-react/layout';


export interface OverflowButtonProps {
/**
Expand All @@ -21,6 +24,12 @@ export interface OverflowButtonProps {
children: React.ReactNode;
}

const TabsOverflowButtonStencil = createStencil({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const TabsOverflowButtonStencil = createStencil({
const tabsOverflowButtonStencil = createStencil({

base: {
gap: system.space.x1,
},
});

export const useTabsOverflowButton = composeHooks(
createElemPropsHook(useTabsModel)(() => {
return {
Expand All @@ -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())}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{...mergeStyles(elemProps, TabsOverflowButtonStencil())}
{...mergeStyles(elemProps, tabsOverflowButtonStencil())}

>
<span>{children}</span>
<SystemIcon icon={chevronDownSmallIcon} />
</StyledTabItem>
Expand Down
Loading