Skip to content

Commit

Permalink
fix: reposition first button and add fading for more feedback. Also f…
Browse files Browse the repository at this point in the history
…ew minor styling fixes
  • Loading branch information
r-mulder committed May 17, 2024
1 parent 016cf49 commit 1dffa52
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 103 deletions.
74 changes: 40 additions & 34 deletions packages/libs/react-ui/src/components/Button/Button.css.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createVar, style } from '@vanilla-extract/css';
import { createVar, layer, style } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';
import { token, tokens, uiBaseBold, uiSmallestBold } from '../../styles';
import { atoms } from '../../styles/atoms.css';

const defaults = layer('defaults');

export const hoverBackgroundColor = createVar();
export const disabledBackgroundColor = createVar();
export const focusBackgroundColor = createVar();
Expand Down Expand Up @@ -141,39 +143,43 @@ const inverseSelectors = {
};

export const buttonReset = style({
position: 'relative',
appearance: 'button',
WebkitAppearance: 'button',
paddingInline: 0,
/* Remove the inheritance of text transform on button in Edge, Firefox, and IE. */
textTransform: 'none',
WebkitFontSmoothing: 'antialiased',
/* Font smoothing for Firefox */
MozOsxFontSmoothing: 'grayscale',
verticalAlign: 'top',
/* prevent touch scrolling on buttons */
touchAction: 'none',
userSelect: 'none',
cursor: 'pointer',
textDecoration: 'none',
isolation: 'isolate',
border: 'none',
':focus': {
outline: 'none',
},
':focus-visible': {
zIndex: 3,
},
selectors: {
/* Fix Firefox */
'&::-moz-focus-inner': {
border: 0,
/* Remove the inner border and padding for button in Firefox. */
borderStyle: 'none',
padding: 0,
/* Use uppercase PX so values don't get converted to rem */
marginBlockStart: '-2PX',
marginBlockEnd: '-2PX',
'@layer': {
[defaults]: {
position: 'relative',
appearance: 'button',
WebkitAppearance: 'button',
paddingInline: 0,
/* Remove the inheritance of text transform on button in Edge, Firefox, and IE. */
textTransform: 'none',
WebkitFontSmoothing: 'antialiased',
/* Font smoothing for Firefox */
MozOsxFontSmoothing: 'grayscale',
verticalAlign: 'top',
/* prevent touch scrolling on buttons */
touchAction: 'none',
userSelect: 'none',
cursor: 'pointer',
textDecoration: 'none',
isolation: 'isolate',
border: 'none',
':focus': {
outline: 'none',
},
':focus-visible': {
zIndex: 3,
},
selectors: {
/* Fix Firefox */
'&::-moz-focus-inner': {
border: 0,
/* Remove the inner border and padding for button in Firefox. */
borderStyle: 'none',
padding: 0,
/* Use uppercase PX so values don't get converted to rem */
marginBlockStart: '-2PX',
marginBlockEnd: '-2PX',
},
},
},
},
});
Expand Down
26 changes: 25 additions & 1 deletion packages/libs/react-ui/src/components/Tabs/Tab.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { MonoClose } from '@kadena/react-icons/system';
import classNames from 'classnames';
import type { ReactNode } from 'react';
import React, { useRef } from 'react';
import type { AriaTabProps } from 'react-aria';
import { mergeProps, useHover, useTab } from 'react-aria';
import type { Node, TabListState } from 'react-stately';
import { tabItemClass } from './Tabs.css';
import { Button } from '../Button';
import { closeButtonClass, tabItemClass } from './Tabs.css';

interface ITabProps extends AriaTabProps {
item: Node<object>;
state: TabListState<object>;
inverse?: boolean;
className?: string;
borderPosition: 'top' | 'bottom';
onClose?: (item: Node<object>) => void;
isCompact?: boolean;
}

/**
Expand All @@ -23,6 +27,8 @@ export const Tab = ({
className,
inverse = false,
borderPosition = 'bottom',
isCompact,
onClose,
}: ITabProps): ReactNode => {
const { key, rendered } = item;
const ref = useRef(null);
Expand All @@ -36,7 +42,11 @@ export const Tab = ({
tabItemClass({
inverse,
borderPosition,
size: isCompact ? 'compact' : 'default',
}),
{
closeable: typeof onClose === 'function',
},
)}
{...mergeProps(tabProps, hoverProps)}
ref={ref}
Expand All @@ -45,6 +55,20 @@ export const Tab = ({
data-hovered={isHovered || undefined}
>
{rendered}
{onClose && (
<Button
className={closeButtonClass}
type="button"
onPress={() => onClose(item)}
aria-label="Close"
variant="transparent"
isCompact
data-parent-selected={state.selectedKey === key || undefined}
data-parent-hovered={isHovered || undefined}
>
<MonoClose aria-hidden="true" />
</Button>
)}
</div>
);
};
8 changes: 7 additions & 1 deletion packages/libs/react-ui/src/components/Tabs/TabPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import classNames from 'classnames';
import type { ReactNode } from 'react';
import React, { useRef } from 'react';
import type { AriaTabPanelProps } from 'react-aria';
Expand All @@ -7,6 +8,7 @@ import { tabContentClass } from './Tabs.css';

interface ITabPanelProps extends AriaTabPanelProps {
state: TabListState<object>;
className?: string;
}

/**
Expand All @@ -17,7 +19,11 @@ export const TabPanel = ({ state, ...props }: ITabPanelProps): ReactNode => {
const { tabPanelProps } = useTabPanel(props, state, ref);

return (
<div className={tabContentClass} {...tabPanelProps} ref={ref}>
<div
className={classNames(tabContentClass, props.className)}
{...tabPanelProps}
ref={ref}
>
{state.selectedItem?.props.children}
</div>
);
Expand Down
81 changes: 68 additions & 13 deletions packages/libs/react-ui/src/components/Tabs/Tabs.css.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import { globalStyle, style } from '@vanilla-extract/css';
import { globalStyle, style, styleVariants } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';
import { token } from '../../styles';
import { token, uiBaseBold, uiSmallestBold } from '../../styles';
import { atoms } from '../../styles/atoms.css';

export const tabsContainerClass = style([
atoms({
display: 'flex',
flexDirection: 'column',
width: '100%',
position: 'relative',
}),
]);

export const tabListWrapperClass = style([
export const scrollContainer = style([
atoms({
overflowX: 'auto',
display: 'flex',
flexDirection: 'row',
position: 'relative',
}),
{
scrollbarWidth: 'none',
paddingLeft: '2px',
position: 'relative',
paddingTop: '2px', // For focus ring
selectors: {
'&.paginationLeft:not(.paginationRight)': {
maskImage:
'linear-gradient(90deg,rgba(255,255,255,0) 32px, rgba(255,255,255,1) 64px)',
},
'&.paginationRight:not(.paginationLeft)': {
maskImage:
'linear-gradient(90deg,rgba(255,255,255,1) calc(100% - 32px),transparent)',
},
'&.paginationLeft.paginationRight': {
maskImage:
'linear-gradient(90deg,rgba(255,255,255,0),rgba(255,255,255,0) 32px,rgba(255,255,255,1) 96px,rgba(255,255,255,1) calc(100% - 32px), transparent)',
},
},
},
]);

Expand All @@ -30,10 +44,18 @@ export const tabListControls = style([
display: 'flex',
flexDirection: 'row',
position: 'relative',
width: '100%',
}),
{
width: 'fit-content',
maxWidth: '100%',
':before': {
content: '""',
position: 'absolute',
bottom: 0,
right: 0,
left: 0,
borderBottom: `2px solid ${token('color.border.base.subtle')}`,
},
},
]);

Expand All @@ -52,12 +74,13 @@ export const selectorLine = style([
atoms({
position: 'absolute',
display: 'block',
height: '100%',
bottom: 0,
borderStyle: 'solid',
}),
{
width: 0,
height: 0,
zIndex: 4,
borderWidth: 0,
borderBottomWidth: token('border.width.normal'),
borderColor: token('color.border.tint.@focus'),
Expand All @@ -78,14 +101,17 @@ export const tabItemClass = recipe({
alignItems: 'center',
cursor: 'pointer',
paddingBlock: 'n2',
paddingInline: 'md',
paddingInline: 'n4',
gap: 'n2',
fontSize: 'md',
fontWeight: 'secondaryFont.bold',
backgroundColor: 'transparent',
color: 'text.base.default',
outline: 'none',
}),
{
zIndex: 3,
borderBlock: `2px solid transparent`,
borderTopLeftRadius: token('radius.xs'),
borderTopRightRadius: token('radius.xs'),
transition:
Expand All @@ -100,10 +126,11 @@ export const tabItemClass = recipe({
color: token('color.text.base.@hover'),
},
'.focusVisible &:focus-visible': {
outline: `2px solid ${token('color.border.tint.@focus')}`,
outline: `2px solid ${token('color.border.tint.outline')}`,
borderRadius: token('radius.xs'),
outlineOffset: '-2px',
zIndex: 4,
},
'&.closeable': { paddingInlineEnd: token('size.n2') },
},
},
],
Expand Down Expand Up @@ -142,14 +169,17 @@ export const tabItemClass = recipe({
},
},
bottom: {
borderBottom: `2px solid ${token('color.border.base.subtle')}`,
selectors: {
'&[data-hovered="true"]:not(&[data-selected="true"])': {
borderBottom: `2px solid ${token('color.border.tint.outline')}`,
},
},
},
},
size: {
default: uiBaseBold,
compact: uiSmallestBold,
},
},
});

Expand All @@ -163,15 +193,40 @@ export const tabContentClass = style([
}),
]);

export const paginationButton = style({
const paginationButtonBase = style({
zIndex: 3,
opacity: 1,
position: 'relative',
transition: 'opacity 0.4s ease, background 0.4s ease',
backgroundColor: 'inherit',
});

export const paginationButton = styleVariants({
left: [
paginationButtonBase,
atoms({ position: 'absolute', left: 0, top: 0, bottom: 0 }),
],
right: [paginationButtonBase],
});

export const hiddenClass = style({
opacity: 0,
transition: 'opacity 0.4s ease',
pointerEvents: 'none',
});

export const closeButtonClass = style({
paddingBlock: token('size.n1'),
opacity: 0,
outlineOffset: '-2px',
cursor: 'pointer',
selectors: {
'&[data-parent-hovered="true"]': {
transition: 'opacity 0.4s ease',
opacity: 1,
},
'&[data-parent-selected="true"]': {
transition: 'opacity 0.4s ease',
opacity: 1,
},
},
});
14 changes: 6 additions & 8 deletions packages/libs/react-ui/src/components/Tabs/Tabs.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,6 @@ const meta: Meta<ITabsProps> = {
type: 'boolean',
},
},
paginated: {
control: {
type: 'boolean',
},
},
},
};

Expand All @@ -121,7 +116,11 @@ export const TabsStory: Story = {
},
render: (props) => {
return (
<Tabs {...props} aria-label={props['aria-label']}>
<Tabs
{...props}
aria-label={props['aria-label']}
onClose={(item) => console.log('closed', item.key)}
>
{ExampleTabs.map((tab) => (
<TabItem key={tab.title} title={tab.title}>
{tab.content}
Expand All @@ -136,13 +135,12 @@ export const DefaultSelectedTabsStory: Story = {
name: 'Scrollable Tabs with defaultSelectedTab',
args: {
['aria-label']: 'generic tabs story',
defaultSelectedKey: ExampleManyTabs[2].title,
defaultSelectedKey: ExampleManyTabs[5].title,
},
render: (props) => {
return (
<Tabs
aria-label={props['aria-label']}
paginated
defaultSelectedKey={props.defaultSelectedKey}
>
{ExampleManyTabs.map((tab) => (
Expand Down

0 comments on commit 1dffa52

Please sign in to comment.