Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
.Button {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
height: 2rem;
padding: 0 0.75rem;
margin: 0;
border: 1px solid oklch(14.5% 0 0deg);
background-color: white;
font-family: inherit;
font-size: 0.875rem;
font-weight: 400;
line-height: 1;
white-space: nowrap;
color: oklch(14.5% 0 0deg);
-webkit-user-select: none;
user-select: none;

@media (prefers-color-scheme: dark) {
border: 1px solid white;
background-color: oklch(14.5% 0 0deg);
color: white;
}

@media (hover: hover) {
&:hover:not([data-disabled]) {
background-color: oklch(97% 0 0deg);

@media (prefers-color-scheme: dark) {
background-color: oklch(26.9% 0 0deg);
}
}
}

&:active:not([data-disabled]) {
background-color: oklch(92.2% 0 0deg);

@media (prefers-color-scheme: dark) {
background-color: oklch(37.1% 0 0deg);
}
}

&[data-pressed] {
background-color: oklch(97% 0 0deg);

@media (prefers-color-scheme: dark) {
background-color: oklch(26.9% 0 0deg);
}
}

&:focus-visible {
outline: 2px solid oklch(14.5% 0 0deg);
outline-offset: -1px;

@media (prefers-color-scheme: dark) {
outline-color: white;
}
}
}

.Positioner {
outline: 0;
}

.MenuPopup {
box-sizing: border-box;
position: relative;
outline: 0;
padding-block: 0.25rem;
border: 1px solid oklch(14.5% 0 0deg);
border-radius: 0;
background-color: white;
color: oklch(14.5% 0 0deg);
box-shadow: 0.25rem 0.25rem 0 rgb(0 0 0 / 12%);
transform-origin: var(--transform-origin);
transition:
transform 100ms ease-out,
opacity 100ms ease-out;

@media (prefers-color-scheme: dark) {
border: 1px solid white;
background-color: oklch(14.5% 0 0deg);
color: white;
box-shadow: none;
}

&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transform: scale(0.98);
}
}

.MenuItem {
outline: 0;
cursor: default;
-webkit-user-select: none;
user-select: none;
padding-block: 0.5rem;
padding-left: 1rem;
padding-right: 2rem;
display: flex;
font-size: 0.875rem;
line-height: 1rem;

&[data-highlighted] {
z-index: 0;
position: relative;
color: white;

@media (prefers-color-scheme: dark) {
color: oklch(14.5% 0 0deg);
}
}

&[data-highlighted]::before {
content: '';
z-index: -1;
position: absolute;
inset-block: 0;
inset-inline: 0.25rem;
background-color: oklch(14.5% 0 0deg);

@media (prefers-color-scheme: dark) {
background-color: white;
}
}
}

.Separator {
margin: 0.25rem;
height: 1px;
background-color: oklch(14.5% 0 0deg);

@media (prefers-color-scheme: dark) {
background-color: white;
}
}

.Backdrop {
position: fixed;
min-height: 100dvh;
inset: 0;
background-color: black;
opacity: 0.2;
transition: opacity 150ms;

/* iOS 26+: Ensure the backdrop covers the entire visible viewport. */
@supports (-webkit-touch-callout: none) {
position: absolute;
}

@media (prefers-color-scheme: dark) {
opacity: 0.5;
}

&[data-starting-style],
&[data-ending-style] {
opacity: 0;
}
}

.DialogPopup {
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 1rem;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 24rem;
max-width: calc(100vw - 3rem);
margin-top: -2rem;
padding: 1rem;
border: 1px solid oklch(14.5% 0 0deg);
background-color: white;
color: oklch(14.5% 0 0deg);
box-shadow: 0.25rem 0.25rem 0 rgb(0 0 0 / 12%);
transition:
transform 100ms ease-out,
opacity 100ms ease-out;

@media (prefers-color-scheme: dark) {
border: 1px solid white;
background-color: oklch(14.5% 0 0deg);
color: white;
box-shadow: none;
}

&[data-starting-style],
&[data-ending-style] {
opacity: 0;
transform: translate(-50%, -50%) scale(0.98);
}
}

.Intro {
display: flex;
flex-direction: column;
gap: 0.25rem;
}

.Title {
margin: 0;
font-size: 1rem;
line-height: 1.5rem;
font-weight: 700;
}

.Description {
margin: 0;
font-size: 0.875rem;
line-height: 1.25rem;
color: oklch(43.9% 0 0deg);

@media (prefers-color-scheme: dark) {
color: oklch(70.8% 0 0deg);
}
}

