Skip to content

Commit

Permalink
Fix locale handling of custom components
Browse files Browse the repository at this point in the history
  • Loading branch information
gilbarbara committed Nov 18, 2024
1 parent af6fa9e commit 826669d
Show file tree
Hide file tree
Showing 30 changed files with 196 additions and 119 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step1-tooltip-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step1-tooltip-webkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step2-tooltip-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step2-tooltip-webkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step3-tooltip-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step3-tooltip-webkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step4-tooltip-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step4-tooltip-webkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step5-tooltip-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step5-tooltip-webkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step6-tooltip-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/step6-tooltip-webkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/tour-ended-chromium.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified e2e/__snapshots__/controlled.spec.tsx/tour-ended-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 1 addition & 2 deletions e2e/scroll.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import './global.d';
import React from 'react';
import { expect, test } from '@playwright/experimental-ct-react';

import { sleep } from '~/modules/helpers';

import { ACTIONS, CallBackProps, EVENTS, LIFECYCLE, STATUS } from '../src';
import { sleep } from '../src/modules/helpers';
import Scroll from '../test/__fixtures__/Scroll';

function formatCallbackResponse(input: Partial<CallBackProps>) {
Expand Down
5 changes: 3 additions & 2 deletions src/components/Beacon.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import innerText from 'react-innertext';
import is from 'is-lite';

import { getReactNodeText } from '~/modules/helpers';

import { BeaconProps } from '~/types';

export default class JoyrideBeacon extends React.Component<BeaconProps> {
Expand Down Expand Up @@ -96,7 +97,7 @@ export default class JoyrideBeacon extends React.Component<BeaconProps> {
step,
styles,
} = this.props;
const title = is.string(locale.open) ? locale.open : innerText(locale.open);
const title = getReactNodeText(locale.open);
const sharedProps = {
'aria-label': title,
onClick: onClickOrHover,
Expand Down
38 changes: 14 additions & 24 deletions src/components/Tooltip/Container.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

import { getText } from '~/modules/helpers';
import { getReactNodeText } from '~/modules/helpers';

import { TooltipRenderProps } from '~/types';

Expand All @@ -11,22 +11,16 @@ function JoyrideTooltipContainer(props: TooltipRenderProps) {
props;
const { content, hideBackButton, hideCloseButton, hideFooter, showSkipButton, styles, title } =
step;
const output: Record<string, React.ReactNode> = {
primary: primaryProps.title,
};
const output: Record<string, React.ReactNode> = {};

if (output.primary) {
output.primary = (
<button
data-test-id="button-primary"
style={styles.buttonNext}
type="button"
{...primaryProps}
>
{output.primary}
</button>
);
}
output.primary = (
<button
data-test-id="button-primary"
style={styles.buttonNext}
type="button"
{...primaryProps}
/>
);

if (showSkipButton && !isLastStep) {
output.skip = (
Expand All @@ -36,17 +30,13 @@ function JoyrideTooltipContainer(props: TooltipRenderProps) {
style={styles.buttonSkip}
type="button"
{...skipProps}
>
{skipProps.title}
</button>
/>
);
}

if (!hideBackButton && index > 0) {
output.back = (
<button data-test-id="button-back" style={styles.buttonBack} type="button" {...backProps}>
{backProps.title}
</button>
<button data-test-id="button-back" style={styles.buttonBack} type="button" {...backProps} />
);
}

Expand All @@ -57,14 +47,14 @@ function JoyrideTooltipContainer(props: TooltipRenderProps) {
return (
<div
key="JoyrideTooltip"
aria-label={getText(title) || getText(content)}
aria-label={getReactNodeText(title ?? content)}
className="react-joyride__tooltip"
style={styles.tooltip}
{...tooltipProps}
>
<div style={styles.tooltipContainer}>
{title && (
<h1 aria-label={getText(title)} style={styles.tooltipTitle}>
<h1 aria-label={getReactNodeText(title)} style={styles.tooltipTitle}>
{title}
</h1>
)}
Expand Down
54 changes: 31 additions & 23 deletions src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';

import { getText } from '~/modules/helpers';
import { getReactNodeText, replaceLocaleContent } from '~/modules/helpers';

import { TooltipProps } from '~/types';

Expand Down Expand Up @@ -43,61 +43,69 @@ export default class JoyrideTooltip extends React.Component<TooltipProps> {

getElementsProps = () => {
const { continuous, index, isLastStep, setTooltipRef, size, step } = this.props;
const { back, close, last, next, nextLabelWithProgress, skip } = step.locale;

const back = getText(step.locale.back);
const close = getText(step.locale.close);
const last = getText(step.locale.last);
const next = getText(step.locale.next);
const skip = getText(step.locale.skip);
const backText = getReactNodeText(back);
const closeText = getReactNodeText(close);
const lastText = getReactNodeText(last);
const nextText = getReactNodeText(next);
const skipText = getReactNodeText(skip);

let primaryLabel = close;
let primaryText = close;
let primary = close;
let primaryText = closeText;

if (continuous) {
primaryLabel = next;
primaryText = next;
primary = next;
primaryText = nextText;

if (step.showProgress && !isLastStep) {
primaryLabel = getText(step.locale.nextLabelWithProgress)
.replace('{step}', String(index + 1))
.replace('{steps}', String(size));
primaryText = `${next} (${index + 1}/${size})`;
const labelWithProgress = getReactNodeText(nextLabelWithProgress, {
step: index + 1,
steps: size,
});

primary = replaceLocaleContent(nextLabelWithProgress, index + 1, size);
primaryText = labelWithProgress;
}

if (isLastStep) {
primaryLabel = last;
primaryText = last;
primary = last;
primaryText = lastText;
}
}

return {
backProps: {
'aria-label': back,
'aria-label': backText,
children: back,
'data-action': 'back',
onClick: this.handleClickBack,
role: 'button',
title: back,
title: backText,
},
closeProps: {
'aria-label': close,
'aria-label': closeText,
children: close,
'data-action': 'close',
onClick: this.handleClickClose,
role: 'button',
title: close,
title: closeText,
},
primaryProps: {
'aria-label': primaryLabel,
'aria-label': primaryText,
children: primary,
'data-action': 'primary',
onClick: this.handleClickPrimary,
role: 'button',
title: primaryText,
},
skipProps: {
'aria-label': skip,
'aria-label': skipText,
children: skip,
'data-action': 'skip',
onClick: this.handleClickSkip,
role: 'button',
title: skip,
title: skipText,
},
tooltipProps: {
'aria-modal': true,
Expand Down
92 changes: 68 additions & 24 deletions src/modules/helpers.ts → src/modules/helpers.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { isValidElement, ReactNode } from 'react';
import { cloneElement, FC, isValidElement, ReactElement, ReactNode } from 'react';
import { createPortal } from 'react-dom';
import innerText from 'react-innertext';
import is from 'is-lite';

import { LIFECYCLE } from '~/literals';
Expand All @@ -8,6 +9,12 @@ import { AnyObject, Lifecycle, NarrowPlainObject, Step } from '~/types';

import { hasPosition } from './dom';

interface GetReactNodeTextOptions {
defaultValue?: any;
step?: number;
steps?: number;
}

interface LogOptions {
/** The data to be logged */
data: any;
Expand Down Expand Up @@ -72,31 +79,27 @@ export function getObjectType(value: unknown): string {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}

/**
* Get text from React components
*/
export function getText(root: ReactNode): string {
const content: Array<string | number> = [];

const recurse = (child: ReactNode) => {
if (typeof child === 'string' || typeof child === 'number') {
content.push(child);
} else if (Array.isArray(child)) {
child.forEach(c => recurse(c));
} else if (isValidElement(child)) {
const { children } = child.props;

if (Array.isArray(children)) {
children.forEach(c => recurse(c));
} else {
recurse(children);
}
}
};
export function getReactNodeText(input: ReactNode, options: GetReactNodeTextOptions = {}): string {
const { defaultValue, step, steps } = options;
let text = innerText(input);

recurse(root);
if (!text) {
if (
isValidElement(input) &&
!Object.values(input.props).length &&
getObjectType(input.type) === 'function'
) {
const component = (input.type as FC)({});

return content.join(' ').trim();
text = getReactNodeText(component, options);
} else {
text = innerText(defaultValue);
}
} else if ((text.includes('{step}') || text.includes('{steps}')) && step && steps) {
text = text.replace('{step}', step.toString()).replace('{steps}', steps.toString());
}

return text;
}

export function hasValidKeys(object: Record<string, unknown>, keys?: Array<string>): boolean {
Expand Down Expand Up @@ -240,6 +243,47 @@ export function pick<T extends Record<string, any>, K extends keyof T>(
return output as Pick<T, K>;
}

export function replaceLocaleContent(input: ReactNode, step: number, steps: number): ReactNode {
const replacer = (text: string) =>
text.replace('{step}', String(step)).replace('{steps}', String(steps));

if (getObjectType(input) === 'string') {
return replacer(input as string);
}

if (!isValidElement(input)) {
return input;
}

const { children } = input.props;

if (getObjectType(children) === 'string' && children.includes('{step}')) {
return cloneElement(input as ReactElement, {
children: replacer(children),
});
}

if (Array.isArray(children)) {
return cloneElement(input as ReactElement, {
children: children.map((child: ReactNode) => {
if (typeof child === 'string') {
return replacer(child);
}

return replaceLocaleContent(child, step, steps);
}),
});
}

if (getObjectType(input.type) === 'function' && !Object.values(input.props).length) {
const component = (input.type as FC)({});

return replaceLocaleContent(component, step, steps);
}

return input;
}

export function shouldScroll(options: ShouldScrollOptions): boolean {
const { isFirstStep, lifecycle, previousLifecycle, scrollToFirstStep, step, target } = options;

Expand Down
2 changes: 1 addition & 1 deletion src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface Locale {
*/
next?: ReactNode;
/**
* Label for the next button with `showProgress.
* Label for the next button with `showProgress`.
* Use the `{step}` and `{steps}` placeholders to display the current step and the total steps.
* @default 'Next (Step {step} of {steps})'
*/
Expand Down
16 changes: 15 additions & 1 deletion test/__fixtures__/CustomOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,25 @@ interface State {
steps: Array<Step>;
}

const tourSteps = [
function Skip() {
return <strong data-test-id="skip-label">Do you really want to skip?</strong>;
}

function NextWithProgress() {
return <strong>{`Go ({step} of {steps})`}</strong>;
}

const tourSteps: Array<Step> = [
...standardSteps.slice(0, 3).map(step => {
if (step.target === '.mission button') {
return {
...step,
showProgress: true,
locale: {
nextLabelWithProgress: <NextWithProgress />,
back: <strong>Go Back</strong>,
skip: <Skip />,
},
target: '.mission h2 span',
};
}
Expand Down
Loading

0 comments on commit 826669d

Please sign in to comment.