@adamosuiteservices/ui 2.11.15 → 2.11.16
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/dist/colors.css +1 -1
- package/dist/styles.css +1 -1
- package/docs/AI-GUIDE.md +321 -321
- package/docs/components/layout/sidebar.md +399 -399
- package/docs/components/layout/toaster.md +436 -436
- package/docs/components/ui/accordion-rounded.md +584 -584
- package/docs/components/ui/accordion.md +269 -269
- package/docs/components/ui/button-group.md +984 -984
- package/docs/components/ui/button.md +1137 -1137
- package/docs/components/ui/calendar.md +1159 -1159
- package/docs/components/ui/card.md +1455 -1455
- package/docs/components/ui/checkbox.md +292 -292
- package/docs/components/ui/collapsible.md +323 -323
- package/docs/components/ui/command.md +454 -454
- package/docs/components/ui/context-menu.md +540 -540
- package/docs/components/ui/dialog.md +628 -628
- package/docs/components/ui/dropdown-menu.md +709 -709
- package/docs/components/ui/field.md +706 -706
- package/docs/components/ui/hover-card.md +446 -446
- package/docs/components/ui/input.md +362 -362
- package/docs/components/ui/kbd.md +434 -434
- package/docs/components/ui/label.md +359 -359
- package/docs/components/ui/pagination.md +650 -650
- package/docs/components/ui/popover.md +536 -536
- package/docs/components/ui/progress.md +182 -182
- package/docs/components/ui/radio-group.md +311 -311
- package/docs/components/ui/select.md +352 -352
- package/docs/components/ui/separator.md +214 -214
- package/docs/components/ui/sheet.md +142 -142
- package/docs/components/ui/skeleton.md +140 -140
- package/docs/components/ui/slider.md +341 -341
- package/docs/components/ui/spinner.md +170 -170
- package/docs/components/ui/switch.md +408 -408
- package/docs/components/ui/tabs-underline.md +106 -106
- package/docs/components/ui/tabs.md +122 -122
- package/docs/components/ui/textarea.md +243 -243
- package/docs/components/ui/toggle.md +237 -237
- package/docs/components/ui/tooltip.md +317 -317
- package/docs/components/ui/typography.md +280 -280
- package/package.json +1 -1
|
@@ -1,536 +1,536 @@
|
|
|
1
|
-
# Popover
|
|
2
|
-
|
|
3
|
-
Contenido flotante anclado a un trigger, basado en Radix UI. Ideal para forms compactos, settings, información adicional o acciones contextuales.
|
|
4
|
-
|
|
5
|
-
## Descripción
|
|
6
|
-
|
|
7
|
-
El componente `Popover` muestra contenido flotante anclado a un elemento trigger.
|
|
8
|
-
|
|
9
|
-
## Importación
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import {
|
|
13
|
-
Popover,
|
|
14
|
-
PopoverContent,
|
|
15
|
-
PopoverTrigger,
|
|
16
|
-
} from "@adamosuiteservices/ui/popover";
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Anatomía
|
|
20
|
-
|
|
21
|
-
```tsx
|
|
22
|
-
<Popover>
|
|
23
|
-
<PopoverTrigger>Open</PopoverTrigger>
|
|
24
|
-
<PopoverContent>Place content for the popover here.</PopoverContent>
|
|
25
|
-
</Popover>
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
**Componentes**: 4 (Popover, PopoverTrigger, PopoverContent, PopoverAnchor)
|
|
29
|
-
|
|
30
|
-
## Props Principales
|
|
31
|
-
|
|
32
|
-
### Popover (Root)
|
|
33
|
-
|
|
34
|
-
| Prop | Tipo | Default | Descripción |
|
|
35
|
-
| -------------- | ------------------------- | ------- | --------------------------------------- |
|
|
36
|
-
| `open` | `boolean` | - | Estado controlado |
|
|
37
|
-
| `defaultOpen` | `boolean` | `false` | Estado inicial no controlado |
|
|
38
|
-
| `onOpenChange` | `(open: boolean) => void` | - | Callback cuando cambia estado |
|
|
39
|
-
| `modal` | `boolean` | `false` | Si es modal (bloquea interacción fuera) |
|
|
40
|
-
|
|
41
|
-
### PopoverTrigger
|
|
42
|
-
|
|
43
|
-
| Prop | Tipo | Descripción |
|
|
44
|
-
| ----------- | --------- | -------------------------------------------------- |
|
|
45
|
-
| `asChild` | `boolean` | Pasa props al child en lugar de renderizar wrapper |
|
|
46
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
47
|
-
|
|
48
|
-
### PopoverContent
|
|
49
|
-
|
|
50
|
-
| Prop | Tipo | Default | Descripción |
|
|
51
|
-
| ------------- | ---------------------------------------- | ---------- | -------------------------------- |
|
|
52
|
-
| `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Lado donde aparece |
|
|
53
|
-
| `align` | `"start" \| "center" \| "end"` | `"center"` | Alineamiento horizontal/vertical |
|
|
54
|
-
| `sideOffset` | `number` | `4` | Distancia desde trigger (px) |
|
|
55
|
-
| `alignOffset` | `number` | `0` | Offset del alineamiento |
|
|
56
|
-
| `className` | `string` | - | Clases CSS adicionales |
|
|
57
|
-
|
|
58
|
-
**Estilos default**: `w-72`, `p-4`, `rounded-md`, `border`, `shadow-md`, `z-50`
|
|
59
|
-
|
|
60
|
-
### PopoverAnchor
|
|
61
|
-
|
|
62
|
-
| Prop | Tipo | Descripción |
|
|
63
|
-
| ----------- | --------- | ---------------------- |
|
|
64
|
-
| `asChild` | `boolean` | Pasa props al child |
|
|
65
|
-
| `className` | `string` | Clases CSS adicionales |
|
|
66
|
-
|
|
67
|
-
**Uso**: Ancla popover a elemento diferente del trigger
|
|
68
|
-
|
|
69
|
-
## Patrones de Uso
|
|
70
|
-
|
|
71
|
-
### Básico
|
|
72
|
-
|
|
73
|
-
```tsx
|
|
74
|
-
<Popover>
|
|
75
|
-
<PopoverTrigger asChild>
|
|
76
|
-
<Button variant="outline">Open popover</Button>
|
|
77
|
-
</PopoverTrigger>
|
|
78
|
-
<PopoverContent className="w-80">
|
|
79
|
-
<div className="grid gap-4">
|
|
80
|
-
<div className="space-y-2">
|
|
81
|
-
<h4 className="font-medium leading-none">Dimensions</h4>
|
|
82
|
-
<p className="text-sm text-muted-foreground">
|
|
83
|
-
Set the dimensions for the layer.
|
|
84
|
-
</p>
|
|
85
|
-
</div>
|
|
86
|
-
<div className="grid gap-2">
|
|
87
|
-
<div className="grid grid-cols-3 items-center gap-4">
|
|
88
|
-
<Label htmlFor="width">Width</Label>
|
|
89
|
-
<Input id="width" defaultValue="100%" className="col-span-2 h-8" />
|
|
90
|
-
</div>
|
|
91
|
-
<div className="grid grid-cols-3 items-center gap-4">
|
|
92
|
-
<Label htmlFor="height">Height</Label>
|
|
93
|
-
<Input id="height" defaultValue="25px" className="col-span-2 h-8" />
|
|
94
|
-
</div>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</PopoverContent>
|
|
98
|
-
</Popover>
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### Simple
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
<Popover>
|
|
105
|
-
<PopoverTrigger asChild>
|
|
106
|
-
<Button variant="outline">Click me</Button>
|
|
107
|
-
</PopoverTrigger>
|
|
108
|
-
<PopoverContent>
|
|
109
|
-
<p>This is a simple popover with just text content.</p>
|
|
110
|
-
</PopoverContent>
|
|
111
|
-
</Popover>
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Con User Profile
|
|
115
|
-
|
|
116
|
-
```tsx
|
|
117
|
-
import {
|
|
118
|
-
Avatar,
|
|
119
|
-
AvatarImage,
|
|
120
|
-
AvatarFallback,
|
|
121
|
-
} from "@adamosuiteservices/ui/avatar";
|
|
122
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
123
|
-
|
|
124
|
-
<Popover>
|
|
125
|
-
<PopoverTrigger asChild>
|
|
126
|
-
<Button variant="ghost" className="h-auto p-2">
|
|
127
|
-
<Avatar className="h-8 w-8">
|
|
128
|
-
<AvatarImage src="https://github.com/shadcn.png" />
|
|
129
|
-
<AvatarFallback>SC</AvatarFallback>
|
|
130
|
-
</Avatar>
|
|
131
|
-
</Button>
|
|
132
|
-
</PopoverTrigger>
|
|
133
|
-
<PopoverContent className="w-80">
|
|
134
|
-
<div className="flex gap-4">
|
|
135
|
-
<Avatar>
|
|
136
|
-
<AvatarImage src="https://github.com/shadcn.png" />
|
|
137
|
-
<AvatarFallback>SC</AvatarFallback>
|
|
138
|
-
</Avatar>
|
|
139
|
-
<div className="space-y-1">
|
|
140
|
-
<h4 className="text-sm font-semibold">@shadcn</h4>
|
|
141
|
-
<p className="text-sm">
|
|
142
|
-
The React Framework – created and maintained by @vercel.
|
|
143
|
-
</p>
|
|
144
|
-
<div className="flex items-center pt-2">
|
|
145
|
-
<Icon symbol="calendar_month" className="mr-2 text-lg opacity-70" />
|
|
146
|
-
<span className="text-xs text-muted-foreground">
|
|
147
|
-
Joined December 2021
|
|
148
|
-
</span>
|
|
149
|
-
</div>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</PopoverContent>
|
|
153
|
-
</Popover>;
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Con Settings
|
|
157
|
-
|
|
158
|
-
```tsx
|
|
159
|
-
import { Switch } from "@adamosuiteservices/ui/switch";
|
|
160
|
-
import { Separator } from "@adamosuiteservices/ui/separator";
|
|
161
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
162
|
-
|
|
163
|
-
<Popover>
|
|
164
|
-
<PopoverTrigger asChild>
|
|
165
|
-
<Button variant="outline" size="icon">
|
|
166
|
-
<Icon symbol="settings" className="text-lg" />
|
|
167
|
-
</Button>
|
|
168
|
-
</PopoverTrigger>
|
|
169
|
-
<PopoverContent className="w-80">
|
|
170
|
-
<div className="grid gap-4">
|
|
171
|
-
<div className="space-y-2">
|
|
172
|
-
<h4 className="font-medium leading-none">Settings</h4>
|
|
173
|
-
<p className="text-sm text-muted-foreground">
|
|
174
|
-
Configure your preferences
|
|
175
|
-
</p>
|
|
176
|
-
</div>
|
|
177
|
-
<div className="grid gap-2">
|
|
178
|
-
<div className="flex items-center justify-between">
|
|
179
|
-
<Label htmlFor="notifications">Push notifications</Label>
|
|
180
|
-
<Switch id="notifications" />
|
|
181
|
-
</div>
|
|
182
|
-
<div className="flex items-center justify-between">
|
|
183
|
-
<Label htmlFor="emails">Email updates</Label>
|
|
184
|
-
<Switch id="emails" defaultChecked />
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
<Separator />
|
|
188
|
-
<div className="flex justify-between">
|
|
189
|
-
<Button variant="outline" size="sm">
|
|
190
|
-
Cancel
|
|
191
|
-
</Button>
|
|
192
|
-
<Button size="sm">Save changes</Button>
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
</PopoverContent>
|
|
196
|
-
</Popover>;
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### Con Calendar
|
|
200
|
-
|
|
201
|
-
```tsx
|
|
202
|
-
import { useState } from "react";
|
|
203
|
-
import { Calendar } from "@adamosuiteservices/ui/calendar";
|
|
204
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
205
|
-
|
|
206
|
-
function App() {
|
|
207
|
-
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
208
|
-
|
|
209
|
-
return (
|
|
210
|
-
<Popover>
|
|
211
|
-
<PopoverTrigger asChild>
|
|
212
|
-
<Button
|
|
213
|
-
variant="outline"
|
|
214
|
-
className={cn(
|
|
215
|
-
"w-[240px] justify-start text-left font-normal",
|
|
216
|
-
!date && "text-muted-foreground"
|
|
217
|
-
)}
|
|
218
|
-
>
|
|
219
|
-
<Icon symbol="calendar_month" className="mr-2 text-lg" />
|
|
220
|
-
{date ? date.toLocaleDateString() : "Pick a date"}
|
|
221
|
-
</Button>
|
|
222
|
-
</PopoverTrigger>
|
|
223
|
-
<PopoverContent className="w-auto p-0" align="start">
|
|
224
|
-
<Calendar
|
|
225
|
-
mode="single"
|
|
226
|
-
selected={date}
|
|
227
|
-
onSelect={setDate}
|
|
228
|
-
initialFocus
|
|
229
|
-
/>
|
|
230
|
-
</PopoverContent>
|
|
231
|
-
</Popover>
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### Con Counter
|
|
237
|
-
|
|
238
|
-
```tsx
|
|
239
|
-
import { useState } from "react";
|
|
240
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
241
|
-
|
|
242
|
-
function App() {
|
|
243
|
-
const [count, setCount] = useState(0);
|
|
244
|
-
|
|
245
|
-
return (
|
|
246
|
-
<Popover>
|
|
247
|
-
<PopoverTrigger asChild>
|
|
248
|
-
<Button variant="outline">Counter: {count}</Button>
|
|
249
|
-
</PopoverTrigger>
|
|
250
|
-
<PopoverContent className="w-64">
|
|
251
|
-
<div className="grid gap-4">
|
|
252
|
-
<div className="space-y-2">
|
|
253
|
-
<h4 className="font-medium leading-none">Counter Control</h4>
|
|
254
|
-
<p className="text-sm text-muted-foreground">
|
|
255
|
-
Adjust the counter value
|
|
256
|
-
</p>
|
|
257
|
-
</div>
|
|
258
|
-
<div className="flex items-center justify-center space-x-2">
|
|
259
|
-
<Button
|
|
260
|
-
variant="outline"
|
|
261
|
-
size="icon"
|
|
262
|
-
onClick={() => setCount(Math.max(0, count - 1))}
|
|
263
|
-
disabled={count === 0}
|
|
264
|
-
>
|
|
265
|
-
<Icon symbol="remove" className="text-lg" />
|
|
266
|
-
</Button>
|
|
267
|
-
<div className="flex items-center justify-center w-16 h-10 border rounded">
|
|
268
|
-
{count}
|
|
269
|
-
</div>
|
|
270
|
-
<Button
|
|
271
|
-
variant="outline"
|
|
272
|
-
size="icon"
|
|
273
|
-
onClick={() => setCount(count + 1)}
|
|
274
|
-
>
|
|
275
|
-
<Icon symbol="add" className="text-lg" />
|
|
276
|
-
</Button>
|
|
277
|
-
</div>
|
|
278
|
-
<div className="flex gap-2">
|
|
279
|
-
<Button variant="outline" size="sm" onClick={() => setCount(0)}>
|
|
280
|
-
Reset
|
|
281
|
-
</Button>
|
|
282
|
-
<Button size="sm" onClick={() => setCount(10)}>
|
|
283
|
-
Set to 10
|
|
284
|
-
</Button>
|
|
285
|
-
</div>
|
|
286
|
-
</div>
|
|
287
|
-
</PopoverContent>
|
|
288
|
-
</Popover>
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Con Info Icon
|
|
294
|
-
|
|
295
|
-
```tsx
|
|
296
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
297
|
-
|
|
298
|
-
<div className="flex items-center gap-4">
|
|
299
|
-
<span>Complex Feature</span>
|
|
300
|
-
<Popover>
|
|
301
|
-
<PopoverTrigger asChild>
|
|
302
|
-
<Button variant="ghost" size="icon" className="h-5 w-5">
|
|
303
|
-
<Icon symbol="info" className="text-sm" />
|
|
304
|
-
</Button>
|
|
305
|
-
</PopoverTrigger>
|
|
306
|
-
<PopoverContent className="w-80">
|
|
307
|
-
<div className="space-y-3">
|
|
308
|
-
<div className="flex items-center gap-2">
|
|
309
|
-
<Icon symbol="error" className="text-lg text-primary" />
|
|
310
|
-
<h4 className="font-medium">About this feature</h4>
|
|
311
|
-
</div>
|
|
312
|
-
<p className="text-sm text-muted-foreground">
|
|
313
|
-
This is a complex feature that requires additional explanation.
|
|
314
|
-
</p>
|
|
315
|
-
<div className="space-y-2">
|
|
316
|
-
<h5 className="text-sm font-medium">Key benefits:</h5>
|
|
317
|
-
<ul className="text-sm space-y-1 text-muted-foreground">
|
|
318
|
-
<li>• Improved performance</li>
|
|
319
|
-
<li>• Better user experience</li>
|
|
320
|
-
<li>• Advanced customization</li>
|
|
321
|
-
</ul>
|
|
322
|
-
</div>
|
|
323
|
-
</div>
|
|
324
|
-
</PopoverContent>
|
|
325
|
-
</Popover>
|
|
326
|
-
</div>;
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Con Notifications
|
|
330
|
-
|
|
331
|
-
```tsx
|
|
332
|
-
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
333
|
-
import { Badge } from "@adamosuiteservices/ui/badge";
|
|
334
|
-
import { Separator } from "@adamosuiteservices/ui/separator";
|
|
335
|
-
|
|
336
|
-
<Popover>
|
|
337
|
-
<PopoverTrigger asChild>
|
|
338
|
-
<Button variant="outline" size="icon" className="relative">
|
|
339
|
-
<Icon symbol="mail" className="text-lg" />
|
|
340
|
-
<Badge
|
|
341
|
-
className="absolute -top-2 -right-2 h-5 w-5 p-0 flex items-center justify-center"
|
|
342
|
-
variant="destructive"
|
|
343
|
-
>
|
|
344
|
-
3
|
|
345
|
-
</Badge>
|
|
346
|
-
</Button>
|
|
347
|
-
</PopoverTrigger>
|
|
348
|
-
<PopoverContent className="w-80">
|
|
349
|
-
<div className="space-y-3">
|
|
350
|
-
<div className="flex items-center justify-between">
|
|
351
|
-
<h4 className="font-medium">Notifications</h4>
|
|
352
|
-
<Button variant="ghost" size="sm">
|
|
353
|
-
Mark all read
|
|
354
|
-
</Button>
|
|
355
|
-
</div>
|
|
356
|
-
<div className="space-y-2">
|
|
357
|
-
<div className="flex items-start gap-3 p-2 rounded hover:bg-muted bg-muted/50">
|
|
358
|
-
<div className="flex-1">
|
|
359
|
-
<p className="text-sm font-medium">New message</p>
|
|
360
|
-
<p className="text-xs text-muted-foreground">2 min ago</p>
|
|
361
|
-
</div>
|
|
362
|
-
<div className="h-2 w-2 bg-primary rounded-full mt-1" />
|
|
363
|
-
</div>
|
|
364
|
-
</div>
|
|
365
|
-
<Separator />
|
|
366
|
-
<Button variant="outline" size="sm" className="w-full">
|
|
367
|
-
View all notifications
|
|
368
|
-
</Button>
|
|
369
|
-
</div>
|
|
370
|
-
</PopoverContent>
|
|
371
|
-
</Popover>;
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### Posicionamiento Custom
|
|
375
|
-
|
|
376
|
-
```tsx
|
|
377
|
-
{
|
|
378
|
-
/* Top */
|
|
379
|
-
}
|
|
380
|
-
<Popover>
|
|
381
|
-
<PopoverTrigger asChild>
|
|
382
|
-
<Button variant="outline">Top</Button>
|
|
383
|
-
</PopoverTrigger>
|
|
384
|
-
<PopoverContent side="top">
|
|
385
|
-
<p>This popover appears on top</p>
|
|
386
|
-
</PopoverContent>
|
|
387
|
-
</Popover>;
|
|
388
|
-
|
|
389
|
-
{
|
|
390
|
-
/* Right */
|
|
391
|
-
}
|
|
392
|
-
<Popover>
|
|
393
|
-
<PopoverTrigger asChild>
|
|
394
|
-
<Button variant="outline">Right</Button>
|
|
395
|
-
</PopoverTrigger>
|
|
396
|
-
<PopoverContent side="right">
|
|
397
|
-
<p>This popover appears on the right</p>
|
|
398
|
-
</PopoverContent>
|
|
399
|
-
</Popover>;
|
|
400
|
-
|
|
401
|
-
{
|
|
402
|
-
/* Start aligned */
|
|
403
|
-
}
|
|
404
|
-
<Popover>
|
|
405
|
-
<PopoverTrigger asChild>
|
|
406
|
-
<Button variant="outline">Start aligned</Button>
|
|
407
|
-
</PopoverTrigger>
|
|
408
|
-
<PopoverContent align="start">
|
|
409
|
-
<p>This popover is aligned to the start</p>
|
|
410
|
-
</PopoverContent>
|
|
411
|
-
</Popover>;
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### Con Anchor
|
|
415
|
-
|
|
416
|
-
```tsx
|
|
417
|
-
<div className="space-y-4">
|
|
418
|
-
<p>
|
|
419
|
-
This is some text with an{" "}
|
|
420
|
-
<Popover>
|
|
421
|
-
<PopoverAnchor asChild>
|
|
422
|
-
<span className="underline cursor-pointer">anchored element</span>
|
|
423
|
-
</PopoverAnchor>
|
|
424
|
-
<PopoverTrigger asChild>
|
|
425
|
-
<Button variant="outline" className="ml-2">
|
|
426
|
-
Show info
|
|
427
|
-
</Button>
|
|
428
|
-
</PopoverTrigger>
|
|
429
|
-
<PopoverContent>
|
|
430
|
-
<p>This popover is anchored to the underlined text above!</p>
|
|
431
|
-
</PopoverContent>
|
|
432
|
-
</Popover>
|
|
433
|
-
</p>
|
|
434
|
-
</div>
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### Controlado
|
|
438
|
-
|
|
439
|
-
```tsx
|
|
440
|
-
import { useState } from "react";
|
|
441
|
-
|
|
442
|
-
function App() {
|
|
443
|
-
const [open, setOpen] = useState(false);
|
|
444
|
-
|
|
445
|
-
return (
|
|
446
|
-
<Popover open={open} onOpenChange={setOpen}>
|
|
447
|
-
<PopoverTrigger asChild>
|
|
448
|
-
<Button variant="outline">Toggle</Button>
|
|
449
|
-
</PopoverTrigger>
|
|
450
|
-
<PopoverContent>
|
|
451
|
-
<div className="space-y-2">
|
|
452
|
-
<p>Controlled popover content</p>
|
|
453
|
-
<Button onClick={() => setOpen(false)}>Close</Button>
|
|
454
|
-
</div>
|
|
455
|
-
</PopoverContent>
|
|
456
|
-
</Popover>
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
## Casos de Uso Comunes
|
|
462
|
-
|
|
463
|
-
**Date pickers**: Mostrar calendar en popover
|
|
464
|
-
**Settings**: Controles compactos de configuración
|
|
465
|
-
**User profiles**: Preview de perfiles al hover/click
|
|
466
|
-
**Notifications**: Lista de notificaciones
|
|
467
|
-
**Quick actions**: Acciones contextuales sin modal
|
|
468
|
-
**Info tooltips**: Información rica vs tooltip simple
|
|
469
|
-
**Forms**: Formularios compactos inline
|
|
470
|
-
|
|
471
|
-
## Estados y Data Attributes
|
|
472
|
-
|
|
473
|
-
### PopoverContent States
|
|
474
|
-
|
|
475
|
-
- **Open**: `data-[state=open]` → `animate-in`, `fade-in-0`, `zoom-in-95`
|
|
476
|
-
- **Closed**: `data-[state=closed]` → `animate-out`, `fade-out-0`, `zoom-out-95`
|
|
477
|
-
|
|
478
|
-
### Side Animations
|
|
479
|
-
|
|
480
|
-
- **Top**: `data-[side=top]` → `slide-in-from-bottom-2`
|
|
481
|
-
- **Right**: `data-[side=right]` → `slide-in-from-left-2`
|
|
482
|
-
- **Bottom**: `data-[side=bottom]` → `slide-in-from-top-2`
|
|
483
|
-
- **Left**: `data-[side=left]` → `slide-in-from-right-2`
|
|
484
|
-
|
|
485
|
-
## Interacción
|
|
486
|
-
|
|
487
|
-
- ✅ **Click to open**: Click en trigger abre popover
|
|
488
|
-
- ✅ **Click outside**: Click fuera cierra popover (si no es modal)
|
|
489
|
-
- ✅ **Escape**: Presionar Esc cierra popover
|
|
490
|
-
- ✅ **Focus trap**: Si `modal=true`, foco queda atrapado dentro
|
|
491
|
-
- ✅ **Keyboard**: Tab navega entre elementos internos
|
|
492
|
-
|
|
493
|
-
## Accesibilidad
|
|
494
|
-
|
|
495
|
-
- ✅ **ARIA**: `role="dialog"`, `aria-labelledby`, `aria-describedby` según contenido
|
|
496
|
-
- ✅ **Focus management**: Focus automático al abrir
|
|
497
|
-
- ✅ **Keyboard**: Esc para cerrar, Tab para navegar
|
|
498
|
-
- ✅ **Portal**: Renderiza en `document.body` para evitar problemas de z-index
|
|
499
|
-
- ✅ **Modal mode**: `modal=true` para bloquear interacción fuera del popover
|
|
500
|
-
|
|
501
|
-
## Notas de Implementación
|
|
502
|
-
|
|
503
|
-
- **Radix UI**: Basado en `@radix-ui/react-popover`
|
|
504
|
-
- **Portal rendering**: Content se renderiza en `document.body`
|
|
505
|
-
- **Collision detection**: Auto-ajusta posición si sale del viewport
|
|
506
|
-
- **Transform origin**: CSS variables de Radix para animaciones
|
|
507
|
-
- **Default width**: `w-72` (288px)
|
|
508
|
-
- **Default padding**: `p-4`
|
|
509
|
-
- **Z-index**: 50 para aparecer sobre contenido
|
|
510
|
-
- **Modal**: Por defecto `modal=false` (permite interacción fuera)
|
|
511
|
-
|
|
512
|
-
## Diferencias con Dialog y HoverCard
|
|
513
|
-
|
|
514
|
-
| Aspecto | Popover | Dialog | HoverCard |
|
|
515
|
-
| ---------------- | ----------------- | --------------------- | --------------- |
|
|
516
|
-
| **Trigger** | Click | Click | Hover |
|
|
517
|
-
| **Modal** | Opcional | Siempre | No |
|
|
518
|
-
| **Backdrop** | Opcional | Sí | No |
|
|
519
|
-
| **Contenido** | Compacto | Extenso | Preview |
|
|
520
|
-
| **Persistencia** | Hasta click fuera | Hasta close explícito | Hasta hover out |
|
|
521
|
-
|
|
522
|
-
## Troubleshooting
|
|
523
|
-
|
|
524
|
-
**No aparece**: Verifica que PopoverContent esté dentro de Popover root
|
|
525
|
-
**Posición incorrecta**: Ajusta `side`, `align`, `sideOffset` y `alignOffset`
|
|
526
|
-
**Contenido cortado**: Verifica que no haya `overflow: hidden` en containers
|
|
527
|
-
**No cierra**: Si `modal=true`, solo cierra con Esc o close explícito
|
|
528
|
-
**Focus no funciona**: Content debe tener elementos focuseables
|
|
529
|
-
**Backdrop no se ve**: Popover no tiene backdrop por defecto, usa `modal=true` y customiza
|
|
530
|
-
**Z-index issues**: Content usa portal, debe aparecer sobre todo
|
|
531
|
-
**Anchor no funciona**: PopoverAnchor debe estar dentro de Popover root
|
|
532
|
-
|
|
533
|
-
## Referencias
|
|
534
|
-
|
|
535
|
-
- **Radix UI Popover**: <https://www.radix-ui.com/primitives/docs/components/popover>
|
|
536
|
-
- **shadcn/ui Popover**: <https://ui.shadcn.com/docs/components/popover>
|
|
1
|
+
# Popover
|
|
2
|
+
|
|
3
|
+
Contenido flotante anclado a un trigger, basado en Radix UI. Ideal para forms compactos, settings, información adicional o acciones contextuales.
|
|
4
|
+
|
|
5
|
+
## Descripción
|
|
6
|
+
|
|
7
|
+
El componente `Popover` muestra contenido flotante anclado a un elemento trigger.
|
|
8
|
+
|
|
9
|
+
## Importación
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import {
|
|
13
|
+
Popover,
|
|
14
|
+
PopoverContent,
|
|
15
|
+
PopoverTrigger,
|
|
16
|
+
} from "@adamosuiteservices/ui/popover";
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Anatomía
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<Popover>
|
|
23
|
+
<PopoverTrigger>Open</PopoverTrigger>
|
|
24
|
+
<PopoverContent>Place content for the popover here.</PopoverContent>
|
|
25
|
+
</Popover>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Componentes**: 4 (Popover, PopoverTrigger, PopoverContent, PopoverAnchor)
|
|
29
|
+
|
|
30
|
+
## Props Principales
|
|
31
|
+
|
|
32
|
+
### Popover (Root)
|
|
33
|
+
|
|
34
|
+
| Prop | Tipo | Default | Descripción |
|
|
35
|
+
| -------------- | ------------------------- | ------- | --------------------------------------- |
|
|
36
|
+
| `open` | `boolean` | - | Estado controlado |
|
|
37
|
+
| `defaultOpen` | `boolean` | `false` | Estado inicial no controlado |
|
|
38
|
+
| `onOpenChange` | `(open: boolean) => void` | - | Callback cuando cambia estado |
|
|
39
|
+
| `modal` | `boolean` | `false` | Si es modal (bloquea interacción fuera) |
|
|
40
|
+
|
|
41
|
+
### PopoverTrigger
|
|
42
|
+
|
|
43
|
+
| Prop | Tipo | Descripción |
|
|
44
|
+
| ----------- | --------- | -------------------------------------------------- |
|
|
45
|
+
| `asChild` | `boolean` | Pasa props al child en lugar de renderizar wrapper |
|
|
46
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
47
|
+
|
|
48
|
+
### PopoverContent
|
|
49
|
+
|
|
50
|
+
| Prop | Tipo | Default | Descripción |
|
|
51
|
+
| ------------- | ---------------------------------------- | ---------- | -------------------------------- |
|
|
52
|
+
| `side` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Lado donde aparece |
|
|
53
|
+
| `align` | `"start" \| "center" \| "end"` | `"center"` | Alineamiento horizontal/vertical |
|
|
54
|
+
| `sideOffset` | `number` | `4` | Distancia desde trigger (px) |
|
|
55
|
+
| `alignOffset` | `number` | `0` | Offset del alineamiento |
|
|
56
|
+
| `className` | `string` | - | Clases CSS adicionales |
|
|
57
|
+
|
|
58
|
+
**Estilos default**: `w-72`, `p-4`, `rounded-md`, `border`, `shadow-md`, `z-50`
|
|
59
|
+
|
|
60
|
+
### PopoverAnchor
|
|
61
|
+
|
|
62
|
+
| Prop | Tipo | Descripción |
|
|
63
|
+
| ----------- | --------- | ---------------------- |
|
|
64
|
+
| `asChild` | `boolean` | Pasa props al child |
|
|
65
|
+
| `className` | `string` | Clases CSS adicionales |
|
|
66
|
+
|
|
67
|
+
**Uso**: Ancla popover a elemento diferente del trigger
|
|
68
|
+
|
|
69
|
+
## Patrones de Uso
|
|
70
|
+
|
|
71
|
+
### Básico
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<Popover>
|
|
75
|
+
<PopoverTrigger asChild>
|
|
76
|
+
<Button variant="outline">Open popover</Button>
|
|
77
|
+
</PopoverTrigger>
|
|
78
|
+
<PopoverContent className="w-80">
|
|
79
|
+
<div className="grid gap-4">
|
|
80
|
+
<div className="space-y-2">
|
|
81
|
+
<h4 className="font-medium leading-none">Dimensions</h4>
|
|
82
|
+
<p className="text-sm text-muted-foreground">
|
|
83
|
+
Set the dimensions for the layer.
|
|
84
|
+
</p>
|
|
85
|
+
</div>
|
|
86
|
+
<div className="grid gap-2">
|
|
87
|
+
<div className="grid grid-cols-3 items-center gap-4">
|
|
88
|
+
<Label htmlFor="width">Width</Label>
|
|
89
|
+
<Input id="width" defaultValue="100%" className="col-span-2 h-8" />
|
|
90
|
+
</div>
|
|
91
|
+
<div className="grid grid-cols-3 items-center gap-4">
|
|
92
|
+
<Label htmlFor="height">Height</Label>
|
|
93
|
+
<Input id="height" defaultValue="25px" className="col-span-2 h-8" />
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</PopoverContent>
|
|
98
|
+
</Popover>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Simple
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<Popover>
|
|
105
|
+
<PopoverTrigger asChild>
|
|
106
|
+
<Button variant="outline">Click me</Button>
|
|
107
|
+
</PopoverTrigger>
|
|
108
|
+
<PopoverContent>
|
|
109
|
+
<p>This is a simple popover with just text content.</p>
|
|
110
|
+
</PopoverContent>
|
|
111
|
+
</Popover>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Con User Profile
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import {
|
|
118
|
+
Avatar,
|
|
119
|
+
AvatarImage,
|
|
120
|
+
AvatarFallback,
|
|
121
|
+
} from "@adamosuiteservices/ui/avatar";
|
|
122
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
123
|
+
|
|
124
|
+
<Popover>
|
|
125
|
+
<PopoverTrigger asChild>
|
|
126
|
+
<Button variant="ghost" className="h-auto p-2">
|
|
127
|
+
<Avatar className="h-8 w-8">
|
|
128
|
+
<AvatarImage src="https://github.com/shadcn.png" />
|
|
129
|
+
<AvatarFallback>SC</AvatarFallback>
|
|
130
|
+
</Avatar>
|
|
131
|
+
</Button>
|
|
132
|
+
</PopoverTrigger>
|
|
133
|
+
<PopoverContent className="w-80">
|
|
134
|
+
<div className="flex gap-4">
|
|
135
|
+
<Avatar>
|
|
136
|
+
<AvatarImage src="https://github.com/shadcn.png" />
|
|
137
|
+
<AvatarFallback>SC</AvatarFallback>
|
|
138
|
+
</Avatar>
|
|
139
|
+
<div className="space-y-1">
|
|
140
|
+
<h4 className="text-sm font-semibold">@shadcn</h4>
|
|
141
|
+
<p className="text-sm">
|
|
142
|
+
The React Framework – created and maintained by @vercel.
|
|
143
|
+
</p>
|
|
144
|
+
<div className="flex items-center pt-2">
|
|
145
|
+
<Icon symbol="calendar_month" className="mr-2 text-lg opacity-70" />
|
|
146
|
+
<span className="text-xs text-muted-foreground">
|
|
147
|
+
Joined December 2021
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</PopoverContent>
|
|
153
|
+
</Popover>;
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Con Settings
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
import { Switch } from "@adamosuiteservices/ui/switch";
|
|
160
|
+
import { Separator } from "@adamosuiteservices/ui/separator";
|
|
161
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
162
|
+
|
|
163
|
+
<Popover>
|
|
164
|
+
<PopoverTrigger asChild>
|
|
165
|
+
<Button variant="outline" size="icon">
|
|
166
|
+
<Icon symbol="settings" className="text-lg" />
|
|
167
|
+
</Button>
|
|
168
|
+
</PopoverTrigger>
|
|
169
|
+
<PopoverContent className="w-80">
|
|
170
|
+
<div className="grid gap-4">
|
|
171
|
+
<div className="space-y-2">
|
|
172
|
+
<h4 className="font-medium leading-none">Settings</h4>
|
|
173
|
+
<p className="text-sm text-muted-foreground">
|
|
174
|
+
Configure your preferences
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="grid gap-2">
|
|
178
|
+
<div className="flex items-center justify-between">
|
|
179
|
+
<Label htmlFor="notifications">Push notifications</Label>
|
|
180
|
+
<Switch id="notifications" />
|
|
181
|
+
</div>
|
|
182
|
+
<div className="flex items-center justify-between">
|
|
183
|
+
<Label htmlFor="emails">Email updates</Label>
|
|
184
|
+
<Switch id="emails" defaultChecked />
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<Separator />
|
|
188
|
+
<div className="flex justify-between">
|
|
189
|
+
<Button variant="outline" size="sm">
|
|
190
|
+
Cancel
|
|
191
|
+
</Button>
|
|
192
|
+
<Button size="sm">Save changes</Button>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</PopoverContent>
|
|
196
|
+
</Popover>;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Con Calendar
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import { useState } from "react";
|
|
203
|
+
import { Calendar } from "@adamosuiteservices/ui/calendar";
|
|
204
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
205
|
+
|
|
206
|
+
function App() {
|
|
207
|
+
const [date, setDate] = useState<Date | undefined>(new Date());
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<Popover>
|
|
211
|
+
<PopoverTrigger asChild>
|
|
212
|
+
<Button
|
|
213
|
+
variant="outline"
|
|
214
|
+
className={cn(
|
|
215
|
+
"w-[240px] justify-start text-left font-normal",
|
|
216
|
+
!date && "text-muted-foreground"
|
|
217
|
+
)}
|
|
218
|
+
>
|
|
219
|
+
<Icon symbol="calendar_month" className="mr-2 text-lg" />
|
|
220
|
+
{date ? date.toLocaleDateString() : "Pick a date"}
|
|
221
|
+
</Button>
|
|
222
|
+
</PopoverTrigger>
|
|
223
|
+
<PopoverContent className="w-auto p-0" align="start">
|
|
224
|
+
<Calendar
|
|
225
|
+
mode="single"
|
|
226
|
+
selected={date}
|
|
227
|
+
onSelect={setDate}
|
|
228
|
+
initialFocus
|
|
229
|
+
/>
|
|
230
|
+
</PopoverContent>
|
|
231
|
+
</Popover>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Con Counter
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { useState } from "react";
|
|
240
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
241
|
+
|
|
242
|
+
function App() {
|
|
243
|
+
const [count, setCount] = useState(0);
|
|
244
|
+
|
|
245
|
+
return (
|
|
246
|
+
<Popover>
|
|
247
|
+
<PopoverTrigger asChild>
|
|
248
|
+
<Button variant="outline">Counter: {count}</Button>
|
|
249
|
+
</PopoverTrigger>
|
|
250
|
+
<PopoverContent className="w-64">
|
|
251
|
+
<div className="grid gap-4">
|
|
252
|
+
<div className="space-y-2">
|
|
253
|
+
<h4 className="font-medium leading-none">Counter Control</h4>
|
|
254
|
+
<p className="text-sm text-muted-foreground">
|
|
255
|
+
Adjust the counter value
|
|
256
|
+
</p>
|
|
257
|
+
</div>
|
|
258
|
+
<div className="flex items-center justify-center space-x-2">
|
|
259
|
+
<Button
|
|
260
|
+
variant="outline"
|
|
261
|
+
size="icon"
|
|
262
|
+
onClick={() => setCount(Math.max(0, count - 1))}
|
|
263
|
+
disabled={count === 0}
|
|
264
|
+
>
|
|
265
|
+
<Icon symbol="remove" className="text-lg" />
|
|
266
|
+
</Button>
|
|
267
|
+
<div className="flex items-center justify-center w-16 h-10 border rounded">
|
|
268
|
+
{count}
|
|
269
|
+
</div>
|
|
270
|
+
<Button
|
|
271
|
+
variant="outline"
|
|
272
|
+
size="icon"
|
|
273
|
+
onClick={() => setCount(count + 1)}
|
|
274
|
+
>
|
|
275
|
+
<Icon symbol="add" className="text-lg" />
|
|
276
|
+
</Button>
|
|
277
|
+
</div>
|
|
278
|
+
<div className="flex gap-2">
|
|
279
|
+
<Button variant="outline" size="sm" onClick={() => setCount(0)}>
|
|
280
|
+
Reset
|
|
281
|
+
</Button>
|
|
282
|
+
<Button size="sm" onClick={() => setCount(10)}>
|
|
283
|
+
Set to 10
|
|
284
|
+
</Button>
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
</PopoverContent>
|
|
288
|
+
</Popover>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Con Info Icon
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
297
|
+
|
|
298
|
+
<div className="flex items-center gap-4">
|
|
299
|
+
<span>Complex Feature</span>
|
|
300
|
+
<Popover>
|
|
301
|
+
<PopoverTrigger asChild>
|
|
302
|
+
<Button variant="ghost" size="icon" className="h-5 w-5">
|
|
303
|
+
<Icon symbol="info" className="text-sm" />
|
|
304
|
+
</Button>
|
|
305
|
+
</PopoverTrigger>
|
|
306
|
+
<PopoverContent className="w-80">
|
|
307
|
+
<div className="space-y-3">
|
|
308
|
+
<div className="flex items-center gap-2">
|
|
309
|
+
<Icon symbol="error" className="text-lg text-primary" />
|
|
310
|
+
<h4 className="font-medium">About this feature</h4>
|
|
311
|
+
</div>
|
|
312
|
+
<p className="text-sm text-muted-foreground">
|
|
313
|
+
This is a complex feature that requires additional explanation.
|
|
314
|
+
</p>
|
|
315
|
+
<div className="space-y-2">
|
|
316
|
+
<h5 className="text-sm font-medium">Key benefits:</h5>
|
|
317
|
+
<ul className="text-sm space-y-1 text-muted-foreground">
|
|
318
|
+
<li>• Improved performance</li>
|
|
319
|
+
<li>• Better user experience</li>
|
|
320
|
+
<li>• Advanced customization</li>
|
|
321
|
+
</ul>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</PopoverContent>
|
|
325
|
+
</Popover>
|
|
326
|
+
</div>;
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Con Notifications
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
import { Icon } from "@adamosuiteservices/ui/icon";
|
|
333
|
+
import { Badge } from "@adamosuiteservices/ui/badge";
|
|
334
|
+
import { Separator } from "@adamosuiteservices/ui/separator";
|
|
335
|
+
|
|
336
|
+
<Popover>
|
|
337
|
+
<PopoverTrigger asChild>
|
|
338
|
+
<Button variant="outline" size="icon" className="relative">
|
|
339
|
+
<Icon symbol="mail" className="text-lg" />
|
|
340
|
+
<Badge
|
|
341
|
+
className="absolute -top-2 -right-2 h-5 w-5 p-0 flex items-center justify-center"
|
|
342
|
+
variant="destructive"
|
|
343
|
+
>
|
|
344
|
+
3
|
|
345
|
+
</Badge>
|
|
346
|
+
</Button>
|
|
347
|
+
</PopoverTrigger>
|
|
348
|
+
<PopoverContent className="w-80">
|
|
349
|
+
<div className="space-y-3">
|
|
350
|
+
<div className="flex items-center justify-between">
|
|
351
|
+
<h4 className="font-medium">Notifications</h4>
|
|
352
|
+
<Button variant="ghost" size="sm">
|
|
353
|
+
Mark all read
|
|
354
|
+
</Button>
|
|
355
|
+
</div>
|
|
356
|
+
<div className="space-y-2">
|
|
357
|
+
<div className="flex items-start gap-3 p-2 rounded hover:bg-muted bg-muted/50">
|
|
358
|
+
<div className="flex-1">
|
|
359
|
+
<p className="text-sm font-medium">New message</p>
|
|
360
|
+
<p className="text-xs text-muted-foreground">2 min ago</p>
|
|
361
|
+
</div>
|
|
362
|
+
<div className="h-2 w-2 bg-primary rounded-full mt-1" />
|
|
363
|
+
</div>
|
|
364
|
+
</div>
|
|
365
|
+
<Separator />
|
|
366
|
+
<Button variant="outline" size="sm" className="w-full">
|
|
367
|
+
View all notifications
|
|
368
|
+
</Button>
|
|
369
|
+
</div>
|
|
370
|
+
</PopoverContent>
|
|
371
|
+
</Popover>;
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Posicionamiento Custom
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
{
|
|
378
|
+
/* Top */
|
|
379
|
+
}
|
|
380
|
+
<Popover>
|
|
381
|
+
<PopoverTrigger asChild>
|
|
382
|
+
<Button variant="outline">Top</Button>
|
|
383
|
+
</PopoverTrigger>
|
|
384
|
+
<PopoverContent side="top">
|
|
385
|
+
<p>This popover appears on top</p>
|
|
386
|
+
</PopoverContent>
|
|
387
|
+
</Popover>;
|
|
388
|
+
|
|
389
|
+
{
|
|
390
|
+
/* Right */
|
|
391
|
+
}
|
|
392
|
+
<Popover>
|
|
393
|
+
<PopoverTrigger asChild>
|
|
394
|
+
<Button variant="outline">Right</Button>
|
|
395
|
+
</PopoverTrigger>
|
|
396
|
+
<PopoverContent side="right">
|
|
397
|
+
<p>This popover appears on the right</p>
|
|
398
|
+
</PopoverContent>
|
|
399
|
+
</Popover>;
|
|
400
|
+
|
|
401
|
+
{
|
|
402
|
+
/* Start aligned */
|
|
403
|
+
}
|
|
404
|
+
<Popover>
|
|
405
|
+
<PopoverTrigger asChild>
|
|
406
|
+
<Button variant="outline">Start aligned</Button>
|
|
407
|
+
</PopoverTrigger>
|
|
408
|
+
<PopoverContent align="start">
|
|
409
|
+
<p>This popover is aligned to the start</p>
|
|
410
|
+
</PopoverContent>
|
|
411
|
+
</Popover>;
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Con Anchor
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
<div className="space-y-4">
|
|
418
|
+
<p>
|
|
419
|
+
This is some text with an{" "}
|
|
420
|
+
<Popover>
|
|
421
|
+
<PopoverAnchor asChild>
|
|
422
|
+
<span className="underline cursor-pointer">anchored element</span>
|
|
423
|
+
</PopoverAnchor>
|
|
424
|
+
<PopoverTrigger asChild>
|
|
425
|
+
<Button variant="outline" className="ml-2">
|
|
426
|
+
Show info
|
|
427
|
+
</Button>
|
|
428
|
+
</PopoverTrigger>
|
|
429
|
+
<PopoverContent>
|
|
430
|
+
<p>This popover is anchored to the underlined text above!</p>
|
|
431
|
+
</PopoverContent>
|
|
432
|
+
</Popover>
|
|
433
|
+
</p>
|
|
434
|
+
</div>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Controlado
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
import { useState } from "react";
|
|
441
|
+
|
|
442
|
+
function App() {
|
|
443
|
+
const [open, setOpen] = useState(false);
|
|
444
|
+
|
|
445
|
+
return (
|
|
446
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
447
|
+
<PopoverTrigger asChild>
|
|
448
|
+
<Button variant="outline">Toggle</Button>
|
|
449
|
+
</PopoverTrigger>
|
|
450
|
+
<PopoverContent>
|
|
451
|
+
<div className="space-y-2">
|
|
452
|
+
<p>Controlled popover content</p>
|
|
453
|
+
<Button onClick={() => setOpen(false)}>Close</Button>
|
|
454
|
+
</div>
|
|
455
|
+
</PopoverContent>
|
|
456
|
+
</Popover>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Casos de Uso Comunes
|
|
462
|
+
|
|
463
|
+
**Date pickers**: Mostrar calendar en popover
|
|
464
|
+
**Settings**: Controles compactos de configuración
|
|
465
|
+
**User profiles**: Preview de perfiles al hover/click
|
|
466
|
+
**Notifications**: Lista de notificaciones
|
|
467
|
+
**Quick actions**: Acciones contextuales sin modal
|
|
468
|
+
**Info tooltips**: Información rica vs tooltip simple
|
|
469
|
+
**Forms**: Formularios compactos inline
|
|
470
|
+
|
|
471
|
+
## Estados y Data Attributes
|
|
472
|
+
|
|
473
|
+
### PopoverContent States
|
|
474
|
+
|
|
475
|
+
- **Open**: `data-[state=open]` → `animate-in`, `fade-in-0`, `zoom-in-95`
|
|
476
|
+
- **Closed**: `data-[state=closed]` → `animate-out`, `fade-out-0`, `zoom-out-95`
|
|
477
|
+
|
|
478
|
+
### Side Animations
|
|
479
|
+
|
|
480
|
+
- **Top**: `data-[side=top]` → `slide-in-from-bottom-2`
|
|
481
|
+
- **Right**: `data-[side=right]` → `slide-in-from-left-2`
|
|
482
|
+
- **Bottom**: `data-[side=bottom]` → `slide-in-from-top-2`
|
|
483
|
+
- **Left**: `data-[side=left]` → `slide-in-from-right-2`
|
|
484
|
+
|
|
485
|
+
## Interacción
|
|
486
|
+
|
|
487
|
+
- ✅ **Click to open**: Click en trigger abre popover
|
|
488
|
+
- ✅ **Click outside**: Click fuera cierra popover (si no es modal)
|
|
489
|
+
- ✅ **Escape**: Presionar Esc cierra popover
|
|
490
|
+
- ✅ **Focus trap**: Si `modal=true`, foco queda atrapado dentro
|
|
491
|
+
- ✅ **Keyboard**: Tab navega entre elementos internos
|
|
492
|
+
|
|
493
|
+
## Accesibilidad
|
|
494
|
+
|
|
495
|
+
- ✅ **ARIA**: `role="dialog"`, `aria-labelledby`, `aria-describedby` según contenido
|
|
496
|
+
- ✅ **Focus management**: Focus automático al abrir
|
|
497
|
+
- ✅ **Keyboard**: Esc para cerrar, Tab para navegar
|
|
498
|
+
- ✅ **Portal**: Renderiza en `document.body` para evitar problemas de z-index
|
|
499
|
+
- ✅ **Modal mode**: `modal=true` para bloquear interacción fuera del popover
|
|
500
|
+
|
|
501
|
+
## Notas de Implementación
|
|
502
|
+
|
|
503
|
+
- **Radix UI**: Basado en `@radix-ui/react-popover`
|
|
504
|
+
- **Portal rendering**: Content se renderiza en `document.body`
|
|
505
|
+
- **Collision detection**: Auto-ajusta posición si sale del viewport
|
|
506
|
+
- **Transform origin**: CSS variables de Radix para animaciones
|
|
507
|
+
- **Default width**: `w-72` (288px)
|
|
508
|
+
- **Default padding**: `p-4`
|
|
509
|
+
- **Z-index**: 50 para aparecer sobre contenido
|
|
510
|
+
- **Modal**: Por defecto `modal=false` (permite interacción fuera)
|
|
511
|
+
|
|
512
|
+
## Diferencias con Dialog y HoverCard
|
|
513
|
+
|
|
514
|
+
| Aspecto | Popover | Dialog | HoverCard |
|
|
515
|
+
| ---------------- | ----------------- | --------------------- | --------------- |
|
|
516
|
+
| **Trigger** | Click | Click | Hover |
|
|
517
|
+
| **Modal** | Opcional | Siempre | No |
|
|
518
|
+
| **Backdrop** | Opcional | Sí | No |
|
|
519
|
+
| **Contenido** | Compacto | Extenso | Preview |
|
|
520
|
+
| **Persistencia** | Hasta click fuera | Hasta close explícito | Hasta hover out |
|
|
521
|
+
|
|
522
|
+
## Troubleshooting
|
|
523
|
+
|
|
524
|
+
**No aparece**: Verifica que PopoverContent esté dentro de Popover root
|
|
525
|
+
**Posición incorrecta**: Ajusta `side`, `align`, `sideOffset` y `alignOffset`
|
|
526
|
+
**Contenido cortado**: Verifica que no haya `overflow: hidden` en containers
|
|
527
|
+
**No cierra**: Si `modal=true`, solo cierra con Esc o close explícito
|
|
528
|
+
**Focus no funciona**: Content debe tener elementos focuseables
|
|
529
|
+
**Backdrop no se ve**: Popover no tiene backdrop por defecto, usa `modal=true` y customiza
|
|
530
|
+
**Z-index issues**: Content usa portal, debe aparecer sobre todo
|
|
531
|
+
**Anchor no funciona**: PopoverAnchor debe estar dentro de Popover root
|
|
532
|
+
|
|
533
|
+
## Referencias
|
|
534
|
+
|
|
535
|
+
- **Radix UI Popover**: <https://www.radix-ui.com/primitives/docs/components/popover>
|
|
536
|
+
- **shadcn/ui Popover**: <https://ui.shadcn.com/docs/components/popover>
|