diff --git a/docs/docs/components/popover.mdx b/docs/docs/components/popover.mdx index 20eebf8..52794e9 100644 --- a/docs/docs/components/popover.mdx +++ b/docs/docs/components/popover.mdx @@ -24,13 +24,13 @@ import { useState } from "react"; const content =
Basic Popover
;
- +
; ``` -Basic Popover}> +Basic Popover}> @@ -41,13 +41,7 @@ const [isOpened, setIsOpened] = useState(false); const content =
This is a controlled Popover
;
- + + +
+``` + + + + + +##### Controlled Tooltip + +```jsx +const [isOpened, setIsOpened] = useState(false); +
+ + + +
; +``` + +export const App = () => { + const [isOpened, setIsOpened] = useState(false); + return ( + + + + ); +}; + + + +##### MultiLine Tooltip + +```jsx +
+ + + +
+``` + + + + + + +##### Target Width + +```jsx +
+ + + +
+``` + + + + + +### Props Reference + +| Attributes | Values | Default Value | Optional ? | +| :----------- | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------: | ---------: | +| position | ` "bottom"` | `"left"` | `"right"` | `"top"` | `"bottom-end"`| `"bottom-start"`| `"left-end"` | `"left-start"` | `"right-end"` | `"right-start"` | `"top-end"` | `"top-start"` | `bottom` | Yes | +| label | `JSX.Element ` | | No | +| isOpened | `boolean`
controlled value | `false` | Yes | +| offset | `string`
distance between Tooltip and Target element | `4px` | Yes | +| borderRadius | `string` | `4px` | Yes | +| width | `string` | `fit-label` | Yes | +| withArrow | `boolean` | `false` | Yes | +| arrowSize | `string` | `7px` | Yes | +| onChange | `(isOpened)=>void` | `()=>{}` | Yes | +| zIndex | `string` | `1` | Yes | +| color | `string` | `#2C2E33` | Yes | +| multiLine | `boolean` | false | Yes | +| labelColor | `string` | `white` | Yes | + +### Keyboard Controls + +| Key | Action | +| :----------------- | :-------------------: | +| Space | Enter | `Toggle Opened State` | +| Tab | `Navigate` | diff --git a/lib/src/components/Popover/Popover.tsx b/lib/src/components/Popover/Popover.tsx index cfdfe45..4bb8e59 100644 --- a/lib/src/components/Popover/Popover.tsx +++ b/lib/src/components/Popover/Popover.tsx @@ -10,7 +10,6 @@ import { } from "react"; import { useTrapFocus } from "src/hooks/useTrapFocus"; import { useClickOutside } from "../../hooks/useClickOutside"; -import { Flex } from "../Flex"; import { contentRecipe, popArrowOffset, @@ -19,7 +18,7 @@ import { popoverContainerStyles, popRadius, popWidth, -} from "./popover.css"; +} from "./popover.styles.css"; import { PopoverType } from "./popover.types"; const PopoverComponent: ForwardRefRenderFunction< @@ -110,27 +109,17 @@ const PopoverComponent: ForwardRefRenderFunction< return (
{ - popRef.current = node as HTMLDivElement; - if (typeof ref === "function") { - ref(node); - } else if (ref) { - ref.current = node; - } - }} - style={{ - ...assignInlineVars({ - [popOffset]: withArrow - ? `${Math.hypot(parseInt(arrowSize) + 4) / 2 + parseInt(offset)}px` - : offset, - [popRadius]: borderRadius, - [popWidth]: targetWidth, - [popArrowSize]: arrowSize, - [popArrowOffset]: `-${Math.hypot(parseInt(arrowSize) + 2) / 2}px`, - }), - ...style, - }} + className={popoverContainerStyles} + ref={popRef} + style={assignInlineVars({ + [popOffset]: withArrow + ? `${Math.hypot(parseInt(arrowSize) + 4) / 2 + parseInt(offset)}px` + : offset, + [popRadius]: borderRadius, + [popWidth]: targetWidth, + [popArrowSize]: arrowSize, + [popArrowOffset]: `-${Math.hypot(parseInt(arrowSize) + 2) / 2}px`, + })} >
{isOpen && ( - { + contentRef.current = node as HTMLDivElement; + if (typeof ref === "function") { + ref(node); + } else if (ref) { + ref.current = node; + } + }} + style={{ + ...assignInlineVars({ + zIndex, + }), + ...style, + }} onKeyDown={internalContentKeyDownHandler} - className={`${contentContainerStyles} hover-popover-content`} + className={`${contentContainerStyles} ${className}`} > {content} - +
)}
); diff --git a/lib/src/components/Popover/index.ts b/lib/src/components/Popover/index.ts index d7ddf30..c543214 100644 --- a/lib/src/components/Popover/index.ts +++ b/lib/src/components/Popover/index.ts @@ -1,2 +1,2 @@ export * from "./Popover"; -export * from "./popover.css"; +export * from "./popover.styles.css"; diff --git a/lib/src/components/Popover/popover.css.ts b/lib/src/components/Popover/popover.styles.css.ts similarity index 100% rename from lib/src/components/Popover/popover.css.ts rename to lib/src/components/Popover/popover.styles.css.ts diff --git a/lib/src/components/Popover/popover.types.ts b/lib/src/components/Popover/popover.types.ts index f77a1f2..59a93f5 100644 --- a/lib/src/components/Popover/popover.types.ts +++ b/lib/src/components/Popover/popover.types.ts @@ -1,21 +1,9 @@ -import { FC } from "react"; +import { positionType } from "../_internal/Types/types"; type divType = Omit; export type PopoverType = divType & { - position?: - | "bottom" - | "left" - | "right" - | "top" - | "bottom-end" - | "bottom-start" - | "left-end" - | "left-start" - | "right-end" - | "right-start" - | "top-end" - | "top-start"; + position?: positionType; content: JSX.Element; offset?: string; borderRadius?: string; @@ -27,8 +15,3 @@ export type PopoverType = divType & { zIndex?: string; trapFocus?: boolean; }; - -export type PopoverSubComponentTypes = { - Target: FC; - Content: FC; -}; diff --git a/lib/src/components/Tooltip/Tooltip.tsx b/lib/src/components/Tooltip/Tooltip.tsx new file mode 100644 index 0000000..2343dd0 --- /dev/null +++ b/lib/src/components/Tooltip/Tooltip.tsx @@ -0,0 +1,156 @@ +import { assignInlineVars } from "@vanilla-extract/dynamic"; +import { + forwardRef, + ForwardRefRenderFunction, + KeyboardEvent, + MutableRefObject, + useEffect, + useRef, + useState, +} from "react"; +import { useClickOutside } from "../../hooks/useClickOutside"; +import { + labelRecipe, + toolArrowOffset, + toolArrowSize, + toolOffset, + tooltipContainerStyles, + toolRadius, + toolWidth, +} from "./tooltip.styles.css"; +import { TooltipType } from "./tooltip.types"; + +const TooltipComponent: ForwardRefRenderFunction< + HTMLDivElement, + TooltipType +> = ( + { + children, + position = "bottom", + label, + offset = "4px", + borderRadius = "4px", + width = "fit-content", + withArrow = false, + arrowSize = "7px", + isOpened, + onChange = () => {}, + className, + style, + zIndex = "1", + color = "#2C2E33", + labelColor = "white", + multiLine = false, + }, + ref +) => { + const [isOpen, setIsOpen] = useState(null); + const [targetWidth, setTargetWidth] = useState(""); + const toolRef = useRef() as MutableRefObject; + const labelRef = useRef() as MutableRefObject; + const targetRef = useRef() as MutableRefObject; + + useEffect(() => { + isOpened !== undefined && setIsOpen(isOpened); + }, [isOpened]); + + useEffect(() => { + isOpen !== null && onChange(isOpen!); + }, [isOpen]); + + useEffect(() => { + if (width === "target") { + setTargetWidth(getTargetWidth()); + } else { + setTargetWidth(width); + } + }, [width, targetRef.current]); + + useClickOutside( + toolRef, + () => { + isOpened === undefined && setIsOpen(false); + }, + isOpen! + ); + + const internalClickHandler = () => { + isOpened === undefined && setIsOpen(!isOpen); + }; + const internalKeyDownHandler = (event: KeyboardEvent) => { + switch (event.code) { + case "Space": + case "Enter": + event.preventDefault(); + internalClickHandler(); + break; + case "Escape": + event.preventDefault(); + isOpened === undefined && setIsOpen(false); + break; + default: + break; + } + }; + + const getTargetWidth = () => { + return `${targetRef?.current?.offsetWidth}px`; + }; + + const labelContainerStyles = labelRecipe({ + position, + withArrow: withArrow ? true : false, + }); + + return ( +
+
+ {children} +
+ {isOpen && ( +
{ + labelRef.current = node as HTMLDivElement; + if (typeof ref === "function") { + ref(node); + } else if (ref) { + ref.current = node; + } + }} + style={{ + ...assignInlineVars({ + zIndex, + backgroundColor: color, + color: labelColor, + whiteSpace: multiLine ? "wrap" : "nowrap", + }), + ...style, + }} + className={`${labelContainerStyles} ${className}`} + > + {label} +
+ )} +
+ ); +}; + +export const Tooltip = forwardRef(TooltipComponent); diff --git a/lib/src/components/Tooltip/index.ts b/lib/src/components/Tooltip/index.ts new file mode 100644 index 0000000..2311965 --- /dev/null +++ b/lib/src/components/Tooltip/index.ts @@ -0,0 +1,2 @@ +export * from "./Tooltip"; +export * from "./tooltip.styles.css"; diff --git a/lib/src/components/Tooltip/tooltip.styles.css.ts b/lib/src/components/Tooltip/tooltip.styles.css.ts new file mode 100644 index 0000000..2a456a0 --- /dev/null +++ b/lib/src/components/Tooltip/tooltip.styles.css.ts @@ -0,0 +1,216 @@ +import { createTheme, createVar, style } from "@vanilla-extract/css"; +import { calc } from "@vanilla-extract/css-utils"; +import { recipe } from "@vanilla-extract/recipes"; + +export const toolOffset: string = createVar(); +export const toolRadius: string = createVar(); +export const toolWidth: string = createVar(); +export const toolArrowSize: string = createVar(); +export const toolArrowOffset: string = createVar(); + +export const tooltipContainerStyles = style({ + position: "relative", + height: "fit-content", + width: "fit-content", +}); + +export const labelRecipe = recipe({ + base: { + boxSizing: "border-box", + position: "absolute", + borderRadius: toolRadius, + padding: "6px 10px", + color: "white", + width: toolWidth, + boxShadow: " rgba(0, 0, 0, 0.08) 0px 4px 12px", + }, + variants: { + position: { + bottom: { + top: calc.add("100%", toolOffset), + left: "50%", + transform: "translateX(-50%)", + }, + left: { + top: "50%", + right: calc.add("100%", toolOffset), + transform: "translateY(-50%)", + }, + right: { + top: "50%", + left: calc.add("100%", toolOffset), + transform: "translateY(-50%)", + }, + top: { + bottom: calc.add("100%", toolOffset), + left: "50%", + transform: "translateX(-50%)", + }, + "bottom-end": { + top: calc.add(toolOffset, "100%"), + right: 0, + }, + "bottom-start": { + top: calc.add("100%", toolOffset), + left: 0, + }, + "left-end": { + bottom: 0, + right: calc.add("100%", toolOffset), + }, + "left-start": { + top: 0, + right: calc.add("100%", toolOffset), + }, + "right-end": { + bottom: 0, + left: calc.add("100%", toolOffset), + }, + "right-start": { + top: 0, + left: calc.add("100%", toolOffset), + }, + "top-end": { + bottom: calc.add("100%", toolOffset), + right: 0, + }, + "top-start": { + bottom: calc.add("100%", toolOffset), + left: 0, + }, + }, + withArrow: { + true: { + ":after": { + content: "", + position: "absolute", + height: toolArrowSize, + width: toolArrowSize, + backgroundColor: "inherit", + }, + }, + }, + }, + compoundVariants: [ + { + variants: { withArrow: true, position: "bottom" }, + style: { + ":after": { + left: calc.subtract("50%", calc.divide(toolArrowSize, 2)), + top: toolArrowOffset, + transform: "rotate(45deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "bottom-start" }, + style: { + ":after": { + left: toolRadius, + top: toolArrowOffset, + transform: "rotate(45deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "bottom-end" }, + style: { + ":after": { + right: toolRadius, + top: toolArrowOffset, + transform: "rotate(45deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "left" }, + style: { + ":after": { + top: calc.subtract("50%", calc.divide(toolArrowSize, 2)), + right: toolArrowOffset, + transform: "rotate(135deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "left-end" }, + style: { + ":after": { + bottom: toolRadius, + right: toolArrowOffset, + transform: "rotate(135deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "left-start" }, + style: { + ":after": { + top: toolRadius, + right: toolArrowOffset, + transform: "rotate(135deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "right" }, + style: { + ":after": { + top: calc.subtract("50%", calc.divide(toolArrowSize, 2)), + left: toolArrowOffset, + transform: "rotate(-45deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "right-start" }, + style: { + ":after": { + top: toolRadius, + left: toolArrowOffset, + transform: "rotate(-45deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "right-end" }, + style: { + ":after": { + bottom: toolRadius, + left: toolArrowOffset, + transform: "rotate(-45deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "top" }, + style: { + ":after": { + left: calc.subtract("50%", calc.divide(toolArrowSize, 2)), + bottom: toolArrowOffset, + transform: "rotate(-135deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "top-start" }, + style: { + ":after": { + left: toolRadius, + bottom: toolArrowOffset, + transform: "rotate(-135deg)", + }, + }, + }, + { + variants: { withArrow: true, position: "top-end" }, + style: { + ":after": { + right: toolRadius, + bottom: toolArrowOffset, + transform: "rotate(-135deg)", + }, + }, + }, + ], +}); diff --git a/lib/src/components/Tooltip/tooltip.types.ts b/lib/src/components/Tooltip/tooltip.types.ts new file mode 100644 index 0000000..a235022 --- /dev/null +++ b/lib/src/components/Tooltip/tooltip.types.ts @@ -0,0 +1,20 @@ +import { FC } from "react"; +import { positionType } from "../_internal/Types/types"; + +type divType = Omit; + +export type TooltipType = divType & { + position?: positionType; + label: string | number; + offset?: string; + borderRadius?: string; + width?: string; + withArrow?: boolean; + arrowSize?: string; + isOpened?: boolean; + onChange?: (isOpened: boolean) => void; + zIndex?: string; + color?: string; + labelColor?: string; + multiLine?: boolean; +}; diff --git a/lib/src/components/_internal/Types/types.ts b/lib/src/components/_internal/Types/types.ts new file mode 100644 index 0000000..95e202d --- /dev/null +++ b/lib/src/components/_internal/Types/types.ts @@ -0,0 +1,13 @@ +export type positionType = + | "bottom" + | "left" + | "right" + | "top" + | "bottom-end" + | "bottom-start" + | "left-end" + | "left-start" + | "right-end" + | "right-start" + | "top-end" + | "top-start"; diff --git a/lib/src/index.ts b/lib/src/index.ts index cf46c9f..bbd4822 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -22,3 +22,4 @@ export * from "./components/Dialog"; export * from "./components/Select"; export * from "./components/NativeSelect"; export * from "./components/Popover"; +export * from "./components/Tooltip";