@purpurds/popover 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/LICENSE.txt +905 -0
- package/dist/metadata.js +8 -0
- package/dist/popover-back.d.ts +9 -0
- package/dist/popover-back.d.ts.map +1 -0
- package/dist/popover-button.d.ts +37 -0
- package/dist/popover-button.d.ts.map +1 -0
- package/dist/popover-content.d.ts +93 -0
- package/dist/popover-content.d.ts.map +1 -0
- package/dist/popover-flow.d.ts +65 -0
- package/dist/popover-flow.d.ts.map +1 -0
- package/dist/popover-footer.d.ts +16 -0
- package/dist/popover-footer.d.ts.map +1 -0
- package/dist/popover-header.d.ts +7 -0
- package/dist/popover-header.d.ts.map +1 -0
- package/dist/popover-internal-context.d.ts +15 -0
- package/dist/popover-internal-context.d.ts.map +1 -0
- package/dist/popover-next.d.ts +9 -0
- package/dist/popover-next.d.ts.map +1 -0
- package/dist/popover-standalone.d.ts +12 -0
- package/dist/popover-standalone.d.ts.map +1 -0
- package/dist/popover-steps.d.ts +6 -0
- package/dist/popover-steps.d.ts.map +1 -0
- package/dist/popover-trigger.d.ts +27 -0
- package/dist/popover-trigger.d.ts.map +1 -0
- package/dist/popover-walkthrough.d.ts +13 -0
- package/dist/popover-walkthrough.d.ts.map +1 -0
- package/dist/popover.cjs.js +42 -0
- package/dist/popover.cjs.js.map +1 -0
- package/dist/popover.d.ts +36 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.es.js +3849 -0
- package/dist/popover.es.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/use-screen-size.hook.d.ts +7 -0
- package/dist/use-screen-size.hook.d.ts.map +1 -0
- package/dist/use-smooth-scroll.d.ts +5 -0
- package/dist/use-smooth-scroll.d.ts.map +1 -0
- package/dist/usePopoverTrigger.d.ts +5 -0
- package/dist/usePopoverTrigger.d.ts.map +1 -0
- package/dist/usePopoverWalkthrough.d.ts +7 -0
- package/dist/usePopoverWalkthrough.d.ts.map +1 -0
- package/eslint.config.mjs +2 -0
- package/package.json +82 -0
- package/src/global.d.ts +4 -0
- package/src/popover-back.test.tsx +63 -0
- package/src/popover-back.tsx +40 -0
- package/src/popover-button.test.tsx +51 -0
- package/src/popover-button.tsx +84 -0
- package/src/popover-content.test.tsx +1122 -0
- package/src/popover-content.tsx +277 -0
- package/src/popover-flow.tsx +170 -0
- package/src/popover-footer.test.tsx +21 -0
- package/src/popover-footer.tsx +32 -0
- package/src/popover-header.test.tsx +22 -0
- package/src/popover-header.tsx +32 -0
- package/src/popover-internal-context.tsx +28 -0
- package/src/popover-next.test.tsx +61 -0
- package/src/popover-next.tsx +40 -0
- package/src/popover-standalone.tsx +48 -0
- package/src/popover-steps.tsx +32 -0
- package/src/popover-trigger.tsx +71 -0
- package/src/popover-walkthrough.test.tsx +346 -0
- package/src/popover-walkthrough.tsx +45 -0
- package/src/popover.module.scss +315 -0
- package/src/popover.stories.tsx +1157 -0
- package/src/popover.test.tsx +642 -0
- package/src/popover.tsx +76 -0
- package/src/use-screen-size.hook.ts +39 -0
- package/src/use-smooth-scroll.ts +62 -0
- package/src/usePopoverTrigger.ts +59 -0
- package/src/usePopoverWalkthrough.ts +85 -0
- package/vitest.setup.ts +30 -0
package/dist/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._purpur-popover__trigger_10ko3_1{position:relative;width:fit-content}._purpur-popover__trigger-highlight_10ko3_5{position:absolute;inset:-2px;border-radius:var(--purpur-border-radius-sm);pointer-events:none}._purpur-popover__trigger-highlight--state1_10ko3_14{border:var(--purpur-border-radius-xs) solid var(--purpur-color-border-interactive-expressive);box-shadow:0 0 0 4px #990ae380}@media(prefers-reduced-motion:no-preference){._purpur-popover__trigger-highlight--state1_10ko3_14{animation:_highlightState1_10ko3_1 var(--purpur-motion-duration-1500) ease-out 3;animation-fill-mode:forwards}}@media(prefers-reduced-motion:reduce){._purpur-popover__trigger-highlight--state1_10ko3_14{opacity:0}}._purpur-popover__trigger-highlight--state1_10ko3_14._purpur-popover__trigger-highlight--negative_10ko3_29{border-color:var(--purpur-color-purple-200);box-shadow:0 0 0 4px #e4b6fb80}._purpur-popover__trigger-highlight--state2_10ko3_33{border:var(--purpur-border-radius-xs) solid var(--purpur-color-border-interactive-expressive);opacity:1}@media(prefers-reduced-motion:reduce){._purpur-popover__trigger-highlight--state2_10ko3_33{opacity:1}}._purpur-popover__trigger-highlight--state2_10ko3_33._purpur-popover__trigger-highlight--negative_10ko3_29{border-color:var(--purpur-color-purple-200)}._purpur-popover__inner_10ko3_45{display:flex;flex-direction:column;align-items:flex-start;gap:var(--purpur-spacing-100);align-self:stretch}._purpur-popover__content_10ko3_52{display:flex;flex-direction:column;align-items:flex-start;gap:var(--purpur-spacing-300);width:288px;padding:var(--purpur-spacing-200);border-radius:var(--purpur-border-radius-md);box-shadow:var(--purpur-shadow-lg);z-index:var(--popover-z-index)}@media(prefers-reduced-motion:no-preference){._purpur-popover__content_10ko3_52[data-state=open]{animation:_fadeIn_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=open][data-side=top]{animation:_fadeIn_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideFromTop_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=open][data-side=bottom]{animation:_fadeIn_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideFromBottom_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=open][data-side=left]{animation:_fadeIn_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideFromLeft_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=open][data-side=right]{animation:_fadeIn_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideFromRight_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=closed]{animation:_fadeOut_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=closed][data-side=top]{animation:_fadeOut_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideToTop_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=closed][data-side=bottom]{animation:_fadeOut_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideToBottom_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=closed][data-side=left]{animation:_fadeOut_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideToLeft_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}._purpur-popover__content_10ko3_52[data-state=closed][data-side=right]{animation:_fadeOut_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards,_slideToRight_10ko3_1 var(--purpur-motion-duration-200) var(--purpur-motion-easing-ease-in-out) forwards}}@media(min-width:600px){._purpur-popover__content_10ko3_52{width:400px}}._purpur-popover__content_10ko3_52{background:var(--purpur-color-background-tone-on-tone-primary)}._purpur-popover__content_10ko3_52 ._purpur-popover__arrow_10ko3_103{fill:var(--purpur-color-background-tone-on-tone-primary)}._purpur-popover__content_10ko3_52 ._purpur-popover__body_10ko3_106,._purpur-popover__content_10ko3_52 ._purpur-popover__icon_10ko3_107,._purpur-popover__content_10ko3_52 ._purpur-popover__steps_10ko3_108,._purpur-popover__content_10ko3_52 ._purpur-popover__title_10ko3_109{color:var(--purpur-color-text-tone-on-tone-primary)}._purpur-popover__content--negative_10ko3_112{background:var(--purpur-color-background-tone-on-tone-secondary)}._purpur-popover__content--negative_10ko3_112 ._purpur-popover__arrow_10ko3_103{fill:var(--purpur-color-background-tone-on-tone-secondary)}._purpur-popover__content--negative_10ko3_112 ._purpur-popover__body_10ko3_106,._purpur-popover__content--negative_10ko3_112 ._purpur-popover__icon_10ko3_107,._purpur-popover__content--negative_10ko3_112 ._purpur-popover__steps_10ko3_108,._purpur-popover__content--negative_10ko3_112 ._purpur-popover__title_10ko3_109{color:var(--purpur-color-text-tone-on-tone-secondary)}._purpur-popover__header-content_10ko3_124{display:flex;align-items:flex-start;gap:var(--purpur-spacing-100);align-self:stretch;padding-right:var(--purpur-spacing-300)}._purpur-popover__header-content_10ko3_124 ._purpur-popover__icon_10ko3_107{display:flex;padding-top:var(--purpur-spacing-25);align-items:center;gap:8px}._purpur-popover__content_10ko3_52 ._purpur-popover__close_10ko3_137{position:absolute;top:6.26px;right:6.26px}._purpur-popover__footer_10ko3_142{display:flex;text-align:center;gap:var(--purpur-spacing-150);align-self:stretch;align-items:center;flex-direction:column-reverse}@media(min-width:600px){._purpur-popover__footer_10ko3_142{flex-direction:row}}._purpur-popover__footer-button-group_10ko3_155{display:flex;justify-content:flex-end;align-items:center;gap:var(--purpur-spacing-200);flex:1 0 0}._purpur-popover__steps_10ko3_108{flex-shrink:0;color:var(--purpur-color-text-tone-on-tone-primary);overflow:hidden;text-overflow:ellipsis}@keyframes _fadeIn_10ko3_1{0%{opacity:0}to{opacity:1}}@keyframes _fadeOut_10ko3_1{0%{opacity:1}to{opacity:0}}@keyframes _slideFromTop_10ko3_1{0%{transform:translateY(-8px)}to{transform:translateY(0)}}@keyframes _slideToTop_10ko3_1{0%{transform:translateY(0)}to{transform:translateY(-8px)}}@keyframes _slideFromBottom_10ko3_1{0%{transform:translateY(8px)}to{transform:translateY(0)}}@keyframes _slideToBottom_10ko3_1{0%{transform:translateY(0)}to{transform:translateY(8px)}}@keyframes _slideFromLeft_10ko3_1{0%{transform:translate(-8px)}to{transform:translate(0)}}@keyframes _slideToLeft_10ko3_1{0%{transform:translate(0)}to{transform:translate(-8px)}}@keyframes _slideFromRight_10ko3_1{0%{transform:translate(8px)}to{transform:translate(0)}}@keyframes _slideToRight_10ko3_1{0%{transform:translate(0)}to{transform:translate(8px)}}@keyframes _highlightState1_10ko3_1{0%{opacity:0}50%{opacity:1}to{opacity:0}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-screen-size.hook.d.ts","sourceRoot":"","sources":["../src/use-screen-size.hook.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB;;CAErB,CAAC;AAEX,eAAO,MAAM,aAAa;;CA6BzB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-smooth-scroll.d.ts","sourceRoot":"","sources":["../src/use-smooth-scroll.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,GAAI,aAAa,MAAM,IAAI;+BAWzC,WAAW,aAAY,MAAM;;CAgD1C,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function usePopoverTrigger(ref: React.Ref<HTMLButtonElement>): {
|
|
2
|
+
triggerRef: import('react').RefObject<HTMLButtonElement | null>;
|
|
3
|
+
context: import('./popover-internal-context').PopoverInternalContextValue | null;
|
|
4
|
+
};
|
|
5
|
+
//# sourceMappingURL=usePopoverTrigger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePopoverTrigger.d.ts","sourceRoot":"","sources":["../src/usePopoverTrigger.ts"],"names":[],"mappings":"AAMA,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC;;;EAoDlE"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function usePopoverWalkthrough(step: number): {
|
|
2
|
+
actuallyOpen: boolean;
|
|
3
|
+
onScrollStart: () => void;
|
|
4
|
+
onScrollComplete: () => void;
|
|
5
|
+
handleOpenChange: (open: boolean, consumerOnOpenChange?: (open: boolean) => void) => void;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=usePopoverWalkthrough.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePopoverWalkthrough.d.ts","sourceRoot":"","sources":["../src/usePopoverWalkthrough.ts"],"names":[],"mappings":"AAIA,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM;;;;6BA6DvC,OAAO,yBAAyB,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI;EAmBjE"}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@purpurds/popover",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "AGPL-3.0-only",
|
|
5
|
+
"main": "./dist/popover.cjs.js",
|
|
6
|
+
"types": "./dist/popover.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"require": "./dist/popover.cjs.js",
|
|
10
|
+
"types": "./dist/popover.d.ts",
|
|
11
|
+
"default": "./dist/popover.es.js"
|
|
12
|
+
},
|
|
13
|
+
"./styles": "./dist/styles.css",
|
|
14
|
+
"./metadata": "./dist/metadata.js"
|
|
15
|
+
},
|
|
16
|
+
"source": "src/popover.tsx",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@radix-ui/react-popover": "^1.0.7",
|
|
19
|
+
"@radix-ui/react-slot": "^1.0.2",
|
|
20
|
+
"classnames": "~2.5.1",
|
|
21
|
+
"@purpurds/button": "8.11.0",
|
|
22
|
+
"@purpurds/icon": "8.11.0",
|
|
23
|
+
"@purpurds/heading": "8.11.0",
|
|
24
|
+
"@purpurds/link": "8.11.0",
|
|
25
|
+
"@purpurds/paragraph": "8.11.0",
|
|
26
|
+
"@purpurds/tokens": "8.11.0",
|
|
27
|
+
"@purpurds/common-types": "8.11.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@rushstack/eslint-patch": "~1.10.0",
|
|
31
|
+
"@storybook/react": "^10.0.8",
|
|
32
|
+
"@testing-library/dom": "~10.4.1",
|
|
33
|
+
"@testing-library/jest-dom": "~6.9.1",
|
|
34
|
+
"@testing-library/react": "~16.3.0",
|
|
35
|
+
"@testing-library/user-event": "~14.5.1",
|
|
36
|
+
"@types/node": "22.17",
|
|
37
|
+
"@types/react-dom": "^19.2.3",
|
|
38
|
+
"@types/react": "^19.2.6",
|
|
39
|
+
"eslint": "9.39.1",
|
|
40
|
+
"jsdom": "~27.2.0",
|
|
41
|
+
"lint-staged": "16.2.6",
|
|
42
|
+
"prettier": "~2.8.8",
|
|
43
|
+
"react": "^19.2.1",
|
|
44
|
+
"react-dom": "^19.2.1",
|
|
45
|
+
"storybook": "^10.0.8",
|
|
46
|
+
"typescript": "^5.9.3",
|
|
47
|
+
"vite": "^7.2.2",
|
|
48
|
+
"vitest": "^4.0.10",
|
|
49
|
+
"vitest-axe": "~0.1.0",
|
|
50
|
+
"vitest-canvas-mock": "~0.3.3",
|
|
51
|
+
"@types/jest-axe": "~3.5.9",
|
|
52
|
+
"@purpurds/component-rig": "1.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@types/react": "^18 || ^19",
|
|
56
|
+
"@types/react-dom": "^18 || ^19",
|
|
57
|
+
"react": "^18 || ^19",
|
|
58
|
+
"react-dom": "^18 || ^19"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@types/react": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@types/react-dom": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build:dev": "vite",
|
|
70
|
+
"build:watch": "vite build --watch",
|
|
71
|
+
"build": "vite build",
|
|
72
|
+
"ci:build": "rushx build",
|
|
73
|
+
"coverage": "vitest run --coverage",
|
|
74
|
+
"lint:fix": "eslint . --fix",
|
|
75
|
+
"lint": "lint-staged --no-stash 2>&1",
|
|
76
|
+
"sbdev": "rush sbdev",
|
|
77
|
+
"test:unit": "vitest run --passWithNoTests",
|
|
78
|
+
"test:watch": "vitest --watch",
|
|
79
|
+
"test": "rushx test:unit",
|
|
80
|
+
"typecheck": "tsc -p ./tsconfig.json"
|
|
81
|
+
}
|
|
82
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { axe } from "vitest-axe";
|
|
5
|
+
|
|
6
|
+
import { PopoverBack } from "./popover-back";
|
|
7
|
+
import { PopoverFlow } from "./popover-flow";
|
|
8
|
+
import { PopoverInternalContext } from "./popover-internal-context";
|
|
9
|
+
|
|
10
|
+
describe("PopoverBack", () => {
|
|
11
|
+
it("renders children and handles click", () => {
|
|
12
|
+
const onClick = vi.fn();
|
|
13
|
+
render(
|
|
14
|
+
<PopoverFlow
|
|
15
|
+
separatorText="of"
|
|
16
|
+
stepText="Step"
|
|
17
|
+
backLabel="Back"
|
|
18
|
+
nextLabel="Next"
|
|
19
|
+
finishLabel="Finish"
|
|
20
|
+
initialStep={2}
|
|
21
|
+
>
|
|
22
|
+
<PopoverInternalContext.Provider
|
|
23
|
+
value={{
|
|
24
|
+
isOpen: false,
|
|
25
|
+
walkthroughStep: 0,
|
|
26
|
+
negative: false,
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<PopoverBack onClick={onClick}>Back</PopoverBack>
|
|
30
|
+
</PopoverInternalContext.Provider>
|
|
31
|
+
</PopoverFlow>
|
|
32
|
+
);
|
|
33
|
+
fireEvent.click(screen.getByText("Back"));
|
|
34
|
+
expect(onClick).toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should be accessible", async () => {
|
|
38
|
+
const { container } = render(
|
|
39
|
+
<PopoverFlow
|
|
40
|
+
separatorText="of"
|
|
41
|
+
stepText="Step"
|
|
42
|
+
backLabel="Back"
|
|
43
|
+
nextLabel="Next"
|
|
44
|
+
finishLabel="Finish"
|
|
45
|
+
initialStep={2}
|
|
46
|
+
>
|
|
47
|
+
<PopoverInternalContext.Provider
|
|
48
|
+
value={{
|
|
49
|
+
isOpen: false,
|
|
50
|
+
walkthroughStep: 0,
|
|
51
|
+
negative: false,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<PopoverBack>Back</PopoverBack>
|
|
55
|
+
</PopoverInternalContext.Provider>
|
|
56
|
+
</PopoverFlow>
|
|
57
|
+
);
|
|
58
|
+
const results = await axe(container);
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
expect(results).toHaveNoViolations();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { Button } from "@purpurds/button";
|
|
3
|
+
|
|
4
|
+
import { usePopoverFlow } from "./popover-flow";
|
|
5
|
+
import { usePopoverNegative } from "./popover-internal-context";
|
|
6
|
+
import { useScreenSize } from "./use-screen-size.hook";
|
|
7
|
+
|
|
8
|
+
export const PopoverBack = ({
|
|
9
|
+
children,
|
|
10
|
+
onClick,
|
|
11
|
+
}: {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
}) => {
|
|
15
|
+
const { back } = usePopoverFlow();
|
|
16
|
+
const { negative: isNegative } = usePopoverNegative();
|
|
17
|
+
const { isMdOrSmaller } = useScreenSize();
|
|
18
|
+
|
|
19
|
+
// Invert negative: if popover is negative (light), buttons should be normal (dark)
|
|
20
|
+
const negative = isNegative ? false : true;
|
|
21
|
+
|
|
22
|
+
const handleClick = () => {
|
|
23
|
+
onClick?.();
|
|
24
|
+
back();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Button
|
|
29
|
+
size="sm"
|
|
30
|
+
variant="secondary"
|
|
31
|
+
onClick={handleClick}
|
|
32
|
+
negative={negative}
|
|
33
|
+
fullWidth={isMdOrSmaller}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</Button>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
PopoverBack.displayName = "PopoverBack";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import * as RadixPopover from "@radix-ui/react-popover";
|
|
3
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
4
|
+
import { describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { axe } from "vitest-axe";
|
|
6
|
+
|
|
7
|
+
import { PopoverButton } from "./popover-button";
|
|
8
|
+
import { PopoverInternalContext } from "./popover-internal-context";
|
|
9
|
+
|
|
10
|
+
describe("PopoverButton", () => {
|
|
11
|
+
it("renders children and handles click", () => {
|
|
12
|
+
const onClick = vi.fn();
|
|
13
|
+
render(
|
|
14
|
+
<RadixPopover.Root>
|
|
15
|
+
<PopoverInternalContext.Provider
|
|
16
|
+
value={{
|
|
17
|
+
isOpen: false,
|
|
18
|
+
walkthroughStep: 0,
|
|
19
|
+
negative: false,
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
<PopoverButton onClick={onClick} dismiss={false}>
|
|
23
|
+
Button
|
|
24
|
+
</PopoverButton>
|
|
25
|
+
</PopoverInternalContext.Provider>
|
|
26
|
+
</RadixPopover.Root>
|
|
27
|
+
);
|
|
28
|
+
fireEvent.click(screen.getByText("Button"));
|
|
29
|
+
expect(onClick).toHaveBeenCalled();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("should be accessible", async () => {
|
|
33
|
+
const { container } = render(
|
|
34
|
+
<RadixPopover.Root>
|
|
35
|
+
<PopoverInternalContext.Provider
|
|
36
|
+
value={{
|
|
37
|
+
isOpen: false,
|
|
38
|
+
walkthroughStep: 0,
|
|
39
|
+
negative: false,
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
<PopoverButton dismiss={false}>Button</PopoverButton>
|
|
43
|
+
</PopoverInternalContext.Provider>
|
|
44
|
+
</RadixPopover.Root>
|
|
45
|
+
);
|
|
46
|
+
const results = await axe(container);
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
expect(results).toHaveNoViolations();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { Button, type ButtonProps } from "@purpurds/button";
|
|
3
|
+
import * as RadixPopover from "@radix-ui/react-popover";
|
|
4
|
+
|
|
5
|
+
import { useOptionalPopoverFlow } from "./popover-flow";
|
|
6
|
+
import { usePopoverNegative } from "./popover-internal-context";
|
|
7
|
+
import { useScreenSize } from "./use-screen-size.hook";
|
|
8
|
+
|
|
9
|
+
export type PopoverButtonProps = RadixPopover.PopoverCloseProps & {
|
|
10
|
+
/**
|
|
11
|
+
* Button label or content.
|
|
12
|
+
*/
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to use the child element as the button instead of wrapping in a Button component.
|
|
16
|
+
* @default false
|
|
17
|
+
*/
|
|
18
|
+
asChild?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Click handler called when the button is clicked.
|
|
21
|
+
*/
|
|
22
|
+
onClick?: () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Visual variant of the button.
|
|
25
|
+
* @default "primary"
|
|
26
|
+
*/
|
|
27
|
+
variant?: ButtonProps["variant"];
|
|
28
|
+
/**
|
|
29
|
+
* Whether clicking the button should dismiss/close the popover.
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
dismiss?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the button should take full width. If not specified, defaults to true on mobile (md or smaller).
|
|
35
|
+
*/
|
|
36
|
+
fullWidth?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const PopoverButton = ({
|
|
40
|
+
children,
|
|
41
|
+
asChild,
|
|
42
|
+
onClick,
|
|
43
|
+
variant = "primary",
|
|
44
|
+
dismiss = true,
|
|
45
|
+
fullWidth,
|
|
46
|
+
}: PopoverButtonProps) => {
|
|
47
|
+
const flow = useOptionalPopoverFlow();
|
|
48
|
+
const { isMdOrSmaller } = useScreenSize();
|
|
49
|
+
const { negative: isNegative } = usePopoverNegative();
|
|
50
|
+
// Invert negative: if popover is negative (light), buttons should be normal (dark)
|
|
51
|
+
const negative = isNegative ? false : true;
|
|
52
|
+
|
|
53
|
+
const handleClick = () => {
|
|
54
|
+
onClick?.();
|
|
55
|
+
if (dismiss) {
|
|
56
|
+
flow?.dismiss();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const isFullWidth = fullWidth ?? isMdOrSmaller;
|
|
61
|
+
|
|
62
|
+
const content = asChild ? (
|
|
63
|
+
children
|
|
64
|
+
) : (
|
|
65
|
+
<Button
|
|
66
|
+
variant={variant}
|
|
67
|
+
size="sm"
|
|
68
|
+
fullWidth={isFullWidth}
|
|
69
|
+
negative={negative}
|
|
70
|
+
onClick={handleClick}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</Button>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Always wrap with RadixPopover.Close for proper dismiss behavior
|
|
77
|
+
if (dismiss) {
|
|
78
|
+
return <RadixPopover.Close asChild>{content}</RadixPopover.Close>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return content;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
PopoverButton.displayName = "PopoverButton";
|