Skip to content

Commit

Permalink
feat: added MultipleTooltip (#69)
Browse files Browse the repository at this point in the history
* feat: MultipleTooltip

---------

Co-authored-by: sunduckcow <[email protected]>
  • Loading branch information
sunduckcow and sunduckcow authored Jul 17, 2023
1 parent 78f43b0 commit ab35916
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 63 deletions.
5 changes: 4 additions & 1 deletion src/components/AsideHeader/AsideHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const b = block('aside-header');
interface AsideHeaderGeneralProps {
logo: LogoProps;
compact: boolean;
multipleTooltip?: boolean;
dict?: AsideHeaderDict;
className?: string;
renderContent?: RenderContentType;
Expand Down Expand Up @@ -73,7 +74,8 @@ export class AsideHeader extends React.Component<AsideHeaderInnerProps> {
}

private renderFirstPane = (size: number) => {
const {dict, menuItems, panelItems, compact, headerDecoration} = this.props;
const {dict, menuItems, panelItems, compact, headerDecoration, multipleTooltip} =
this.props;

return (
<React.Fragment>
Expand All @@ -88,6 +90,7 @@ export class AsideHeader extends React.Component<AsideHeaderInnerProps> {
enableCollapsing={true}
dict={dict}
onItemClick={this.onItemClick}
multipleTooltip={multipleTooltip}
/>
) : (
<div className={b('menu-items')} />
Expand Down
15 changes: 14 additions & 1 deletion src/components/AsideHeader/__stories__/AsideHeader.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,18 @@ export default {
component: AsideHeader,
} as Meta;

const ShowcaseTemplate: Story = () => <AsideHeaderShowcase />;
const ShowcaseTemplate: Story = (args) => <AsideHeaderShowcase {...args} />;
export const Showcase = ShowcaseTemplate.bind({});

const CompactTemplate: Story = (args) => <AsideHeaderShowcase {...args} />;
export const Compact = CompactTemplate.bind({});
Compact.args = {
initialCompact: true,
};

const MultipleTooltipTemplate: Story = (args) => <AsideHeaderShowcase {...args} />;
export const MultipleTooltip = MultipleTooltipTemplate.bind({});
MultipleTooltip.args = {
multipleTooltip: true,
initialCompact: true,
};
17 changes: 13 additions & 4 deletions src/components/AsideHeader/__stories__/AsideHeaderShowcase.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, {FC} from 'react';
import block from 'bem-cn-lite';

import {AsideHeader, FooterItem} from '../..';
Expand All @@ -25,10 +25,18 @@ enum Panel {
Components = 'components',
}

export function AsideHeaderShowcase() {
interface AsideHeaderShowcaseProps {
multipleTooltip?: boolean;
initialCompact?: boolean;
}

export const AsideHeaderShowcase: FC<AsideHeaderShowcaseProps> = ({
multipleTooltip = false,
initialCompact = false,
}) => {
const [popupVisible, setPopupVisible] = React.useState(false);
const [visiblePanel, setVisiblePanel] = React.useState<Panel>();
const [compact, setCompact] = React.useState(false);
const [compact, setCompact] = React.useState(initialCompact);
const [headerDecoration, setHeaderDecoration] = React.useState<string>(BOOLEAN_OPTIONS.Yes);

const navRef = React.useRef<AsideHeader>(null);
Expand Down Expand Up @@ -78,6 +86,7 @@ export function AsideHeaderShowcase() {
},
]}
compact={compact}
multipleTooltip={multipleTooltip}
renderFooter={({compact}) => (
<React.Fragment>
<FooterItem
Expand Down Expand Up @@ -197,4 +206,4 @@ export function AsideHeaderShowcase() {
/>
</div>
);
}
};
227 changes: 170 additions & 57 deletions src/components/CompositeBar/CompositeBar.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {FC, useRef} from 'react';
import React, {FC, ReactNode, useCallback, useContext, useRef} from 'react';
import AutoSizer from 'react-virtualized-auto-sizer';
import {List} from '@gravity-ui/uikit';

Expand All @@ -12,16 +12,20 @@ import {
getMoreButtonItem,
getAutosizeListItems,
} from './utils';
import {Item} from './Item/Item';
import {Item, ItemProps} from './Item/Item';

import './CompositeBar.scss';
import {MultipleTooltip, MultipleTooltipContext, MultipleTooltipProvider} from './MultipleTooltip';
import {COLLAPSE_ITEM_ID} from './constants';
import {ASIDE_HEADER_COMPACT_WIDTH} from '../constants';

const b = block('composite-bar');

interface CompositeBarBaseProps {
items: MenuItem[];
compact: boolean;
onItemClick?: (item: MenuItem, collapsed: boolean) => void;
multipleTooltip?: boolean;
}

interface CompositeBarViewProps extends CompositeBarBaseProps {
Expand All @@ -38,33 +42,134 @@ const CompositeBarView: FC<CompositeBarViewProps> = ({
compact,
onItemClick,
collapseItems,
multipleTooltip = true,
}) => {
const ref = useRef<List<MenuItem>>(null);
const tooltipRef = useRef<HTMLDivElement>(null);
const {
setValue: setMultipleTooltipContextValue,
active: multipleTooltipActive,
activeIndex,
lastClickedItemIndex,
} = useContext(MultipleTooltipContext);

const onTooltipMouseEnter = useCallback(
(e) => {
if (
!multipleTooltipActive &&
activeIndex !== lastClickedItemIndex &&
e.clientX <= ASIDE_HEADER_COMPACT_WIDTH
) {
setMultipleTooltipContextValue?.({
active: true,
});
}
},
[multipleTooltipActive, activeIndex, lastClickedItemIndex, setMultipleTooltipContextValue],
);

const onTooltipMouseLeave = useCallback(() => {
if (multipleTooltipActive) {
setMultipleTooltipContextValue?.({
active: false,
lastClickedItemIndex: undefined,
});
}
}, [multipleTooltipActive, setMultipleTooltipContextValue]);

const onMouseEnterByIndex = useCallback(
(itemIndex) => () => {
if (multipleTooltip) {
let multipleTooltipActiveValue = multipleTooltipActive;
if (!multipleTooltipActive && itemIndex !== lastClickedItemIndex) {
multipleTooltipActiveValue = true;
}
if (
activeIndex === itemIndex &&
multipleTooltipActive === multipleTooltipActiveValue
) {
return;
}
setMultipleTooltipContextValue({
activeIndex: itemIndex,
active: multipleTooltipActiveValue,
});
}
},
[multipleTooltipActive, activeIndex, lastClickedItemIndex, setMultipleTooltipContextValue],
);

const onMouseLeave = useCallback(() => {
if (compact) {
ref.current?.activateItem(undefined as unknown as number);
if (
multipleTooltip &&
(activeIndex !== undefined || lastClickedItemIndex !== undefined)
) {
setMultipleTooltipContextValue({
activeIndex: undefined,
lastClickedItemIndex: undefined,
});
}
}
}, [activeIndex, lastClickedItemIndex, setMultipleTooltipContextValue]);

const onItemClickByIndex = useCallback(
(itemIndex): ItemProps['onItemClick'] =>
(item, collapsed) => {
if (
compact &&
multipleTooltip &&
itemIndex !== lastClickedItemIndex &&
item.id !== COLLAPSE_ITEM_ID
) {
setMultipleTooltipContextValue({
lastClickedItemIndex: itemIndex,
active: false,
});
}
onItemClick?.(item, collapsed);
},
[lastClickedItemIndex, setMultipleTooltipContextValue],
);

return (
<List<MenuItem>
ref={ref}
items={items}
selectedItemIndex={getSelectedItemIndex(items)}
itemHeight={getItemHeight}
itemClassName={b('root-menu-item')}
itemsHeight={getItemsHeight}
virtualized={false}
filterable={false}
sortable={false}
renderItem={(item) => (
<Item
item={item}
onMouseLeave={() => {
if (compact) {
ref.current?.activateItem(undefined as unknown as number);
}
}}
compact={compact}
collapseItems={collapseItems}
onItemClick={onItemClick}
<>
<div
ref={tooltipRef}
onMouseEnter={onTooltipMouseEnter}
onMouseLeave={onTooltipMouseLeave}
>
<List<MenuItem>
ref={ref}
items={items}
selectedItemIndex={getSelectedItemIndex(items)}
itemHeight={getItemHeight}
itemClassName={b('root-menu-item')}
itemsHeight={getItemsHeight}
virtualized={false}
filterable={false}
sortable={false}
renderItem={(item, _isItemActive, itemIndex) => (
<Item
item={item}
onMouseEnter={onMouseEnterByIndex(itemIndex)}
onMouseLeave={onMouseLeave}
onItemClick={onItemClickByIndex}
compact={compact}
collapseItems={collapseItems}
enableTooltip={!multipleTooltip}
/>
)}
/>
)}
/>
</div>
<MultipleTooltip
open={compact && multipleTooltip && multipleTooltipActive}
anchorRef={tooltipRef}
placement={['right-start']}
items={items}
/>
</>
);
};

Expand All @@ -74,45 +179,53 @@ export const CompositeBar: FC<CompositeBarProps> = ({
enableCollapsing,
dict,
onItemClick,
multipleTooltip = false,
}) => {
if (items.length === 0) {
return null;
}
let node: ReactNode = null;

if (!enableCollapsing) {
return (
if (enableCollapsing) {
const minHeight = getItemsMinHeight(items);
const collapseItem = getMoreButtonItem(dict);
node = (
<div className={b({autosizer: true})} style={{minHeight}}>
{items.length !== 0 && (
<AutoSizer>
{({width, height}) => {
const {listItems, collapseItems} = getAutosizeListItems(
items,
height,
collapseItem,
);
return (
<div style={{width, height}}>
<CompositeBarView
items={listItems}
compact={compact}
onItemClick={onItemClick}
collapseItems={collapseItems}
multipleTooltip={multipleTooltip}
/>
</div>
);
}}
</AutoSizer>
)}
</div>
);
} else {
node = (
<div className={b()}>
<CompositeBarView items={items} compact={compact} onItemClick={onItemClick} />
<CompositeBarView
items={items}
compact={compact}
onItemClick={onItemClick}
multipleTooltip={multipleTooltip}
/>
</div>
);
}

const minHeight = getItemsMinHeight(items);
const collapseItem = getMoreButtonItem(dict);

return (
<div className={b({autosizer: true})} style={{minHeight}}>
{items.length !== 0 && (
<AutoSizer>
{({width, height}) => {
const {listItems, collapseItems} = getAutosizeListItems(
items,
height,
collapseItem,
);
return (
<div style={{width, height}}>
<CompositeBarView
items={listItems}
compact={compact}
onItemClick={onItemClick}
collapseItems={collapseItems}
/>
</div>
);
}}
</AutoSizer>
)}
</div>
);
return <MultipleTooltipProvider>{node}</MultipleTooltipProvider>;
};
Loading

0 comments on commit ab35916

Please sign in to comment.