@reliability-design/web 1.4.6 → 1.4.8
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/CHANGELOG.md +246 -49
- package/dist/components/rel-accordion/README.md +133 -132
- package/dist/components/rel-alert/README.md +145 -141
- package/dist/components/rel-autocomplete/README.md +172 -61
- package/dist/components/rel-avatar/README.md +118 -130
- package/dist/components/rel-badge/README.md +97 -93
- package/dist/components/rel-breadcrumb/README.md +96 -102
- package/dist/components/rel-button/README.md +190 -130
- package/dist/components/rel-calendar/README.md +109 -109
- package/dist/components/rel-card/README.md +75 -94
- package/dist/components/rel-cardmedia/README.md +100 -127
- package/dist/components/rel-carousel/README.md +186 -166
- package/dist/components/rel-checkbox/README.md +96 -75
- package/dist/components/rel-chip/README.md +138 -121
- package/dist/components/rel-container/README.md +107 -99
- package/dist/components/rel-date-picker/README.md +200 -289
- package/dist/components/rel-divider/README.md +88 -100
- package/dist/components/rel-drawer/README.md +187 -200
- package/dist/components/rel-kbd/README.md +141 -151
- package/dist/components/rel-layout/README.md +34 -0
- package/dist/components/rel-menu/README.md +180 -151
- package/dist/components/rel-menubar/README.md +218 -329
- package/dist/components/rel-otp/README.md +171 -440
- package/dist/components/rel-pagination/README.md +100 -424
- package/dist/components/rel-popover/README.md +203 -568
- package/dist/components/rel-progress/README.md +88 -110
- package/dist/components/rel-radio/README.md +131 -138
- package/dist/components/rel-range-picker/README.md +165 -0
- package/dist/components/rel-resizable/README.md +199 -140
- package/dist/components/rel-segmented-button/README.md +156 -141
- package/dist/components/rel-select/README.md +156 -93
- package/dist/components/rel-sheet/README.md +168 -224
- package/dist/components/rel-skeleton/README.md +104 -0
- package/dist/components/rel-slider/README.md +131 -135
- package/dist/components/rel-snackbar/README.md +168 -134
- package/dist/components/rel-stepper/README.md +162 -160
- package/dist/components/rel-switch/README.md +117 -102
- package/dist/components/rel-tab/README.md +173 -163
- package/dist/components/rel-textfield/README.md +176 -69
- package/dist/components/rel-time-picker/README.md +122 -157
- package/dist/components/rel-toast/README.md +165 -148
- package/dist/components/rel-tooltip/README.md +119 -111
- package/dist/components/rel-upload/README.md +220 -0
- package/dist/index.d.mts +1726 -447
- package/dist/index.d.ts +1726 -447
- package/dist/index.js +13715 -10870
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +14018 -11163
- package/dist/index.mjs.map +1 -1
- package/dist/styles/focus-ring.ts +84 -0
- package/dist/styles/vars.css +892 -866
- package/dist/{vars-XNMTSRVN.css → vars-DXX6QOAG.css} +892 -866
- package/package.json +3 -2
- package/dist/components/rel-accordion/rel-accordion.ts +0 -336
- package/dist/components/rel-alert/rel-alert.ts +0 -305
- package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -720
- package/dist/components/rel-avatar/rel-avatar.ts +0 -142
- package/dist/components/rel-badge/rel-badge.ts +0 -175
- package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
- package/dist/components/rel-button/rel-button.ts +0 -560
- package/dist/components/rel-calendar/index.ts +0 -1
- package/dist/components/rel-calendar/rel-calendar.ts +0 -565
- package/dist/components/rel-card/rel-card.ts +0 -88
- package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
- package/dist/components/rel-carousel/index.ts +0 -1
- package/dist/components/rel-carousel/rel-carousel.ts +0 -723
- package/dist/components/rel-chart/README.md +0 -286
- package/dist/components/rel-chart/index.ts +0 -1
- package/dist/components/rel-chart/rel-chart.ts +0 -754
- package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
- package/dist/components/rel-chip/rel-chip.ts +0 -483
- package/dist/components/rel-container/rel-container.ts +0 -95
- package/dist/components/rel-date-picker/date-format.test.ts +0 -43
- package/dist/components/rel-date-picker/date-format.ts +0 -47
- package/dist/components/rel-date-picker/date-parse.test.ts +0 -116
- package/dist/components/rel-date-picker/date-parse.ts +0 -178
- package/dist/components/rel-date-picker/index.ts +0 -1
- package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1676
- package/dist/components/rel-divider/rel-divider.ts +0 -54
- package/dist/components/rel-drawer/index.ts +0 -1
- package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
- package/dist/components/rel-drawer/rel-drawer.ts +0 -607
- package/dist/components/rel-kbd/rel-kbd.ts +0 -188
- package/dist/components/rel-layout/rel-layout.ts +0 -49
- package/dist/components/rel-menu/rel-menu.ts +0 -541
- package/dist/components/rel-menubar/index.ts +0 -2
- package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
- package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
- package/dist/components/rel-menubar/rel-menubar.ts +0 -777
- package/dist/components/rel-nav-menu/README.md +0 -289
- package/dist/components/rel-nav-menu/index.ts +0 -3
- package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
- package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
- package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
- package/dist/components/rel-navbar/README.md +0 -126
- package/dist/components/rel-navbar/rel-navbar.ts +0 -105
- package/dist/components/rel-otp/index.ts +0 -1
- package/dist/components/rel-otp/rel-otp.ts +0 -527
- package/dist/components/rel-pagination/index.ts +0 -1
- package/dist/components/rel-pagination/rel-pagination.ts +0 -522
- package/dist/components/rel-popover/index.ts +0 -2
- package/dist/components/rel-popover/rel-popover-content.ts +0 -81
- package/dist/components/rel-popover/rel-popover.ts +0 -475
- package/dist/components/rel-progress/rel-progress.ts +0 -234
- package/dist/components/rel-radio/rel-radio.ts +0 -481
- package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1688
- package/dist/components/rel-resizable/index.ts +0 -3
- package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
- package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
- package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
- package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -217
- package/dist/components/rel-select/rel-select.ts +0 -1416
- package/dist/components/rel-sheet/rel-sheet.ts +0 -610
- package/dist/components/rel-sidebar/README.md +0 -209
- package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1191
- package/dist/components/rel-skeleton/rel-skeleton.ts +0 -143
- package/dist/components/rel-slider/rel-slider.ts +0 -673
- package/dist/components/rel-snackbar/rel-snackbar.ts +0 -329
- package/dist/components/rel-stepper/rel-stepper.ts +0 -567
- package/dist/components/rel-switch/rel-switch.ts +0 -199
- package/dist/components/rel-tab/rel-tab.ts +0 -264
- package/dist/components/rel-table/README.md +0 -178
- package/dist/components/rel-table/rel-table-body.ts +0 -22
- package/dist/components/rel-table/rel-table-caption.ts +0 -30
- package/dist/components/rel-table/rel-table-cell.ts +0 -43
- package/dist/components/rel-table/rel-table-footer.ts +0 -24
- package/dist/components/rel-table/rel-table-head.ts +0 -47
- package/dist/components/rel-table/rel-table-header.ts +0 -22
- package/dist/components/rel-table/rel-table-row.ts +0 -37
- package/dist/components/rel-table/rel-table.ts +0 -62
- package/dist/components/rel-textfield/rel-textfield.ts +0 -1054
- package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1482
- package/dist/components/rel-time-picker/time-parse.test.ts +0 -67
- package/dist/components/rel-time-picker/time-parse.ts +0 -85
- package/dist/components/rel-toast/rel-toast.ts +0 -206
- package/dist/components/rel-tooltip/rel-tooltip.ts +0 -193
- package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
- package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
- package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
- package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
- package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
- package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
- package/dist/components/rel-topbar/rel-topbar.ts +0 -446
- package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
- package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
- package/dist/components/rel-upload/rel-upload.ts +0 -968
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
# rel-nav-menu
|
|
2
|
-
|
|
3
|
-
A horizontal navigation menu component with dropdown panels, perfect for top-level site navigation. Features keyboard navigation, accessible ARIA attributes, and token-driven styling.
|
|
4
|
-
|
|
5
|
-
## What It Is
|
|
6
|
-
|
|
7
|
-
The navigation menu consists of three custom elements:
|
|
8
|
-
- `<rel-nav-menu>` - Root container that manages navigation state and interactions
|
|
9
|
-
- `<rel-nav-menu-item>` - Individual navigation items (can be links or dropdown triggers)
|
|
10
|
-
- `<rel-nav-menu-panel>` - Styled container for dropdown content
|
|
11
|
-
|
|
12
|
-
## Basic Usage
|
|
13
|
-
|
|
14
|
-
```html
|
|
15
|
-
<rel-nav-menu>
|
|
16
|
-
<rel-nav-menu-item label="Features" href="#features"></rel-nav-menu-item>
|
|
17
|
-
<rel-nav-menu-item label="Pricing" href="#pricing"></rel-nav-menu-item>
|
|
18
|
-
<rel-nav-menu-item label="About" href="#about"></rel-nav-menu-item>
|
|
19
|
-
<rel-nav-menu-item label="Contact" href="#contact"></rel-nav-menu-item>
|
|
20
|
-
</rel-nav-menu>
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Dropdown with Content
|
|
24
|
-
|
|
25
|
-
```html
|
|
26
|
-
<rel-nav-menu>
|
|
27
|
-
<rel-nav-menu-item label="Getting started">
|
|
28
|
-
<rel-nav-menu-panel>
|
|
29
|
-
<a href="#intro" data-nav-item>
|
|
30
|
-
<div data-title>Introduction</div>
|
|
31
|
-
<div data-desc>Re-usable components built with Tailwind CSS.</div>
|
|
32
|
-
</a>
|
|
33
|
-
<a href="#install" data-nav-item>
|
|
34
|
-
<div data-title>Installation</div>
|
|
35
|
-
<div data-desc>How to install dependencies and structure your app.</div>
|
|
36
|
-
</a>
|
|
37
|
-
<a href="#typography" data-nav-item>
|
|
38
|
-
<div data-title>Typography</div>
|
|
39
|
-
<div data-desc>Styles for headings, paragraphs, lists...</div>
|
|
40
|
-
</a>
|
|
41
|
-
</rel-nav-menu-panel>
|
|
42
|
-
</rel-nav-menu-item>
|
|
43
|
-
|
|
44
|
-
<rel-nav-menu-item label="Components">
|
|
45
|
-
<rel-nav-menu-panel>
|
|
46
|
-
<a href="#accordion" data-nav-item>
|
|
47
|
-
<div data-title>Accordion</div>
|
|
48
|
-
<div data-desc>Vertically stacked interactive headings.</div>
|
|
49
|
-
</a>
|
|
50
|
-
<a href="#alert" data-nav-item>
|
|
51
|
-
<div data-title>Alert Dialog</div>
|
|
52
|
-
<div data-desc>Modal dialog that interrupts the user.</div>
|
|
53
|
-
</a>
|
|
54
|
-
</rel-nav-menu-panel>
|
|
55
|
-
</rel-nav-menu-item>
|
|
56
|
-
|
|
57
|
-
<rel-nav-menu-item label="Docs" href="#docs"></rel-nav-menu-item>
|
|
58
|
-
</rel-nav-menu>
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Simple Dropdown List
|
|
62
|
-
|
|
63
|
-
For simpler dropdowns without titles and descriptions:
|
|
64
|
-
|
|
65
|
-
```html
|
|
66
|
-
<rel-nav-menu>
|
|
67
|
-
<rel-nav-menu-item label="Status">
|
|
68
|
-
<rel-nav-menu-panel>
|
|
69
|
-
<a href="#backlog" data-nav-item>Backlog</a>
|
|
70
|
-
<a href="#todo" data-nav-item>To Do</a>
|
|
71
|
-
<a href="#in-progress" data-nav-item>In Progress</a>
|
|
72
|
-
<a href="#done" data-nav-item>Done</a>
|
|
73
|
-
</rel-nav-menu-panel>
|
|
74
|
-
</rel-nav-menu-item>
|
|
75
|
-
</rel-nav-menu>
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## API Reference
|
|
79
|
-
|
|
80
|
-
### rel-nav-menu
|
|
81
|
-
|
|
82
|
-
Root navigation container.
|
|
83
|
-
|
|
84
|
-
**Attributes:**
|
|
85
|
-
- `open` (string) - ID of currently open item (reflects to attribute)
|
|
86
|
-
- `disabled` (boolean) - Disables all interactions
|
|
87
|
-
- `density` ("comfortable" | "compact") - Spacing density, default: "comfortable"
|
|
88
|
-
|
|
89
|
-
**Events:**
|
|
90
|
-
- `rel-nav-open-change` - Emitted when open state changes
|
|
91
|
-
```typescript
|
|
92
|
-
detail: { openId: string }
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**Example:**
|
|
96
|
-
```javascript
|
|
97
|
-
const navMenu = document.querySelector('rel-nav-menu');
|
|
98
|
-
navMenu.addEventListener('rel-nav-open-change', (e) => {
|
|
99
|
-
console.log('Open item:', e.detail.openId);
|
|
100
|
-
});
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### rel-nav-menu-item
|
|
104
|
-
|
|
105
|
-
Individual navigation item (link or dropdown trigger).
|
|
106
|
-
|
|
107
|
-
**Attributes:**
|
|
108
|
-
- `label` (string, required) - Text displayed on the trigger
|
|
109
|
-
- `href` (string, optional) - If provided, renders as link (no dropdown)
|
|
110
|
-
- `item-id` (string, optional) - Stable identifier; auto-generated from label if not provided
|
|
111
|
-
- `disabled` (boolean) - Disables this item
|
|
112
|
-
- `icon` (string, optional) - Reserved for future icon support
|
|
113
|
-
|
|
114
|
-
**Behavior:**
|
|
115
|
-
- If `href` is set and no dropdown content → renders as anchor link
|
|
116
|
-
- If dropdown content exists → renders as button with dropdown
|
|
117
|
-
- Auto-generates `item-id` from label if not specified
|
|
118
|
-
|
|
119
|
-
**Slots:**
|
|
120
|
-
- Default slot: dropdown panel content (if any)
|
|
121
|
-
|
|
122
|
-
### rel-nav-menu-panel
|
|
123
|
-
|
|
124
|
-
Styled container for dropdown content.
|
|
125
|
-
|
|
126
|
-
**Usage:**
|
|
127
|
-
Place inside `<rel-nav-menu-item>` to create dropdown content.
|
|
128
|
-
|
|
129
|
-
**Content Structure:**
|
|
130
|
-
Use data attributes for styling:
|
|
131
|
-
- `data-nav-item` - On anchor/link elements for hover styling
|
|
132
|
-
- `data-title` - For the main title text
|
|
133
|
-
- `data-desc` - For the description text
|
|
134
|
-
|
|
135
|
-
## Keyboard Navigation
|
|
136
|
-
|
|
137
|
-
Full keyboard accessibility support:
|
|
138
|
-
|
|
139
|
-
| Key | Action |
|
|
140
|
-
|-----|--------|
|
|
141
|
-
| `Tab` / `Shift+Tab` | Navigate through triggers |
|
|
142
|
-
| `ArrowLeft` | Move focus to previous trigger |
|
|
143
|
-
| `ArrowRight` | Move focus to next trigger |
|
|
144
|
-
| `Enter` / `Space` | Open/close focused dropdown (if it has content) |
|
|
145
|
-
| `ArrowDown` | Move focus into open dropdown panel (first focusable element) |
|
|
146
|
-
| `Escape` | Close open dropdown and return focus to trigger |
|
|
147
|
-
|
|
148
|
-
## Interaction Behavior
|
|
149
|
-
|
|
150
|
-
### Mouse Interactions
|
|
151
|
-
- **Click** a trigger to toggle its dropdown
|
|
152
|
-
- **Click** another trigger to switch dropdowns
|
|
153
|
-
- **Click outside** to close dropdown
|
|
154
|
-
- **Hover switching**: When a dropdown is open, hovering another trigger switches to it
|
|
155
|
-
|
|
156
|
-
### Accessibility
|
|
157
|
-
- Root has `role="navigation"`
|
|
158
|
-
- Dropdown triggers have `aria-haspopup="menu"`, `aria-expanded`, and `aria-controls`
|
|
159
|
-
- Panels have matching `id` attributes
|
|
160
|
-
- Disabled items are marked with `aria-disabled`
|
|
161
|
-
- Focus management with visible focus indicators
|
|
162
|
-
|
|
163
|
-
## Styling Tokens
|
|
164
|
-
|
|
165
|
-
The component uses design tokens from the Reliability Design System:
|
|
166
|
-
|
|
167
|
-
### Colors
|
|
168
|
-
- `--rel-bg-surface-elevated` - Nav container background
|
|
169
|
-
- `--rel-bg-neutral-elevated-1` - Trigger hover/active state, panel item hover
|
|
170
|
-
- `--rel-border-default` - Borders
|
|
171
|
-
- `--rel-text-label` - Text color
|
|
172
|
-
- `--rel-text-placeholder` - Description text
|
|
173
|
-
- `--rel-bg-surface` - Panel background
|
|
174
|
-
- `--rel-bg-info` - Focus outline
|
|
175
|
-
|
|
176
|
-
### Typography
|
|
177
|
-
- `--rel-fonts-font-family` - Font family (Poppins)
|
|
178
|
-
- `--rel-fonts-weight-body-medium` - Trigger text weight
|
|
179
|
-
- `--rel-fonts-weight-body-semibold` - Panel item title weight
|
|
180
|
-
- `--rel-body-sm-regular-font-size` - Trigger and title text size
|
|
181
|
-
- `--rel-body-xs-regular-font-size` - Description text size
|
|
182
|
-
- `--rel-body-xs-regular-line-height` - Description line height
|
|
183
|
-
|
|
184
|
-
### Spacing
|
|
185
|
-
- `--rel-spacing-3xs` (2px) - Compact spacing
|
|
186
|
-
- `--rel-spacing-2xs` (4px) - Container padding, panel gaps
|
|
187
|
-
- `--rel-spacing-xs` (8px) - Trigger padding, panel padding
|
|
188
|
-
- `--rel-spacing-sm` (12px) - Panel item padding
|
|
189
|
-
- `--rel-spacing-md` (16px) - Trigger horizontal padding
|
|
190
|
-
|
|
191
|
-
### Borders & Effects
|
|
192
|
-
- `--rel-stroke-sm` (1px) - Border width
|
|
193
|
-
- `--rel-border-radius-xs` (2px) - Panel item radius
|
|
194
|
-
- `--rel-border-radius-sm` (8px) - Container and panel radius
|
|
195
|
-
- `--rel-shadows-shadow-300` - Panel drop shadow
|
|
196
|
-
|
|
197
|
-
### Z-Index
|
|
198
|
-
- Dropdown panels: `z-index: 1000`
|
|
199
|
-
|
|
200
|
-
## Positioning
|
|
201
|
-
|
|
202
|
-
Dropdown panels use fixed positioning with smart collision detection:
|
|
203
|
-
- **Default**: Opens below trigger, start-aligned
|
|
204
|
-
- **Right overflow**: Shifts left to fit viewport
|
|
205
|
-
- **Bottom overflow**: Flips above trigger if more space available
|
|
206
|
-
|
|
207
|
-
## Complete Example
|
|
208
|
-
|
|
209
|
-
```html
|
|
210
|
-
<rel-nav-menu id="main-nav">
|
|
211
|
-
<rel-nav-menu-item label="Getting started">
|
|
212
|
-
<rel-nav-menu-panel>
|
|
213
|
-
<a href="#intro" data-nav-item>
|
|
214
|
-
<div data-title>Introduction</div>
|
|
215
|
-
<div data-desc>Re-usable components built using Radix UI and Tailwind CSS.</div>
|
|
216
|
-
</a>
|
|
217
|
-
<a href="#installation" data-nav-item>
|
|
218
|
-
<div data-title>Installation</div>
|
|
219
|
-
<div data-desc>How to install dependencies and structure your app.</div>
|
|
220
|
-
</a>
|
|
221
|
-
<a href="#typography" data-nav-item>
|
|
222
|
-
<div data-title>Typography</div>
|
|
223
|
-
<div data-desc>Styles for headings, paragraphs, lists, and more.</div>
|
|
224
|
-
</a>
|
|
225
|
-
</rel-nav-menu-panel>
|
|
226
|
-
</rel-nav-menu-item>
|
|
227
|
-
|
|
228
|
-
<rel-nav-menu-item label="Components">
|
|
229
|
-
<rel-nav-menu-panel>
|
|
230
|
-
<a href="#accordion" data-nav-item>
|
|
231
|
-
<div data-title>Accordion</div>
|
|
232
|
-
<div data-desc>A vertically stacked set of interactive headings.</div>
|
|
233
|
-
</a>
|
|
234
|
-
<a href="#alert-dialog" data-nav-item>
|
|
235
|
-
<div data-title>Alert Dialog</div>
|
|
236
|
-
<div data-desc>A modal dialog that interrupts the user.</div>
|
|
237
|
-
</a>
|
|
238
|
-
<a href="#avatar" data-nav-item>
|
|
239
|
-
<div data-title>Avatar</div>
|
|
240
|
-
<div data-desc>An image element with a fallback.</div>
|
|
241
|
-
</a>
|
|
242
|
-
</rel-nav-menu-panel>
|
|
243
|
-
</rel-nav-menu-item>
|
|
244
|
-
|
|
245
|
-
<rel-nav-menu-item label="Status">
|
|
246
|
-
<rel-nav-menu-panel>
|
|
247
|
-
<a href="#backlog" data-nav-item>Backlog</a>
|
|
248
|
-
<a href="#todo" data-nav-item>To Do</a>
|
|
249
|
-
<a href="#in-progress" data-nav-item>In Progress</a>
|
|
250
|
-
<a href="#done" data-nav-item>Done</a>
|
|
251
|
-
</rel-nav-menu-panel>
|
|
252
|
-
</rel-nav-menu-item>
|
|
253
|
-
|
|
254
|
-
<rel-nav-menu-item label="Documentation" href="#docs"></rel-nav-menu-item>
|
|
255
|
-
</rel-nav-menu>
|
|
256
|
-
|
|
257
|
-
<script>
|
|
258
|
-
const navMenu = document.getElementById('main-nav');
|
|
259
|
-
navMenu.addEventListener('rel-nav-open-change', (e) => {
|
|
260
|
-
console.log('Navigation state:', e.detail.openId);
|
|
261
|
-
});
|
|
262
|
-
</script>
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
## Browser Support
|
|
266
|
-
|
|
267
|
-
Compatible with all modern browsers supporting:
|
|
268
|
-
- Web Components (Custom Elements v1)
|
|
269
|
-
- Shadow DOM
|
|
270
|
-
- ES2020+ features
|
|
271
|
-
|
|
272
|
-
## Framework Integration
|
|
273
|
-
|
|
274
|
-
Works seamlessly with:
|
|
275
|
-
- Vanilla JavaScript
|
|
276
|
-
- Angular
|
|
277
|
-
- React (via wrapper)
|
|
278
|
-
- Vue
|
|
279
|
-
- Any framework supporting Web Components
|
|
280
|
-
|
|
281
|
-
## Comparison with Menubar
|
|
282
|
-
|
|
283
|
-
While `rel-menubar` is designed for application menus (File/Edit/View), `rel-nav-menu` is optimized for:
|
|
284
|
-
- **Website navigation** - Top-level site sections
|
|
285
|
-
- **Content-rich dropdowns** - With titles, descriptions, and icons
|
|
286
|
-
- **Flexible layouts** - Horizontal navigation bars
|
|
287
|
-
- **Marketing sites** - Product features, documentation, etc.
|
|
288
|
-
|
|
289
|
-
Use `rel-menubar` for desktop application UIs, use `rel-nav-menu` for website navigation.
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
2
|
-
import { customElement, property } from "lit/decorators.js";
|
|
3
|
-
|
|
4
|
-
@customElement("rel-nav-menu-item")
|
|
5
|
-
export class RelNavMenuItem extends LitElement {
|
|
6
|
-
@property({ type: String }) label = "";
|
|
7
|
-
@property({ type: String }) href = "";
|
|
8
|
-
@property({ type: String, attribute: "item-id" }) itemId = "";
|
|
9
|
-
@property({ type: Boolean, reflect: true }) disabled = false;
|
|
10
|
-
@property({ type: String }) icon = "";
|
|
11
|
-
|
|
12
|
-
/** Promoted from @state so rel-nav-menu can read it as a property. */
|
|
13
|
-
@property({ type: Boolean, reflect: true }) hasContent = false;
|
|
14
|
-
|
|
15
|
-
/** Set by rel-nav-menu to open/close this item's panel. */
|
|
16
|
-
@property({ type: Boolean, reflect: true }) open = false;
|
|
17
|
-
|
|
18
|
-
static styles = css`
|
|
19
|
-
:host {
|
|
20
|
-
color: var(--rel-text-default);
|
|
21
|
-
display: inline-block;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.trigger {
|
|
25
|
-
display: inline-flex;
|
|
26
|
-
align-items: center;
|
|
27
|
-
gap: var(--rel-spacing-2xs);
|
|
28
|
-
padding: var(--rel-spacing-xs) var(--rel-spacing-md);
|
|
29
|
-
color: var(--rel-text-label);
|
|
30
|
-
background: transparent;
|
|
31
|
-
border: none;
|
|
32
|
-
border-radius: var(--rel-border-radius-sm);
|
|
33
|
-
cursor: pointer;
|
|
34
|
-
transition: all 0.15s ease;
|
|
35
|
-
text-decoration: none;
|
|
36
|
-
outline: none;
|
|
37
|
-
user-select: none;
|
|
38
|
-
font-family: var(--rel-body-sm-medium-font-family);
|
|
39
|
-
font-weight: var(--rel-body-sm-medium-font-weight);
|
|
40
|
-
font-size: var(--rel-body-sm-medium-font-size);
|
|
41
|
-
line-height: var(--rel-body-sm-medium-line-height);
|
|
42
|
-
letter-spacing: var(--rel-body-sm-medium-letter-spacing);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.trigger:hover:not(:disabled) {
|
|
46
|
-
background: var(--rel-bg-surface-elevated);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.trigger:focus-visible {
|
|
50
|
-
outline: 2px solid var(--rel-border-focus);
|
|
51
|
-
outline-offset: 2px;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/* Active state driven by :host([open]) — no external class manipulation needed */
|
|
55
|
-
:host([open]) .trigger,
|
|
56
|
-
.trigger.active {
|
|
57
|
-
background: var(--rel-bg-surface-elevated);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
.trigger:disabled {
|
|
61
|
-
opacity: 0.5;
|
|
62
|
-
cursor: not-allowed;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
:host([disabled]) .trigger {
|
|
66
|
-
opacity: 0.5;
|
|
67
|
-
cursor: not-allowed;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/* Panel lives inside the item's own shadow DOM */
|
|
71
|
-
.panel {
|
|
72
|
-
position: fixed;
|
|
73
|
-
min-width: 320px;
|
|
74
|
-
max-width: 600px;
|
|
75
|
-
background: var(--rel-bg-surface);
|
|
76
|
-
border: var(--rel-stroke-sm) solid var(--rel-border-default);
|
|
77
|
-
border-radius: var(--rel-border-radius-sm);
|
|
78
|
-
box-shadow: var(--rel-shadows-shadow-300);
|
|
79
|
-
padding: var(--rel-spacing-xs);
|
|
80
|
-
z-index: 1000;
|
|
81
|
-
display: none;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.panel.open {
|
|
85
|
-
display: block;
|
|
86
|
-
}
|
|
87
|
-
`;
|
|
88
|
-
|
|
89
|
-
connectedCallback() {
|
|
90
|
-
super.connectedCallback();
|
|
91
|
-
if (!this.itemId && this.label) {
|
|
92
|
-
this.itemId = this.label.toLowerCase().replace(/\s+/g, "-");
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
firstUpdated() {
|
|
97
|
-
this.updateHasContent();
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
updated(changed: Map<string, any>) {
|
|
101
|
-
if (changed.has("open") && this.open) {
|
|
102
|
-
// Wait for paint so the panel has layout dimensions before positioning.
|
|
103
|
-
this.updateComplete.then(() => this.positionPanel());
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ── Content detection ──────────────────────────────────────────────────────
|
|
108
|
-
|
|
109
|
-
private updateHasContent() {
|
|
110
|
-
const slot = this.shadowRoot?.querySelector("slot");
|
|
111
|
-
if (slot) {
|
|
112
|
-
const nodes = slot.assignedNodes({ flatten: true });
|
|
113
|
-
this.hasContent = nodes.some(
|
|
114
|
-
(node) => node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim()
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private handleSlotChange() {
|
|
120
|
-
this.updateHasContent();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// ── Panel positioning (moved from rel-nav-menu) ────────────────────────────
|
|
124
|
-
|
|
125
|
-
private positionPanel() {
|
|
126
|
-
const trigger = this.shadowRoot?.querySelector(".trigger") as HTMLElement;
|
|
127
|
-
const panel = this.shadowRoot?.querySelector(".panel") as HTMLElement;
|
|
128
|
-
if (!trigger || !panel) return;
|
|
129
|
-
|
|
130
|
-
const triggerRect = trigger.getBoundingClientRect();
|
|
131
|
-
const panelRect = panel.getBoundingClientRect();
|
|
132
|
-
const viewportWidth = window.innerWidth;
|
|
133
|
-
const viewportHeight = window.innerHeight;
|
|
134
|
-
|
|
135
|
-
let top = triggerRect.bottom + 8;
|
|
136
|
-
let left = triggerRect.left;
|
|
137
|
-
|
|
138
|
-
// Handle right overflow
|
|
139
|
-
if (left + panelRect.width > viewportWidth) {
|
|
140
|
-
left = viewportWidth - panelRect.width - 16;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Handle left overflow
|
|
144
|
-
if (left < 0) left = 16;
|
|
145
|
-
|
|
146
|
-
// Handle bottom overflow — flip above if more space
|
|
147
|
-
if (top + panelRect.height > viewportHeight) {
|
|
148
|
-
const spaceAbove = triggerRect.top;
|
|
149
|
-
const spaceBelow = viewportHeight - triggerRect.bottom;
|
|
150
|
-
if (spaceAbove > spaceBelow && spaceAbove > panelRect.height) {
|
|
151
|
-
top = triggerRect.top - panelRect.height - 8;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
panel.style.top = `${top}px`;
|
|
156
|
-
panel.style.left = `${left}px`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// ── Events dispatched upward — rel-nav-menu listens via connectedCallback ──
|
|
160
|
-
|
|
161
|
-
private handleTriggerClick() {
|
|
162
|
-
if (this.disabled) return;
|
|
163
|
-
this.dispatchEvent(
|
|
164
|
-
new CustomEvent("rel-nav-item-click", {
|
|
165
|
-
detail: { itemId: this.itemId, hasContent: this.hasContent },
|
|
166
|
-
bubbles: true,
|
|
167
|
-
composed: true,
|
|
168
|
-
})
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private handleTriggerMouseEnter() {
|
|
173
|
-
if (this.disabled) return;
|
|
174
|
-
this.dispatchEvent(
|
|
175
|
-
new CustomEvent("rel-nav-item-hover", {
|
|
176
|
-
detail: { itemId: this.itemId },
|
|
177
|
-
bubbles: true,
|
|
178
|
-
composed: true,
|
|
179
|
-
})
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ── ID helpers ─────────────────────────────────────────────────────────────
|
|
184
|
-
|
|
185
|
-
get triggerId() {
|
|
186
|
-
return `nav-trigger-${this.itemId}`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
get panelId() {
|
|
190
|
-
return `nav-panel-${this.itemId}`;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ── Render ─────────────────────────────────────────────────────────────────
|
|
194
|
-
|
|
195
|
-
render() {
|
|
196
|
-
const isLink = !!this.href && !this.hasContent;
|
|
197
|
-
|
|
198
|
-
return html`
|
|
199
|
-
${isLink
|
|
200
|
-
? html`
|
|
201
|
-
<a
|
|
202
|
-
class="trigger"
|
|
203
|
-
href="${this.href}"
|
|
204
|
-
id="${this.triggerId}"
|
|
205
|
-
?disabled="${this.disabled}"
|
|
206
|
-
@click="${this.handleTriggerClick}"
|
|
207
|
-
@mouseenter="${this.handleTriggerMouseEnter}"
|
|
208
|
-
>
|
|
209
|
-
${this.label}
|
|
210
|
-
</a>
|
|
211
|
-
`
|
|
212
|
-
: html`
|
|
213
|
-
<button
|
|
214
|
-
class="trigger"
|
|
215
|
-
id="${this.triggerId}"
|
|
216
|
-
type="button"
|
|
217
|
-
?disabled="${this.disabled}"
|
|
218
|
-
aria-haspopup="${this.hasContent ? "menu" : "false"}"
|
|
219
|
-
aria-controls="${this.hasContent ? this.panelId : ""}"
|
|
220
|
-
aria-expanded="${this.open ? "true" : "false"}"
|
|
221
|
-
@click="${this.handleTriggerClick}"
|
|
222
|
-
@mouseenter="${this.handleTriggerMouseEnter}"
|
|
223
|
-
>
|
|
224
|
-
${this.label}
|
|
225
|
-
</button>
|
|
226
|
-
`}
|
|
227
|
-
|
|
228
|
-
<!--
|
|
229
|
-
Panel rendered inside this shadow root.
|
|
230
|
-
Slot projects the consumer's light-DOM children directly — no cloning.
|
|
231
|
-
rel-nav-menu sets item.open = true/false to show/hide.
|
|
232
|
-
-->
|
|
233
|
-
<div
|
|
234
|
-
class="panel ${this.open ? "open" : ""}"
|
|
235
|
-
id="${this.panelId}"
|
|
236
|
-
role="menu"
|
|
237
|
-
@click="${(e: Event) => e.stopPropagation()}"
|
|
238
|
-
>
|
|
239
|
-
<slot @slotchange="${this.handleSlotChange}"></slot>
|
|
240
|
-
</div>
|
|
241
|
-
`;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { LitElement, html, css } from "lit";
|
|
2
|
-
import { customElement } from "lit/decorators.js";
|
|
3
|
-
|
|
4
|
-
@customElement("rel-nav-menu-panel")
|
|
5
|
-
export class RelNavMenuPanel extends LitElement {
|
|
6
|
-
static styles = css`
|
|
7
|
-
:host {
|
|
8
|
-
color: var(--rel-text-default);
|
|
9
|
-
display: block;
|
|
10
|
-
width: 100%;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.panel-content {
|
|
14
|
-
display: flex;
|
|
15
|
-
flex-direction: column;
|
|
16
|
-
gap: var(--rel-spacing-2xs);
|
|
17
|
-
padding: var(--rel-spacing-xs);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
::slotted(a[data-nav-item]) {
|
|
21
|
-
display: block;
|
|
22
|
-
padding: var(--rel-spacing-sm);
|
|
23
|
-
border-radius: var(--rel-border-radius-xs);
|
|
24
|
-
transition: background 0.15s ease;
|
|
25
|
-
text-decoration: none;
|
|
26
|
-
color: inherit;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
::slotted(a[data-nav-item]:hover) {
|
|
30
|
-
background: var(--rel-bg-surface-elevated);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
::slotted([data-title]) {
|
|
34
|
-
color: var(--rel-text-label);
|
|
35
|
-
margin-bottom: var(--rel-spacing-3xs);
|
|
36
|
-
|
|
37
|
-
font-family: var(--rel-body-sm-semibold-font-family);
|
|
38
|
-
|
|
39
|
-
font-weight: var(--rel-body-sm-semibold-font-weight);
|
|
40
|
-
|
|
41
|
-
font-size: var(--rel-body-sm-semibold-font-size);
|
|
42
|
-
|
|
43
|
-
line-height: var(--rel-body-sm-semibold-line-height);
|
|
44
|
-
|
|
45
|
-
letter-spacing: var(--rel-body-sm-semibold-letter-spacing);
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
::slotted([data-desc]) {
|
|
50
|
-
color: var(--rel-text-placeholder);
|
|
51
|
-
|
|
52
|
-
font-family: var(--rel-body-xs-regular-font-family);
|
|
53
|
-
|
|
54
|
-
font-weight: var(--rel-body-xs-regular-font-weight);
|
|
55
|
-
|
|
56
|
-
font-size: var(--rel-body-xs-regular-font-size);
|
|
57
|
-
|
|
58
|
-
line-height: var(--rel-body-xs-regular-line-height);
|
|
59
|
-
|
|
60
|
-
letter-spacing: var(--rel-body-xs-regular-letter-spacing);
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
`;
|
|
64
|
-
|
|
65
|
-
render() {
|
|
66
|
-
return html`
|
|
67
|
-
<div class="panel-content">
|
|
68
|
-
<slot></slot>
|
|
69
|
-
</div>
|
|
70
|
-
`;
|
|
71
|
-
}
|
|
72
|
-
}
|