Skip to content

Commit

Permalink
feat(client): handle vertical tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jozwiaczek committed Mar 22, 2021
1 parent 9f5cf38 commit 1196461
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ export const TabLabel = styled.p<TabLabelProps>`
export const TabButton = styled.button<TabButtonProps>`
position: relative;
overflow: hidden;
width: ${({ width, variant }) => (variant === 'fullWidth' ? '100%' : `${width}px`)};
height: 100%;
width: ${({ width }) => width};
height: ${({ height }) => height};
display: flex;
background: transparent;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { MouseEvent, ReactNode } from 'react';

type TabsVariant = 'scrollable' | 'fullWidth' | 'default';
type TabsOrientation = 'horizontal' | 'vertical';

interface TabProps {
label?: string;
Expand All @@ -10,11 +11,14 @@ interface TabProps {
onChange?: (event: MouseEvent, newValue: number) => void;
index?: number;
tabWidth?: number;
tabHeight?: number;
variant?: TabsVariant;
orientation?: TabsOrientation;
}

interface TabButtonProps {
width: number;
width: string;
height: string;
isActive: boolean;
variant: TabsVariant;
}
Expand Down
15 changes: 13 additions & 2 deletions packages/client/src/elements/layouts/TabbedLayout/Tab/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const Tab = ({
index,
onlyAdmin = false,
tabWidth = 160,
tabHeight = 160,
orientation = 'horizontal',
variant = 'default',
}: TabProps) => {
const { t } = useTranslation();
Expand All @@ -27,15 +29,24 @@ const Tab = ({

const onClick = (event: MouseEvent) => {
onChange && onChange(event, index as number);
itemRef.current?.scrollIntoView({ behavior: 'smooth', inline: 'end' });
variant === 'scrollable' &&
itemRef.current?.scrollIntoView({ behavior: 'smooth', inline: 'end' });
};
const isActive = value === index;

let width = variant === 'fullWidth' ? '100%' : `${tabWidth}px`;
let height = '100%';
if (orientation === 'vertical') {
width = `${tabWidth}px`;
height = variant === 'fullWidth' ? '100%' : `${tabHeight}px`;
}

return (
<TabButton
ref={itemRef}
onClick={onClick}
width={tabWidth}
width={width}
height={height}
isActive={isActive}
variant={variant}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { TabsOpt, TabsOrientation } from './Tabs/Tabs.types';

const MockRoot = styled.div<{ orientation?: TabsOrientation }>`
width: 800px;
height: 100%;
height: 800px;
border: 2px solid ${({ theme }) => theme.palette.divider.default};
${({ orientation }) =>
orientation === 'vertical' &&
Expand All @@ -27,6 +27,7 @@ const MockTabsWrapper = styled.div<{ orientation?: TabsOrientation }>`
orientation === 'vertical' &&
`
height: 100%;
width: 160px;
`};
`;

Expand All @@ -52,6 +53,31 @@ export default {
options: ['horizontal', 'vertical'],
},
},
indicatorThin: {
control: {
type: 'number',
},
},
indicatorWidth: {
control: {
type: 'number',
},
},
indicatorHeight: {
control: {
type: 'number',
},
},
tabWidth: {
control: {
type: 'number',
},
},
tabHeight: {
control: {
type: 'number',
},
},
},
} as Meta;

Expand Down Expand Up @@ -108,10 +134,6 @@ const Template: Story<TabsOpt> = (tabsOptions) => {
};

export const defaultView = Template.bind({});
defaultView.args = {
indicatorWidth: undefined,
tabWidth: undefined,
};

export const fullWidthView = Template.bind({});
fullWidthView.args = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,18 @@ export const TabsIndicator = styled.span<TabsIndicatorProps>`
position: absolute;
background: ${({ theme }) => theme.palette.primary.light};
${({ position }) => `${position}: 0`};
left: ${({ left }) => `${left}px`};
height: ${({ height }) => height}px;
width: ${({ width }) => width}px;
transition: left 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
border-radius: ${({ theme }) => theme.sizes.borderRadius};
${({ orientation, animationSpace }) =>
orientation === 'vertical'
? `
transition: top 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
top: ${animationSpace}px;
`
: `
transition: left 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
left: ${animationSpace}px;
`}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface TabsOpt {
indicatorHeight?: number;
indicatorThin?: number;
tabWidth?: number;
tabHeight?: number;
}

interface TabsProps {
Expand All @@ -24,23 +25,24 @@ interface TabsProps {
}

interface TabsIndicatorProps {
left: number;
animationSpace: number;
width: number;
height: number;
position: TabMarkerPosition;
orientation: TabsOrientation;
}

interface TabsWrapperProps {
variant: TabsVariant;
orientation?: TabsOrientation;
}

interface GetIndicatorLeft {
interface GetIndicatorAnimationSpace {
value: number;
totalChildren: number;
containerWidth: number;
tabWidth: number;
indicatorWidth: number;
containerSize: number;
tabSize: number;
indicatorSize: number;
variant: TabsVariant;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@ import { Children, isValidElement, ReactNode } from 'react';

import { Role } from '../../../../enums/role.enum';
import { User } from '../../../../providers/api/CurrentUserProvider/CurrentUserProvider.types';
import { GetIndicatorLeft, GetIndicatorSizeProps, IndicatorSize } from './Tabs.types';
import { GetIndicatorAnimationSpace, GetIndicatorSizeProps, IndicatorSize } from './Tabs.types';

export const getIndicatorLeft = ({
export const getIndicatorAnimationSpace = ({
value,
totalChildren,
containerWidth,
tabWidth,
indicatorWidth,
containerSize,
tabSize,
indicatorSize,
variant,
}: GetIndicatorLeft): number => {
const totalChildrenWidth = totalChildren * tabWidth;
const totalEmptyWidth = containerWidth - totalChildrenWidth;
}: GetIndicatorAnimationSpace): number => {
const totalChildrenSize = totalChildren * tabSize;
const totalEmptyWidth = containerSize - totalChildrenSize;
const singleEmptyWidth = variant === 'default' ? 0 : totalEmptyWidth / (totalChildren + 1);
const trimmedSingleEmptyWidth = singleEmptyWidth <= 0 ? 0 : singleEmptyWidth;
const additionalSpaceForIndicatorWidth =
tabWidth !== indicatorWidth ? (tabWidth - indicatorWidth) / 2 : 0;
tabSize !== indicatorSize ? (tabSize - indicatorSize) / 2 : 0;

if (value === 0) {
return trimmedSingleEmptyWidth + additionalSpaceForIndicatorWidth;
}

const emptyWidthForValue = (value + 1) * trimmedSingleEmptyWidth;
const tabsWidthForValue = value * tabWidth;
const tabsWidthForValue = value * tabSize;

return emptyWidthForValue + tabsWidthForValue + additionalSpaceForIndicatorWidth;
};
Expand Down
36 changes: 23 additions & 13 deletions packages/client/src/elements/layouts/TabbedLayout/Tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import { useCurrentUser } from '../../../../hooks';
import { TabProps } from '../Tab/Tab.types';
import { TabsIndicator, TabsWrapper } from './Tabs.styled';
import { IndicatorSize, TabsProps } from './Tabs.types';
import { countAvailableChildren, getIndicatorLeft, getIndicatorSize } from './Tabs.utils';
import { countAvailableChildren, getIndicatorAnimationSpace, getIndicatorSize } from './Tabs.utils';

const Tabs = ({ children, onChange, value, options = {} }: TabsProps) => {
const tabsWrapperRef = useRef<HTMLDivElement>(null);
const [indicatorLeft, setIndicatorLeft] = useState(0);
const [indicatorAnimationSpace, setIndicatorAnimationSpace] = useState(0);
const [currentUser] = useCurrentUser();
const [indicatorSize, setIndicatorSize] = useState<IndicatorSize>({ width: 0, height: 0 });

const {
tabWidth = 160,
tabHeight = 160,
indicatorThin = 5,
variant = 'default',
orientation = 'horizontal',
Expand All @@ -31,30 +32,35 @@ const Tabs = ({ children, onChange, value, options = {} }: TabsProps) => {

const internalIndicatorPosition =
indicatorPosition || (orientation === 'horizontal' ? 'bottom' : 'left');
console.log('L:34 | indicatorPosition: ', indicatorPosition);

useLayoutEffect(() => {
const tabbedContainerWidth = tabsWrapperRef.current?.offsetWidth || 0;
const totalChildren = countAvailableChildren(children, currentUser);
const tabWidthOnFullWidth = tabbedContainerWidth / totalChildren;
const internalTabWidth = variant === 'fullWidth' ? tabWidthOnFullWidth : tabWidth;

const containerWidth = tabsWrapperRef.current?.offsetWidth || 0;
const containerHeight = tabsWrapperRef.current?.offsetHeight || 0;
const containerSize = orientation === 'vertical' ? containerHeight : containerWidth;

const tabSizeOnFullWidth = containerSize / totalChildren;
const tabSize = orientation === 'vertical' ? tabHeight : tabWidth;
const internalTabSize = variant === 'fullWidth' ? tabSizeOnFullWidth : tabSize;

const internalIndicatorSize = getIndicatorSize({
defaultThin: indicatorThin,
tabSize: internalTabWidth,
tabSize: internalTabSize,
width: indicatorWidth,
height: indicatorHeight,
orientation,
});
setIndicatorSize(internalIndicatorSize);

setIndicatorLeft(
getIndicatorLeft({
setIndicatorAnimationSpace(
getIndicatorAnimationSpace({
value,
totalChildren,
containerWidth: tabbedContainerWidth,
tabWidth: internalTabWidth,
indicatorWidth: internalIndicatorSize.width,
containerSize,
tabSize: internalTabSize,
indicatorSize:
orientation === 'vertical' ? internalIndicatorSize.height : internalIndicatorSize.width,
variant,
}),
);
Expand All @@ -65,6 +71,7 @@ const Tabs = ({ children, onChange, value, options = {} }: TabsProps) => {
indicatorThin,
indicatorWidth,
orientation,
tabHeight,
tabWidth,
value,
variant,
Expand All @@ -73,7 +80,8 @@ const Tabs = ({ children, onChange, value, options = {} }: TabsProps) => {
return (
<TabsWrapper ref={tabsWrapperRef} variant={variant} orientation={orientation}>
<TabsIndicator
left={indicatorLeft}
orientation={orientation}
animationSpace={indicatorAnimationSpace}
position={internalIndicatorPosition}
width={indicatorSize.width}
height={indicatorSize.height}
Expand All @@ -86,7 +94,9 @@ const Tabs = ({ children, onChange, value, options = {} }: TabsProps) => {
onChange,
index,
variant,
orientation,
tabWidth,
tabHeight,
};

return cloneElement(child, tabInjectProps);
Expand Down

0 comments on commit 1196461

Please sign in to comment.