Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions libs/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1081,11 +1081,26 @@ export namespace Components {
* If provided, renders the dropdown-item as an anchor (`<a>`) element instead of a button.
*/
"href": string | undefined;
/**
* HTTP method to use for link navigation. For non-GET methods (post, put, patch, delete), the component will handle form submission internally. Also adds data-method and data-turbo-method attributes to the internal anchor for framework integration. Only applies when href is provided.
* @defaultValue undefined (link navigates normally)
*/
"httpMethod"?: 'get' | 'post' | 'put' | 'patch' | 'delete';
/**
* Specifies where to open the linked document when href is provided. Takes precedence over the `external` prop if both are set. Only applies when href is set.
* @defaultValue undefined
*/
"target"?: '_blank' | '_self' | '_parent' | '_top';
/**
* Sets data-turbo attribute on the internal anchor. Useful for enabling or disabling framework-specific navigation handling. Only applies when href is provided.
* @defaultValue undefined (no data-turbo attribute)
*/
"turbo"?: boolean;
/**
* Sets data-turbo-frame attribute on the internal anchor. Useful for framework integration with frame-based navigation. Only applies when href is provided.
* @defaultValue undefined (no data-turbo-frame attribute)
*/
"turboFrame"?: string;
}
interface PdsDropdownMenuSeparator {
/**
Expand Down Expand Up @@ -2502,6 +2517,7 @@ declare global {
};
interface HTMLPdsDropdownMenuItemElementEventMap {
"pdsClick": {itemIndex: number, item: HTMLPdsDropdownMenuItemElement, content: string};
"pdsBeforeSubmit": { href: string; method: string };
}
interface HTMLPdsDropdownMenuItemElement extends Components.PdsDropdownMenuItem, HTMLStencilElement {
addEventListener<K extends keyof HTMLPdsDropdownMenuItemElementEventMap>(type: K, listener: (this: HTMLPdsDropdownMenuItemElement, ev: PdsDropdownMenuItemCustomEvent<HTMLPdsDropdownMenuItemElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
Expand Down Expand Up @@ -4064,6 +4080,15 @@ declare namespace LocalJSX {
* If provided, renders the dropdown-item as an anchor (`<a>`) element instead of a button.
*/
"href"?: string | undefined;
/**
* HTTP method to use for link navigation. For non-GET methods (post, put, patch, delete), the component will handle form submission internally. Also adds data-method and data-turbo-method attributes to the internal anchor for framework integration. Only applies when href is provided.
* @defaultValue undefined (link navigates normally)
*/
"httpMethod"?: 'get' | 'post' | 'put' | 'patch' | 'delete';
/**
* Emitted before form submission for non-GET http methods. Call event.preventDefault() to cancel the submission and handle it yourself. Useful for custom confirmation dialogs or app-specific handling.
*/
"onPdsBeforeSubmit"?: (event: PdsDropdownMenuItemCustomEvent<{ href: string; method: string }>) => void;
/**
* Emitted when the dropdown-item is clicked.
*/
Expand All @@ -4073,6 +4098,16 @@ declare namespace LocalJSX {
* @defaultValue undefined
*/
"target"?: '_blank' | '_self' | '_parent' | '_top';
/**
* Sets data-turbo attribute on the internal anchor. Useful for enabling or disabling framework-specific navigation handling. Only applies when href is provided.
* @defaultValue undefined (no data-turbo attribute)
*/
"turbo"?: boolean;
/**
* Sets data-turbo-frame attribute on the internal anchor. Useful for framework integration with frame-based navigation. Only applies when href is provided.
* @defaultValue undefined (no data-turbo-frame attribute)
*/
"turboFrame"?: string;
}
interface PdsDropdownMenuSeparator {
/**
Expand Down
114 changes: 114 additions & 0 deletions libs/core/src/components/pds-dropdown-menu/docs/pds-dropdown-menu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,120 @@ Menu items can open links in a new tab using the `external` boolean prop. This d

>**Note:** You can also use the `target` prop for more control (e.g., `target="_blank"`, `target="_parent"`). The `target` prop takes precedence over `external` if both are set.

### With HTTP Methods

Menu items support non-GET HTTP methods (`post`, `put`, `patch`, `delete`). When `http-method` is specified, the component handles form submission internally, which is useful for actions that modify data on the server.

<DocCanvas
mdxSource={{
react: `
<PdsDropdownMenu>
<PdsButton slot="trigger">Actions</PdsButton>
<PdsDropdownMenuItem href="/items/123">View</PdsDropdownMenuItem>
<PdsDropdownMenuItem href="/items/123/duplicate" httpMethod="post">
<PdsIcon name="copy" /> Duplicate
</PdsDropdownMenuItem>
<PdsDropdownMenuSeparator />
<PdsDropdownMenuItem href="/items/123" httpMethod="delete" destructive>
<PdsIcon name="trash" /> Delete
</PdsDropdownMenuItem>
</PdsDropdownMenu>
`,
webComponent:`
<pds-dropdown-menu>
<pds-button slot="trigger">Actions</pds-button>
<pds-dropdown-menu-item href="/items/123">View</pds-dropdown-menu-item>
<pds-dropdown-menu-item href="/items/123/duplicate" http-method="post">
<pds-icon name="copy"></pds-icon> Duplicate
</pds-dropdown-menu-item>
<pds-dropdown-menu-separator />
<pds-dropdown-menu-item href="/items/123" http-method="delete" destructive>
<pds-icon name="trash"></pds-icon> Delete
</pds-dropdown-menu-item>
</pds-dropdown-menu>
`
}}
>
<div style={{ height: '250px', width: '100%', textAlign: 'center' }}>
<pds-dropdown-menu>
<pds-button slot="trigger">Actions</pds-button>
<pds-dropdown-menu-item href="/items/123">View</pds-dropdown-menu-item>
<pds-dropdown-menu-item href="/items/123/duplicate" http-method="post"><pds-icon name="copy"></pds-icon> Duplicate</pds-dropdown-menu-item>
<pds-dropdown-menu-separator />
<pds-dropdown-menu-item href="/items/123" http-method="delete" destructive><pds-icon name="trash"></pds-icon> Delete</pds-dropdown-menu-item>
</pds-dropdown-menu>
</div>
</DocCanvas>

>**How it works:** For non-GET methods, clicking the menu item creates a hidden form with the proper `action`, adds the CSRF token (if available via `meta[name="csrf-token"]`), adds a `_method` field for method override, and submits the form. The internal anchor also receives `data-method` and `data-turbo-method` attributes.

### With Data Attributes

Menu items can add common data attributes to the internal anchor element using the `turbo-frame` and `turbo` props. These are useful for integration with JavaScript frameworks that rely on data attributes for navigation behavior.

<DocCanvas
mdxSource={{
react: `
<PdsDropdownMenu>
<PdsButton slot="trigger">Navigation</PdsButton>
<PdsDropdownMenuItem href="/items/123" turboFrame="content">
Target Frame
</PdsDropdownMenuItem>
<PdsDropdownMenuItem href="/items/123" turboFrame="_top">
Full Page Navigation
</PdsDropdownMenuItem>
<PdsDropdownMenuItem href="/items/123" turbo={false}>
Disable Framework Handling
</PdsDropdownMenuItem>
</PdsDropdownMenu>
`,
webComponent:`
<pds-dropdown-menu>
<pds-button slot="trigger">Navigation</pds-button>
<pds-dropdown-menu-item href="/items/123" turbo-frame="content">
Target Frame
</pds-dropdown-menu-item>
<pds-dropdown-menu-item href="/items/123" turbo-frame="_top">
Full Page Navigation
</pds-dropdown-menu-item>
<pds-dropdown-menu-item href="/items/123" turbo="false">
Disable Framework Handling
</pds-dropdown-menu-item>
</pds-dropdown-menu>
`
}}
>
<div style={{ height: '200px', width: '100%', textAlign: 'center' }}>
<pds-dropdown-menu>
<pds-button slot="trigger">Navigation</pds-button>
<pds-dropdown-menu-item href="/items/123" turbo-frame="content">Target Frame</pds-dropdown-menu-item>
<pds-dropdown-menu-item href="/items/123" turbo-frame="_top">Full Page Navigation</pds-dropdown-menu-item>
<pds-dropdown-menu-item href="/items/123" turbo="false">Disable Framework Handling</pds-dropdown-menu-item>
</pds-dropdown-menu>
</div>
</DocCanvas>

### Intercepting Form Submission

The `pdsBeforeSubmit` event is emitted before form submission for non-GET methods. You can cancel this event to handle the submission yourself (e.g., to show a confirmation dialog).

```javascript
// Example: Intercept delete to show confirmation
menuItem.addEventListener('pdsBeforeSubmit', (event) => {
event.preventDefault(); // Cancel automatic form submission

if (confirm('Are you sure you want to delete this item?')) {
// Manually submit if confirmed
const form = document.createElement('form');
form.method = 'POST';
form.action = event.detail.href;
// Add CSRF token and _method field...
document.body.appendChild(form);
form.submit();
}
});
```

### With Disabled Items

Menu items can be disabled using the disabled attribute.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
margin: calc(var(--pine-border-width) + 2px);
padding: var(--pine-dimension-xs);
text-align: start; /* Ensure text aligns properly */
text-decoration: none; /* Prevent underline on anchor elements */
width: 100%; /* Ensure full width */

&:hover {
Expand All @@ -44,6 +45,10 @@
outline-offset: var(--pine-border-width);
}

/* External icon spacing */
pds-icon {
margin-inline-start: var(--pine-dimension-2xs);
}
}

:host(.destructive) {
Expand All @@ -66,17 +71,3 @@
}
}

/* Remove outline on contained links using the custom property */
pds-link::part(link):focus,
pds-link::part(link):focus-visible {
box-shadow: none;
outline: none;
}

pds-link::part(link) {
display: block;
margin: calc(var(--pine-dimension-xs) * -1);
padding: var(--pine-dimension-xs);
text-decoration: none;
width: calc(100% + var(--pine-dimension-xs) * 2);
}
Loading