Unverified Commit 1d114764 authored by gabriellsh's avatar gabriellsh Committed by GitHub
Browse files

chore: Change tooltip to use Position element (#268)


Co-authored-by: default avatarGuilherme Gazzo <guilherme@gazzo.xyz>
parent 661398df
......@@ -215,15 +215,16 @@ Returns **function (...args: P): T** a stable callback
### usePosition
Hook to deal with sessionStorage
Hook to deal and position an element using an anchor
#### Parameters
- `reference` **[Element](https://developer.mozilla.org/docs/Web/API/Element)** the anchor
- `targetEl` **[Element](https://developer.mozilla.org/docs/Web/API/Element)** the element to be positioned
- `options` **PostionOptions** options to position
- `reference` **RefObject&lt;[Element](https://developer.mozilla.org/docs/Web/API/Element)>** the anchor
- `target` **RefObject&lt;[Element](https://developer.mozilla.org/docs/Web/API/Element)>**
- `options` **[PositionOptions](https://developer.mozilla.org/docs/Web/API/PositionOptions)** options to position
- `targetEl` the element to be positioned
Returns **(PositionStyle | null)** a state and a setter function
Returns **PositionResult** The style containing top and left position
### useResizeObserver
......
......@@ -12,6 +12,6 @@ getPositionStyle: ({ placement, container, targetBoundaries, variantStore, targe
target: DOMRect;
container: DOMRect;
targetBoundaries: Boundaries;
variantStore: VariantBoundaries;
}) => PositionStyle | null
variantStore?: VariantBoundaries;
}) => PositionResult
```
......@@ -8,8 +8,8 @@
```typescript
getTargetBoundaries: ({ referenceBox, target, margin }: {
referenceBox: DOMRect;
target: DOMRect;
referenceBox?: DOMRect;
target?: DOMRect;
margin?: number;
}) => Boundaries
}) => Boundaries | null
```
......@@ -8,7 +8,7 @@
```typescript
getVariantBoundaries: ({ referenceBox, target }: {
referenceBox: DOMRect;
target: DOMRect;
}) => VariantBoundaries
referenceBox?: DOMRect;
target?: DOMRect;
}) => VariantBoundaries | null
```
......@@ -33,7 +33,7 @@
| [useMediaQuery](./fuselage-hooks.usemediaquery.md) | Hook to listen to a media query. |
| [useMergedRefs](./fuselage-hooks.usemergedrefs.md) | Hook to merge refs and callbacks refs into a single callback ref. Useful when your component need a internal ref while receiving a forwared ref. |
| [useMutableCallback](./fuselage-hooks.usemutablecallback.md) | Hook to create a stable callback from a mutable one. |
| [usePosition](./fuselage-hooks.useposition.md) | Hook to deal with sessionStorage |
| [usePosition](./fuselage-hooks.useposition.md) | Hook to deal and position an element using an anchor |
| [useResizeObserver](./fuselage-hooks.useresizeobserver.md) | Hook to track dimension changes in a DOM element using the ResizeObserver API. |
| [useSafely](./fuselage-hooks.usesafely.md) | Hook that wraps pairs of state and dispatcher to provide a new dispatcher which can be safe and asynchronically called even after the component unmounted. |
| [useSessionStorage](./fuselage-hooks.usesessionstorage.md) | Hook to deal with sessionStorage |
......@@ -47,6 +47,6 @@
| --- | --- |
| [Placements](./fuselage-hooks.placements.md) | |
| [PositionFlipOrder](./fuselage-hooks.positionfliporder.md) | |
| [PositionOptions\_2](./fuselage-hooks.positionoptions_2.md) | |
| [Positions](./fuselage-hooks.positions.md) | |
| [PostionOptions](./fuselage-hooks.postionoptions.md) | |
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [@rocket.chat/fuselage-hooks](./fuselage-hooks.md) &gt; [PostionOptions](./fuselage-hooks.postionoptions.md)
[Home](./index.md) &gt; [@rocket.chat/fuselage-hooks](./fuselage-hooks.md) &gt; [PositionOptions\_2](./fuselage-hooks.positionoptions_2.md)
## PostionOptions type
## PositionOptions\_2 type
<b>Signature:</b>
```typescript
export declare type PostionOptions = {
export declare type PositionOptions = {
margin?: number;
container?: Element;
placement?: Placements;
......
......@@ -4,10 +4,10 @@
## usePosition variable
Hook to deal with sessionStorage
Hook to deal and position an element using an anchor
<b>Signature:</b>
```typescript
usePosition: (reference: Element, targetEl: Element, options: PostionOptions) => PositionStyle | null
usePosition: (reference: RefObject<Element>, target: RefObject<Element>, options: PositionOptions) => PositionResult
```
......@@ -48,14 +48,14 @@ describe('usePosition hook', () => {
it('returns a style for placement bottom-start', () => {
const targetBoundaries = getTargetBoundaries({ referenceBox, target });
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'bottom-start', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('0px');
expect(style.top).toEqual('300px');
const result = getPositionStyle({ placement: 'bottom-start', container, targetBoundaries, variantStore, target });
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('300px');
});
it('returns a style for placement bottom-start if the element height does not fit', () => {
const targetBoundaries = getTargetBoundaries({ referenceBox, target });
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'bottom-start',
const result = getPositionStyle({ placement: 'bottom-start',
container: {
...container,
bottom: 300,
......@@ -64,8 +64,8 @@ describe('usePosition hook', () => {
targetBoundaries,
variantStore,
target });
expect(style.left).toEqual('0px');
expect(style.top).toEqual('150px');
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('150px');
});
it('returns a style for placement bottom-middle', () => {
......@@ -73,15 +73,15 @@ describe('usePosition hook', () => {
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'bottom-middle', container, targetBoundaries, variantStore, target });
const result = getPositionStyle({ placement: 'bottom-middle', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('25px');
expect(style.top).toEqual('300px');
expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('300px');
});
it('returns a style for placement bottom-middle if the element height does not fit', () => {
const targetBoundaries = getTargetBoundaries({ referenceBox, target });
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'bottom-middle',
const result = getPositionStyle({ placement: 'bottom-middle',
container: {
...container,
bottom: 300,
......@@ -90,8 +90,8 @@ describe('usePosition hook', () => {
targetBoundaries,
variantStore,
target });
expect(style.left).toEqual('25px');
expect(style.top).toEqual('150px');
expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('150px');
});
it('returns a style for placement bottom-end', () => {
......@@ -99,15 +99,15 @@ describe('usePosition hook', () => {
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'bottom-end', container, targetBoundaries, variantStore, target });
const result = getPositionStyle({ placement: 'bottom-end', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('50px');
expect(style.top).toEqual('300px');
expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('300px');
});
it('returns a style for placement bottom-end if the element height does not fit', () => {
const targetBoundaries = getTargetBoundaries({ referenceBox, target });
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'bottom-end',
const result = getPositionStyle({ placement: 'bottom-end',
container: {
...container,
bottom: 300,
......@@ -116,61 +116,61 @@ describe('usePosition hook', () => {
targetBoundaries,
variantStore,
target });
expect(style.left).toEqual('50px');
expect(style.top).toEqual('150px');
expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('150px');
});
it('returns a style for placement top-start', () => {
const targetBoundaries = getTargetBoundaries({ referenceBox, target });
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'top-start', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('0px');
expect(style.top).toEqual('150px');
const result = getPositionStyle({ placement: 'top-start', container, targetBoundaries, variantStore, target });
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('150px');
});
it('returns a style for placement top-start if the element height does not fit', () => {
const box = { ...referenceBox, top: 10, y: 10, bottom: 110 };
const targetBoundaries = getTargetBoundaries({ referenceBox: box, target });
const variantStore = getVariantBoundaries({ referenceBox: box, target });
const style = getPositionStyle({ placement: 'top-start', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('0px');
expect(style.top).toEqual('110px');
const result = getPositionStyle({ placement: 'top-start', container, targetBoundaries, variantStore, target });
expect(result.style.left).toEqual('0px');
expect(result.style.top).toEqual('110px');
});
it('returns a style for placement top-middle', () => {
const targetBoundaries = getTargetBoundaries({ referenceBox, target });
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'top-middle', container, targetBoundaries, variantStore, target });
const result = getPositionStyle({ placement: 'top-middle', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('25px');
expect(style.top).toEqual('150px');
expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('150px');
});
it('returns a style for placement top-middle if the element height does not fit', () => {
const box = { ...referenceBox, top: 10, y: 10, bottom: 110 };
const targetBoundaries = getTargetBoundaries({ referenceBox: box, target });
const variantStore = getVariantBoundaries({ referenceBox: box, target });
const style = getPositionStyle({ placement: 'top-middle', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('25px');
expect(style.top).toEqual('110px');
const result = getPositionStyle({ placement: 'top-middle', container, targetBoundaries, variantStore, target });
expect(result.style.left).toEqual('25px');
expect(result.style.top).toEqual('110px');
});
it('returns a style for placement top-end', () => {
const targetBoundaries = getTargetBoundaries({ referenceBox, target });
const variantStore = getVariantBoundaries({ referenceBox, target });
const style = getPositionStyle({ placement: 'top-end', container, targetBoundaries, variantStore, target });
const result = getPositionStyle({ placement: 'top-end', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('50px');
expect(style.top).toEqual('150px');
expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('150px');
});
it('returns a style for placement top-end if the element height does not fit', () => {
const box = { ...referenceBox, top: 10, y: 10, bottom: 110 };
const targetBoundaries = getTargetBoundaries({ referenceBox: box, target });
const variantStore = getVariantBoundaries({ referenceBox: box, target });
const style = getPositionStyle({ placement: 'top-end', container, targetBoundaries, variantStore, target });
expect(style.left).toEqual('50px');
expect(style.top).toEqual('110px');
const result = getPositionStyle({ placement: 'top-end', container, targetBoundaries, variantStore, target });
expect(result.style.left).toEqual('50px');
expect(result.style.top).toEqual('110px');
});
});
});
import { useEffect, RefObject, useCallback, useRef } from 'react';
import { useEffect, RefObject, useRef } from 'react';
import { useDebouncedState } from './useDebouncedState';
import { useMutableCallback } from './useMutableCallback';
export type PositionOptions = {
margin?: number;
......@@ -41,6 +42,21 @@ type PositionStyle = {
opacity?: 0 | 1,
}
type PositionResult = {
style?: PositionStyle,
placement?: Placements,
};
enum PlacementMap {
t = 'top',
b = 'bottom',
l = 'left',
r = 'right',
s = 'start',
e = 'end',
m = 'middle',
}
export type Positions = 'top' | 'left' | 'bottom' | 'right';
export type Placements =
......@@ -103,9 +119,9 @@ const useBoundingClientRect = (element: RefObject<Element>, watch = false, callb
}, [watch, callback]);
export const getPositionStyle = ({ placement = 'bottom-start', container, targetBoundaries, variantStore, target } : { placement: Placements, target: DOMRect, container: DOMRect, targetBoundaries: Boundaries, variantStore?: VariantBoundaries }) : PositionStyle | null => {
export const getPositionStyle = ({ placement = 'bottom-start', container, targetBoundaries, variantStore, target } : { placement: Placements, target: DOMRect, container: DOMRect, targetBoundaries: Boundaries, variantStore?: VariantBoundaries }) : PositionResult => {
if (!targetBoundaries) {
return;
return {};
}
const { top, left, bottom, right } = container;
......@@ -138,15 +154,16 @@ export const getPositionStyle = ({ placement = 'bottom-start', container, target
if (variantPoint < variantMinimum || (variantPoint + variantBox) > variantMaximum) {
continue;
}
const style = {
[positionKey]: `${ point }px`,
[variantKey]: `${ variantPoint }px`,
position: 'fixed',
zIndex: '9999',
opacity: 1,
} as PositionStyle;
return style;
return {
style: {
[positionKey]: `${ point }px`,
[variantKey]: `${ variantPoint }px`,
position: 'fixed',
zIndex: '9999',
opacity: 1,
},
placement: `${ PlacementMap[placementAttempt] }-${ PlacementMap[v] }`,
} as PositionResult;
}
}
......@@ -158,12 +175,15 @@ export const getPositionStyle = ({ placement = 'bottom-start', container, target
const variantPoint = variantStore[`${ directionVertical ? 'v' : 'h' }${ variantsAttempts[0] }`];
return {
top: `${ point }px`,
left: `${ variantPoint }px`,
position: 'fixed',
zIndex: '9999',
opacity: 1,
};
style: {
top: `${ point }px`,
left: `${ variantPoint }px`,
position: 'fixed',
zIndex: '9999',
opacity: 1,
},
placement: `${ PlacementMap[placementAttempt] }-${ PlacementMap[variantsAttempts[0]] }`,
} as PositionResult;
};
export const getTargetBoundaries = ({ referenceBox, target, margin = 0 } : { referenceBox?: DOMRect, target?: DOMRect, margin?: number }) : Boundaries | null => referenceBox && target && {
......@@ -192,17 +212,19 @@ export const getVariantBoundaries = ({ referenceBox, target } : { referenceBox?:
* @public
*/
export const usePosition = (reference: RefObject<Element>, target: RefObject<Element>, options: PositionOptions) : PositionStyle | null => {
export const usePosition = (reference: RefObject<Element>, target: RefObject<Element>, options: PositionOptions) : PositionResult => {
const { margin = 8, placement = 'bottom-start', container: containerElement = document.body, watch = true } = options;
const container = useRef(containerElement);
const [style, setStyle] = useDebouncedState(null, 10);
const callback = useCallback(() => {
const [style, setStyle] = useDebouncedState({} as PositionResult, 10);
const callback = useMutableCallback(() => {
const boundaries = target.current.getBoundingClientRect();
const targetBoundaries = getTargetBoundaries({ referenceBox: reference.current.getBoundingClientRect(), target: boundaries, margin });
const variantStore = getVariantBoundaries({ referenceBox: reference.current.getBoundingClientRect(), target: boundaries });
setStyle(getPositionStyle({ placement, container: container.current.getBoundingClientRect(), targetBoundaries, variantStore, target: boundaries }));
}, [setStyle]);
});
useBoundingClientRect(target, watch, callback);
useBoundingClientRect(reference, watch, callback);
......
......@@ -10,8 +10,7 @@ import AnimatedVisibility from '../AnimatedVisibility';
const Position = ({ anchor, children, placement, margin, className, ...props }) => {
const target = useRef();
const positionStyle = usePosition(anchor, target, useMemo(() => ({ placement, margin }), [placement, margin]));
const { style: positionStyle, placement: positionPlacement } = usePosition(anchor, target, useMemo(() => ({ placement, margin }), [placement, margin])) || {};
const style = useMemo(() => ({ position: 'fixed', ...positionStyle }), [positionStyle]);
const portalContainer = useMemo(() => {
......@@ -27,6 +26,7 @@ const Position = ({ anchor, children, placement, margin, className, ...props })
ref: target,
style,
...props,
placement: positionPlacement,
}),
portalContainer,
);
......
import PropTypes from 'prop-types';
import React, { useMemo } from 'react';
import React from 'react';
import { Box } from '../Box';
export function Tooltip({
arrowPosition,
export const Tooltip = React.forwardRef(function Tooltip({
placement,
...props
}) {
const [direction, position] = useMemo(() => {
const [dir, pos] = arrowPosition ? arrowPosition.split('-') : [false, false];
if (!dir || dir === 'left' || dir === 'right') {
return [String(dir), false];
}
return [String(dir), pos ? String(pos) : 'center'];
}, [arrowPosition]);
}, ref) {
const [direction, position] = placement ? placement.split('-') : [false, false];
return <Box
is='div'
ref={ref}
rcx-tooltip
rcx-tooltip--dir={direction}
rcx-tooltip--pos={position}
{...props}
/>;
}
});
Tooltip.propTypes = {
position: PropTypes.oneOf(['up', 'up-left', 'up-right', 'down', 'down-left', 'down-right', 'left', 'right']),
position: PropTypes.oneOf([
'top-start', 'top-middle', 'top-end',
'bottom-start', 'bottom-middle', 'bottom-end',
'left-start', 'left-middle', 'left-end',
'right-start', 'right-middle', 'right-end',
'top', 'left', 'bottom', 'right',
]),
};
......@@ -25,23 +25,23 @@ interface.
<Margins inline='neg-x8'>
<Box>
<Margins all='x8'>
<Tooltip children='Tooltip' arrowPosition='up-left' />
<Tooltip children='Tooltip' arrowPosition='up' />
<Tooltip children='Tooltip' arrowPosition='up-right' />
<Tooltip children='Tooltip' placement='bottom-start' />
<Tooltip children='Tooltip' placement='bottom-middle' />
<Tooltip children='Tooltip' placement='bottom-end' />
</Margins>
</Box>
<Box>
<Margins all='x8'>
<Tooltip children='Tooltip' arrowPosition='left' />
<Tooltip children='Tooltip' arrowPosition={null} />
<Tooltip children='Tooltip' arrowPosition='right' />
<Tooltip children='Tooltip' placement='right' />
<Tooltip children='Tooltip' placement={null} />
<Tooltip children='Tooltip' placement='left' />
</Margins>
</Box>
<Box>
<Margins all='x8'>
<Tooltip children='Tooltip' arrowPosition='down-left' />
<Tooltip children='Tooltip' arrowPosition='down' />
<Tooltip children='Tooltip' arrowPosition='down-right' />
<Tooltip children='Tooltip' placement='top-start' />
<Tooltip children='Tooltip' placement='top-middle' />
<Tooltip children='Tooltip' placement='top-end' />
</Margins>
</Box>
</Margins>
......
......@@ -21,17 +21,17 @@ $tooltip-text-color: theme('tooltip-text-color', colors.surface());
lengths.border-radius(none)
(lengths.border-radius(2) + to-rem(1));
@if $direction == 'up' {
@if $direction == 'bottom' {
inset-block-start: lengths.inset(-4);
transform: rotate(135deg);
}
@if $direction == 'down' {
@if $direction == 'top' {
inset-block-end: lengths.inset(-4);
transform: rotate(-45deg);
}
@if $direction == 'left' {
@if $direction == 'right' {
inset-block-start: 50%;
inset-inline-start: lengths.inset(-4);
......@@ -39,7 +39,7 @@ $tooltip-text-color: theme('tooltip-text-color', colors.surface());
transform: rotate(45deg);
}
@if $direction == 'right' {
@if $direction == 'left' {
inset-block-start: 50%;
inset-inline-end: lengths.inset(-4);
......@@ -71,12 +71,12 @@ $tooltip-text-color: theme('tooltip-text-color', colors.surface());
@include typography.use-font-scale(p2);
&--dir-up {
@include triangle-direction('up');
&--dir-top {
@include triangle-direction('top');
}
&--dir-down {
@include triangle-direction('down');
&--dir-bottom {
@include triangle-direction('bottom');
}
&--dir-left {
......@@ -88,7 +88,7 @@ $tooltip-text-color: theme('tooltip-text-color', colors.surface());
}
&--pos {
&-center {
&-middle {
&::after {
inset-inline-start: 50%;
......@@ -96,7 +96,7 @@ $tooltip-text-color: theme('tooltip-text-color', colors.surface());
}
}
&-left {
&-start {
&::after {
inset-inline-start: lengths.inset(8);
......@@ -104,7 +104,7 @@ $tooltip-text-color: theme('tooltip-text-color', colors.surface());
}
}
&-right {
&-end {
&::after {
inset-inline-start: initial;
inset-inline-end: lengths.inset(8);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment