Skip to content

Commit 4bad4df

Browse files
committed
feat: add Popup, Title, Description, Header & Footer components to Popover
1 parent 6932c59 commit 4bad4df

36 files changed

Lines changed: 634 additions & 139 deletions

apps/showcase/__store__/index.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,10 +1503,6 @@ export const Store: Record<string, Record<string, Record<string, { component: Re
15031503
'component': React.lazy(() => import('demo/styled/popover/controlled-demo')),
15041504
'filePath': 'demo/styled/popover/controlled-demo.tsx',
15051505
},
1506-
'select-data-demo': {
1507-
'component': React.lazy(() => import('demo/styled/popover/select-data-demo')),
1508-
'filePath': 'demo/styled/popover/select-data-demo.tsx',
1509-
},
15101506
},
15111507
'progressbar': {
15121508
'basic-demo': {

apps/showcase/demo/styled/popover/alignment-demo.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
'use client';
2+
import { Times } from '@primeicons/react/times';
23
import { Button } from '@primereact/ui/button';
34
import { InputText } from '@primereact/ui/inputtext';
45
import { Popover } from '@primereact/ui/popover';
56
import * as React from 'react';
6-
import { Times } from '@primeicons/react/times';
77

88
const sides = ['top', 'right', 'bottom', 'left'] as const;
99
const aligns = ['start', 'center', 'end'] as const;
1010

11-
export default function BasicDemo() {
11+
export default function AlignmentDemo() {
1212
const [side, setSide] = React.useState<(typeof sides)[number]>('bottom');
1313
const [align, setAlign] = React.useState<(typeof aligns)[number]>('start');
1414

@@ -48,28 +48,27 @@ export default function BasicDemo() {
4848
</Popover.Trigger>
4949
<Popover.Portal>
5050
<Popover.Positioner sideOffset={12} side={side} align={align}>
51-
<Popover.Content className="max-w-72 w-full">
52-
<div className="flex items-start justify-between w-full">
53-
<h3 className="text-surface-900 dark:text-surface-0 text-sm font-medium">Create a New Workspace</h3>
54-
</div>
55-
<p className="text-surface-500 dark:text-surface-400 text-sm mt-2">
56-
Name your workspace to get started. You can always change this later.
57-
</p>
58-
<InputText placeholder="Workspace Name" className="mt-3 w-full" />
59-
<div className="flex items-center mt-6">
51+
<Popover.Popup className="max-w-72 w-full">
52+
<Popover.Header>
53+
<Popover.Title>Create a New Workspace</Popover.Title>
54+
<Popover.Close as={Button} severity="secondary" variant="text" size="small" iconOnly>
55+
<Times />
56+
</Popover.Close>
57+
</Popover.Header>
58+
<Popover.Content>
59+
<Popover.Description>Name your workspace to get started. You can always change this later.</Popover.Description>
60+
<InputText placeholder="Workspace Name" className="mt-3 w-full" />
61+
</Popover.Content>
62+
<Popover.Footer>
6063
<span className="text-xs text-surface-500 dark:text-surface-400 ">1 of 3</span>
6164
<div className="flex-1 flex items-center justify-end gap-2">
6265
<Button severity="secondary" variant="outlined" size="small">
6366
Back
6467
</Button>
6568
<Button size="small">Next</Button>
6669
</div>
67-
</div>
68-
<Popover.Close as={Button} severity="secondary" variant="text" size="small" iconOnly className="absolute top-2 right-2">
69-
<Times />
70-
</Popover.Close>
71-
<Popover.Arrow />
72-
</Popover.Content>
70+
</Popover.Footer>
71+
</Popover.Popup>
7372
</Popover.Positioner>
7473
</Popover.Portal>
7574
</Popover.Root>

apps/showcase/demo/styled/popover/basic-demo.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import { Times } from '@primeicons/react/times';
12
import { Button } from '@primereact/ui/button';
23
import { InputText } from '@primereact/ui/inputtext';
34
import { Popover } from '@primereact/ui/popover';
4-
import { Times } from '@primeicons/react/times';
55

66
export default function BasicDemo() {
77
return (
@@ -12,28 +12,28 @@ export default function BasicDemo() {
1212
</Popover.Trigger>
1313
<Popover.Portal>
1414
<Popover.Positioner sideOffset={12} side="bottom" align="start">
15-
<Popover.Content className="max-w-72 w-full">
16-
<div className="flex items-start justify-between w-full">
17-
<h3 className="text-surface-900 dark:text-surface-0 text-sm font-medium">Create a New Workspace</h3>
18-
</div>
19-
<p className="text-surface-500 dark:text-surface-400 text-sm mt-2">
20-
Name your workspace to get started. You can always change this later.
21-
</p>
22-
<InputText placeholder="Workspace Name" className="mt-3 w-full" />
23-
<div className="flex items-center mt-6">
15+
<Popover.Popup className="max-w-72 w-full">
16+
<Popover.Header>
17+
<Popover.Title>Create a New Workspace</Popover.Title>
18+
<Popover.Close as={Button} severity="secondary" variant="text" size="small" iconOnly>
19+
<Times />
20+
</Popover.Close>
21+
</Popover.Header>
22+
<Popover.Content>
23+
<Popover.Description>Name your workspace to get started. You can always change this later.</Popover.Description>
24+
<InputText placeholder="Workspace Name" className="mt-3 w-full" />
25+
</Popover.Content>
26+
<Popover.Footer>
2427
<span className="text-xs text-surface-500 dark:text-surface-400 ">1 of 3</span>
2528
<div className="flex-1 flex items-center justify-end gap-2">
2629
<Button severity="secondary" variant="outlined" size="small">
2730
Back
2831
</Button>
2932
<Button size="small">Next</Button>
3033
</div>
31-
</div>
32-
<Popover.Close as={Button} severity="secondary" variant="text" size="small" iconOnly className="absolute top-2 right-2">
33-
<Times />
34-
</Popover.Close>
34+
</Popover.Footer>
3535
<Popover.Arrow />
36-
</Popover.Content>
36+
</Popover.Popup>
3737
</Popover.Positioner>
3838
</Popover.Portal>
3939
</Popover.Root>
Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use client';
2+
import { Times } from '@primeicons/react/times';
23
import { usePopoverOpenChangeEvent } from '@primereact/types/shared/popover';
34
import { Button } from '@primereact/ui/button';
45
import { InputText } from '@primereact/ui/inputtext';
5-
import { Label } from '@primereact/ui/label';
66
import { Popover } from '@primereact/ui/popover';
77
import React from 'react';
88

9-
function ControlledDemo() {
9+
export default function ControlledDemo() {
1010
const [open, setOpen] = React.useState(false);
1111

1212
return (
@@ -16,32 +16,31 @@ function ControlledDemo() {
1616
<Popover.Root open={open} onOpenChange={(e: usePopoverOpenChangeEvent) => setOpen(e.value)}>
1717
<Popover.Trigger>Popover Trigger</Popover.Trigger>
1818
<Popover.Portal>
19-
<Popover.Content>
20-
<div className="flex flex-col gap-2 p-2 max-w-xs">
21-
<p className="text-lg font-semibold mb-0.5">Dimensions</p>
22-
<div className="grid grid-cols-2 items-center">
23-
<Label htmlFor="width">Width</Label>
24-
<InputText id="width" fluid />
25-
</div>
26-
<div className="grid grid-cols-2 items-center">
27-
<Label htmlFor="maxWidth">Max. width</Label>
28-
<InputText id="maxWidth" fluid />
29-
</div>
30-
<div className="grid grid-cols-2 items-center">
31-
<Label htmlFor="height">Height</Label>
32-
<InputText id="height" fluid />
33-
</div>
34-
<div className="grid grid-cols-2 items-center">
35-
<Label htmlFor="maxHeight">Max. height</Label>
36-
<InputText id="maxHeight" fluid />
37-
</div>
38-
</div>
39-
<Popover.Close className="absolute top-4 right-4" />
40-
</Popover.Content>
19+
<Popover.Positioner sideOffset={12}>
20+
<Popover.Popup className="max-w-72 w-full">
21+
<Popover.Header>
22+
<Popover.Title>Create a New Workspace</Popover.Title>
23+
<Popover.Close as={Button} severity="secondary" variant="text" size="small" iconOnly>
24+
<Times />
25+
</Popover.Close>
26+
</Popover.Header>
27+
<Popover.Content>
28+
<Popover.Description>Name your workspace to get started. You can always change this later.</Popover.Description>
29+
<InputText placeholder="Workspace Name" className="mt-3 w-full" />
30+
</Popover.Content>
31+
<Popover.Footer>
32+
<span className="text-xs text-surface-500 dark:text-surface-400 ">1 of 3</span>
33+
<div className="flex-1 flex items-center justify-end gap-2">
34+
<Button severity="secondary" variant="outlined" size="small">
35+
Back
36+
</Button>
37+
<Button size="small">Next</Button>
38+
</div>
39+
</Popover.Footer>
40+
</Popover.Popup>
41+
</Popover.Positioner>
4142
</Popover.Portal>
4243
</Popover.Root>
4344
</div>
4445
);
4546
}
46-
47-
export default ControlledDemo;

apps/showcase/demo/styled/popover/select-data-demo.tsx

Lines changed: 0 additions & 55 deletions
This file was deleted.

apps/showcase/docs/styled/components/popover/features.mdx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@ import { Popover } from '@primereact/ui/popover';
1313
```
1414

1515
```tsx
16-
<Popover>
16+
<Popover.Root>
1717
<Popover.Trigger></Popover.Trigger>
1818
<Popover.Portal>
19-
<Popover.Content>
20-
<Popover.Close />
21-
</Popover.Content>
19+
<Popover.Positioner sideOffset={12} side="bottom" align="start">
20+
<Popover.Popup>
21+
<Popover.Content></Popover.Content>
22+
<Popover.Close></Popover.Close>
23+
<Popover.Arrow />
24+
</Popover.Popup>
25+
</Popover.Positioner>
2226
</Popover.Portal>
23-
</Popover>
27+
</Popover.Root>
2428
```
2529

2630
## Examples
@@ -33,6 +37,8 @@ Use the `open` and `onOpenChange` props to control the popover state.
3337

3438
### Alignment
3539

40+
Use the `side` and `align` props to align the popover. To give an offset to the popover, use the `sideOffset` and `alignOffset` props.
41+
3642
<DocDemoViewer name="popover:alignment-demo" />
3743

3844
## Accessibility

packages/@primereact/headless/src/popover/usePopover.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,22 @@ export const usePopover = withHeadless({
2525
const arrowRef = React.useRef<HTMLDivElement>(null);
2626

2727
const setAnchorRef = React.useCallback((node: HTMLElement | null) => {
28+
if (node === anchorRef.current) return;
29+
2830
anchorRef.current = node;
2931
setAnchorEl(node);
3032
}, []);
3133

3234
const setPositionerRef = React.useCallback((node: HTMLDivElement | null) => {
35+
if (node === positionerRef.current) return;
36+
3337
positionerRef.current = node;
3438
setPositionerEl(node);
3539
}, []);
3640

3741
const setArrowRef = React.useCallback((node: HTMLDivElement | null) => {
42+
if (node === arrowRef.current) return;
43+
3844
arrowRef.current = node;
3945
setArrowEl(node);
4046
}, []);

packages/@primereact/styles/src/popover/Popover.style.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,57 @@ import type { PopoverRootInstance } from '@primereact/types/shared/popover';
33

44
const theme = /*css*/ `
55
.p-popover-positioner{
6-
z-index: 2000;
6+
z-index: 2000;
77
}
8-
.p-popover-content{
8+
9+
.p-popover-popup{
910
position: relative;
10-
padding: 1rem;
1111
background: light-dark(var(--p-surface-0), var(--p-surface-900));
1212
border-radius: 0.5rem;
1313
border: 1px solid var(--p-content-border-color);
1414
opacity: 0;
1515
scale: 0.93;
1616
transition: opacity 250ms cubic-bezier(0.16, 1, 0.3, 1), scale 250ms cubic-bezier(0.16, 1, 0.3, 1);
1717
transform-origin: var(--transform-origin);
18+
box-shadow: 0 4px 8px 0 rgb(0 0 0 / 0.05);
1819
1920
&[data-open]{
2021
opacity: 1;
2122
scale: 1;
2223
}
2324
}
2425
26+
.p-popover-content{
27+
padding: 0.5rem 1rem 1rem 1rem;
28+
}
29+
30+
.p-popover-header{
31+
display: flex;
32+
justify-content: space-between;
33+
align-items: center;
34+
gap: 0.5rem;
35+
padding: 1rem 1rem 0 1rem;
36+
}
37+
38+
.p-popover-footer{
39+
display: flex;
40+
justify-content: space-between;
41+
align-items: center;
42+
gap: 0.5rem;
43+
padding: 0 1rem 1rem 1rem;
44+
}
45+
46+
.p-popover-title{
47+
color: light-dark(var(--p-surface-900), var(--p-surface-0));
48+
font-size: 0.875rem;
49+
font-weight: 500;
50+
}
51+
52+
.p-popover-description{
53+
color: light-dark(var(--p-surface-500), var(--p-surface-400));
54+
font-size: 0.875rem;
55+
}
56+
2557
.p-popover-arrow{
2658
position: absolute;
2759
border: 1px solid var(--p-content-border-color);
@@ -62,8 +94,13 @@ export const styles = createStyles<PopoverRootInstance>({
6294
style: theme,
6395
classes: {
6496
overlay: 'p-popover p-component',
97+
popup: 'p-popover-popup',
6598
content: 'p-popover-content',
6699
positioner: 'p-popover-positioner',
67-
arrow: 'p-popover-arrow'
100+
arrow: 'p-popover-arrow',
101+
title: 'p-popover-title',
102+
description: 'p-popover-description',
103+
footer: 'p-popover-footer',
104+
header: 'p-popover-header'
68105
}
69106
});

packages/@primereact/types/src/shared/popover/PopoverContent.types.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,7 @@ export interface PopoverContentPassThrough {
3030
/**
3131
* Defines valid properties in PopoverContent component.
3232
*/
33-
export interface PopoverContentProps extends BaseComponentProps<PopoverContentInstance, unknown, PopoverContentPassThrough> {
34-
/**
35-
* Whether to focus the first focusable element when the popover is opened.
36-
* @default true
37-
*/
38-
autoFocus?: boolean;
39-
}
33+
export interface PopoverContentProps extends BaseComponentProps<PopoverContentInstance, unknown, PopoverContentPassThrough> {}
4034

4135
/**
4236
* Defines valid state in PopoverContent component.

0 commit comments

Comments
 (0)