@delightui/components 0.1.105 → 0.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -1
- package/dist/cjs/components/molecules/Modal/DemoModal.d.ts +8 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/index.d.ts +3 -0
- package/dist/cjs/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
- package/dist/cjs/components/molecules/Modal/index.d.ts +2 -0
- package/dist/cjs/components/molecules/index.d.ts +2 -0
- package/dist/cjs/library.css +19 -6
- package/dist/cjs/library.js +3 -3
- package/dist/cjs/library.js.map +1 -1
- package/dist/esm/components/molecules/Modal/DemoModal.d.ts +8 -0
- package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.d.ts +41 -0
- package/dist/esm/components/molecules/Modal/ModalContext/ModalContext.types.d.ts +87 -0
- package/dist/esm/components/molecules/Modal/ModalContext/index.d.ts +3 -0
- package/dist/esm/components/molecules/Modal/ModalContext/useModal.d.ts +34 -0
- package/dist/esm/components/molecules/Modal/index.d.ts +2 -0
- package/dist/esm/components/molecules/index.d.ts +2 -0
- package/dist/esm/library.css +19 -6
- package/dist/esm/library.js +3 -3
- package/dist/esm/library.js.map +1 -1
- package/dist/index.d.ts +108 -2
- package/docs/README.md +264 -0
- package/docs/components/atoms/ActionImage.md +119 -0
- package/docs/components/atoms/Button.md +197 -0
- package/docs/components/atoms/Checkbox.md +299 -0
- package/docs/components/atoms/CheckboxItem.md +314 -0
- package/docs/components/atoms/Chip.md +380 -0
- package/docs/components/atoms/CustomToggle.md +270 -0
- package/docs/components/atoms/Icon.md +365 -0
- package/docs/components/atoms/IconButton.md +407 -0
- package/docs/components/atoms/Image.md +448 -0
- package/docs/components/atoms/Input.md +430 -0
- package/docs/components/atoms/ListItem.md +502 -0
- package/docs/components/atoms/Password.md +472 -0
- package/docs/components/atoms/RadioButton.md +614 -0
- package/docs/components/atoms/RadioButtonItem.md +588 -0
- package/docs/components/atoms/ResponsiveComponent.md +612 -0
- package/docs/components/atoms/SelectListItem.md +609 -0
- package/docs/components/atoms/Slider.md +605 -0
- package/docs/components/atoms/Spinner.md +605 -0
- package/docs/components/atoms/Text.md +463 -0
- package/docs/components/atoms/TextArea.md +670 -0
- package/docs/components/atoms/ToastNotification.md +668 -0
- package/docs/components/atoms/Toggle.md +737 -0
- package/docs/components/atoms/ToggleButton.md +751 -0
- package/docs/components/atoms/Tooltip.md +391 -0
- package/docs/components/molecules/Accordion.md +440 -0
- package/docs/components/molecules/AccordionGroup.md +547 -0
- package/docs/components/molecules/ActionCard.md +546 -0
- package/docs/components/molecules/Breadcrumb.md +403 -0
- package/docs/components/molecules/Breadcrumbs.md +485 -0
- package/docs/components/molecules/ButtonGroup.md +383 -0
- package/docs/components/molecules/Card.md +298 -0
- package/docs/components/molecules/ChipInput.md +646 -0
- package/docs/components/molecules/ContextMenu.md +768 -0
- package/docs/components/molecules/CustomTimeSelector.md +116 -0
- package/docs/components/molecules/DatePicker.md +516 -0
- package/docs/components/molecules/DateTimeSelector.md +166 -0
- package/docs/components/molecules/FormField.md +312 -0
- package/docs/components/molecules/Grid.md +577 -0
- package/docs/components/molecules/GridItem.md +834 -0
- package/docs/components/molecules/GridList.md +244 -0
- package/docs/components/molecules/List.md +485 -0
- package/docs/components/molecules/Modal.md +470 -0
- package/docs/components/molecules/ModalFooter.md +702 -0
- package/docs/components/molecules/ModalHeader.md +756 -0
- package/docs/components/molecules/ModalProvider.md +205 -0
- package/docs/components/molecules/Nav.md +530 -0
- package/docs/components/molecules/NavItem.md +572 -0
- package/docs/components/molecules/NavLink.md +499 -0
- package/docs/components/molecules/Option.md +521 -0
- package/docs/components/molecules/Pagination.md +592 -0
- package/docs/components/molecules/PaginationNumberField.md +722 -0
- package/docs/components/molecules/Popover.md +516 -0
- package/docs/components/molecules/ProgressBar.md +624 -0
- package/docs/components/molecules/RadioGroup.md +831 -0
- package/docs/components/molecules/RepeaterList.md +185 -0
- package/docs/components/molecules/Select.md +402 -0
- package/docs/components/molecules/SortableTrigger.md +82 -0
- package/docs/components/molecules/useModal.md +379 -0
- package/docs/components/organisms/Dropzone.md +346 -0
- package/docs/components/organisms/DropzoneClear.md +135 -0
- package/docs/components/organisms/DropzoneContent.md +216 -0
- package/docs/components/organisms/DropzoneFilename.md +191 -0
- package/docs/components/organisms/DropzoneSupportedFormats.md +184 -0
- package/docs/components/organisms/DropzoneTrigger.md +209 -0
- package/docs/components/organisms/Form.md +533 -0
- package/docs/components/organisms/SlideOutPanel.md +662 -0
- package/docs/components/organisms/TabContent.md +902 -0
- package/docs/components/organisms/TabItem.md +1091 -0
- package/docs/components/organisms/Table.md +611 -0
- package/docs/components/organisms/TableBody.md +679 -0
- package/docs/components/organisms/TableCell.md +482 -0
- package/docs/components/organisms/TableHeader.md +513 -0
- package/docs/components/organisms/TableHeaderCell.md +661 -0
- package/docs/components/organisms/TableRow.md +715 -0
- package/docs/components/organisms/Tabs.md +1330 -0
- package/docs/components/utils/ConditionalView.md +568 -0
- package/docs/components/utils/RenderStateView.md +726 -0
- package/docs/components/utils/WrapTextNodes.md +614 -0
- package/package.json +3 -2
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
# Popover
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
A floating popover component that displays content in an overlay positioned relative to a target element. Provides flexible positioning options, trigger actions, alignment controls, and accessibility features for creating tooltips, dropdown menus, contextual information panels, and interactive overlays.
|
|
6
|
+
|
|
7
|
+
## Aliases
|
|
8
|
+
|
|
9
|
+
- Popover
|
|
10
|
+
- Tooltip
|
|
11
|
+
- Dropdown
|
|
12
|
+
- Overlay
|
|
13
|
+
- FloatingPanel
|
|
14
|
+
- Callout
|
|
15
|
+
|
|
16
|
+
## Props Breakdown
|
|
17
|
+
|
|
18
|
+
**Extends:** Standalone interface (no HTML element inheritance)
|
|
19
|
+
|
|
20
|
+
| Prop | Type | Default | Required | Description |
|
|
21
|
+
|------|------|---------|----------|-------------|
|
|
22
|
+
| `content` | `ReactNode` | - | Yes | The content to display in the popover |
|
|
23
|
+
| `target` | `ReactNode` | - | Yes | The target element that triggers the popover |
|
|
24
|
+
| `container` | `HTMLElement \| React.RefObject<HTMLElement> \| null` | - | No | Container element for the popover to render in |
|
|
25
|
+
| `show` | `boolean` | `false` | No | Controls popover visibility |
|
|
26
|
+
| `action` | `'Click' \| 'Hover' \| 'DoubleClick'` | `'Click'` | No | Action type to trigger the popover |
|
|
27
|
+
| `direction` | `'Up' \| 'Down' \| 'Right' \| 'Left'` | `'Down'` | No | Direction in which the popover should open |
|
|
28
|
+
| `alignment` | `'Start' \| 'End' \| 'Center'` | `'Center'` | No | Alignment of the popover relative to target |
|
|
29
|
+
| `offset` | `Offset` | - | No | Offset for positioning the popover |
|
|
30
|
+
| `onOpen` | `() => void` | - | No | Callback function invoked when popover opens |
|
|
31
|
+
| `onHide` | `() => void` | - | No | Callback function invoked when popover hides |
|
|
32
|
+
| `hideOnClickAway` | `boolean` | `true` | No | Whether popover should hide on click away |
|
|
33
|
+
| `keepContentOnItemClick` | `boolean` | `false` | No | Whether content should stay on item click |
|
|
34
|
+
| `disabled` | `boolean` | `false` | No | Whether the popover is disabled |
|
|
35
|
+
| `flip` | `boolean` | `false` | No | Whether content should change placement to stay on screen |
|
|
36
|
+
| `contentPosition` | `'absolute' \| 'fixed'` | `'absolute'` | No | Position strategy for the popover |
|
|
37
|
+
| `contentProps` | `ContentProps` | - | No | Props for the content container |
|
|
38
|
+
| `className` | `string` | - | No | Additional CSS class names for the target |
|
|
39
|
+
| `overlayClassName` | `string` | - | No | Additional CSS class names for the overlay |
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
### Basic Usage
|
|
44
|
+
```tsx
|
|
45
|
+
import { Popover, Button, Text } from '@delightui/components';
|
|
46
|
+
|
|
47
|
+
function BasicExample() {
|
|
48
|
+
return (
|
|
49
|
+
<Popover
|
|
50
|
+
target={<Button>Show Popover</Button>}
|
|
51
|
+
content={
|
|
52
|
+
<div className="popover-content">
|
|
53
|
+
<Text>This is a basic popover with simple content.</Text>
|
|
54
|
+
</div>
|
|
55
|
+
}
|
|
56
|
+
/>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Controlled Popover
|
|
62
|
+
```tsx
|
|
63
|
+
function ControlledExample() {
|
|
64
|
+
const [showPopover, setShowPopover] = useState(false);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Popover
|
|
68
|
+
target={
|
|
69
|
+
<Button onClick={() => setShowPopover(!showPopover)}>
|
|
70
|
+
Toggle Popover
|
|
71
|
+
</Button>
|
|
72
|
+
}
|
|
73
|
+
content={
|
|
74
|
+
<div className="controlled-popover">
|
|
75
|
+
<Text type="Heading6">Controlled Popover</Text>
|
|
76
|
+
<Text>This popover is controlled by state.</Text>
|
|
77
|
+
<Button
|
|
78
|
+
size="Small"
|
|
79
|
+
onClick={() => setShowPopover(false)}
|
|
80
|
+
>
|
|
81
|
+
Close
|
|
82
|
+
</Button>
|
|
83
|
+
</div>
|
|
84
|
+
}
|
|
85
|
+
show={showPopover}
|
|
86
|
+
onHide={() => setShowPopover(false)}
|
|
87
|
+
/>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Hover Trigger
|
|
93
|
+
```tsx
|
|
94
|
+
function HoverExample() {
|
|
95
|
+
return (
|
|
96
|
+
<Popover
|
|
97
|
+
target={
|
|
98
|
+
<div className="hover-target">
|
|
99
|
+
<Icon icon="Info" />
|
|
100
|
+
<Text>Hover for info</Text>
|
|
101
|
+
</div>
|
|
102
|
+
}
|
|
103
|
+
content={
|
|
104
|
+
<div className="info-popover">
|
|
105
|
+
<Text type="Heading6">Additional Information</Text>
|
|
106
|
+
<Text>
|
|
107
|
+
This popover appears when you hover over the target element.
|
|
108
|
+
It's perfect for tooltips and contextual help.
|
|
109
|
+
</Text>
|
|
110
|
+
</div>
|
|
111
|
+
}
|
|
112
|
+
action="Hover"
|
|
113
|
+
direction="Up"
|
|
114
|
+
alignment="Center"
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Dropdown Menu
|
|
121
|
+
```tsx
|
|
122
|
+
function DropdownMenuExample() {
|
|
123
|
+
const handleMenuAction = (action) => {
|
|
124
|
+
console.log(`Menu action: ${action}`);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const menuContent = (
|
|
128
|
+
<div className="dropdown-menu">
|
|
129
|
+
<button
|
|
130
|
+
className="menu-item"
|
|
131
|
+
onClick={() => handleMenuAction('edit')}
|
|
132
|
+
>
|
|
133
|
+
<Icon icon="Edit" />
|
|
134
|
+
<Text>Edit</Text>
|
|
135
|
+
</button>
|
|
136
|
+
<button
|
|
137
|
+
className="menu-item"
|
|
138
|
+
onClick={() => handleMenuAction('copy')}
|
|
139
|
+
>
|
|
140
|
+
<Icon icon="Copy" />
|
|
141
|
+
<Text>Copy</Text>
|
|
142
|
+
</button>
|
|
143
|
+
<button
|
|
144
|
+
className="menu-item destructive"
|
|
145
|
+
onClick={() => handleMenuAction('delete')}
|
|
146
|
+
>
|
|
147
|
+
<Icon icon="Delete" />
|
|
148
|
+
<Text>Delete</Text>
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Popover
|
|
155
|
+
target={
|
|
156
|
+
<Button type="Ghost">
|
|
157
|
+
<Icon icon="MoreVertical" />
|
|
158
|
+
</Button>
|
|
159
|
+
}
|
|
160
|
+
content={menuContent}
|
|
161
|
+
direction="Down"
|
|
162
|
+
alignment="End"
|
|
163
|
+
hideOnClickAway={true}
|
|
164
|
+
/>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Form Popover
|
|
170
|
+
```tsx
|
|
171
|
+
function FormPopoverExample() {
|
|
172
|
+
const [formData, setFormData] = useState({ name: '', email: '' });
|
|
173
|
+
|
|
174
|
+
const handleSubmit = (e) => {
|
|
175
|
+
e.preventDefault();
|
|
176
|
+
console.log('Form submitted:', formData);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const formContent = (
|
|
180
|
+
<div className="form-popover">
|
|
181
|
+
<Text type="Heading6">Quick Contact</Text>
|
|
182
|
+
|
|
183
|
+
<form onSubmit={handleSubmit}>
|
|
184
|
+
<FormField name="name" label="Name">
|
|
185
|
+
<Input
|
|
186
|
+
value={formData.name}
|
|
187
|
+
onChange={(e) => setFormData(prev => ({
|
|
188
|
+
...prev,
|
|
189
|
+
name: e.target.value
|
|
190
|
+
}))}
|
|
191
|
+
placeholder="Your name"
|
|
192
|
+
/>
|
|
193
|
+
</FormField>
|
|
194
|
+
|
|
195
|
+
<FormField name="email" label="Email">
|
|
196
|
+
<Input
|
|
197
|
+
type="email"
|
|
198
|
+
value={formData.email}
|
|
199
|
+
onChange={(e) => setFormData(prev => ({
|
|
200
|
+
...prev,
|
|
201
|
+
email: e.target.value
|
|
202
|
+
}))}
|
|
203
|
+
placeholder="your@email.com"
|
|
204
|
+
/>
|
|
205
|
+
</FormField>
|
|
206
|
+
|
|
207
|
+
<div className="form-actions">
|
|
208
|
+
<Button type="Outlined" size="Small">
|
|
209
|
+
Cancel
|
|
210
|
+
</Button>
|
|
211
|
+
<Button actionType="submit" size="Small">
|
|
212
|
+
Submit
|
|
213
|
+
</Button>
|
|
214
|
+
</div>
|
|
215
|
+
</form>
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
return (
|
|
220
|
+
<Popover
|
|
221
|
+
target={<Button>Contact Form</Button>}
|
|
222
|
+
content={formContent}
|
|
223
|
+
direction="Down"
|
|
224
|
+
alignment="Start"
|
|
225
|
+
keepContentOnItemClick={true}
|
|
226
|
+
contentProps={{ width: '300px' }}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### User Profile Popover
|
|
233
|
+
```tsx
|
|
234
|
+
function UserProfileExample() {
|
|
235
|
+
const user = {
|
|
236
|
+
name: 'John Doe',
|
|
237
|
+
email: 'john@example.com',
|
|
238
|
+
avatar: '/avatars/john.jpg',
|
|
239
|
+
role: 'Administrator'
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const profileContent = (
|
|
243
|
+
<div className="profile-popover">
|
|
244
|
+
<div className="profile-header">
|
|
245
|
+
<Image
|
|
246
|
+
src={user.avatar}
|
|
247
|
+
alt={user.name}
|
|
248
|
+
className="profile-avatar"
|
|
249
|
+
/>
|
|
250
|
+
<div className="profile-info">
|
|
251
|
+
<Text type="Heading6">{user.name}</Text>
|
|
252
|
+
<Text type="BodySmall" className="profile-email">
|
|
253
|
+
{user.email}
|
|
254
|
+
</Text>
|
|
255
|
+
<Chip size="Small" style="Neutral">
|
|
256
|
+
{user.role}
|
|
257
|
+
</Chip>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
<div className="profile-actions">
|
|
262
|
+
<Button type="Ghost" size="Small">
|
|
263
|
+
<Icon icon="User" />
|
|
264
|
+
View Profile
|
|
265
|
+
</Button>
|
|
266
|
+
<Button type="Ghost" size="Small">
|
|
267
|
+
<Icon icon="Settings" />
|
|
268
|
+
Settings
|
|
269
|
+
</Button>
|
|
270
|
+
<Button type="Ghost" size="Small">
|
|
271
|
+
<Icon icon="Logout" />
|
|
272
|
+
Sign Out
|
|
273
|
+
</Button>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return (
|
|
279
|
+
<Popover
|
|
280
|
+
target={
|
|
281
|
+
<Button type="Ghost" className="profile-trigger">
|
|
282
|
+
<Image
|
|
283
|
+
src={user.avatar}
|
|
284
|
+
alt={user.name}
|
|
285
|
+
className="trigger-avatar"
|
|
286
|
+
/>
|
|
287
|
+
</Button>
|
|
288
|
+
}
|
|
289
|
+
content={profileContent}
|
|
290
|
+
direction="Down"
|
|
291
|
+
alignment="End"
|
|
292
|
+
contentProps={{ width: '280px' }}
|
|
293
|
+
/>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Notification Popover
|
|
299
|
+
```tsx
|
|
300
|
+
function NotificationPopoverExample() {
|
|
301
|
+
const [notifications, setNotifications] = useState([
|
|
302
|
+
{ id: 1, message: 'New message from Sarah', time: '2 min ago', read: false },
|
|
303
|
+
{ id: 2, message: 'Project deadline approaching', time: '1 hour ago', read: false },
|
|
304
|
+
{ id: 3, message: 'Weekly report generated', time: '3 hours ago', read: true }
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
const markAsRead = (id) => {
|
|
308
|
+
setNotifications(prev =>
|
|
309
|
+
prev.map(notif =>
|
|
310
|
+
notif.id === id ? { ...notif, read: true } : notif
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const unreadCount = notifications.filter(n => !n.read).length;
|
|
316
|
+
|
|
317
|
+
const notificationContent = (
|
|
318
|
+
<div className="notification-popover">
|
|
319
|
+
<div className="notification-header">
|
|
320
|
+
<Text type="Heading6">Notifications</Text>
|
|
321
|
+
{unreadCount > 0 && (
|
|
322
|
+
<Chip size="Small" style="Primary">
|
|
323
|
+
{unreadCount} new
|
|
324
|
+
</Chip>
|
|
325
|
+
)}
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
<div className="notification-list">
|
|
329
|
+
{notifications.map(notification => (
|
|
330
|
+
<div
|
|
331
|
+
key={notification.id}
|
|
332
|
+
className={`notification-item ${notification.read ? 'read' : 'unread'}`}
|
|
333
|
+
onClick={() => markAsRead(notification.id)}
|
|
334
|
+
>
|
|
335
|
+
<Text type="BodySmall">{notification.message}</Text>
|
|
336
|
+
<Text type="Caption" className="notification-time">
|
|
337
|
+
{notification.time}
|
|
338
|
+
</Text>
|
|
339
|
+
</div>
|
|
340
|
+
))}
|
|
341
|
+
</div>
|
|
342
|
+
|
|
343
|
+
<div className="notification-footer">
|
|
344
|
+
<Button type="Ghost" size="Small">
|
|
345
|
+
View All
|
|
346
|
+
</Button>
|
|
347
|
+
</div>
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<Popover
|
|
353
|
+
target={
|
|
354
|
+
<Button type="Ghost" className="notification-trigger">
|
|
355
|
+
<Icon icon="Bell" />
|
|
356
|
+
{unreadCount > 0 && (
|
|
357
|
+
<span className="notification-badge">{unreadCount}</span>
|
|
358
|
+
)}
|
|
359
|
+
</Button>
|
|
360
|
+
}
|
|
361
|
+
content={notificationContent}
|
|
362
|
+
direction="Down"
|
|
363
|
+
alignment="End"
|
|
364
|
+
contentProps={{ width: '320px', maxHeight: '400px' }}
|
|
365
|
+
/>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Rich Content Popover
|
|
371
|
+
```tsx
|
|
372
|
+
function RichContentExample() {
|
|
373
|
+
const productInfo = {
|
|
374
|
+
name: 'Premium Wireless Headphones',
|
|
375
|
+
price: '$299.99',
|
|
376
|
+
rating: 4.8,
|
|
377
|
+
image: '/products/headphones.jpg',
|
|
378
|
+
features: ['Noise Cancellation', 'Wireless', '30hr Battery']
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const richContent = (
|
|
382
|
+
<div className="product-popover">
|
|
383
|
+
<div className="product-image">
|
|
384
|
+
<Image
|
|
385
|
+
src={productInfo.image}
|
|
386
|
+
alt={productInfo.name}
|
|
387
|
+
fit="Cover"
|
|
388
|
+
/>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div className="product-details">
|
|
392
|
+
<Text type="Heading6">{productInfo.name}</Text>
|
|
393
|
+
|
|
394
|
+
<div className="product-rating">
|
|
395
|
+
<div className="stars">
|
|
396
|
+
{[...Array(5)].map((_, i) => (
|
|
397
|
+
<Icon
|
|
398
|
+
key={i}
|
|
399
|
+
icon={i < Math.floor(productInfo.rating) ? 'StarFilled' : 'StarOutlined'}
|
|
400
|
+
className="star"
|
|
401
|
+
/>
|
|
402
|
+
))}
|
|
403
|
+
</div>
|
|
404
|
+
<Text type="BodySmall">({productInfo.rating})</Text>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<Text type="Heading5" className="product-price">
|
|
408
|
+
{productInfo.price}
|
|
409
|
+
</Text>
|
|
410
|
+
|
|
411
|
+
<div className="product-features">
|
|
412
|
+
{productInfo.features.map(feature => (
|
|
413
|
+
<Chip key={feature} size="Small" style="Neutral">
|
|
414
|
+
{feature}
|
|
415
|
+
</Chip>
|
|
416
|
+
))}
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<div className="product-actions">
|
|
420
|
+
<Button size="Small">Add to Cart</Button>
|
|
421
|
+
<Button type="Outlined" size="Small">
|
|
422
|
+
<Icon icon="Heart" />
|
|
423
|
+
</Button>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<Popover
|
|
431
|
+
target={
|
|
432
|
+
<div className="product-preview">
|
|
433
|
+
<Image
|
|
434
|
+
src={productInfo.image}
|
|
435
|
+
alt={productInfo.name}
|
|
436
|
+
className="preview-image"
|
|
437
|
+
/>
|
|
438
|
+
<Text>Hover for details</Text>
|
|
439
|
+
</div>
|
|
440
|
+
}
|
|
441
|
+
content={richContent}
|
|
442
|
+
action="Hover"
|
|
443
|
+
direction="Right"
|
|
444
|
+
alignment="Center"
|
|
445
|
+
contentProps={{ width: '350px' }}
|
|
446
|
+
flip={true}
|
|
447
|
+
/>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Multi-position Popover
|
|
453
|
+
```tsx
|
|
454
|
+
function MultiPositionExample() {
|
|
455
|
+
const [position, setPosition] = useState('Down');
|
|
456
|
+
const [alignment, setAlignment] = useState('Center');
|
|
457
|
+
|
|
458
|
+
const positions = ['Up', 'Down', 'Left', 'Right'];
|
|
459
|
+
const alignments = ['Start', 'Center', 'End'];
|
|
460
|
+
|
|
461
|
+
const configContent = (
|
|
462
|
+
<div className="config-popover">
|
|
463
|
+
<Text type="Heading6">Popover Configuration</Text>
|
|
464
|
+
|
|
465
|
+
<div className="config-section">
|
|
466
|
+
<Text type="BodySmall">Position:</Text>
|
|
467
|
+
<div className="button-group">
|
|
468
|
+
{positions.map(pos => (
|
|
469
|
+
<Button
|
|
470
|
+
key={pos}
|
|
471
|
+
size="Small"
|
|
472
|
+
type={position === pos ? 'Primary' : 'Ghost'}
|
|
473
|
+
onClick={() => setPosition(pos)}
|
|
474
|
+
>
|
|
475
|
+
{pos}
|
|
476
|
+
</Button>
|
|
477
|
+
))}
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<div className="config-section">
|
|
482
|
+
<Text type="BodySmall">Alignment:</Text>
|
|
483
|
+
<div className="button-group">
|
|
484
|
+
{alignments.map(align => (
|
|
485
|
+
<Button
|
|
486
|
+
key={align}
|
|
487
|
+
size="Small"
|
|
488
|
+
type={alignment === align ? 'Primary' : 'Ghost'}
|
|
489
|
+
onClick={() => setAlignment(align)}
|
|
490
|
+
>
|
|
491
|
+
{align}
|
|
492
|
+
</Button>
|
|
493
|
+
))}
|
|
494
|
+
</div>
|
|
495
|
+
</div>
|
|
496
|
+
|
|
497
|
+
<Text type="Caption">
|
|
498
|
+
Current: {position} / {alignment}
|
|
499
|
+
</Text>
|
|
500
|
+
</div>
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<div className="center-container">
|
|
505
|
+
<Popover
|
|
506
|
+
target={<Button>Configure Popover</Button>}
|
|
507
|
+
content={configContent}
|
|
508
|
+
direction={position}
|
|
509
|
+
alignment={alignment}
|
|
510
|
+
keepContentOnItemClick={true}
|
|
511
|
+
contentProps={{ width: '280px' }}
|
|
512
|
+
/>
|
|
513
|
+
</div>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
```
|