@reliability-design/web 1.0.2 → 1.0.6
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/components/rel-accordion/README.md +132 -0
- package/dist/components/rel-accordion/rel-accordion.ts +61 -29
- package/dist/components/rel-alert/README.md +141 -0
- package/dist/components/rel-alert/rel-alert.ts +110 -67
- package/dist/components/rel-avatar/README.md +119 -0
- package/dist/components/rel-avatar/rel-avatar.ts +8 -15
- package/dist/components/rel-badge/README.md +86 -0
- package/dist/components/rel-badge/rel-badge.ts +28 -21
- package/dist/components/rel-breadcrumb/README.md +102 -0
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +24 -18
- package/dist/components/rel-button/README.md +130 -0
- package/dist/components/rel-button/rel-button.ts +39 -59
- package/dist/components/rel-calendar/README.md +109 -0
- package/dist/components/rel-calendar/index.ts +1 -0
- package/dist/components/rel-calendar/rel-calendar.ts +467 -0
- package/dist/components/rel-card/README.md +94 -0
- package/dist/components/rel-card/rel-card.ts +21 -11
- package/dist/components/rel-cardmedia/README.md +127 -0
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +22 -18
- package/dist/components/rel-carousel/README.md +166 -0
- package/dist/components/rel-carousel/index.ts +1 -0
- package/dist/components/rel-carousel/rel-carousel.ts +469 -0
- package/dist/components/rel-chart/README.md +286 -0
- package/dist/components/rel-chart/index.ts +1 -0
- package/dist/components/rel-chart/rel-chart.ts +736 -0
- package/dist/components/rel-checkbox/README.md +77 -0
- package/dist/components/rel-checkbox/rel-checkbox.ts +36 -45
- package/dist/components/rel-chip/README.md +114 -0
- package/dist/components/rel-chip/rel-chip.ts +47 -122
- package/dist/components/rel-container/README.md +99 -0
- package/dist/components/rel-container/rel-container.ts +1 -6
- package/dist/components/rel-date-picker/README.md +253 -0
- package/dist/components/rel-date-picker/index.ts +1 -0
- package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
- package/dist/components/rel-divider/README.md +100 -0
- package/dist/components/rel-divider/rel-divider.ts +37 -37
- package/dist/components/rel-drawer/README.md +200 -0
- package/dist/components/rel-drawer/index.ts +1 -0
- package/dist/components/rel-drawer/rel-drawer.ts +621 -0
- package/dist/components/rel-kbd/README.md +151 -0
- package/dist/components/rel-kbd/rel-kbd.ts +118 -0
- package/dist/components/rel-layout/README.md +88 -0
- package/dist/components/rel-layout/rel-layout.ts +12 -14
- package/dist/components/rel-menu/README.md +151 -0
- package/dist/components/rel-menu/rel-menu.ts +18 -10
- package/dist/components/rel-menubar/README.md +329 -0
- package/dist/components/rel-menubar/index.ts +2 -0
- package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
- package/dist/components/rel-menubar/rel-menubar.ts +576 -0
- package/dist/components/rel-nav-menu/README.md +289 -0
- package/dist/components/rel-nav-menu/index.ts +3 -0
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +143 -0
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +331 -0
- package/dist/components/rel-navbar/README.md +126 -0
- package/dist/components/rel-navbar/rel-navbar.ts +91 -0
- package/dist/components/rel-otp/README.md +440 -0
- package/dist/components/rel-otp/index.ts +1 -0
- package/dist/components/rel-otp/rel-otp.ts +504 -0
- package/dist/components/rel-pagination/README.md +410 -0
- package/dist/components/rel-pagination/index.ts +1 -0
- package/dist/components/rel-pagination/rel-pagination.ts +376 -0
- package/dist/components/rel-popover/README.md +568 -0
- package/dist/components/rel-popover/index.ts +2 -0
- package/dist/components/rel-popover/rel-popover-content.ts +55 -0
- package/dist/components/rel-popover/rel-popover.ts +327 -0
- package/dist/components/rel-progress/README.md +110 -0
- package/dist/components/rel-progress/rel-progress.ts +58 -20
- package/dist/components/rel-quote-page/README.md +166 -0
- package/dist/components/rel-quote-page/rel-quote-page.ts +816 -0
- package/dist/components/rel-radio/README.md +120 -0
- package/dist/components/rel-radio/rel-radio.ts +41 -73
- package/dist/components/rel-resizable/README.md +140 -0
- package/dist/components/rel-resizable/index.ts +3 -0
- package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
- package/dist/components/rel-resizable/rel-resizable-handle.ts +149 -0
- package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
- package/dist/components/rel-segmented-button/README.md +141 -0
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
- package/dist/components/rel-select/README.md +107 -0
- package/dist/components/rel-select/rel-select.ts +14 -14
- package/dist/components/rel-sheet/README.md +224 -0
- package/dist/components/rel-sheet/rel-sheet.ts +683 -0
- package/dist/components/rel-sidebar/README.md +209 -0
- package/dist/components/rel-sidebar/rel-sidebar.ts +1158 -0
- package/dist/components/rel-slider/README.md +135 -0
- package/dist/components/rel-slider/rel-slider.ts +19 -7
- package/dist/components/rel-snackbar/README.md +134 -0
- package/dist/components/rel-snackbar/rel-snackbar.ts +73 -2
- package/dist/components/rel-stepper/README.md +160 -0
- package/dist/components/rel-stepper/rel-stepper.ts +29 -8
- package/dist/components/rel-switch/README.md +102 -0
- package/dist/components/rel-switch/rel-switch.ts +22 -11
- package/dist/components/rel-tab/README.md +163 -0
- package/dist/components/rel-tab/rel-tab.ts +46 -22
- package/dist/components/rel-table/README.md +178 -0
- package/dist/components/rel-table/rel-table-body.ts +16 -0
- package/dist/components/rel-table/rel-table-caption.ts +22 -0
- package/dist/components/rel-table/rel-table-cell.ts +49 -0
- package/dist/components/rel-table/rel-table-footer.ts +18 -0
- package/dist/components/rel-table/rel-table-head.ts +33 -0
- package/dist/components/rel-table/rel-table-header.ts +16 -0
- package/dist/components/rel-table/rel-table-row.ts +32 -0
- package/dist/components/rel-table/rel-table.ts +47 -0
- package/dist/components/rel-textfield/README.md +90 -0
- package/dist/components/rel-textfield/rel-textfield.ts +37 -21
- package/dist/components/rel-toast/README.md +148 -0
- package/dist/components/rel-toast/rel-toast.ts +32 -14
- package/dist/components/rel-tooltip/README.md +103 -0
- package/dist/components/rel-tooltip/rel-tooltip.ts +9 -9
- package/dist/index.d.mts +846 -7
- package/dist/index.d.ts +846 -7
- package/dist/index.js +10860 -1803
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10820 -1809
- package/dist/index.mjs.map +1 -1
- package/dist/styles/vars.css +293 -183
- package/dist/vars-ITL2K3KO.css +294 -0
- package/package.json +2 -2
- package/dist/components/rel-accordion/rel-accordion.stories.ts +0 -313
- package/dist/components/rel-alert/rel-alert.stories.ts +0 -412
- package/dist/components/rel-avatar/rel-avatar.stories.ts +0 -327
- package/dist/components/rel-badge/rel-badge.stories.ts +0 -350
- package/dist/components/rel-breadcrumb/rel-breadcrumb.stories.ts +0 -306
- package/dist/components/rel-button/rel-button.stories.ts +0 -356
- package/dist/components/rel-card/rel-card.stories.ts +0 -299
- package/dist/components/rel-cardmedia/rel-cardmedia.stories.ts +0 -368
- package/dist/components/rel-checkbox/rel-checkbox.stories.ts +0 -414
- package/dist/components/rel-chip/rel-chip.stories.ts +0 -552
- package/dist/components/rel-container/rel-container.stories.ts +0 -239
- package/dist/components/rel-divider/rel-divider.stories.ts +0 -229
- package/dist/components/rel-layout/rel-layout.stories.ts +0 -713
- package/dist/components/rel-menu/rel-menu.stories.ts +0 -910
- package/dist/components/rel-progress/rel-progress.stories.ts +0 -817
- package/dist/components/rel-radio/rel-radio.stories.ts +0 -1048
- package/dist/components/rel-segmented-button/rel-segmented-button.stories.ts +0 -1079
- package/dist/components/rel-select/rel-select.stories.ts +0 -1201
- package/dist/components/rel-slider/rel-slider.stories.ts +0 -730
- package/dist/components/rel-snackbar/rel-snackbar.stories.ts +0 -808
- package/dist/components/rel-stepper/rel-stepper.stories.ts +0 -752
- package/dist/components/rel-switch/rel-switch.stories.ts +0 -441
- package/dist/components/rel-tab/rel-tab.stories.ts +0 -492
- package/dist/components/rel-textfield/rel-textfield.stories.ts +0 -710
- package/dist/components/rel-toast/rel-toast.stories.ts +0 -394
- package/dist/components/rel-tooltip/rel-tooltip.stories.ts +0 -364
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
# rel-popover
|
|
2
|
+
|
|
3
|
+
A flexible popover component that displays rich content anchored to a trigger element. Features smart positioning with collision detection, controlled and uncontrolled modes, focus management, and accessibility support.
|
|
4
|
+
|
|
5
|
+
## What It Is
|
|
6
|
+
|
|
7
|
+
The popover consists of two custom elements:
|
|
8
|
+
- `<rel-popover>` - Root controller that manages state, positioning, and interactions
|
|
9
|
+
- `<rel-popover-content>` - Optional styled container for popover content with consistent design tokens
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```html
|
|
14
|
+
<rel-popover>
|
|
15
|
+
<button slot="trigger">Open Popover</button>
|
|
16
|
+
<rel-popover-content>
|
|
17
|
+
<h3>Popover Title</h3>
|
|
18
|
+
<p>This is the popover content. It can contain any HTML elements.</p>
|
|
19
|
+
</rel-popover-content>
|
|
20
|
+
</rel-popover>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Controlled Mode
|
|
24
|
+
|
|
25
|
+
Control the open state externally:
|
|
26
|
+
|
|
27
|
+
```html
|
|
28
|
+
<rel-popover id="my-popover" open>
|
|
29
|
+
<button slot="trigger">Open Popover</button>
|
|
30
|
+
<rel-popover-content>
|
|
31
|
+
<p>Controlled popover content</p>
|
|
32
|
+
<button data-popover-close>Close</button>
|
|
33
|
+
</rel-popover-content>
|
|
34
|
+
</rel-popover>
|
|
35
|
+
|
|
36
|
+
<script>
|
|
37
|
+
const popover = document.getElementById('my-popover');
|
|
38
|
+
|
|
39
|
+
// Listen to open state changes
|
|
40
|
+
popover.addEventListener('rel-open-change', (e) => {
|
|
41
|
+
console.log('Popover is now:', e.detail.open ? 'open' : 'closed');
|
|
42
|
+
|
|
43
|
+
// Update external state
|
|
44
|
+
// You control when to update the 'open' attribute
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Programmatically control
|
|
48
|
+
document.getElementById('external-btn').addEventListener('click', () => {
|
|
49
|
+
popover.open = true;
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Positioning Options
|
|
55
|
+
|
|
56
|
+
```html
|
|
57
|
+
<!-- Position above trigger, aligned to start -->
|
|
58
|
+
<rel-popover side="top" align="start">
|
|
59
|
+
<button slot="trigger">Top Start</button>
|
|
60
|
+
<rel-popover-content>
|
|
61
|
+
<p>Positioned above, aligned to start</p>
|
|
62
|
+
</rel-popover-content>
|
|
63
|
+
</rel-popover>
|
|
64
|
+
|
|
65
|
+
<!-- Position to the right with offset -->
|
|
66
|
+
<rel-popover side="right" sideOffset="16" alignOffset="8">
|
|
67
|
+
<button slot="trigger">Right with Offset</button>
|
|
68
|
+
<rel-popover-content>
|
|
69
|
+
<p>Positioned to right with custom spacing</p>
|
|
70
|
+
</rel-popover-content>
|
|
71
|
+
</rel-popover>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Form Example with Focus Trap
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<rel-popover trapFocus>
|
|
78
|
+
<button slot="trigger">Edit Profile</button>
|
|
79
|
+
<rel-popover-content aria-label="Edit profile form">
|
|
80
|
+
<h3>Edit Profile</h3>
|
|
81
|
+
<form>
|
|
82
|
+
<label>
|
|
83
|
+
Name:
|
|
84
|
+
<input type="text" value="John Doe" />
|
|
85
|
+
</label>
|
|
86
|
+
<label>
|
|
87
|
+
Email:
|
|
88
|
+
<input type="email" value="john@example.com" />
|
|
89
|
+
</label>
|
|
90
|
+
<div style="display: flex; gap: 8px; margin-top: 12px;">
|
|
91
|
+
<button type="submit">Save</button>
|
|
92
|
+
<button type="button" data-popover-close>Cancel</button>
|
|
93
|
+
</div>
|
|
94
|
+
</form>
|
|
95
|
+
</rel-popover-content>
|
|
96
|
+
</rel-popover>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## API Reference
|
|
100
|
+
|
|
101
|
+
### rel-popover
|
|
102
|
+
|
|
103
|
+
Root controller component that manages popover behavior.
|
|
104
|
+
|
|
105
|
+
**Attributes:**
|
|
106
|
+
|
|
107
|
+
- `open` (boolean, optional) - Controlled open state. When set, component is in controlled mode
|
|
108
|
+
- `defaultOpen` (boolean) - Initial open state for uncontrolled mode, default: `false`
|
|
109
|
+
- `disabled` (boolean) - Disables the popover, default: `false`
|
|
110
|
+
- `side` ("top" | "right" | "bottom" | "left") - Preferred side to position popover, default: `"bottom"`
|
|
111
|
+
- `align` ("start" | "center" | "end") - Alignment relative to trigger, default: `"center"`
|
|
112
|
+
- `sideOffset` (number) - Distance from trigger in pixels, default: `8`
|
|
113
|
+
- `alignOffset` (number) - Offset along the alignment axis in pixels, default: `0`
|
|
114
|
+
- `trapFocus` (boolean) - Trap focus within popover (useful for forms), default: `false`
|
|
115
|
+
- `closeOnEscape` (boolean) - Close when Escape key is pressed, default: `true`
|
|
116
|
+
- `closeOnOutsideClick` (boolean) - Close when clicking outside, default: `true`
|
|
117
|
+
|
|
118
|
+
**Events:**
|
|
119
|
+
|
|
120
|
+
- `rel-open-change` - Emitted when open state changes
|
|
121
|
+
```typescript
|
|
122
|
+
detail: { open: boolean }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Slots:**
|
|
126
|
+
|
|
127
|
+
- `trigger` (named) - The trigger element (usually a button)
|
|
128
|
+
- Default slot - The popover content
|
|
129
|
+
|
|
130
|
+
**Example:**
|
|
131
|
+
```javascript
|
|
132
|
+
const popover = document.querySelector('rel-popover');
|
|
133
|
+
|
|
134
|
+
// Listen to state changes
|
|
135
|
+
popover.addEventListener('rel-open-change', (e) => {
|
|
136
|
+
console.log('Open:', e.detail.open);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Programmatic control (controlled mode)
|
|
140
|
+
popover.open = true;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### rel-popover-content
|
|
144
|
+
|
|
145
|
+
Styled container for popover content with consistent design tokens.
|
|
146
|
+
|
|
147
|
+
**Attributes:**
|
|
148
|
+
|
|
149
|
+
- `aria-label` (string) - Accessible label for the popover dialog
|
|
150
|
+
|
|
151
|
+
**Usage:**
|
|
152
|
+
```html
|
|
153
|
+
<rel-popover-content aria-label="User settings">
|
|
154
|
+
<h3>Settings</h3>
|
|
155
|
+
<p>Configure your preferences</p>
|
|
156
|
+
</rel-popover-content>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Styling:**
|
|
160
|
+
The content component provides consistent styling:
|
|
161
|
+
- White background with border and shadow
|
|
162
|
+
- Proper padding and spacing
|
|
163
|
+
- Heading and paragraph styles with design tokens
|
|
164
|
+
- Maximum width of 400px (can be overridden)
|
|
165
|
+
|
|
166
|
+
**Custom Content:**
|
|
167
|
+
You don't have to use `rel-popover-content`. Any HTML can go in the default slot:
|
|
168
|
+
```html
|
|
169
|
+
<rel-popover>
|
|
170
|
+
<button slot="trigger">Open</button>
|
|
171
|
+
<div class="my-custom-popover">
|
|
172
|
+
Custom styled content
|
|
173
|
+
</div>
|
|
174
|
+
</rel-popover>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Controlled vs Uncontrolled
|
|
178
|
+
|
|
179
|
+
### Uncontrolled (Default)
|
|
180
|
+
Component manages its own open state:
|
|
181
|
+
```html
|
|
182
|
+
<rel-popover defaultOpen>
|
|
183
|
+
<button slot="trigger">Toggle</button>
|
|
184
|
+
<rel-popover-content>Content</rel-popover-content>
|
|
185
|
+
</rel-popover>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The component will:
|
|
189
|
+
- Toggle open/close when trigger is clicked
|
|
190
|
+
- Emit `rel-open-change` events
|
|
191
|
+
- Update its internal state automatically
|
|
192
|
+
|
|
193
|
+
### Controlled
|
|
194
|
+
You manage the open state externally:
|
|
195
|
+
```html
|
|
196
|
+
<rel-popover id="controlled" open="${this.isOpen}">
|
|
197
|
+
<button slot="trigger">Toggle</button>
|
|
198
|
+
<rel-popover-content>Content</rel-popover-content>
|
|
199
|
+
</rel-popover>
|
|
200
|
+
|
|
201
|
+
<script>
|
|
202
|
+
let isOpen = false;
|
|
203
|
+
const popover = document.getElementById('controlled');
|
|
204
|
+
|
|
205
|
+
popover.addEventListener('rel-open-change', (e) => {
|
|
206
|
+
isOpen = e.detail.open;
|
|
207
|
+
popover.open = isOpen; // Update the attribute
|
|
208
|
+
});
|
|
209
|
+
</script>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
When `open` attribute is set, the component:
|
|
213
|
+
- Will NOT update its own state
|
|
214
|
+
- Emits `rel-open-change` events
|
|
215
|
+
- You must update the `open` attribute in response to events
|
|
216
|
+
|
|
217
|
+
## Positioning Behavior
|
|
218
|
+
|
|
219
|
+
The popover uses smart positioning with collision detection:
|
|
220
|
+
|
|
221
|
+
### Side Preference
|
|
222
|
+
Specify the preferred side with the `side` attribute:
|
|
223
|
+
- `"bottom"` (default) - Opens below trigger
|
|
224
|
+
- `"top"` - Opens above trigger
|
|
225
|
+
- `"right"` - Opens to the right
|
|
226
|
+
- `"left"` - Opens to the left
|
|
227
|
+
|
|
228
|
+
### Collision Detection
|
|
229
|
+
If the popover would overflow the viewport, it automatically flips to the opposite side:
|
|
230
|
+
- Bottom → Top (if more space above)
|
|
231
|
+
- Top → Bottom (if more space below)
|
|
232
|
+
- Right → Left (if more space on left)
|
|
233
|
+
- Left → Right (if more space on right)
|
|
234
|
+
|
|
235
|
+
### Alignment
|
|
236
|
+
Control how the popover aligns to its trigger:
|
|
237
|
+
|
|
238
|
+
For top/bottom sides:
|
|
239
|
+
- `"start"` - Aligns to left edge of trigger
|
|
240
|
+
- `"center"` (default) - Centers horizontally
|
|
241
|
+
- `"end"` - Aligns to right edge of trigger
|
|
242
|
+
|
|
243
|
+
For left/right sides:
|
|
244
|
+
- `"start"` - Aligns to top edge of trigger
|
|
245
|
+
- `"center"` (default) - Centers vertically
|
|
246
|
+
- `"end"` - Aligns to bottom edge of trigger
|
|
247
|
+
|
|
248
|
+
### Offsets
|
|
249
|
+
Fine-tune positioning with offsets:
|
|
250
|
+
- `sideOffset` - Distance from trigger (default: 8px)
|
|
251
|
+
- `alignOffset` - Shift along alignment axis (default: 0)
|
|
252
|
+
|
|
253
|
+
```html
|
|
254
|
+
<!-- 16px away from trigger, shifted 8px to the right -->
|
|
255
|
+
<rel-popover side="bottom" sideOffset="16" alignOffset="8">
|
|
256
|
+
<button slot="trigger">Custom Offset</button>
|
|
257
|
+
<rel-popover-content>Content</rel-popover-content>
|
|
258
|
+
</rel-popover>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Dynamic Repositioning
|
|
262
|
+
The popover automatically repositions when:
|
|
263
|
+
- Window is resized
|
|
264
|
+
- Page is scrolled
|
|
265
|
+
- Popover content size changes
|
|
266
|
+
|
|
267
|
+
## Close Triggers
|
|
268
|
+
|
|
269
|
+
The popover can close in several ways:
|
|
270
|
+
|
|
271
|
+
1. **Click Outside** (default enabled)
|
|
272
|
+
- Click anywhere outside the popover
|
|
273
|
+
- Controlled by `closeOnOutsideClick` attribute
|
|
274
|
+
|
|
275
|
+
2. **Escape Key** (default enabled)
|
|
276
|
+
- Press Escape to close
|
|
277
|
+
- Controlled by `closeOnEscape` attribute
|
|
278
|
+
|
|
279
|
+
3. **Close Button**
|
|
280
|
+
- Add `data-popover-close` to any element
|
|
281
|
+
```html
|
|
282
|
+
<button data-popover-close>Close</button>
|
|
283
|
+
<button type="button" data-popover-close>Cancel</button>
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
4. **Programmatic**
|
|
287
|
+
```javascript
|
|
288
|
+
popover.open = false;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Focus Management
|
|
292
|
+
|
|
293
|
+
### Default Behavior
|
|
294
|
+
When popover opens:
|
|
295
|
+
1. Saves currently focused element
|
|
296
|
+
2. Focuses first focusable element in popover
|
|
297
|
+
3. Returns focus to trigger when closed
|
|
298
|
+
|
|
299
|
+
### Focus Trap
|
|
300
|
+
Enable focus trapping for forms and modal-like behavior:
|
|
301
|
+
```html
|
|
302
|
+
<rel-popover trapFocus>
|
|
303
|
+
<button slot="trigger">Edit</button>
|
|
304
|
+
<rel-popover-content>
|
|
305
|
+
<form>
|
|
306
|
+
<input type="text" />
|
|
307
|
+
<button>Save</button>
|
|
308
|
+
<button data-popover-close>Cancel</button>
|
|
309
|
+
</form>
|
|
310
|
+
</rel-popover-content>
|
|
311
|
+
</rel-popover>
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
With `trapFocus` enabled:
|
|
315
|
+
- Tab cycles through focusable elements
|
|
316
|
+
- When reaching last element, Tab wraps to first
|
|
317
|
+
- Shift+Tab cycles backward
|
|
318
|
+
- Cannot tab out of the popover
|
|
319
|
+
|
|
320
|
+
## Keyboard Navigation
|
|
321
|
+
|
|
322
|
+
| Key | Action |
|
|
323
|
+
|-----|--------|
|
|
324
|
+
| `Click Trigger` | Toggle popover open/close |
|
|
325
|
+
| `Escape` | Close popover (if `closeOnEscape` is true) |
|
|
326
|
+
| `Tab` | Move to next focusable element |
|
|
327
|
+
| `Shift + Tab` | Move to previous focusable element |
|
|
328
|
+
| `Tab` (with `trapFocus`) | Cycle through elements, wrapping at edges |
|
|
329
|
+
|
|
330
|
+
## Accessibility
|
|
331
|
+
|
|
332
|
+
The component implements WCAG accessibility guidelines:
|
|
333
|
+
|
|
334
|
+
### ARIA Attributes
|
|
335
|
+
- Trigger has `aria-haspopup="dialog"`
|
|
336
|
+
- Trigger has `aria-expanded` reflecting open state
|
|
337
|
+
- Trigger has `aria-controls` pointing to content
|
|
338
|
+
- Content has `role="dialog"`
|
|
339
|
+
- Content can have `aria-label` for screen readers
|
|
340
|
+
|
|
341
|
+
### Focus Management
|
|
342
|
+
- Automatically focuses first focusable element
|
|
343
|
+
- Returns focus to trigger on close
|
|
344
|
+
- Optional focus trapping for modal behavior
|
|
345
|
+
|
|
346
|
+
### Keyboard Support
|
|
347
|
+
- Full keyboard navigation
|
|
348
|
+
- Escape key to close
|
|
349
|
+
- Tab navigation through content
|
|
350
|
+
|
|
351
|
+
### Best Practices
|
|
352
|
+
```html
|
|
353
|
+
<!-- Good: Descriptive trigger and labeled content -->
|
|
354
|
+
<rel-popover>
|
|
355
|
+
<button slot="trigger" aria-label="Open user settings">
|
|
356
|
+
<svg>...</svg>
|
|
357
|
+
</button>
|
|
358
|
+
<rel-popover-content aria-label="User settings">
|
|
359
|
+
<h3>Settings</h3>
|
|
360
|
+
<!-- ... -->
|
|
361
|
+
</rel-popover-content>
|
|
362
|
+
</rel-popover>
|
|
363
|
+
|
|
364
|
+
<!-- Good: Close button with clear label -->
|
|
365
|
+
<button type="button" data-popover-close aria-label="Close settings">
|
|
366
|
+
×
|
|
367
|
+
</button>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Styling Tokens
|
|
371
|
+
|
|
372
|
+
The component uses design tokens from the Reliability Design System:
|
|
373
|
+
|
|
374
|
+
### Colors (rel-popover-content)
|
|
375
|
+
- `--rel-colors-white` - Content background
|
|
376
|
+
- `--rel-colors-gray-border` - Border color
|
|
377
|
+
- `--rel-colors-gray-heading` - Text color
|
|
378
|
+
- `--rel-colors-gray-placeholder` - Paragraph text color
|
|
379
|
+
|
|
380
|
+
### Typography
|
|
381
|
+
- `--rel-fontFamilies-primary` - Font family (Poppins)
|
|
382
|
+
- `--rel-fontWeights-semibold` - Heading weight
|
|
383
|
+
- `--rel-fontSizes-sm` - Base text size
|
|
384
|
+
- `--rel-lineHeights-md` - Paragraph line height
|
|
385
|
+
|
|
386
|
+
### Spacing
|
|
387
|
+
- `--rel-spacing-space-md` (16px) - Content padding
|
|
388
|
+
- `--rel-spacing-space-xs` (8px) - Heading bottom margin
|
|
389
|
+
- `--rel-spacing-space-sm` (12px) - Paragraph bottom margin
|
|
390
|
+
|
|
391
|
+
### Borders & Effects
|
|
392
|
+
- `--rel-borders-width-stroke-100` (1px) - Border width
|
|
393
|
+
- `--rel-borders-radius-border-radius-200` (8px) - Border radius
|
|
394
|
+
- `--rel-shadows-shadow-300` - Drop shadow
|
|
395
|
+
|
|
396
|
+
### Z-Index
|
|
397
|
+
- Popover content: `z-index: 1001`
|
|
398
|
+
|
|
399
|
+
### Custom Styling
|
|
400
|
+
Use CSS parts to style the component:
|
|
401
|
+
```css
|
|
402
|
+
/* Style the trigger wrapper */
|
|
403
|
+
rel-popover::part(trigger) {
|
|
404
|
+
display: block;
|
|
405
|
+
width: 100%;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* Style the content wrapper */
|
|
409
|
+
rel-popover::part(content-wrapper) {
|
|
410
|
+
/* Custom styles */
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/* Style the content container */
|
|
414
|
+
rel-popover-content::part(content) {
|
|
415
|
+
max-width: 600px;
|
|
416
|
+
padding: 24px;
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Complete Example
|
|
421
|
+
|
|
422
|
+
```html
|
|
423
|
+
<!DOCTYPE html>
|
|
424
|
+
<html lang="en">
|
|
425
|
+
<head>
|
|
426
|
+
<meta charset="UTF-8">
|
|
427
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
428
|
+
<title>Popover Demo</title>
|
|
429
|
+
<script type="module" src="./dist/index.js"></script>
|
|
430
|
+
</head>
|
|
431
|
+
<body>
|
|
432
|
+
<div style="padding: 100px;">
|
|
433
|
+
<!-- Basic Popover -->
|
|
434
|
+
<rel-popover id="basic-popover">
|
|
435
|
+
<button slot="trigger">Open Popover</button>
|
|
436
|
+
<rel-popover-content aria-label="Information">
|
|
437
|
+
<h3>About Popovers</h3>
|
|
438
|
+
<p>Popovers are floating containers that display rich content anchored to a trigger element.</p>
|
|
439
|
+
<button data-popover-close>Got it</button>
|
|
440
|
+
</rel-popover-content>
|
|
441
|
+
</rel-popover>
|
|
442
|
+
|
|
443
|
+
<!-- Positioning Variations -->
|
|
444
|
+
<rel-popover side="top" align="start">
|
|
445
|
+
<button slot="trigger">Top Start</button>
|
|
446
|
+
<rel-popover-content>
|
|
447
|
+
<p>Positioned above, aligned to start</p>
|
|
448
|
+
</rel-popover-content>
|
|
449
|
+
</rel-popover>
|
|
450
|
+
|
|
451
|
+
<rel-popover side="right" align="center">
|
|
452
|
+
<button slot="trigger">Right Center</button>
|
|
453
|
+
<rel-popover-content>
|
|
454
|
+
<p>Positioned to the right, centered</p>
|
|
455
|
+
</rel-popover-content>
|
|
456
|
+
</rel-popover>
|
|
457
|
+
|
|
458
|
+
<!-- Form with Focus Trap -->
|
|
459
|
+
<rel-popover id="form-popover" trapFocus>
|
|
460
|
+
<button slot="trigger">Edit Profile</button>
|
|
461
|
+
<rel-popover-content aria-label="Edit profile form">
|
|
462
|
+
<h3>Edit Profile</h3>
|
|
463
|
+
<form id="profile-form">
|
|
464
|
+
<div style="margin-bottom: 12px;">
|
|
465
|
+
<label>
|
|
466
|
+
Name:<br>
|
|
467
|
+
<input type="text" name="name" value="John Doe" style="width: 100%;">
|
|
468
|
+
</label>
|
|
469
|
+
</div>
|
|
470
|
+
<div style="margin-bottom: 12px;">
|
|
471
|
+
<label>
|
|
472
|
+
Email:<br>
|
|
473
|
+
<input type="email" name="email" value="john@example.com" style="width: 100%;">
|
|
474
|
+
</label>
|
|
475
|
+
</div>
|
|
476
|
+
<div style="display: flex; gap: 8px;">
|
|
477
|
+
<button type="submit">Save</button>
|
|
478
|
+
<button type="button" data-popover-close>Cancel</button>
|
|
479
|
+
</div>
|
|
480
|
+
</form>
|
|
481
|
+
</rel-popover-content>
|
|
482
|
+
</rel-popover>
|
|
483
|
+
|
|
484
|
+
<!-- Controlled Mode -->
|
|
485
|
+
<div>
|
|
486
|
+
<button id="external-open">Open Controlled Popover</button>
|
|
487
|
+
<button id="external-close">Close Controlled Popover</button>
|
|
488
|
+
<p>State: <span id="state-display">closed</span></p>
|
|
489
|
+
|
|
490
|
+
<rel-popover id="controlled-popover">
|
|
491
|
+
<button slot="trigger">Controlled Trigger</button>
|
|
492
|
+
<rel-popover-content>
|
|
493
|
+
<h3>Controlled Popover</h3>
|
|
494
|
+
<p>This popover's state is controlled externally.</p>
|
|
495
|
+
<button data-popover-close>Close</button>
|
|
496
|
+
</rel-popover-content>
|
|
497
|
+
</rel-popover>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
|
|
501
|
+
<script>
|
|
502
|
+
// Event logging
|
|
503
|
+
document.getElementById('basic-popover').addEventListener('rel-open-change', (e) => {
|
|
504
|
+
console.log('Basic popover:', e.detail.open ? 'opened' : 'closed');
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// Form handling
|
|
508
|
+
document.getElementById('profile-form').addEventListener('submit', (e) => {
|
|
509
|
+
e.preventDefault();
|
|
510
|
+
const formData = new FormData(e.target);
|
|
511
|
+
console.log('Saved:', Object.fromEntries(formData));
|
|
512
|
+
document.getElementById('form-popover').open = false;
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// Controlled mode
|
|
516
|
+
const controlledPopover = document.getElementById('controlled-popover');
|
|
517
|
+
const stateDisplay = document.getElementById('state-display');
|
|
518
|
+
|
|
519
|
+
let isOpen = false;
|
|
520
|
+
|
|
521
|
+
controlledPopover.addEventListener('rel-open-change', (e) => {
|
|
522
|
+
isOpen = e.detail.open;
|
|
523
|
+
controlledPopover.open = isOpen;
|
|
524
|
+
stateDisplay.textContent = isOpen ? 'open' : 'closed';
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
document.getElementById('external-open').addEventListener('click', () => {
|
|
528
|
+
isOpen = true;
|
|
529
|
+
controlledPopover.open = isOpen;
|
|
530
|
+
stateDisplay.textContent = 'open';
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
document.getElementById('external-close').addEventListener('click', () => {
|
|
534
|
+
isOpen = false;
|
|
535
|
+
controlledPopover.open = isOpen;
|
|
536
|
+
stateDisplay.textContent = 'closed';
|
|
537
|
+
});
|
|
538
|
+
</script>
|
|
539
|
+
</body>
|
|
540
|
+
</html>
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
## Browser Support
|
|
544
|
+
|
|
545
|
+
Compatible with all modern browsers supporting:
|
|
546
|
+
- Web Components (Custom Elements v1)
|
|
547
|
+
- Shadow DOM
|
|
548
|
+
- ES2020+ features
|
|
549
|
+
- ResizeObserver API
|
|
550
|
+
|
|
551
|
+
## Framework Integration
|
|
552
|
+
|
|
553
|
+
Works seamlessly with:
|
|
554
|
+
- Vanilla JavaScript
|
|
555
|
+
- Angular
|
|
556
|
+
- React (via wrapper)
|
|
557
|
+
- Vue
|
|
558
|
+
- Any framework supporting Web Components
|
|
559
|
+
|
|
560
|
+
## Comparison with Tooltip
|
|
561
|
+
|
|
562
|
+
While `rel-tooltip` is for brief labels on hover, `rel-popover` is for:
|
|
563
|
+
- **Rich interactive content** - Forms, menus, complex layouts
|
|
564
|
+
- **Explicit user action** - Click to open (not hover)
|
|
565
|
+
- **Focus management** - Can trap focus for forms
|
|
566
|
+
- **Controlled state** - External state management support
|
|
567
|
+
|
|
568
|
+
Use `rel-tooltip` for simple labels, use `rel-popover` for interactive content.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { LitElement, html, css } from "lit";
|
|
2
|
+
import { customElement, property } from "lit/decorators.js";
|
|
3
|
+
|
|
4
|
+
@customElement("rel-popover-content")
|
|
5
|
+
export class RelPopoverContent extends LitElement {
|
|
6
|
+
@property({ type: String, attribute: "aria-label" }) ariaLabel = "";
|
|
7
|
+
|
|
8
|
+
static styles = css`
|
|
9
|
+
:host {
|
|
10
|
+
display: block;
|
|
11
|
+
width: 100%;
|
|
12
|
+
max-width: 400px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.popover-content {
|
|
16
|
+
background: var(--rel-colors-white);
|
|
17
|
+
border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
|
|
18
|
+
border-radius: var(--rel-borders-radius-border-radius-200);
|
|
19
|
+
box-shadow: var(--rel-shadows-shadow-300);
|
|
20
|
+
padding: var(--rel-spacing-space-md);
|
|
21
|
+
font-family: var(--rel-fontFamilies-primary);
|
|
22
|
+
font-size: var(--rel-fontSizes-sm);
|
|
23
|
+
color: var(--rel-colors-gray-heading);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.popover-content ::slotted(h1),
|
|
27
|
+
.popover-content ::slotted(h2),
|
|
28
|
+
.popover-content ::slotted(h3),
|
|
29
|
+
.popover-content ::slotted(h4),
|
|
30
|
+
.popover-content ::slotted(h5),
|
|
31
|
+
.popover-content ::slotted(h6) {
|
|
32
|
+
margin: 0 0 var(--rel-spacing-space-xs);
|
|
33
|
+
font-weight: var(--rel-fontWeights-semibold);
|
|
34
|
+
color: var(--rel-colors-gray-heading);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.popover-content ::slotted(p) {
|
|
38
|
+
margin: 0 0 var(--rel-spacing-space-sm);
|
|
39
|
+
color: var(--rel-colors-gray-placeholder);
|
|
40
|
+
line-height: var(--rel-lineHeights-md);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.popover-content ::slotted(p:last-child) {
|
|
44
|
+
margin-bottom: 0;
|
|
45
|
+
}
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
return html`
|
|
50
|
+
<div class="popover-content" part="content" role="dialog" aria-label="${this.ariaLabel}">
|
|
51
|
+
<slot></slot>
|
|
52
|
+
</div>
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
}
|