Skip to content

Commit

Permalink
feat(popover): add popover component (#326)
Browse files Browse the repository at this point in the history
* fix: add popover component using popover api

* fix: continued work on popover api implementation

* fix: added popovertarget and active props

* fix: added button styles and updated demo

* fix: add keydown and click events

* fix: refactoring popover

* tests: update spec and e2e tests

* tests: update spec tests and docs

* docs: remove broken onclick

* fix: add repositioning on scroll

* fix: remove console log

* docs: update popover docs

* docs: update copy

* fix: update maxwidth type to number and append type

* refactor: rename tooltip placement type to placement type

* style: add eol

* docs: add eol

* fix: add return to closesttarget keydown

* fix: add toggle popover target action and docs

* style: updated font variables

* docs: updated demo code

* chore: resolve linting errors

* fix: remove blank line

* fix: update to allow hide function to work externally

* docs: elaborate the manual section for needed function

* tests: add spec for left placement

* docs: update demo code

* fix: removed affordance of the ability to close the popover from within the overlay

* fix: remove unneeded events and code and update positioning

* style: updated max width

* tests: update tests given code changes

* docs: update prop description

* test: update e2e tests

* docs: update popover docs

* docs: updated white space
  • Loading branch information
QuintonJason authored Dec 12, 2024
1 parent c3f00c3 commit 0adb8d5
Show file tree
Hide file tree
Showing 14 changed files with 731 additions and 3 deletions.
85 changes: 85 additions & 0 deletions libs/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
import { BoxColumnType, BoxShadowSizeType, BoxTShirtSizeType } from "./utils/types";
import { CheckboxChangeEventDetail } from "./components/pds-checkbox/checkbox-interface";
import { PlacementType } from "./utils/types";
import { TextareaChangeEventDetail } from "./components/pds-textarea/textarea-interface";
export { BoxColumnType, BoxShadowSizeType, BoxTShirtSizeType } from "./utils/types";
export { CheckboxChangeEventDetail } from "./components/pds-checkbox/checkbox-interface";
export { PlacementType } from "./utils/types";
export { TextareaChangeEventDetail } from "./components/pds-textarea/textarea-interface";
export namespace Components {
interface PdsAccordion {
Expand Down Expand Up @@ -476,6 +478,35 @@ export namespace Components {
*/
"variant": 'spinner' | 'typing';
}
interface PdsPopover {
/**
* A unique identifier used for the underlying component `id` attribute.
*/
"componentId": string;
/**
* Sets the maximum width of the popover content
* @defaultValue 352
*/
"maxWidth"?: number;
/**
* Determines the preferred position of the popover
* @defaultValue "right"
*/
"placement": PlacementType;
/**
* Determines the action that triggers the popover. For manual popovers, the consumer is responsible for toggling this value.
* @defaultValue "show"
*/
"popoverTargetAction": 'show' | 'toggle' | 'hide';
/**
* Determines the type of popover. Auto popovers can be "light dismissed" by clicking outside of the popover. Manual popovers require the consumer to handle the visibility of the popover.
*/
"popoverType": 'auto' | 'manual';
/**
* Text that appears on the trigger element
*/
"text": string;
}
interface PdsProgress {
/**
* Determines whether or not progress is animated.
Expand Down Expand Up @@ -977,6 +1008,10 @@ export namespace Components {
"showTooltip": () => Promise<void>;
}
}
export interface PdsButtonCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLPdsButtonElement;
}
export interface PdsCheckboxCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLPdsCheckboxElement;
Expand Down Expand Up @@ -1052,7 +1087,18 @@ declare global {
prototype: HTMLPdsBoxElement;
new (): HTMLPdsBoxElement;
};
interface HTMLPdsButtonElementEventMap {
"pdsClick": any;
}
interface HTMLPdsButtonElement extends Components.PdsButton, HTMLStencilElement {
addEventListener<K extends keyof HTMLPdsButtonElementEventMap>(type: K, listener: (this: HTMLPdsButtonElement, ev: PdsButtonCustomEvent<HTMLPdsButtonElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLPdsButtonElementEventMap>(type: K, listener: (this: HTMLPdsButtonElement, ev: PdsButtonCustomEvent<HTMLPdsButtonElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLPdsButtonElement: {
prototype: HTMLPdsButtonElement;
Expand Down Expand Up @@ -1151,6 +1197,12 @@ declare global {
prototype: HTMLPdsLoaderElement;
new (): HTMLPdsLoaderElement;
};
interface HTMLPdsPopoverElement extends Components.PdsPopover, HTMLStencilElement {
}
var HTMLPdsPopoverElement: {
prototype: HTMLPdsPopoverElement;
new (): HTMLPdsPopoverElement;
};
interface HTMLPdsProgressElement extends Components.PdsProgress, HTMLStencilElement {
}
var HTMLPdsProgressElement: {
Expand Down Expand Up @@ -1389,6 +1441,7 @@ declare global {
"pds-input": HTMLPdsInputElement;
"pds-link": HTMLPdsLinkElement;
"pds-loader": HTMLPdsLoaderElement;
"pds-popover": HTMLPdsPopoverElement;
"pds-progress": HTMLPdsProgressElement;
"pds-radio": HTMLPdsRadioElement;
"pds-row": HTMLPdsRowElement;
Expand Down Expand Up @@ -1605,6 +1658,7 @@ declare namespace LocalJSX {
* Provides button with a submittable name
*/
"name"?: string;
"onPdsClick"?: (event: PdsButtonCustomEvent<any>) => void;
/**
* Provides button with a type
* @defaultValue button
Expand Down Expand Up @@ -1892,6 +1946,35 @@ declare namespace LocalJSX {
*/
"variant"?: 'spinner' | 'typing';
}
interface PdsPopover {
/**
* A unique identifier used for the underlying component `id` attribute.
*/
"componentId"?: string;
/**
* Sets the maximum width of the popover content
* @defaultValue 352
*/
"maxWidth"?: number;
/**
* Determines the preferred position of the popover
* @defaultValue "right"
*/
"placement"?: PlacementType;
/**
* Determines the action that triggers the popover. For manual popovers, the consumer is responsible for toggling this value.
* @defaultValue "show"
*/
"popoverTargetAction"?: 'show' | 'toggle' | 'hide';
/**
* Determines the type of popover. Auto popovers can be "light dismissed" by clicking outside of the popover. Manual popovers require the consumer to handle the visibility of the popover.
*/
"popoverType"?: 'auto' | 'manual';
/**
* Text that appears on the trigger element
*/
"text"?: string;
}
interface PdsProgress {
/**
* Determines whether or not progress is animated.
Expand Down Expand Up @@ -2438,6 +2521,7 @@ declare namespace LocalJSX {
"pds-input": PdsInput;
"pds-link": PdsLink;
"pds-loader": PdsLoader;
"pds-popover": PdsPopover;
"pds-progress": PdsProgress;
"pds-radio": PdsRadio;
"pds-row": PdsRow;
Expand Down Expand Up @@ -2475,6 +2559,7 @@ declare module "@stencil/core" {
"pds-input": LocalJSX.PdsInput & JSXBase.HTMLAttributes<HTMLPdsInputElement>;
"pds-link": LocalJSX.PdsLink & JSXBase.HTMLAttributes<HTMLPdsLinkElement>;
"pds-loader": LocalJSX.PdsLoader & JSXBase.HTMLAttributes<HTMLPdsLoaderElement>;
"pds-popover": LocalJSX.PdsPopover & JSXBase.HTMLAttributes<HTMLPdsPopoverElement>;
"pds-progress": LocalJSX.PdsProgress & JSXBase.HTMLAttributes<HTMLPdsProgressElement>;
"pds-radio": LocalJSX.PdsRadio & JSXBase.HTMLAttributes<HTMLPdsRadioElement>;
"pds-row": LocalJSX.PdsRow & JSXBase.HTMLAttributes<HTMLPdsRowElement>;
Expand Down
5 changes: 4 additions & 1 deletion libs/core/src/components/pds-button/pds-button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Element, Host, h, Prop } from '@stencil/core';
import { Component, Element, Event, EventEmitter, Host, h, Prop } from '@stencil/core';
import { hasShadowDom } from '../../utils/utils';

import { caretDown } from '@pine-ds/icons/icons';
Expand Down Expand Up @@ -54,6 +54,8 @@ export class PdsButton {
*/
@Prop() variant: 'primary' | 'secondary' | 'accent' | 'disclosure' | 'destructive' | 'unstyled' = 'primary';

@Event() pdsClick: EventEmitter;

private handleClick = (ev: Event) => {
if (this.type != 'button') {
// If button clicked IS NOT associated with a form
Expand All @@ -71,6 +73,7 @@ export class PdsButton {
}
}
}
this.pdsClick.emit(ev);
}

private classNames() {
Expand Down
7 changes: 7 additions & 0 deletions libs/core/src/components/pds-button/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
| `variant` | `variant` | Sets button variant styles as outlined in Figma documentation | `"accent" \| "destructive" \| "disclosure" \| "primary" \| "secondary" \| "unstyled"` | `'primary'` |


## Events

| Event | Description | Type |
| ---------- | ----------- | ------------------ |
| `pdsClick` | | `CustomEvent<any>` |


## Shadow Parts

| Part | Description |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { html } from 'lit';
import { withActions } from '@storybook/addon-actions/decorator';

import { customArgsWithIconControl } from '../../../stories/_helpers';

export default {
argTypes: customArgsWithIconControl({ component: 'pds-button', property: 'icon' }),
component: 'pds-button',
title: 'components/Button'
decorators: [withActions],
title: 'components/Button',
parameters: {
actions: {
handles: ['pdsClick'],
}
}
}

const BaseTemplate = (args) => html`
Expand Down
157 changes: 157 additions & 0 deletions libs/core/src/components/pds-popover/docs/pds-popover.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import { DocArgsTable, DocCanvas } from '@pine-ds/doc-components';
import { components } from '../../../../dist/docs.json';

# Popover
Popovers display contextual information when users click or initiates a keyboard event on an element. They typically contain more information than tooltips and can include interactive elements.

## Guidelines

#### When to use

- Use popovers for complex information that might overwhelm the main content
- When content needs to include interactive elements
- For non-essential information that requires user interaction to display

#### When not to use

- Don't use popovers for critical information that must be readily available
- Avoid using for simple text-only tooltips
- Don't nest popovers within other popovers

## Properties

<DocArgsTable componentName="pds-popover" docSource={components} />

### Behavior Types

#### Auto (Default)

Closes automatically when clicking outside the popover content. Can also be closed by pressing the `Escape` key.

<DocCanvas client:only
mdxSource={{
react: `
<PdsPopover componentId="auto" text="Auto Popover">
<p>Clicks outside will close this popover</p>
</PdsPopover>
`,
webComponent: `
<pds-popover component-id="auto" text="Auto Popover">
<p>Clicks outside will close this popover</p>
</pds-popover>
`
}}>
<pds-popover component-id="auto" text="Auto Popover">
<p>Clicks outside will close this popover</p>
</pds-popover>
</DocCanvas>

#### Manual

Popovers with the `popoverType="manual"` require explicit user interaction to close. This means they won't automatically disappear when clicking outside or pressing Escape.

The `popoverTargetAction` property determines how the trigger button interacts with the popover:
- `show`: Only shows the popover (default)
- `hide`: Only hides the popover
- `toggle`: Alternates between showing and hiding

For manual popovers, using `popoverTargetAction="toggle"` is recommended as it allows users to both open and close the popover using the trigger button.

Learn more about popover target actions in the [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/HTMLButtonElement/popoverTargetAction#showhide_popover_action_with_a_manual_popover).

<DocCanvas client:only
mdxSource={{
react: `
<PdsPopover componentId="manual" text="Manual Popover" popoverTargetAction="toggle" popoverType="manual">
<p>This popover requires explicit closing</p>
</PdsPopover>
`,
webComponent: `
<pds-popover component-id="manual" text="Manual Popover" popover-target-action="toggle" popover-type="manual">
<p>This popover requires explicit closing</p>
</pds-popover>
`
}}>
<pds-popover component-id="manual" text="Manual Popover" popover-target-action="toggle" popover-type="manual">
<p>This popover requires explicit closing</p>
</pds-popover>
</DocCanvas>

### Target Actions

The `popoverTargetAction` prop determines how the trigger button interacts with the popover.

#### Show (Default)

Shows the popover when triggered. Clicking the popover trigger while open will not close the popover.

<DocCanvas client:only
mdxSource={{
react: `
<PdsPopover componentId="show" text="Show Action">
<p>This popover uses the show action</p>
</PdsPopover>
`,
webComponent: `
<pds-popover component-id="show" text="Show Action">
<p>This popover uses the show action</p>
</pds-popover>
`
}}>
<pds-popover component-id="show" text="Show Action">
<p>This popover uses the show action</p>
</pds-popover>
</DocCanvas>

#### Toggle

Toggles the popover visibility state when triggered. Clicking the popover trigger while open will close the popover.

<DocCanvas client:only
mdxSource={{
react: `
<PdsPopover componentId="toggle" text="Toggle Action" popoverTargetAction="toggle">
<p>This popover uses the toggle action</p>
</PdsPopover>
`,
webComponent: `
<pds-popover component-id="toggle" text="Toggle Action" popover-target-action="toggle">
<p>This popover uses the toggle action</p>
</pds-popover>
`
}}>
<pds-popover component-id="toggle" text="Toggle Action" popover-target-action="toggle">
<p>This popover uses the toggle action</p>
</pds-popover>
</DocCanvas>

### Width/Sizing

The `maxWidth` prop allows adjusting the maximum width of the popover content. Default is 352px.

<DocCanvas client:only
mdxSource={{
react: `
<PdsPopover componentId="wide" text="Wide Popover" maxWidth="520px">
<p>This is a wider popover with more content space available.</p>
</PdsPopover>
`,
webComponent: `
<pds-popover component-id="wide" text="Wide Popover" max-width="520px">
<p>This is a wider popover with more content space available.</p>
</pds-popover>
`
}}>
<pds-popover component-id="wide" text="Wide Popover" max-width="520px">
<p>This is a wider popover with more content space available.</p>
</pds-popover>
</DocCanvas>


## Additional Resources

[Mdn Web Docs: Popover Attribute](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/popover)

[MDN Web Docs: Popover Api](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API)

[MDN: Using the Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API/Using)
Loading

0 comments on commit 0adb8d5

Please sign in to comment.