Skip to content
Merged
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
66 changes: 66 additions & 0 deletions packages/@react-spectrum/s2/chromatic/Popover.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

import {AccountMenu, AutocompletePopover, CustomTrigger, HelpCenter, MenuTrigger} from '../stories/Popover.stories';
import type {Meta, StoryObj} from '@storybook/react';
import {Popover} from '../src';
import {userEvent} from '@storybook/test';

const meta: Meta<typeof Popover> = {
component: Popover,
parameters: {
chromaticProvider: {colorSchemes: ['light'], backgrounds: ['base'], locales: ['en-US'], disableAnimations: true},
chromatic: {ignoreSelectors: ['[role="progressbar"]']}
},
tags: ['autodocs'],
title: 'S2 Chromatic/Popover'
};

export default meta;
type Story = StoryObj<typeof Popover>;

export const Default: Story = {
...HelpCenter,
play: async () => {
await userEvent.tab();
await userEvent.keyboard('{Enter}');
}
};

export const AccountMenuExample: Story = {
...AccountMenu,
name: 'Account Menu',
play: Default.play
};

export const Autocomplete: Story = {
...AutocompletePopover,
name: 'Autocomplete Popover',
play: Default.play
};

export const Custom: Story = {
...CustomTrigger,
name: 'Custom Trigger',
play: Default.play
};

export const MenuTriggerExample: Story = {
...MenuTrigger,
name: 'MenuTrigger',
play: async () => {
await userEvent.tab();
await userEvent.keyboard('{Enter}');
await userEvent.tab();
await userEvent.keyboard('{Enter}');
}
};
12 changes: 7 additions & 5 deletions packages/@react-spectrum/s2/src/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -581,11 +581,13 @@ function MenuTrigger(props: MenuTriggerProps): ReactNode {
shouldFlip: props.shouldFlip
}}>
<PopoverContext.Provider value={{hideArrow: true, offset: 8, crossOffset: 0, placement, shouldFlip}}>
<AriaMenuTrigger {...props}>
<PressResponder onPressStart={onPressStart} isPressed={isPressed}>
{props.children}
</PressResponder>
</AriaMenuTrigger>
<InPopoverContext.Provider value={false}>
<AriaMenuTrigger {...props}>
<PressResponder onPressStart={onPressStart} isPressed={isPressed}>
{props.children}
</PressResponder>
</AriaMenuTrigger>
</InPopoverContext.Provider>
</PopoverContext.Provider>
</InternalMenuTriggerContext.Provider>
);
Expand Down
40 changes: 39 additions & 1 deletion packages/@react-spectrum/s2/stories/Popover.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,23 @@
* governing permissions and limitations under the License.
*/

import {ActionButton, Avatar, Button, Card, CardPreview, Content, DialogTrigger, Divider, Form, Image, Menu, MenuItem, MenuSection, Popover, SearchField, SubmenuTrigger, Switch, Tab, TabList, TabPanel, Tabs, Text, TextField} from '../src';
import {ActionButton, ActionMenu, Avatar, Button, Card, CardPreview, Content, DialogTrigger, Divider, Form, Image, Menu, MenuItem, MenuSection, Popover, SearchField, SubmenuTrigger, Switch, Tab, TabList, TabPanel, Tabs, Text, TextField} from '../src';
import Cloud from '../s2wf-icons/S2_Icon_Cloud_20_N.svg';
import Comment from '../s2wf-icons/S2_Icon_Comment_20_N.svg';
import CommentText from '../s2wf-icons/S2_Icon_CommentText_20_N.svg';
import Copy from '../s2wf-icons/S2_Icon_Copy_20_N.svg';
import Education from '../s2wf-icons/S2_Icon_Education_20_N.svg';
import File from '../s2wf-icons/S2_Icon_File_20_N.svg';
import Help from '../s2wf-icons/S2_Icon_HelpCircle_20_N.svg';
import Lightbulb from '../s2wf-icons/S2_Icon_Lightbulb_20_N.svg';
import type {Meta, StoryObj} from '@storybook/react';
import Org from '../s2wf-icons/S2_Icon_Buildings_20_N.svg';
import Paste from '../s2wf-icons/S2_Icon_Paste_20_N.svg';
import {Autocomplete as RACAutocomplete, useFilter} from 'react-aria-components';
import {ReactElement, useRef, useState} from 'react';
import Settings from '../s2wf-icons/S2_Icon_Settings_20_N.svg';
import {style} from '../style/spectrum-theme' with {type: 'macro'};
import ThumbUp from '../s2wf-icons/S2_Icon_ThumbUp_20_N.svg';
import User from '../s2wf-icons/S2_Icon_User_20_N.svg';
import Users from '../s2wf-icons/S2_Icon_UserGroup_20_N.svg';

Expand Down Expand Up @@ -227,3 +232,36 @@ const CustomTriggerRender = (): ReactElement => {
export const CustomTrigger: StoryObj<typeof CustomTriggerRender> = {
render: () => <CustomTriggerRender />
};

export const MenuTrigger: Story = {
render: (args) => (
<DialogTrigger {...args}>
<ActionButton aria-label="Account" styles={style({marginX: 'auto'})}>
<Comment />
</ActionButton>
<Popover {...args} hideArrow placement="bottom end" styles={style({width: 400})}>
<div className={style({paddingTop: 4, paddingX: 8, display: 'flex', flexDirection: 'column', gap: 12})}>
<div className={style({display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between'})}>
<div className={style({display: 'flex', gap: 8})}>
<Avatar src="https://i.imgur.com/xIe7Wlb.png" size={56} />
<h2 className={style({font: 'heading'})}>Author Name</h2>
</div>
<ActionMenu>
<MenuItem><Copy /><Text>Copy body text</Text></MenuItem>
<MenuItem><Paste /><Text>Paste</Text></MenuItem>
</ActionMenu>
</div>
<p className={style({font: 'body', margin: 0})}>The experience is smooth and well thought out, though there’s room to refine some details for clarity and efficiency.</p>
<div className={style({display: 'flex', flexDirection: 'row', gap: 12})}>
<ActionButton isQuiet><ThumbUp /><Text>Like</Text></ActionButton>
<ActionButton isQuiet><CommentText /><Text>Reply</Text></ActionButton>
</div>
</div>
</Popover>
</DialogTrigger>
),
argTypes: {
hideArrow: {table: {disable: true}},
placement: {table: {disable: true}}
}
};