@reliability-design/web 1.0.3 → 1.1.1

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.
Files changed (126) hide show
  1. package/dist/components/rel-accordion/README.md +132 -0
  2. package/dist/components/rel-accordion/rel-accordion.ts +47 -16
  3. package/dist/components/rel-alert/README.md +141 -0
  4. package/dist/components/rel-alert/rel-alert.ts +101 -20
  5. package/dist/components/rel-avatar/README.md +119 -0
  6. package/dist/components/rel-avatar/rel-avatar.ts +3 -2
  7. package/dist/components/rel-badge/README.md +86 -0
  8. package/dist/components/rel-badge/rel-badge.ts +11 -7
  9. package/dist/components/rel-breadcrumb/README.md +102 -0
  10. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +12 -6
  11. package/dist/components/rel-button/README.md +130 -0
  12. package/dist/components/rel-button/rel-button.ts +64 -8
  13. package/dist/components/rel-calendar/README.md +109 -0
  14. package/dist/components/rel-calendar/index.ts +1 -0
  15. package/dist/components/rel-calendar/rel-calendar.ts +467 -0
  16. package/dist/components/rel-card/README.md +94 -0
  17. package/dist/components/rel-card/rel-card.ts +12 -3
  18. package/dist/components/rel-cardmedia/README.md +127 -0
  19. package/dist/components/rel-cardmedia/rel-cardmedia.ts +14 -4
  20. package/dist/components/rel-carousel/README.md +166 -0
  21. package/dist/components/rel-carousel/index.ts +1 -0
  22. package/dist/components/rel-carousel/rel-carousel.ts +468 -0
  23. package/dist/components/rel-chart/README.md +286 -0
  24. package/dist/components/rel-chart/index.ts +1 -0
  25. package/dist/components/rel-chart/rel-chart.ts +736 -0
  26. package/dist/components/rel-checkbox/README.md +77 -0
  27. package/dist/components/rel-checkbox/rel-checkbox.ts +51 -12
  28. package/dist/components/rel-chip/README.md +114 -0
  29. package/dist/components/rel-chip/rel-chip.ts +83 -9
  30. package/dist/components/rel-container/README.md +99 -0
  31. package/dist/components/rel-date-picker/README.md +253 -0
  32. package/dist/components/rel-date-picker/index.ts +1 -0
  33. package/dist/components/rel-date-picker/rel-date-picker.ts +916 -0
  34. package/dist/components/rel-divider/README.md +100 -0
  35. package/dist/components/rel-divider/rel-divider.ts +9 -0
  36. package/dist/components/rel-drawer/README.md +200 -0
  37. package/dist/components/rel-drawer/index.ts +1 -0
  38. package/dist/components/rel-drawer/rel-drawer-footer.ts +29 -0
  39. package/dist/components/rel-drawer/rel-drawer.ts +612 -0
  40. package/dist/components/rel-kbd/README.md +151 -0
  41. package/dist/components/rel-kbd/rel-kbd.ts +118 -0
  42. package/dist/components/rel-layout/README.md +88 -0
  43. package/dist/components/rel-menu/README.md +151 -0
  44. package/dist/components/rel-menu/rel-menu.ts +64 -24
  45. package/dist/components/rel-menubar/README.md +329 -0
  46. package/dist/components/rel-menubar/index.ts +2 -0
  47. package/dist/components/rel-menubar/rel-menubar-menu.ts +16 -0
  48. package/dist/components/rel-menubar/rel-menubar.ts +605 -0
  49. package/dist/components/rel-nav-menu/README.md +289 -0
  50. package/dist/components/rel-nav-menu/index.ts +3 -0
  51. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +240 -0
  52. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +53 -0
  53. package/dist/components/rel-nav-menu/rel-nav-menu.ts +227 -0
  54. package/dist/components/rel-navbar/README.md +126 -0
  55. package/dist/components/rel-navbar/rel-navbar.ts +104 -0
  56. package/dist/components/rel-otp/README.md +440 -0
  57. package/dist/components/rel-otp/index.ts +1 -0
  58. package/dist/components/rel-otp/rel-otp.ts +504 -0
  59. package/dist/components/rel-pagination/README.md +410 -0
  60. package/dist/components/rel-pagination/index.ts +1 -0
  61. package/dist/components/rel-pagination/rel-pagination.ts +381 -0
  62. package/dist/components/rel-popover/README.md +568 -0
  63. package/dist/components/rel-popover/index.ts +2 -0
  64. package/dist/components/rel-popover/rel-popover-content.ts +55 -0
  65. package/dist/components/rel-popover/rel-popover.ts +330 -0
  66. package/dist/components/rel-progress/README.md +110 -0
  67. package/dist/components/rel-progress/rel-progress.ts +32 -5
  68. package/dist/components/rel-radio/README.md +120 -0
  69. package/dist/components/rel-radio/rel-radio.ts +31 -6
  70. package/dist/components/rel-resizable/README.md +140 -0
  71. package/dist/components/rel-resizable/index.ts +3 -0
  72. package/dist/components/rel-resizable/rel-resizable-group.ts +374 -0
  73. package/dist/components/rel-resizable/rel-resizable-handle.ts +145 -0
  74. package/dist/components/rel-resizable/rel-resizable-panel.ts +47 -0
  75. package/dist/components/rel-segmented-button/README.md +141 -0
  76. package/dist/components/rel-segmented-button/rel-segmented-button.ts +18 -13
  77. package/dist/components/rel-select/README.md +107 -0
  78. package/dist/components/rel-select/rel-select.ts +28 -14
  79. package/dist/components/rel-sheet/README.md +224 -0
  80. package/dist/components/rel-sheet/rel-sheet.ts +684 -0
  81. package/dist/components/rel-sidebar/README.md +209 -0
  82. package/dist/components/rel-sidebar/rel-sidebar.ts +1166 -0
  83. package/dist/components/rel-slider/README.md +135 -0
  84. package/dist/components/rel-slider/rel-slider.ts +44 -9
  85. package/dist/components/rel-snackbar/README.md +134 -0
  86. package/dist/components/rel-snackbar/rel-snackbar.ts +109 -14
  87. package/dist/components/rel-stepper/README.md +160 -0
  88. package/dist/components/rel-stepper/rel-stepper.ts +38 -27
  89. package/dist/components/rel-switch/README.md +102 -0
  90. package/dist/components/rel-switch/rel-switch.ts +34 -12
  91. package/dist/components/rel-tab/README.md +163 -0
  92. package/dist/components/rel-tab/rel-tab.ts +79 -23
  93. package/dist/components/rel-table/README.md +178 -0
  94. package/dist/components/rel-table/rel-table-body.ts +21 -0
  95. package/dist/components/rel-table/rel-table-caption.ts +27 -0
  96. package/dist/components/rel-table/rel-table-cell.ts +42 -0
  97. package/dist/components/rel-table/rel-table-footer.ts +23 -0
  98. package/dist/components/rel-table/rel-table-head.ts +45 -0
  99. package/dist/components/rel-table/rel-table-header.ts +21 -0
  100. package/dist/components/rel-table/rel-table-row.ts +36 -0
  101. package/dist/components/rel-table/rel-table.ts +53 -0
  102. package/dist/components/rel-textfield/README.md +90 -0
  103. package/dist/components/rel-textfield/rel-textfield.ts +55 -39
  104. package/dist/components/rel-toast/README.md +148 -0
  105. package/dist/components/rel-toast/rel-toast.ts +63 -24
  106. package/dist/components/rel-tooltip/README.md +103 -0
  107. package/dist/components/rel-tooltip/rel-tooltip.ts +123 -80
  108. package/dist/components/rel-topbar/rel-topbar-actions.ts +23 -0
  109. package/dist/components/rel-topbar/rel-topbar-brand.ts +46 -0
  110. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +33 -0
  111. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +28 -0
  112. package/dist/components/rel-topbar/rel-topbar-item.ts +315 -0
  113. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +23 -0
  114. package/dist/components/rel-topbar/rel-topbar-profile.ts +248 -0
  115. package/dist/components/rel-topbar/rel-topbar-utilities.ts +23 -0
  116. package/dist/components/rel-topbar/rel-topbar.ts +329 -0
  117. package/dist/index.d.mts +831 -7
  118. package/dist/index.d.ts +831 -7
  119. package/dist/index.js +9500 -1564
  120. package/dist/index.js.map +1 -1
  121. package/dist/index.mjs +9240 -1344
  122. package/dist/index.mjs.map +1 -1
  123. package/dist/styles/vars.css +187 -25
  124. package/dist/vars-TBB4BMSD.css +346 -0
  125. package/package.json +2 -1
  126. package/dist/vars-6ZIUPIU4.css +0 -184
