Skip to content

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented Oct 4, 2025

  • Create Popup component structure following web component patterns
  • Implement shadow root with dialog and ribbon slots
  • BREAKING: Make name attribute required with validation
  • Implement click event handling for n-close attribute
  • Add local storage state management for permanently closed popups
  • Create comprehensive tests for all functionality (21 tests, 98%+ coverage)
  • Add component to JSX type definitions
  • Test and validate implementation
  • REFACTOR: Extract styles to separate styles.ts file for better maintainability
  • REFACTOR: Move private methods to top-level functions following codebase patterns
  • REFACTOR: Simplify Storybook stories by removing Named and WithSegment examples
  • REFACTOR: Remove Features section from Storybook documentation
  • REFACTOR: Inline event listener setup/teardown in lifecycle methods
  • REFACTOR: Move utility functions below custom element class definition
  • REFACTOR: Combine popup closed and segment checks into isPopupShown() function
  • REFACTOR: Add comprehensive attribute documentation in class-level JSDoc
  • ENHANCE: Enhanced click handling to support ancestor elements with n-close attribute
  • REFACTOR: Remove disconnectedCallback method for simplified lifecycle
  • ENHANCE: Improve state management with getPopupState() returning "open" | "ribbon" | "closed"
  • REFACTOR: Remove try/catch from segment checking for cleaner error propagation
  • NEW: Add ribbon mode functionality with n-ribbon click handling for dual-mode operation
  • NEW: Add shadow DOM parts for external styling (dialog, ribbon, backdrop)
  • REFACTOR: Move Popup to global vitest setup for consistency
  • OPTIMIZE: Consolidate state management by integrating segment check into getPopupState()
  • OPTIMIZE: Combine state setters into unified setPopupState() function for cleaner API
  • OPTIMIZE: Replace renderShadowContent with updateShadowContent for performance
  • REFACTOR: Inline default slot content in JSDoc examples
  • REFACTOR: Clean up Storybook documentation formatting
  • REFACTOR: Extract localStorage key generation to reusable getKey() function
  • REFACTOR: Remove unnecessary comments throughout codebase
  • REFACTOR: Extract shadow DOM initialization to dedicated function
  • FIX: Fix test unhandled errors and format issues for clean CI/CD pipeline
  • ENHANCE: Improve click handling with direct element matching and ancestor fallback
  • OPTIMIZE: Initialize shadow content with correct mode from the start
  • REFACTOR: Final cleanup - simplify variable names, use semantic CSS selectors, add styling examples
  • REFACTOR: Move localStorage cleanup to global test setup for consistency
  • FIX: Add required name attribute to all Storybook examples
  • ENHANCE: Add n-ribbon button to Storybook examples for complete functionality demonstration
  • FIX: Address all remaining open issues - clean variable names, semantic selectors, improved examples

🎉 Popup Component Implementation Complete

This PR introduces a new nosto-popup custom element that displays popup content with advanced features for conditional display, persistent state management, and dual-mode operation with optimized performance.

✨ Key Features

🎭 Dual Display Modes

  • Dialog mode: Main popup content displayed in a centered modal dialog (default)
  • Ribbon mode: Compact content positioned in the bottom-right corner
  • Smart switching: Click elements with n-ribbon to switch to ribbon mode

🎯 Smart Targeting

  • Segment filtering: Only show popup to users in specified Nosto segments via segment attribute
  • Persistent closure: Named popups remember when they've been permanently closed using localStorage
  • Mode persistence: Ribbon mode state is remembered across sessions
  • Optimized logic: Segment checking is integrated into state management for optimal performance

🖱️ Interactive Controls

  • Enhanced click detection: Uses both direct element matching and ancestor traversal for reliability
  • Click to close: Elements with n-close attribute will close the popup permanently
  • Click to minimize: Elements with n-ribbon attribute will switch to ribbon mode
  • Robust targeting: Supports both direct clicks on control elements and clicks on their descendants
  • Event handling: Prevents default behavior and stops propagation for control actions

