Skip to content

Commit

Permalink
Merge branch 'release/0.8.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Eitan Elbaz committed Nov 19, 2021
2 parents 1c7f77f + 88e45e0 commit f40248e
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 21 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ Got an object which moves around frequently or changes size? No problem.
*
*/
trackFrequency: 10,


/**
* If set, will position the modal in the position indicated, if there is space.
*
* Default: auto
*/
preferredModalPosition?: 'auto' | 'top' | 'right' | 'bottom' | 'left',
},
]}
>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-grand-tour",
"version": "0.7.0",
"version": "0.8.0",
"description": "",
"main": "./build/index.js",
"typings": "./typings/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "react-grand-tour-playground",
"homepage": "https://eitanelbaz.github.io/ReactGrandTour",
"version": "0.0.1",
"version": "0.0.2",
"private": true,
"config-overrides-path": "./config-overrides",
"engines": {
Expand Down
50 changes: 50 additions & 0 deletions playground/src/components/KitchenSink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,56 @@ const KitchenSink = () => (
<WideTable />
</Section>
</Box>
<Box width="100%" px={10} mt={8}>
<Section title="Preferred Positioning" openAtStep={13}>
<Box px={2}>
<Grid container spacing={2}>
<Grid item xs={12}>
<Box
width={200}
height={80}
bgcolor="secondary.main"
id={HomeStepIds.prefRight}
borderRadius={8}
display="flex"
alignItems="center"
justifyContent="center"
>
<Typography color="primary">Prefer Right</Typography>
</Box>
</Grid>
<Grid item xs={12}>
<Box
width={200}
height={80}
bgcolor="secondary.main"
id={HomeStepIds.prefLeft}
borderRadius={8}
display="flex"
alignItems="center"
justifyContent="center"
>
<Typography color="primary">Prefer Left</Typography>
</Box>
</Grid>
<Grid item xs={12}>
<Box
width={200}
height={80}
bgcolor="secondary.main"
id={HomeStepIds.prefTop}
borderRadius={8}
display="flex"
alignItems="center"
justifyContent="center"
>
<Typography color="primary">Prefer Top</Typography>
</Box>
</Grid>
</Grid>
</Box>
</Section>
</Box>
</>
);

Expand Down
6 changes: 4 additions & 2 deletions playground/src/components/WideTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const WideTable = () => (
{Array(50)
.fill(undefined)
.map((val, i) => (
<TableCell>
// eslint-disable-next-line react/no-array-index-key
<TableCell key={i}>
<Typography noWrap variant="subtitle1">
Header {i + 1}
</Typography>
Expand All @@ -30,7 +31,8 @@ const WideTable = () => (
{Array(5)
.fill(undefined)
.map((rval, ri) => (
<TableRow id={`row-${ri}`}>
// eslint-disable-next-line react/no-array-index-key
<TableRow key={ri} id={`row-${ri}`}>
{Array(50)
.fill(undefined)
.map((cval, ci) => (
Expand Down
20 changes: 20 additions & 0 deletions playground/src/tours/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const HomeStepIds = {
verticalTwo: 'home-vertical-two',
horizontalTwo: 'home-horizontal-two',
horVertTwo: 'home-hotvert-two',
prefRight: 'preferRight',
prefLeft: 'preferLeft',
prefTop: 'preferTop',
bye: 'home-bye',
};

Expand Down Expand Up @@ -64,6 +67,7 @@ export const HomeSteps: ReactGrandTourStep[] = [
{
selector: `#${HomeStepIds.one}`,
content: 'Welcome to React Grand Tour!',
preferredModalPosition: 'bottom',
},
{
selector: `#${HomeStepIds.two}`,
Expand All @@ -74,6 +78,7 @@ export const HomeSteps: ReactGrandTourStep[] = [
selector: `#${HomeStepIds.three}`,
content: 'The highlighted area will track moving objects',
track: true,
preferredModalPosition: 'right',
},
{
selector: `#${HomeStepIds.four}`,
Expand Down Expand Up @@ -129,6 +134,21 @@ export const HomeSteps: ReactGrandTourStep[] = [
selector: `#row-1-col-1`,
content: 'And that 😱',
},
{
selector: `#${HomeStepIds.prefRight}`,
content: 'I prefer to position this modal on the right',
preferredModalPosition: 'right',
},
{
selector: `#${HomeStepIds.prefLeft}`,
content: "I prefer to position this modal on the left, but there isn't enough space 😞",
preferredModalPosition: 'left',
},
{
selector: `#${HomeStepIds.prefTop}`,
content: 'I prefer to position this modal on top',
preferredModalPosition: 'top',
},
{
selector: `#${HomeStepIds.bye}`,
content: '',
Expand Down
2 changes: 2 additions & 0 deletions src/components/Step/Step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const Step = React.memo(
dialogWrapper,
track = false,
trackFrequency = 10,
preferredModalPosition = 'auto',
}: Omit<Props, 'contentWrapper'> & {
element: Element;
anchorElement?: Element;
Expand Down Expand Up @@ -83,6 +84,7 @@ const Step = React.memo(
boundaries={boundaries}
anchorBoundaries={anchorBoundaries}
differentAnchor={anchorElement != null}
preferredPosition={preferredModalPosition}
/>
<Highlight track={track} anchorHighlight={anchorElement != null} />
<Modal
Expand Down
10 changes: 8 additions & 2 deletions src/components/Step/StepPostioner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ import React from 'react';
import useThrottle from 'react-use/esm/useThrottle';
import { getModalPosition, isSafari } from '../../lib';
import { styleObjectToCssStyleString } from '../../styles';
import { ReactGrandTourStep } from '../../types';

const StepPositioner: React.FC<{
boundaries: DOMRect;
anchorBoundaries: DOMRect;
differentAnchor: boolean;
}> = ({ boundaries, anchorBoundaries, differentAnchor }) => {
preferredPosition: ReactGrandTourStep['preferredModalPosition'];
}> = ({ boundaries, anchorBoundaries, differentAnchor, preferredPosition }) => {
const modalContainer = document.querySelector('.__react-grand-tour__modal-container');
const modalPos = getModalPosition(anchorBoundaries, modalContainer?.clientHeight ?? 0);
const modalPos = getModalPosition(
anchorBoundaries,
modalContainer?.clientHeight ?? 0,
preferredPosition,
);
const position = useThrottle(modalPos, isSafari ? 450 : 50);

return (
Expand Down
87 changes: 72 additions & 15 deletions src/lib/getModalPosition.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ModalPosition } from '../types';
import { ModalPosition, ReactGrandTourStep } from '../types';

const alignHorizontally = (
currentPosition: ModalPosition,
Expand All @@ -24,6 +24,8 @@ const alignHorizontally = (
return { ...currentPosition, top: topMargin };
};

const modalPadding = 20;

const alignVeritcally = (
currentPosition: ModalPosition,
leftMargin: number,
Expand All @@ -46,9 +48,27 @@ const alignVeritcally = (
return { ...currentPosition, left: left - 10 };
};

const getPreferredPosition = (
preferredPosition: ReactGrandTourStep['preferredModalPosition'],
spaceTop: boolean,
spaceRight: boolean,
spaceBottom: boolean,
spaceLeft: boolean,
): ReactGrandTourStep['preferredModalPosition'] => {
if (preferredPosition === 'auto') return preferredPosition;

if (preferredPosition === 'top' && !spaceTop) return 'auto';
if (preferredPosition === 'right' && !spaceRight) return 'auto';
if (preferredPosition === 'bottom' && !spaceBottom) return 'auto';
if (preferredPosition === 'left' && !spaceLeft) return 'auto';

return preferredPosition;
};

const getModalPosition = (
{ top, left, right, width, height, bottom }: DOMRect,
modalHeight: number,
preferredPosition: ReactGrandTourStep['preferredModalPosition'],
): ModalPosition => {
let result: ModalPosition = {};
const { clientWidth: docWidth, clientHeight: docHeight } = document.documentElement;
Expand All @@ -62,32 +82,69 @@ const getModalPosition = (
result.width = docWidth < 350 ? docWidth - 20 : 330;
result.height = modalHeight;

// place modal on left side if there is more space on the left than the right and enough space to fit the modal
if (leftMargin > rightMargin && leftMargin >= result.width + 20) {
// set modals right border to align alongside element left border with 10px gap
result.right = docWidth - left + 20;
result = alignHorizontally(result, topMargin, bottomMargin);
}
let positionDecided = false;

const hasSpaceRight = rightMargin >= result.width + modalPadding;
const hasSpaceLeft = leftMargin >= result.width + modalPadding;
const hasSpaceTop = topMargin > modalHeight;
const hasSpaceBottom = bottomMargin > modalHeight;
const feasiblePreferredPosition = getPreferredPosition(
preferredPosition,
hasSpaceTop,
hasSpaceRight,
hasSpaceBottom,
hasSpaceLeft,
);

// place Modal to right side if there is enough space
else if (rightMargin >= result.width + 20) {
if (
!positionDecided &&
hasSpaceRight &&
(feasiblePreferredPosition === 'auto' || feasiblePreferredPosition === 'right')
) {
// Set modals left border to align alongside element right border with 10px gap
result.left = left + width + 20;
result.left = left + width + modalPadding;
result = alignHorizontally(result, topMargin, bottomMargin);
positionDecided = true;
}
// place modal above element if top margin is larger than bottom margin and there is enough room
else if (topMargin > bottomMargin && topMargin > modalHeight) {
// place modal on left side if there is enough space to fit the modal
if (
// leftMargin > rightMargin &&
!positionDecided &&
hasSpaceLeft &&
(feasiblePreferredPosition === 'auto' || feasiblePreferredPosition === 'left')
) {
// set modals right border to align alongside element left border with 10px gap
result.right = docWidth - left + modalPadding;
result = alignHorizontally(result, topMargin, bottomMargin);
positionDecided = true;
}
// place modal above element if there is enough room
if (
// topMargin > bottomMargin &&
!positionDecided &&
hasSpaceTop &&
(feasiblePreferredPosition === 'auto' || feasiblePreferredPosition === 'top')
) {
// align bottom of modal with top of element being tracked
result.bottom = top - 20;
result.bottom = top - modalPadding;
result = alignVeritcally(result, leftMargin, rightMargin, left, right, width, docWidth);
positionDecided = true;
}
// place modal under element if there is enough room
else if (bottomMargin > modalHeight) {
if (
!positionDecided &&
hasSpaceBottom &&
(feasiblePreferredPosition === 'auto' || feasiblePreferredPosition === 'bottom')
) {
// align top of modal with bottom of element being tracked
result.top = top + height + 20;
result.top = top + height + modalPadding;
result = alignVeritcally(result, leftMargin, rightMargin, left, right, width, docWidth);
positionDecided = true;
}

// if modal doesnt fit anywhere put modal in the middle of the tracked element
else {
if (!positionDecided) {
result.top = top + height / 3;
result.left = left + 10 + width / 2 - result.width / 2;
}
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ export type ReactGrandTourStep = {
* Default 10.
*/
trackFrequency?: number;

/**
* If set, will position the modal in the position indicated, if there is space.
*
* Default: auto
*/
preferredModalPosition?: 'auto' | 'top' | 'right' | 'bottom' | 'left';
};

export type ModalPosition = {
Expand Down

0 comments on commit f40248e

Please sign in to comment.