@@ -0,0 +1,289 @@
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-colors-gray-light` - Nav container background
169
+ - `--rel-colors-gray-background` - Trigger hover/active state, panel item hover
170
+ - `--rel-colors-gray-border` - Borders
171
+ - `--rel-colors-gray-heading` - Text color
172
+ - `--rel-colors-gray-placeholder` - Description text
173
+ - `--rel-colors-white` - Panel background
174
+ - `--rel-colors-blue-primary` - Focus outline
175
+
176
+ ### Typography
177
+ - `--rel-fontFamilies-primary` - Font family (Poppins)
178
+ - `--rel-fontWeights-medium` - Trigger text weight
179
+ - `--rel-fontWeights-semibold` - Panel item title weight
180
+ - `--rel-fontSizes-sm` - Trigger and title text size
181
+ - `--rel-fontSizes-xs` - Description text size
182
+ - `--rel-lineHeights-xs` - Description line height
183
+
184
+ ### Spacing
185
+ - `--rel-spacing-space-3xs` (2px) - Compact spacing
186
+ - `--rel-spacing-space-2xs` (4px) - Container padding, panel gaps
187
+ - `--rel-spacing-space-xs` (8px) - Trigger padding, panel padding
188
+ - `--rel-spacing-space-sm` (12px) - Panel item padding
189
+ - `--rel-spacing-space-md` (16px) - Trigger horizontal padding
190
+
191
+ ### Borders & Effects
192
+ - `--rel-borders-width-stroke-100` (1px) - Border width
193
+ - `--rel-borders-radius-border-radius-100` (2px) - Panel item radius
194
+ - `--rel-borders-radius-border-radius-200` (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.
@@ -0,0 +1,3 @@
1
+ export * from "./rel-nav-menu";
2
+ export * from "./rel-nav-menu-item";
3
+ export * from "./rel-nav-menu-panel";
@@ -0,0 +1,240 @@
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
+ display: inline-block;
21
+ }
22
+
23
+ .trigger {
24
+ display: inline-flex;
25
+ align-items: center;
26
+ gap: var(--rel-spacing-space-2xs);
27
+ padding: var(--rel-spacing-space-xs) var(--rel-spacing-space-md);
28
+ font-family: var(--rel-fontFamilies-primary);
29
+ font-size: var(--rel-fontSizes-sm);
30
+ font-weight: var(--rel-fontWeights-medium);
31
+ color: var(--rel-colors-gray-heading);
32
+ background: transparent;
33
+ border: none;
34
+ border-radius: var(--rel-borders-radius-border-radius-200);
35
+ cursor: pointer;
36
+ transition: all 0.15s ease;
37
+ text-decoration: none;
38
+ outline: none;
39
+ user-select: none;
40
+ }
41
+
42
+ .trigger:hover:not(:disabled) {
43
+ background: var(--rel-surface-secondary, #fafafa);
44
+ }
45
+
46
+ .trigger:focus-visible {
47
+ outline: 2px solid var(--rel-colors-blue-primary);
48
+ outline-offset: 2px;
49
+ }
50
+
51
+ /* Active state driven by :host([open]) — no external class manipulation needed */
52
+ :host([open]) .trigger,
53
+ .trigger.active {
54
+ background: var(--rel-surface-secondary, #fafafa);
55
+ }
56
+
57
+ .trigger:disabled {
58
+ opacity: 0.5;
59
+ cursor: not-allowed;
60
+ }
61
+
62
+ :host([disabled]) .trigger {
63
+ opacity: 0.5;
64
+ cursor: not-allowed;
65
+ }
66
+
67
+ /* Panel lives inside the item's own shadow DOM */
68
+ .panel {
69
+ position: fixed;
70
+ min-width: 320px;
71
+ max-width: 600px;
72
+ background: var(--rel-colors-white);
73
+ border: var(--rel-borders-width-stroke-100) solid var(--rel-colors-gray-border);
74
+ border-radius: var(--rel-borders-radius-border-radius-200);
75
+ box-shadow: var(--rel-shadows-shadow-300);
76
+ padding: var(--rel-spacing-space-xs);
77
+ z-index: 1000;
78
+ display: none;
79
+ }
80
+
81
+ .panel.open {
82
+ display: block;
83
+ }
84
+ `;
85
+
86
+ connectedCallback() {
87
+ super.connectedCallback();
88
+ if (!this.itemId && this.label) {
89
+ this.itemId = this.label.toLowerCase().replace(/\s+/g, "-");
90
+ }
91
+ }
92
+
93
+ firstUpdated() {
94
+ this.updateHasContent();
95
+ }
96
+
97
+ updated(changed: Map<string, any>) {
98
+ if (changed.has("open") && this.open) {
99
+ // Wait for paint so the panel has layout dimensions before positioning.
100
+ this.updateComplete.then(() => this.positionPanel());
101
+ }
102
+ }
103
+
104
+ // ── Content detection ──────────────────────────────────────────────────────
105
+
106
+ private updateHasContent() {
107
+ const slot = this.shadowRoot?.querySelector("slot");
108
+ if (slot) {
109
+ const nodes = slot.assignedNodes({ flatten: true });
110
+ this.hasContent = nodes.some(
111
+ (node) => node.nodeType === Node.ELEMENT_NODE || node.textContent?.trim()
112
+ );
113
+ }
114
+ }
115
+
116
+ private handleSlotChange() {
117
+ this.updateHasContent();
118
+ }
119
+
120
+ // ── Panel positioning (moved from rel-nav-menu) ────────────────────────────
121
+
122
+ private positionPanel() {
123
+ const trigger = this.shadowRoot?.querySelector(".trigger") as HTMLElement;
124
+ const panel = this.shadowRoot?.querySelector(".panel") as HTMLElement;
125
+ if (!trigger || !panel) return;
126
+
127
+ const triggerRect = trigger.getBoundingClientRect();
128
+ const panelRect = panel.getBoundingClientRect();
129
+ const viewportWidth = window.innerWidth;
130
+ const viewportHeight = window.innerHeight;
131
+
132
+ let top = triggerRect.bottom + 8;
133
+ let left = triggerRect.left;
134
+
135
+ // Handle right overflow
136
+ if (left + panelRect.width > viewportWidth) {
137
+ left = viewportWidth - panelRect.width - 16;
138
+ }
139
+
140
+ // Handle left overflow
141
+ if (left < 0) left = 16;
142
+
143
+ // Handle bottom overflow — flip above if more space
144
+ if (top + panelRect.height > viewportHeight) {
145
+ const spaceAbove = triggerRect.top;
146
+ const spaceBelow = viewportHeight - triggerRect.bottom;
147
+ if (spaceAbove > spaceBelow && spaceAbove > panelRect.height) {
148
+ top = triggerRect.top - panelRect.height - 8;
149
+ }
150
+ }
151
+
152
+ panel.style.top = `${top}px`;
153
+ panel.style.left = `${left}px`;
154
+ }
155
+
156
+ // ── Events dispatched upward — rel-nav-menu listens via connectedCallback ──
157
+
158
+ private handleTriggerClick() {
159
+ if (this.disabled) return;
160
+ this.dispatchEvent(
161
+ new CustomEvent("rel-nav-item-click", {
162
+ detail: { itemId: this.itemId, hasContent: this.hasContent },
163
+ bubbles: true,
164
+ composed: true,
165
+ })
166
+ );
167
+ }
168
+
169
+ private handleTriggerMouseEnter() {
170
+ if (this.disabled) return;
171
+ this.dispatchEvent(
172
+ new CustomEvent("rel-nav-item-hover", {
173
+ detail: { itemId: this.itemId },
174
+ bubbles: true,
175
+ composed: true,
176
+ })
177
+ );
178
+ }
179
+
180
+ // ── ID helpers ─────────────────────────────────────────────────────────────
181
+
182
+ get triggerId() {
183
+ return `nav-trigger-${this.itemId}`;
184
+ }
185
+
186
+ get panelId() {
187
+ return `nav-panel-${this.itemId}`;
188
+ }
189
+
190
+ // ── Render ─────────────────────────────────────────────────────────────────
191
+
192
+ render() {
193
+ const isLink = !!this.href && !this.hasContent;
194
+
195
+ return html`
196
+ ${isLink
197
+ ? html`
198
+ <a
199
+ class="trigger"
200
+ href="${this.href}"
201
+ id="${this.triggerId}"
202
+ ?disabled="${this.disabled}"
203
+ @click="${this.handleTriggerClick}"
204
+ @mouseenter="${this.handleTriggerMouseEnter}"
205
+ >
206
+ ${this.label}
207
+ </a>
208
+ `
209
+ : html`
210
+ <button
211
+ class="trigger"
212
+ id="${this.triggerId}"
213
+ type="button"
214
+ ?disabled="${this.disabled}"
215
+ aria-haspopup="${this.hasContent ? "menu" : "false"}"
216
+ aria-controls="${this.hasContent ? this.panelId : ""}"
217
+ aria-expanded="${this.open ? "true" : "false"}"
218
+ @click="${this.handleTriggerClick}"
219
+ @mouseenter="${this.handleTriggerMouseEnter}"
220
+ >
221
+ ${this.label}
222
+ </button>
223
+ `}
224
+
225
+ <!--
226
+ Panel rendered inside this shadow root.
227
+ Slot projects the consumer's light-DOM children directly — no cloning.
228
+ rel-nav-menu sets item.open = true/false to show/hide.
229
+ -->
230
+ <div
231
+ class="panel ${this.open ? "open" : ""}"
232
+ id="${this.panelId}"
233
+ role="menu"
234
+ @click="${(e: Event) => e.stopPropagation()}"
235
+ >
236
+ <slot @slotchange="${this.handleSlotChange}"></slot>
237
+ </div>
238
+ `;
239
+ }
240
+ }
@@ -0,0 +1,53 @@
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
+ display: block;
9
+ width: 100%;
10
+ }
11
+
12
+ .panel-content {
13
+ display: flex;
14
+ flex-direction: column;
15
+ gap: var(--rel-spacing-space-2xs);
16
+ padding: var(--rel-spacing-space-xs);
17
+ }
18
+
19
+ ::slotted(a[data-nav-item]) {
20
+ display: block;
21
+ padding: var(--rel-spacing-space-sm);
22
+ border-radius: var(--rel-borders-radius-border-radius-100);
23
+ transition: background 0.15s ease;
24
+ text-decoration: none;
25
+ color: inherit;
26
+ }
27
+
28
+ ::slotted(a[data-nav-item]:hover) {
29
+ background: var(--rel-surface-secondary, #fafafa);
30
+ }
31
+
32
+ ::slotted([data-title]) {
33
+ font-size: var(--rel-fontSizes-sm);
34
+ font-weight: var(--rel-fontWeights-semibold);
35
+ color: var(--rel-colors-gray-heading);
36
+ margin-bottom: var(--rel-spacing-space-3xs);
37
+ }
38
+
39
+ ::slotted([data-desc]) {
40
+ font-size: var(--rel-fontSizes-xs);
41
+ color: var(--rel-colors-gray-placeholder);
42
+ line-height: var(--rel-lineHeights-xs);
43
+ }
44
+ `;
45
+
46
+ render() {
47
+ return html`
48
+ <div class="panel-content">
49
+ <slot></slot>
50
+ </div>
51
+ `;
52
+ }
53
+ }