Skip to content

Commit 0e80a2a

Browse files
Merge pull request #306 from dannya/sidebarcard-tooltipswhenclosed
feat: `SidebarCard`: add optional `tooltipsWhenClosed` prop
2 parents 7320ffd + 165bd57 commit 0e80a2a

File tree

3 files changed

+53
-12
lines changed

3 files changed

+53
-12
lines changed

packages/components/src/Cards/SidebarCard/SidebarCard.stories.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,28 @@ export const SidebarExample: SidebarStory = {
283283
},
284284
};
285285

286+
export const SidebarTooltipsWhenClosedExample: SidebarStory = {
287+
render: Template,
288+
args: {
289+
menuItems: [
290+
{
291+
title: "Air Conditioner",
292+
description: "On - currently 23°",
293+
icon: "mdi:fan",
294+
active: false,
295+
onClick() {
296+
console.info("do something on click!");
297+
},
298+
},
299+
],
300+
weatherCardProps: {
301+
entity: "weather.entity",
302+
includeForecast: true,
303+
},
304+
tooltipsWhenClosed: true,
305+
},
306+
};
307+
286308
export const CustomMenuItemsExample: SidebarStory = {
287309
render: TemplateMenuItems,
288310
args: {

packages/components/src/Cards/SidebarCard/index.tsx

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled from "@emotion/styled";
33
import { css, Global } from "@emotion/react";
44
import { Icon } from "@iconify/react";
55
import { useHass } from "@hakit/core";
6-
import { TimeCard, WeatherCard, Row, Column, fallback, mq, useBreakpoint } from "@components";
6+
import { Tooltip, TimeCard, WeatherCard, Row, Column, fallback, mq, useBreakpoint } from "@components";
77
import type { WeatherCardProps, TimeCardProps } from "@components";
88
import { ErrorBoundary } from "react-error-boundary";
99

@@ -264,6 +264,8 @@ export interface SidebarCardProps extends React.ComponentProps<"div"> {
264264
children?: React.ReactNode;
265265
/** a method to apply a sort function to the sidebar menu items before they render */
266266
sortSidebarMenuItems?: (a: MenuItem, b: MenuItem) => number;
267+
/** Show Tooltips on item hover when the sidebar is closed @default false */
268+
tooltipsWhenClosed?: boolean;
267269
}
268270
function InternalSidebarCard({
269271
weatherCardProps,
@@ -274,6 +276,7 @@ function InternalSidebarCard({
274276
},
275277
startOpen = true,
276278
collapsible = true,
279+
tooltipsWhenClosed = true,
277280
menuItems = [],
278281
children,
279282
autoIncludeRoutes = true,
@@ -384,6 +387,18 @@ function InternalSidebarCard({
384387
<Divider className="divider" />
385388
<Menu open={open} className="menu">
386389
{concatenatedMenuItems.map((item, index) => {
390+
const innerItemContent = (
391+
<a>
392+
{typeof item.icon === "string" ? <Icon className="icon" icon={item.icon} /> : item.icon}
393+
{open && (
394+
<div className="menu-inner">
395+
{item.title}
396+
{item.description && <span>{item.description}</span>}
397+
</div>
398+
)}
399+
</a>
400+
);
401+
387402
return (
388403
<li
389404
onClick={(event) => {
@@ -393,15 +408,13 @@ function InternalSidebarCard({
393408
key={index}
394409
className={item.active ? "active" : "inactive"}
395410
>
396-
<a>
397-
{typeof item.icon === "string" ? <Icon className="icon" icon={item.icon} /> : item.icon}
398-
{open && (
399-
<div className="menu-inner">
400-
{item.title}
401-
{item.description && <span>{item.description}</span>}
402-
</div>
403-
)}
404-
</a>
411+
{tooltipsWhenClosed && !open ? (
412+
<Tooltip title={item.title} placement="right" offsetX={-16}>
413+
{innerItemContent}
414+
</Tooltip>
415+
) : (
416+
innerItemContent
417+
)}
405418
</li>
406419
);
407420
})}

packages/components/src/Shared/Tooltip/index.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,17 @@ const TooltipSpan = styled.span<Pick<TooltipProps, "placement">>`
8484
export interface TooltipProps extends Omit<React.ComponentPropsWithRef<"div">, "title"> {
8585
/** the placement of the tooltip @default 'top' */
8686
placement?: "top" | "right" | "bottom" | "left";
87+
/** the X-axis offset of the tooltip @default 0 */
88+
offsetX?: number;
89+
/** the Y-axis offset of the tooltip @default 0 */
90+
offsetY?: number;
8791
/** the title of the tooltip */
8892
title?: React.ReactNode | null;
8993
/** the children of the tooltip */
9094
children: React.ReactNode;
9195
}
9296

93-
function InternalTooltip({ placement = "top", title = null, children, ref, ...rest }: TooltipProps) {
97+
function InternalTooltip({ placement = "top", offsetX = 0, offsetY = 0, title = null, children, ref, ...rest }: TooltipProps) {
9498
const tooltipRef = useRef<HTMLSpanElement | null>(null);
9599
const childRef = useRef<HTMLDivElement | null>(null);
96100
const portalRoot = useHass((store) => store.portalRoot);
@@ -122,6 +126,8 @@ function InternalTooltip({ placement = "top", title = null, children, ref, ...re
122126
left = childRect.left;
123127
break;
124128
}
129+
top = top + offsetY;
130+
left = left + offsetX;
125131
el.style.top = `${top}px`;
126132
el.style.left = `${left}px`;
127133
// to ensure animations play out, we need to update these values after the next tick
@@ -130,7 +136,7 @@ function InternalTooltip({ placement = "top", title = null, children, ref, ...re
130136
el.style.visibility = "visible";
131137
}, 0);
132138
},
133-
[placement],
139+
[placement, offsetX, offsetY],
134140
);
135141

136142
const handleMouseEnter = useCallback(() => {

0 commit comments

Comments
 (0)