Skip to content

Commit bd14203

Browse files
authored
Allow arrow padding to be configured for a step. (#3051)
* Allow arrow padding to be configured for a step. * fixup! Allow arrow padding to be configured for a step. * fixup! Allow arrow padding to be configured for a step.
1 parent e286bad commit bd14203

File tree

5 files changed

+115
-4
lines changed

5 files changed

+115
-4
lines changed

docs-src/src/content/docs/guides/usage.md

+11-1
Original file line numberDiff line numberDiff line change
@@ -297,4 +297,14 @@ myTour.addStep({
297297
});
298298
```
299299

300-
Furthermore, while Shepherd provides some basic arrow styling, you can style it as you wish by targeting the `.shepherd-arrow` element.
300+
You can also provide an options object, to configure the arrow's [padding](https://floating-ui.com/docs/arrow#padding). The padding is the closest the arrow will get to the edge of the step.
301+
302+
```js
303+
myTour.addStep({
304+
id: 'Step 1',
305+
arrow: { padding: 10 }
306+
});
307+
```
308+
309+
310+
Furthermore, while Shepherd provides some basic arrow styling, you can style it as you wish by targeting the `.shepherd-arrow` element.

shepherd.js/src/step.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ export interface StepOptions {
6868
advanceOn?: StepOptionsAdvanceOn;
6969

7070
/**
71-
* Whether to display the arrow for the tooltip or not
71+
* Whether to display the arrow for the tooltip or not, or options for the arrow.
7272
*/
73-
arrow?: boolean;
73+
arrow?: boolean | StepOptionsArrow;
7474

7575
/**
7676
* A function that returns a promise.
@@ -222,6 +222,14 @@ export type PopperPlacement =
222222
| 'left-start'
223223
| 'left-end';
224224

225+
export interface StepOptionsArrow {
226+
/*
227+
* The padding from the edge for the arrow.
228+
* Not used if this is not a -start or -end placement.
229+
*/
230+
padding?: number;
231+
}
232+
225233
export interface StepOptionsAttachTo {
226234
element?:
227235
| HTMLElement

shepherd.js/src/utils/floating-ui.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -211,8 +211,16 @@ export function getFloatingUIOptions(
211211
);
212212

213213
if (arrowEl) {
214+
const arrowOptions =
215+
typeof step.options.arrow === 'object'
216+
? step.options.arrow
217+
: { padding: 4 };
218+
214219
options.middleware.push(
215-
arrow({ element: arrowEl, padding: hasEdgeAlignment ? 4 : 0 })
220+
arrow({
221+
element: arrowEl,
222+
padding: hasEdgeAlignment ? arrowOptions.padding : 0
223+
})
216224
);
217225
}
218226

test/cypress/integration/test.acceptance.cy.js

+47
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,53 @@ describe('Shepherd Acceptance Tests', () => {
531531
});
532532
});
533533
});
534+
535+
describe("arrow padding", () => {
536+
it('uses provided arrow padding', () => {
537+
const tour = setupTour(Shepherd, {}, () => [
538+
{
539+
text: 'Test',
540+
attachTo: {
541+
element: '.hero-example',
542+
on: 'left-end'
543+
},
544+
arrow: true,
545+
classes: 'shepherd-step-element shepherd-transparent-text first-step',
546+
id: 'welcome'
547+
}
548+
]);
549+
550+
tour.start();
551+
cy.wait(250);
552+
553+
cy.get('[data-shepherd-step-id="welcome"] .shepherd-arrow').then((arrowElement) => {
554+
const finalPosition = arrowElement.css(['top']);
555+
expect(finalPosition).to.deep.equal({ top: "4px" });
556+
});
557+
});
558+
559+
it('uses a default arrow padding if not provided', () => {
560+
const tour = setupTour(Shepherd, {}, () => [
561+
{
562+
text: 'Test',
563+
attachTo: {
564+
element: '.hero-example',
565+
on: 'left-end'
566+
},
567+
arrow: { padding: 10 },
568+
classes: 'shepherd-step-element shepherd-transparent-text first-step',
569+
id: 'welcome'
570+
}
571+
]);
572+
tour.start();
573+
cy.wait(250);
574+
575+
cy.get('[data-shepherd-step-id="welcome"] .shepherd-arrow').then((arrowElement) => {
576+
const finalPosition = arrowElement.css(['top']);
577+
expect(finalPosition).to.deep.equal({ top: "10px" });
578+
});
579+
});
580+
});
534581
});
535582

536583
describe('Steps: rendering', () => {

test/unit/components/shepherd-element.spec.js

+38
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,44 @@ describe('components/ShepherdElement', () => {
4444
container.querySelectorAll('.shepherd-element .shepherd-arrow').length
4545
).toBe(0);
4646
});
47+
48+
it('arrow: object with padding shows arrow', async () => {
49+
const testElement = document.createElement('div');
50+
const tour = new Tour();
51+
const step = new Step(tour, {
52+
arrow: { padding: 10 },
53+
attachTo: { element: testElement, on: 'top' }
54+
});
55+
56+
const { container } = render(ShepherdElement, {
57+
props: {
58+
step
59+
}
60+
});
61+
62+
expect(
63+
container.querySelectorAll('.shepherd-element .shepherd-arrow').length
64+
).toBe(1);
65+
});
66+
67+
it('arrow: empty object shows arrow', async () => {
68+
const testElement = document.createElement('div');
69+
const tour = new Tour();
70+
const step = new Step(tour, {
71+
arrow: {},
72+
attachTo: { element: testElement, on: 'top' }
73+
});
74+
75+
const { container } = render(ShepherdElement, {
76+
props: {
77+
step
78+
}
79+
});
80+
81+
expect(
82+
container.querySelectorAll('.shepherd-element .shepherd-arrow').length
83+
).toBe(1);
84+
});
4785
});
4886

4987
describe('handleKeyDown', () => {

0 commit comments

Comments
 (0)