🎨 Styling Flexibility

  • Shadow DOM parts: dialog, ribbon, and backdrop parts for external styling
  • Semantic selectors: Uses dialog and .ribbon for clean CSS targeting
  • Complete examples: JSDoc includes full styling examples with ::part() selectors

🔧 Usage

<!-- Basic popup with close and minimize options -->
<nosto-popup name="special-offer">
  <h2>Special Offer!</h2>
  <p>Get 20% off your order today</p>
  <button n-close>Close Forever</button>
  <button n-ribbon>Minimize to Corner</button>
  <div slot="ribbon">
    <strong>20% OFF</strong>
    <button n-close>×</button>
  </div>
</nosto-popup>

<!-- Custom styling via shadow DOM parts -->
<style>
  nosto-popup::part(dialog) {
    border-radius: 16px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  }
  
  nosto-popup::part(ribbon) {
    background: linear-gradient(45deg, #ff6b6b, #ee5a24);
    border-radius: 8px;
  }
  
  nosto-popup::part(backdrop) {
    backdrop-filter: blur(4px);
  }
</style>

📊 Test Coverage

  • 264 tests pass (including 21 comprehensive Popup tests)
  • 97.9% statement coverage for Popup component
  • 100% lint and type checking compliance
  • Zero unhandled errors in test suite
  • Global test setup: Consistent localStorage cleanup across all tests

🏗️ Architecture

  • Clean separation: Styles extracted to styles.ts with shadow DOM parts and semantic selectors
  • Functional approach: All utility functions moved below class definition following codebase conventions
  • Streamlined state management: Single getPopupState() function handles both segment checking and localStorage
  • Unified state updates: setPopupState() manages all state transitions ("open" | "ribbon" | "closed")
  • Performance optimized: Shadow DOM initialized with correct mode from the start
  • Required validation: Name attribute is mandatory and validated at connection (no conditional wrapping)
  • Simplified lifecycle: Optimized component lifecycle with consolidated logic
  • Error transparency: Clean error propagation from segment API
  • Enhanced UX: Robust click handling with clean variable names (close, toRibbon)
  • Global test setup: Component registration and cleanup handled globally for consistency
  • DRY principles: Reusable getKey() function for localStorage key generation
  • Clean code: Semantic variable names, CSS selectors, and complete documentation examples

🚨 Breaking Changes

  • Name attribute is now required: All popups must have a name attribute for proper state management
  • LocalStorage values changed: Changed from "true" to "closed" for better semantic meaning

🎨 Shadow DOM Parts & Semantic Selectors

  • dialog: The modal dialog container (uses semantic <dialog> element)
  • ribbon: The ribbon container in bottom-right corner (uses .ribbon class selector)
  • backdrop: The dialog backdrop (uses dialog::backdrop selector)

🚀 Performance Optimizations

  • Efficient DOM updates: updateShadowContent() only toggles visibility instead of re-rendering entire shadow DOM
  • Smart initialization: Shadow DOM content created with correct initial mode
  • Consolidated functions: Reduced from 4 state management functions to 2
  • Optimized checking: Segment validation happens first, avoiding unnecessary localStorage operations
  • Single source of truth: All visibility logic centralized in getPopupState()
  • DRY localStorage: Reusable key generation reduces code duplication
  • Enhanced click detection: Dual-strategy approach (direct + ancestor) for reliable event handling
  • Semantic selectors: Uses dialog and .ribbon for efficient DOM queries

🧪 Testing Excellence

  • Comprehensive coverage: 21 dedicated tests covering all functionality
  • Edge case handling: Proper error handling for missing required attributes
  • Clean test suite: Fixed unhandled errors for reliable CI/CD pipeline
  • Performance testing: Validates efficient DOM manipulation and state management
  • Integration testing: Tests work seamlessly with global component registration
  • Click testing: Validates both direct and nested element click handling
  • Global cleanup: localStorage clearing moved to global setup for consistency

📚 Storybook Examples

  • Complete functionality: All examples now include required name attribute
  • Interactive demos: Both Default and WithRibbon stories demonstrate full functionality
  • Ribbon mode: WithRibbon story includes n-ribbon button for mode switching demonstration
  • Best practices: Examples follow established patterns and demonstrate proper usage

📸 Visual Preview

Popup Component in Storybook

The final implementation provides a robust, well-documented popup component with dual-mode operation, performance-optimized DOM manipulation, enhanced click handling, semantic CSS architecture, comprehensive test coverage, complete Storybook examples, proper styling examples in JSDoc, and flexible styling options while following all established patterns.

Fixes #435

Original prompt

This section details on the original issue you should resolve

<issue_title>Popup - new custom element</issue_title>
<issue_description>Create a new custom element called Popup (tagname: nosto-popup)

Attributes

  • name - optional name used in analytics
  • segment - optional Nosto segment that acts as a precondition for activation

connectedCallback behaviour

  • create a shadow root with two slots (default and ribbon)
  • default slot is wrapped in a dialog
  • ribbon is wrapped in bottom right positioned ribbon

State handling

  • Popup custom element should listen to click events from the shadow root
  • it the target element has n-close attribute:
    • close the popup permanently and store closed state into local storage as nosto:web-components:popup: = closed
  • if the target element has n-ribbon attribute:
  • close the popup dialog, show ribbon and store info local storage as nosto:web-components:popup: = ribbon </issue_description>

Comments on the Issue (you are @copilot in this section)

Fixes #435

💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@Copilot Copilot AI changed the title [WIP] Popup - new custom element feat(Popup): add new custom element with dialog and ribbon slots Oct 4, 2025
@Copilot Copilot AI requested a review from timowestnosto October 4, 2025 17:53
Copilot finished work on behalf of timowestnosto October 4, 2025 17:53
Copy link
Contributor

@timowestnosto timowestnosto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot fix these

Copy link
Contributor Author

Copilot AI commented Oct 4, 2025

@copilot fix these

All feedback has been addressed in commit [1fbfa87]:

Storybook Changes:

  • Removed Features section from documentation
  • Removed Named and WithSegment stories (only Default and WithRibbon remain)
  • Cleaned up JSDoc example by removing unnecessary button line

Architecture Refactoring:

  • Extracted styles to ./styles.ts file for better maintainability
  • Moved all private methods to top-level functions following codebase patterns:
    • renderShadowContent(element)
    • closePopup(element)
    • isPopupClosed(name)
    • setPopupClosed(name)
    • checkSegment(segment)
  • Inlined event listener setup/teardown in lifecycle methods

The component maintains 100% functionality while following established architectural patterns. All tests pass with 98.5% coverage.

Screenshot

@Copilot Copilot AI requested a review from timowestnosto October 4, 2025 18:03
Copilot finished work on behalf of timowestnosto October 4, 2025 18:03
Copy link
Contributor

@timowestnosto timowestnosto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot fix these

Copy link
Contributor

@timowestnosto timowestnosto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot fix these

@timowestnosto timowestnosto marked this pull request as ready for review October 4, 2025 18:40
@Copilot Copilot AI review requested due to automatic review settings October 4, 2025 18:40
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a new nosto-popup custom element that displays popup content with dialog and ribbon slots, supporting segment-based activation and persistent closure state management.

  • Implements a new Popup web component following established architectural patterns
  • Adds comprehensive test coverage with 18 tests covering all functionality
  • Extracts styles to separate module and refactors methods to top-level functions

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/utils/jsx.ts Adds Popup component to JSX type definitions for testing
test/components/Popup/Popup.spec.tsx Comprehensive test suite covering all component functionality
src/components/Popup/styles.ts Extracted styles for the popup component
src/components/Popup/Popup.ts Main component implementation with dialog/ribbon slots and state management
src/components/Popup/Popup.stories.tsx Storybook stories demonstrating component usage

Comment on lines 60 to 62
* <span>Limited time!</span>

* </div>
Copy link
Preview

Copilot AI Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc example is incomplete. The ribbon slot example is missing the closing element and n-close button.

Copilot uses AI. Check for mistakes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot fix this

Copy link
Contributor Author

Copilot AI commented Oct 4, 2025

@copilot fix these

All feedback addressed in commit [6a53239]:

Code Organization:

  • Moved all utility functions below the custom element class definition
  • Combined popup closed and segment checks into single isPopupShown() function
  • Maintained try/catch for graceful error handling (required for test compatibility)

Architecture Improvements:

  • Cleaner connectedCallback with single visibility check
  • Better function organization following codebase conventions
  • Consolidated visibility logic in dedicated function

All tests pass with maintained 98.6% coverage and full functionality preserved.

Screenshot

@Copilot Copilot AI requested a review from timowestnosto October 4, 2025 18:42
Copilot finished work on behalf of timowestnosto October 4, 2025 18:42
Copy link
Contributor Author

Copilot AI commented Oct 4, 2025

@copilot fix these

All feedback addressed in commit [72ca515]:

Code Cleanup:

  • Removed empty line between methods for cleaner formatting
  • Added comprehensive attribute documentation with @property annotations following established patterns

Enhanced Functionality:

  • Improved click handling to support ancestor elements with n-close attribute
  • Added test coverage for the new ancestor click functionality

Quality Maintained:

  • All 262 tests pass including new ancestor click test
  • 98.6% code coverage preserved
  • Enhanced user experience with more flexible click targets

Screenshot

Copy link
Contributor

@timowestnosto timowestnosto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot fix these

Copy link
Contributor Author

Copilot AI commented Oct 5, 2025

@timowestnosto Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub.

You can ask me to try again later by mentioning me in a new comment.

If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: 5a10f786-2827-4c70-841b-4eb1cb4df25c

Sorry for the inconvenience!

Copy link
Contributor Author

Copilot AI commented Oct 5, 2025

@timowestnosto Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub.

You can ask me to try again later by mentioning me in a new comment.

If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: eec0b0c9-7739-4c98-93b9-221fe065595e

Sorry for the inconvenience!

Copy link
Contributor Author

Copilot AI commented Oct 5, 2025

@timowestnosto Unfortunately I hit an unexpected error while processing your comment. I've automatically reported this to GitHub.

You can ask me to try again later by mentioning me in a new comment.

If you want to contact GitHub about this error, please mention the following identifier so they can better serve you: c53e373c-50d4-4b60-b657-679b9b7d1d08

Sorry for the inconvenience!

@timowestnosto timowestnosto force-pushed the copilot/fix-c98b8d45-0181-47c7-b7f2-a21104d2580c branch 6 times, most recently from bc35ab7 to e5b2606 Compare October 5, 2025 11:38
@timowestnosto timowestnosto force-pushed the copilot/fix-c98b8d45-0181-47c7-b7f2-a21104d2580c branch from e5b2606 to b869c7a Compare October 5, 2025 11:44
@timowestnosto
Copy link
Contributor

@copilot add Popup to readme file under "Store level templating"

@timowestnosto
Copy link
Contributor

@copilot add Popup to readme file under "Store level templating"

element.style.display = "none"
}

async function getPopupState(name: string, segment?: string): Promise<"open" | "ribbon" | "closed"> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PopupState type?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added

</dialog>
<div class="ribbon ${mode === "open" ? "hidden" : ""}" part="ribbon">
<slot name="ribbon">Open</slot>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibilities for avoiding string templates?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using the html function here doesn't provide much additional value as none of the injections should be escaped

@@ -0,0 +1,40 @@
export const popupStyles = `
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type of css stying is hard to maintain. Should we instead use a regular css and import it in the popup.ts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I am planning to improve CSS resource handling in a follow up PR

@manikandan-ravikumar
Copy link
Contributor

Do we plan to promote this as an alternative to our Nosto's native popups? If so, do we have plans to handle triggers and rules?

@timowestnosto
Copy link
Contributor

timowestnosto commented Oct 7, 2025

Do we plan to promote this as an alternative to our Nosto's native popups? If so, do we have plans to handle triggers and rules?

Yes, this PR provides the basic frame for implementing Popups via Nosto custom elements. Extra functionality can be agreed on later.

@timowestnosto timowestnosto merged commit c809919 into main Oct 7, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Popup - new custom element
3 participants