Skip to content

Commit

Permalink
Cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
sirineJ committed Jan 6, 2025
1 parent 0e03bd0 commit 275cd6a
Show file tree
Hide file tree
Showing 17 changed files with 409 additions and 199 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-coins-sniff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sumup-oss/circuit-ui": minor
---

Refactored the DateInput and Toggletip components to use the Popover component under the hood.
5 changes: 5 additions & 0 deletions .changeset/tasty-radios-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sumup-oss/circuit-ui": minor
---

Updated the popover component to allow any content inside the popover by adding a `children` prop. Under the hood, the component now uses the dialog element instead of the portal component.
7 changes: 4 additions & 3 deletions packages/circuit-ui/components/DateInput/DateInput.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@
}

.divider {
width: calc(100% + 32px) !important;
margin-left: -16px;
width: calc(100% + var(--cui-spacings-tera));
margin-left: calc(-1 * var(--cui-spacings-mega));
}

.buttons {
Expand All @@ -167,7 +167,8 @@

.popover {
max-width: min(410px, 100vw);
border: var(--cui-border-width-kilo) solid var(--cui-border-subtle) !important;
border-color: var(--cui-border-subtle);
box-shadow: none;
}

.popover::after {
Expand Down
14 changes: 13 additions & 1 deletion packages/circuit-ui/components/DateInput/DateInput.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

import { useState } from 'react';
import { userEvent, within } from '@storybook/test';

import { Stack } from '../../../../.storybook/components/index.js';

Expand Down Expand Up @@ -94,13 +95,24 @@ export const Validations = (args: DateInputProps) => (

Validations.args = baseArgs;

const openCalendar = async ({
canvasElement,
}: {
canvasElement: HTMLCanvasElement;
}) => {
const canvas = within(canvasElement);
const referenceEl = canvas.getAllByRole('button');

await userEvent.click(referenceEl[0]);
};

export const Optional = (args: DateInputProps) => <DateInput {...args} />;

Optional.args = {
...baseArgs,
optionalLabel: 'optional',
};

Optional.play = openCalendar;
export const Readonly = (args: DateInputProps) => <DateInput {...args} />;

Readonly.args = {
Expand Down
1 change: 1 addition & 0 deletions packages/circuit-ui/components/DateInput/DateInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ export const DateInput = forwardRef<HTMLInputElement, DateInputProps>(
offset={4}
placement={placement}
closeButtonLabel={closeCalendarButtonLabel}
locale={locale}
component={() => (
<IconButton
ref={calendarButtonRef}
Expand Down
29 changes: 17 additions & 12 deletions packages/circuit-ui/components/Popover/Popover.mdx
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import { Meta, Status, Props, Story } from '../../../../.storybook/components';
import * as Stories from './Popover.stories';
import { Meta, Status, Props, Story } from "../../../../.storybook/components";
import * as Stories from "./Popover.stories";

<Meta of={Stories} />

# Popover

Popover menus are a common pattern to display a list of subsequent action options, when interacting with an actionable component.
The Popover component shows content on top of other page content in an ephemeral manner.

<Story of={Stories.Base} />
<Props />
## When to use it

- The reference element, which triggers Popover, can be primary, secondary, tertiary buttons, an overflow icon, or components with embedded buttons such as the [ImageInput](Forms/ImageInput) component.
- Each Popover action item is represented by an appropriate HTML element (e.g., a button element or an anchor element).
- If needed, the dividing line can be used to separate Popover action items.
- The leading icon is optional.
- The Popover is powered by [Floating UI](https://floating-ui.com/docs/react-dom). You can easily change the position of the Popover relative to the reference element by passing in the `placement` prop. If you want to offset the Popover in the x and y directions, use the `offset` prop.
In most cases, the [Tooltip](Components/Tooltip/Docs) or the [Toggletip](Components/Toggletip/Docs) components should cover your needs. However, if you need to show more interactive content, the Popover component will be the best fit.
Examples:

<Story of={Stories.Offset} />
- To display content in dropdown types, such as profile menus, filters, media previews.. etc.
- To display list of actions that can be taken on a specific item (use the `actions` prop)

## Usage guidelines
## How to use it

- **Do** use clear, concise and actionable labels for Popover items
- **Do** always think about the priority of the action option to be taken and put the option order in logical order
- The component will either accept an `actions` prop, which is an array of objects, each object representing an action item in the Popover, or a `children` prop, which represents the content of the Popover, but not both at the same time.
- The reference element, which triggers the Popover, can be a primary, secondary, tertiary button, an overflow icon, or components with embedded buttons such as the [ImageInput](Forms/ImageInput) component.
- When using the `actions` prop, each Popover action item is represented by an appropriate HTML element (e.g., a button element or an anchor element).
- If needed, the dividing line can be used to separate Popover action items.
- The leading icon is optional.
- On small screens, the popover will show your content inside a [Modal](Components/Modal/Docs) component.
- The Popover is powered by [Floating UI](https://floating-ui.com/docs/react-dom). You can easily change the position of the Popover relative to the reference element by passing in the `placement` prop. If you want to offset the Popover in the x and y directions, use the `offset` prop.
- if the `hasArrow` prop is set to `true`, the Popover will have an arrow pointing to the reference element, and will ignore the provided `offset` prop to position the arrow correctly.
60 changes: 19 additions & 41 deletions packages/circuit-ui/components/Popover/Popover.module.css
Original file line number Diff line number Diff line change
@@ -1,36 +1,3 @@
.item {
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
font-size: var(--cui-body-m-font-size);
line-height: var(--cui-body-m-line-height);
text-align: left;
background: var(--cui-bg-elevated) !important;
}

@media (max-width: 479px) {
.item {
padding: var(--cui-spacings-kilo) 0 !important;
}

.item:first-child {
padding-top: var(--cui-spacings-bit) !important;
}

.item:last-child {
padding-bottom: var(--cui-spacings-bit) !important;
}

.divider {
margin: var(--cui-spacings-byte) 0 !important;
}
}

.icon {
margin-right: var(--cui-spacings-kilo);
}

.trigger {
display: inline-block;
}
Expand All @@ -41,7 +8,6 @@
background-color: var(--cui-bg-elevated);
border: 1px solid var(--cui-border-subtle);
border-radius: var(--cui-border-radius-byte);
box-shadow: 0 3px 8px 0 rgb(0 0 0 / 20%);
}

.content {
Expand All @@ -52,11 +18,12 @@
max-height: var(--popover-max-height);
padding: 0;
margin: 0;
overflow-y: auto;
overflow: visible;
pointer-events: none;
visibility: hidden;
background-color: var(--cui-bg-elevated);
border: none;
box-shadow: 0 3px 8px 0 rgb(0 0 0 / 20%);
opacity: 0;
}

Expand All @@ -67,30 +34,37 @@
}

@media (min-width: 480px) {
.content:not(.with-actions) {
.content {
padding: var(--cui-spacings-mega);
border: 1px solid var(--cui-border-normal);
border: var(--cui-border-width-kilo) solid var(--cui-border-subtle);
border-radius: var(--cui-border-radius-byte);
}
}

.with-actions {
.content.with-actions {
padding: 0;
border: none;
border-radius: var(--cui-border-radius-byte);
}

.divider {
width: calc(100% - var(--cui-spacings-mega) * 2);
margin: var(--cui-spacings-byte) auto;
}

@media (min-width: 480px) {
.divider {
width: calc(100% - var(--cui-spacings-mega) * 2);
}
}

.content.open::after {
position: absolute;
right: 0;
bottom: 1px;
bottom: 0;
left: 0;
display: block;
height: var(--cui-spacings-kilo);
margin: 0 1px;
margin: 0;
content: "";
background: linear-gradient(
color-mix(in sRGB, var(--cui-bg-elevated) 0%, transparent),
Expand All @@ -114,24 +88,28 @@
.content[data-side="top"] .arrow {
top: calc(100% - (var(--tooltip-arrow-size) / 2));
left: calc(50% - (var(--tooltip-arrow-size) / 2));
margin-top: 1px;
transform: rotate(45deg);
}

.content[data-side="right"] .arrow {
right: calc(100% - (var(--tooltip-arrow-size) / 2));
bottom: calc(50% - (var(--tooltip-arrow-size) / 2));
margin-right: 1px;
transform: rotate(135deg);
}

.content[data-side="bottom"] .arrow {
bottom: calc(100% - (var(--tooltip-arrow-size) / 2));
left: calc(50% - (var(--tooltip-arrow-size) / 2));
margin-bottom: 1px;
transform: rotate(225deg);
}

.content[data-side="left"] .arrow {
bottom: calc(50% - (var(--tooltip-arrow-size) / 2));
left: calc(100% - (var(--tooltip-arrow-size) / 2));
margin-left: 1px;
transform: rotate(315deg);
}

Expand Down
64 changes: 61 additions & 3 deletions packages/circuit-ui/components/Popover/Popover.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
*/

import { afterEach, describe, expect, it, vi } from 'vitest';
import { createRef } from 'react';
import { createRef, type FC } from 'react';
import { Add, Delete, type IconProps } from '@sumup-oss/icons';

import { act, axe, render, userEvent, screen } from '../../util/test-utils.js';

import { Popover, type PopoverProps } from './Popover.js';
import { type Action, Popover, type PopoverProps } from './Popover.js';

describe('Popover', () => {
afterEach(() => {
Expand Down Expand Up @@ -56,6 +57,21 @@ describe('Popover', () => {
onToggle: vi.fn(createStateSetter(true)),
};

const actions: Action[] = [
{
onClick: vi.fn(),
children: 'Add',
icon: Add as FC<IconProps>,
},
{ type: 'divider' },
{
onClick: vi.fn(),
children: 'Remove',
icon: Delete as FC<IconProps>,
destructive: true,
},
];

it('should forward a ref', () => {
const ref = createRef<HTMLDialogElement>();
renderPopover({ ...baseProps, ref });
Expand Down Expand Up @@ -140,6 +156,20 @@ describe('Popover', () => {
expect(baseProps.onToggle).toHaveBeenCalledTimes(1);
});

it('should close the popover when clicking a popover item', async () => {
renderPopover({
...baseProps,
children: undefined,
actions,
});

const popoverItems = screen.getAllByRole('menuitem');

await userEvent.click(popoverItems[0]);

expect(baseProps.onToggle).toHaveBeenCalledTimes(1);
});

it('should move focus to the first popover item after opening', async () => {
const isOpen = false;
const onToggle = vi.fn(createStateSetter(isOpen));
Expand Down Expand Up @@ -187,8 +217,23 @@ describe('Popover', () => {
});
});

it('should render the popover with menu semantics by default ', async () => {
renderPopover({
...baseProps,
children: undefined,
actions,
});

const menu = screen.getByRole('menu');
expect(menu).toBeVisible();
const menuitems = screen.getAllByRole('menuitem');
expect(menuitems.length).toBe(2);

await flushMicrotasks();
});

it('should render the popover without menu semantics ', async () => {
renderPopover({ ...baseProps });
renderPopover({ ...baseProps, role: 'none' });

const menu = screen.queryByRole('menu');
expect(menu).toBeNull();
Expand All @@ -197,4 +242,17 @@ describe('Popover', () => {

await flushMicrotasks();
});

it('should hide dividers from the accessibility tree', async () => {
const { baseElement } = renderPopover({
...baseProps,
children: undefined,
actions,
});

const dividers = baseElement.querySelectorAll('hr[aria-hidden="true"');
expect(dividers.length).toBe(1);

await flushMicrotasks();
});
});
Loading

0 comments on commit 275cd6a

Please sign in to comment.