.Actions {
display: flex;
justify-content: end;
gap: 0.75rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
'use client';
import * as React from 'react';
import { Dialog } from '@base-ui/react/dialog';
import { Menu } from '@base-ui/react/menu';
import styles from './index.module.css';

export default function ExampleDialog() {
const [dialogOpen, setDialogOpen] = React.useState(false);

return (
<React.Fragment>
<Menu.Root>
<Menu.Trigger className={styles.Button}>
Playlist <CaretDownIcon />
</Menu.Trigger>
<Menu.Portal>
<Menu.Positioner className={styles.Positioner} sideOffset={8} align="start">
<Menu.Popup className={styles.MenuPopup}>
<Menu.Item className={styles.MenuItem}>Play</Menu.Item>
<Menu.Item className={styles.MenuItem}>Share</Menu.Item>
<Menu.Separator className={styles.Separator} />
{/* Open the dialog when the menu item is clicked */}
<Menu.Item className={styles.MenuItem} onClick={() => setDialogOpen(true)}>
Details…
</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>

{/* Control the dialog state */}
<Dialog.Root open={dialogOpen} onOpenChange={setDialogOpen}>
<Dialog.Portal>
<Dialog.Backdrop className={styles.Backdrop} />
<Dialog.Popup className={styles.DialogPopup}>
<div className={styles.Intro}>
<Dialog.Title className={styles.Title}>Playlist details</Dialog.Title>
<Dialog.Description className={styles.Description}>
This playlist contains 24 songs and was last updated today.
</Dialog.Description>
</div>
<div className={styles.Actions}>
<Dialog.Close className={styles.Button}>Close</Dialog.Close>
</div>
</Dialog.Popup>
</Dialog.Portal>
</Dialog.Root>
</React.Fragment>
);
}

function CaretDownIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="currentColor"
{...props}
style={{ display: 'block', ...props.style }}
>
<path d="M12 6H4l4 4.5z" />
</svg>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createDemoWithVariants } from 'docs/src/utils/createDemo';
import CssModules from './css-modules';
import Tailwind from './tailwind';

export const DemoDialogOpenFromMenu = createDemoWithVariants(import.meta.url, {
CssModules,
Tailwind,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
'use client';
import * as React from 'react';
import { Dialog } from '@base-ui/react/dialog';
import { Menu } from '@base-ui/react/menu';

export default function ExampleDialog() {
const [dialogOpen, setDialogOpen] = React.useState(false);

return (
<React.Fragment>
<Menu.Root>
<Menu.Trigger className={buttonClass}>
Playlist <CaretDownIcon />
</Menu.Trigger>
<Menu.Portal>
<Menu.Positioner className="outline-hidden" sideOffset={8} align="start">
<Menu.Popup className="relative origin-[var(--transform-origin)] border border-neutral-950 bg-white py-1 text-neutral-950 shadow-[0.25rem_0.25rem_0] shadow-black/12 outline-hidden transition-[scale,opacity] duration-100 ease-out data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-starting-style:scale-[0.98] data-starting-style:opacity-0 dark:border-white dark:bg-neutral-950 dark:text-white dark:shadow-none">
<Menu.Item className={itemClass}>Play</Menu.Item>
<Menu.Item className={itemClass}>Share</Menu.Item>
<Menu.Separator className="mx-1 my-1 h-px bg-neutral-950 dark:bg-white" />
{/* Open the dialog when the menu item is clicked */}
<Menu.Item className={itemClass} onClick={() => setDialogOpen(true)}>
Details…
</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>

{/* Control the dialog state */}
<Dialog.Root open={dialogOpen} onOpenChange={setDialogOpen}>
<Dialog.Portal>
<Dialog.Backdrop className="fixed inset-0 min-h-dvh bg-black opacity-20 transition-opacity duration-150 data-ending-style:opacity-0 data-starting-style:opacity-0 dark:opacity-50 supports-[-webkit-touch-callout:none]:absolute" />
<Dialog.Popup className="fixed top-1/2 left-1/2 -mt-8 flex w-96 max-w-[calc(100vw-3rem)] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 bg-white dark:bg-neutral-950 p-4 text-neutral-950 dark:text-white border border-neutral-950 dark:border-white shadow-[0.25rem_0.25rem_0] shadow-black/12 dark:shadow-none transition-[scale,opacity] duration-100 ease-out data-ending-style:scale-[0.98] data-ending-style:opacity-0 data-starting-style:scale-[0.98] data-starting-style:opacity-0">
<div className="flex flex-col gap-1">
<Dialog.Title className="text-base font-bold">Playlist details</Dialog.Title>
<Dialog.Description className="text-sm text-neutral-600 dark:text-neutral-400">
This playlist contains 24 songs and was last updated today.
</Dialog.Description>
</div>
<div className="flex justify-end gap-3">
<Dialog.Close className={buttonClass}>Close</Dialog.Close>
</div>
</Dialog.Popup>
</Dialog.Portal>
</Dialog.Root>
</React.Fragment>
);
}

const buttonClass =
'flex h-8 items-center justify-center gap-1.5 border border-neutral-950 bg-white px-3 text-sm leading-none whitespace-nowrap font-normal text-neutral-950 select-none hover:not-data-disabled:bg-neutral-100 active:not-data-disabled:bg-neutral-200 data-pressed:bg-neutral-100 dark:border-white dark:bg-neutral-950 dark:text-white dark:hover:not-data-disabled:bg-neutral-800 dark:active:not-data-disabled:bg-neutral-700 dark:data-pressed:bg-neutral-800 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-neutral-950 dark:focus-visible:outline-white';

const itemClass =
"flex cursor-default py-2 pr-8 pl-4 text-sm leading-4 outline-hidden select-none data-highlighted:relative data-highlighted:z-0 data-highlighted:text-white data-highlighted:before:absolute data-highlighted:before:inset-x-1 data-highlighted:before:inset-y-0 data-highlighted:before:z-[-1] data-highlighted:before:bg-neutral-950 data-highlighted:before:content-[''] dark:data-highlighted:text-neutral-950 dark:data-highlighted:before:bg-white";

function CaretDownIcon(props: React.ComponentProps<'svg'>) {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="currentColor"
{...props}
style={{ display: 'block', ...props.style }}
>
<path d="M12 6H4l4 4.5z" />
</svg>
);
}
Loading
Loading