Skip to content

Commit e5b2606

Browse files
committed
chore: more manual fixes
1 parent 5d01322 commit e5b2606

File tree

5 files changed

+66
-34
lines changed

5 files changed

+66
-34
lines changed

src/components/Popup/Popup.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Supports segment-based activation and persistent closure state using localStorag
3131
export default meta
3232
type Story = StoryObj
3333

34+
// reset popular storage key for demo purposes
35+
localStorage.clear()
36+
3437
export const Default: Story = {
3538
args: {},
3639
render: () => html`
@@ -43,13 +46,20 @@ export const Default: Story = {
4346
>
4447
Sign Up Now
4548
</button>
49+
<button
50+
n-ribbon
51+
style="background: transparent; border: 1px solid #ccc; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; margin-left: 0.5rem;"
52+
>
53+
Maybe Later
54+
</button>
4655
<button
4756
n-close
4857
style="background: transparent; border: 1px solid #ccc; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; margin-left: 0.5rem;"
4958
>
5059
Close
5160
</button>
5261
</div>
62+
<div slot="ribbon">Hello from the ribbon!</div>
5363
</nosto-popup>
5464
`
5565
}

src/components/Popup/Popup.ts

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -56,46 +56,57 @@ export class Popup extends NostoElement {
5656

5757
private handleClick(event: Event) {
5858
const target = event.target as HTMLElement
59-
const toOpen = target?.matches(".ribbon") || target?.closest(".ribbon")
6059
const toClose = target?.matches("[n-close]") || target?.closest("[n-close]")
6160
const toRibbon = target?.matches("[n-ribbon]") || target?.closest("[n-ribbon]")
61+
const toOpen = target?.matches("[slot='ribbon']") || target?.closest("[slot='ribbon']")
62+
console.log("Popup clicked:", target, { toOpen, toClose, toRibbon })
6263

6364
if (toOpen || toClose || toRibbon) {
6465
event.preventDefault()
6566
event.stopPropagation()
6667
}
67-
if (toOpen) {
68-
updateShadowContent(this, "open")
69-
} else if (toClose) {
68+
if (toClose) {
7069
closePopup(this)
70+
} else if (toOpen) {
71+
updateShadowContent(this, "open")
7172
} else if (toRibbon) {
7273
setPopupState(this.name, "ribbon")
7374
updateShadowContent(this, "ribbon")
7475
}
7576
}
7677
}
7778

79+
const key = "nosto:web-components:popup"
80+
81+
type PopupData = {
82+
name: string
83+
state: "open" | "ribbon" | "closed"
84+
}
85+
7886
function initializeShadowContent(element: Popup, mode: "open" | "ribbon" = "open") {
7987
element.shadowRoot!.innerHTML = `
8088
<style>${popupStyles}</style>
81-
<dialog ${mode === "open" ? "open" : ""} part="dialog" ${mode === "ribbon" ? 'class="hidden"' : ""}>
89+
<dialog part="dialog">
8290
<slot name="default"></slot>
8391
</dialog>
8492
<div class="ribbon ${mode === "open" ? "hidden" : ""}" part="ribbon">
85-
<slot name="ribbon"></slot>
93+
<slot name="ribbon">Open</slot>
8694
</div>
8795
`
96+
if (mode === "open") {
97+
element.shadowRoot?.querySelector("dialog")?.showModal()
98+
}
8899
}
89100

90101
function updateShadowContent(element: Popup, mode: "open" | "ribbon" = "open") {
91-
const dialog = element.shadowRoot?.querySelector("dialog")
102+
const dialog = element.shadowRoot?.querySelector<HTMLDialogElement>("dialog")!
92103
const ribbon = element.shadowRoot?.querySelector(".ribbon")
93104
if (dialog && ribbon) {
94105
if (mode === "ribbon") {
95-
dialog.classList.add("hidden")
106+
dialog?.close()
96107
ribbon.classList.remove("hidden")
97108
} else {
98-
dialog.classList.remove("hidden")
109+
dialog.showModal()
99110
ribbon.classList.add("hidden")
100111
}
101112
}
@@ -110,25 +121,19 @@ async function getPopupState(name: string, segment?: string): Promise<"open" | "
110121
if (segment && !(await checkSegment(segment))) {
111122
return "closed"
112123
}
113-
const key = getKey(name)
114-
const state = localStorage.getItem(key)
115-
if (state === "closed" || state === "ribbon") {
116-
return state
124+
const dataStr = localStorage.getItem(key)
125+
if (dataStr) {
126+
const data = JSON.parse(dataStr) as PopupData
127+
if (data.name !== name) {
128+
return "closed"
129+
}
130+
return data.state
117131
}
118132
return "open"
119133
}
120134

121135
function setPopupState(name: string, state: "open" | "ribbon" | "closed") {
122-
const key = getKey(name)
123-
if (state === "open") {
124-
localStorage.removeItem(key)
125-
} else {
126-
localStorage.setItem(key, state)
127-
}
128-
}
129-
130-
function getKey(name: string) {
131-
return `nosto:web-components:popup:${name}`
136+
localStorage.setItem(key, JSON.stringify({ name, state }))
132137
}
133138

134139
async function checkSegment(segment: string) {

src/components/Popup/styles.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ export const popupStyles = `
1919
padding: 0;
2020
background: transparent;
2121
pointer-events: auto;
22-
z-index: 1001;
2322
}
2423
2524
dialog::backdrop {
26-
background: rgba(0, 0, 0, 0.5);
25+
background: rgba(0, 0, 0, 0.65);
26+
backdrop-filter: blur(2px);
2727
}
2828
2929
.ribbon {

test/components/Popup/Popup.spec.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ import { mockNostojs } from "@nosto/nosto-js/testing"
55
import { createElement } from "../../utils/jsx"
66

77
describe("Popup", () => {
8+
const popupKey = "nosto:web-components:popup"
9+
10+
function getPopupData() {
11+
return JSON.parse(localStorage.getItem(popupKey)!)
12+
}
13+
14+
function setPopupData(data: { name: string; state: "open" | "ribbon" | "closed" }) {
15+
localStorage.setItem(popupKey, JSON.stringify(data))
16+
}
17+
818
beforeEach(() => {
919
// Clear localStorage before each test
1020
localStorage.clear()
@@ -58,7 +68,7 @@ describe("Popup", () => {
5868
describe("Named popups and persistence", () => {
5969
it("should hide popup if it was previously closed and name is set", async () => {
6070
const popupName = "test-popup"
61-
localStorage.setItem(`nosto:web-components:popup:${popupName}`, "closed")
71+
setPopupData({ name: popupName, state: "closed" })
6272

6373
const popup = (
6474
<nosto-popup name={popupName}>
@@ -208,7 +218,7 @@ describe("Popup", () => {
208218
const closeButton = popup.querySelector("[n-close]") as HTMLButtonElement
209219
closeButton.click()
210220

211-
expect(localStorage.getItem(`nosto:web-components:popup:${popupName}`)).toBe("closed")
221+
expect(getPopupData()).toEqual({ name: popupName, state: "closed" })
212222
})
213223

214224
it("should always store closed state in localStorage since name is required", async () => {
@@ -226,7 +236,7 @@ describe("Popup", () => {
226236
const closeButton = popup.querySelector("[n-close]") as HTMLButtonElement
227237
closeButton.click()
228238

229-
expect(localStorage.getItem("nosto:web-components:popup:always-stores-popup")).toBe("closed")
239+
expect(getPopupData()).toEqual({ name: "always-stores-popup", state: "closed" })
230240
})
231241

232242
it("should handle click events on ribbon content with n-close", async () => {
@@ -249,7 +259,7 @@ describe("Popup", () => {
249259
ribbonCloseButton.click()
250260

251261
expect(popup.style.display).toBe("none")
252-
expect(localStorage.getItem("nosto:web-components:popup:ribbon-popup")).toBe("closed")
262+
expect(getPopupData()).toEqual({ name: "ribbon-popup", state: "closed" })
253263
})
254264

255265
it("should not close popup when clicking elements without n-close attribute", async () => {
@@ -332,7 +342,7 @@ describe("Popup", () => {
332342
const popupName = "segment-popup"
333343

334344
// First, close the popup
335-
localStorage.setItem(`nosto:web-components:popup:${popupName}`, "closed")
345+
setPopupData({ name: popupName, state: "closed" })
336346

337347
// Set up segments that would normally show the popup
338348
mockNostojs({
@@ -396,18 +406,18 @@ describe("Popup", () => {
396406
ribbonButton.click()
397407

398408
// Check that ribbon state is stored
399-
expect(localStorage.getItem("nosto:web-components:popup:ribbon-test-popup")).toBe("ribbon")
409+
expect(getPopupData()).toEqual({ name: "ribbon-test-popup", state: "ribbon" })
400410

401411
// Check DOM structure after switch
402412
const dialog = popup.shadowRoot?.querySelector('[part="dialog"]')
403413
const ribbon = popup.shadowRoot?.querySelector('[part="ribbon"]')
404-
expect(dialog?.classList.contains("hidden")).toBe(true)
414+
expect(dialog?.hasAttribute("open")).toBe(false)
405415
expect(ribbon?.classList.contains("hidden")).toBe(false)
406416
})
407417

408418
it("should render in ribbon mode when localStorage state is 'ribbon'", async () => {
409419
const popupName = "persistent-ribbon-popup"
410-
localStorage.setItem(`nosto:web-components:popup:${popupName}`, "ribbon")
420+
setPopupData({ name: popupName, state: "ribbon" })
411421

412422
const popup = (
413423
<nosto-popup name={popupName}>
@@ -421,7 +431,7 @@ describe("Popup", () => {
421431

422432
const dialog = popup.shadowRoot?.querySelector('[part="dialog"]')
423433
const ribbon = popup.shadowRoot?.querySelector('[part="ribbon"]')
424-
expect(dialog?.classList.contains("hidden")).toBe(true)
434+
expect(dialog?.hasAttribute("open")).toBe(false)
425435
expect(ribbon?.classList.contains("hidden")).toBe(false)
426436
})
427437

test/setup.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ import "@/components/SectionCampaign/SectionCampaign"
1111
import "@/components/SimpleCard/SimpleCard"
1212
import "@/components/SkuOptions/SkuOptions"
1313

14+
HTMLDialogElement.prototype.showModal = function () {
15+
this.toggleAttribute("open", true)
16+
}
17+
HTMLDialogElement.prototype.close = function () {
18+
this.toggleAttribute("open", false)
19+
}
20+
1421
beforeAll(() => {
1522
// Components are automatically registered by their @customElement decorators
1623
// when the modules are imported above.

0 commit comments

Comments
 (0)