@birdycom/omniaui 0.0.0-stage → 0.11.0

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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +20 -2
  3. package/bin/lib/add.mjs +126 -0
  4. package/bin/lib/catalog.mjs +169 -0
  5. package/bin/lib/project.mjs +90 -0
  6. package/bin/omniaui.mjs +70 -0
  7. package/dist/code-block.css +163 -0
  8. package/dist/components-extra.css +914 -0
  9. package/dist/components.css +10081 -0
  10. package/dist/components.min.css +1 -0
  11. package/dist/data-table.d.ts +9 -0
  12. package/dist/data-table.js +505 -0
  13. package/dist/docs-ui.css +265 -0
  14. package/dist/oc-theme.css +69 -0
  15. package/dist/parts/.gitkeep +0 -0
  16. package/dist/parts/app-accordion-accent.css +60 -0
  17. package/dist/parts/app-alpha-tabs.css +68 -0
  18. package/dist/parts/app-busy-overlay.css +25 -0
  19. package/dist/parts/app-column-menu.css +85 -0
  20. package/dist/parts/app-confirm-dialog.css +70 -0
  21. package/dist/parts/app-cookie-consent.css +63 -0
  22. package/dist/parts/app-data-table.css +174 -0
  23. package/dist/parts/app-greeting.css +47 -0
  24. package/dist/parts/app-list-toolbar.css +81 -0
  25. package/dist/parts/app-page-header.css +54 -0
  26. package/dist/parts/app-severity-card.css +91 -0
  27. package/dist/parts/app-sidebar-dark.css +15 -0
  28. package/dist/parts/app-sortable-row.css +52 -0
  29. package/dist/parts/app-stat-card.css +68 -0
  30. package/dist/parts/app-table-actions.css +71 -0
  31. package/dist/parts/app-table-compact.css +54 -0
  32. package/dist/parts/app-top-progress.css +68 -0
  33. package/dist/parts/app-tree.css +104 -0
  34. package/dist/parts/app-upgrade-banner.css +90 -0
  35. package/dist/parts/app-version-banner.css +80 -0
  36. package/dist/parts/data-chart-line.css +167 -0
  37. package/dist/parts/forms-affix-input.css +75 -0
  38. package/dist/parts/forms-barcode-input.css +75 -0
  39. package/dist/parts/forms-chip-filter.css +91 -0
  40. package/dist/parts/forms-color-select.css +89 -0
  41. package/dist/parts/forms-decimal-input.css +51 -0
  42. package/dist/parts/forms-filter-counts.css +94 -0
  43. package/dist/parts/forms-language-select.css +76 -0
  44. package/dist/parts/forms-number-input.css +95 -0
  45. package/dist/parts/forms-password-requirements.css +55 -0
  46. package/dist/parts/forms-phone-input.css +85 -0
  47. package/dist/parts/forms-time-input.css +74 -0
  48. package/dist/parts/forms-toggle-field.css +69 -0
  49. package/dist/parts/media-art.css +21 -0
  50. package/dist/parts/media-attachment.css +160 -0
  51. package/dist/parts/media-browser-frame.css +73 -0
  52. package/dist/parts/media-card.css +83 -0
  53. package/dist/parts/media-chat-composer.css +90 -0
  54. package/dist/parts/media-doc-viewer.css +151 -0
  55. package/dist/parts/media-emoji-picker.css +88 -0
  56. package/dist/parts/media-entity-card.css +52 -0
  57. package/dist/parts/media-flip-card.css +78 -0
  58. package/dist/parts/media-image-zoom.css +68 -0
  59. package/dist/parts/media-lightbox.css +131 -0
  60. package/dist/parts/media-message-thread.css +136 -0
  61. package/dist/parts/media-message.css +160 -0
  62. package/dist/parts/media-row.css +78 -0
  63. package/dist/parts/media-stack-card.css +82 -0
  64. package/dist/parts/media-terminal-log.css +76 -0
  65. package/dist/parts/media-thumb.css +71 -0
  66. package/dist/parts/site-fact-list.css +25 -0
  67. package/dist/parts/site-link-arrow.css +34 -0
  68. package/dist/parts/site-marquee.css +34 -0
  69. package/dist/parts/site-next-teaser.css +75 -0
  70. package/dist/parts/site-price-card.css +75 -0
  71. package/dist/parts/site-product-mark.css +66 -0
  72. package/dist/parts/site-project-card.css +111 -0
  73. package/dist/parts/site-prose.css +124 -0
  74. package/dist/parts/site-stack-list.css +48 -0
  75. package/dist/parts/site-timeline.css +54 -0
  76. package/dist/showcase.css +404 -0
  77. package/manifest.json +1108 -0
  78. package/package.json +67 -4
  79. package/snippets/accordion-accent.html +24 -0
  80. package/snippets/accordion.html +11 -0
  81. package/snippets/affix-input.html +30 -0
  82. package/snippets/alert-dialog.html +22 -0
  83. package/snippets/alert.html +5 -0
  84. package/snippets/alpha-tabs.html +37 -0
  85. package/snippets/aspect-ratio.html +15 -0
  86. package/snippets/attachment.html +43 -0
  87. package/snippets/avatar.html +6 -0
  88. package/snippets/back-link.html +5 -0
  89. package/snippets/badge.html +5 -0
  90. package/snippets/barcode-input.html +18 -0
  91. package/snippets/breadcrumb.html +6 -0
  92. package/snippets/browser-frame.html +18 -0
  93. package/snippets/busy-overlay.html +31 -0
  94. package/snippets/button-group.html +21 -0
  95. package/snippets/button.html +8 -0
  96. package/snippets/calendar.html +43 -0
  97. package/snippets/card.html +17 -0
  98. package/snippets/carousel.html +21 -0
  99. package/snippets/chart-line.html +46 -0
  100. package/snippets/chart.html +27 -0
  101. package/snippets/chat-composer.html +21 -0
  102. package/snippets/checkbox.html +5 -0
  103. package/snippets/checklist.html +23 -0
  104. package/snippets/chip-filter.html +14 -0
  105. package/snippets/code-window.html +20 -0
  106. package/snippets/collapsible.html +19 -0
  107. package/snippets/color-field.html +13 -0
  108. package/snippets/color-select.html +18 -0
  109. package/snippets/column-menu.html +38 -0
  110. package/snippets/combobox.html +19 -0
  111. package/snippets/command.html +24 -0
  112. package/snippets/confirm-dialog.html +32 -0
  113. package/snippets/context-menu.html +25 -0
  114. package/snippets/cookie-consent.html +70 -0
  115. package/snippets/copy-field.html +19 -0
  116. package/snippets/counter.html +2 -0
  117. package/snippets/data-table.html +151 -0
  118. package/snippets/date-picker.html +32 -0
  119. package/snippets/decimal-input.html +20 -0
  120. package/snippets/definition-list.html +20 -0
  121. package/snippets/demo-panel.html +18 -0
  122. package/snippets/doc-viewer.html +58 -0
  123. package/snippets/drawer.html +30 -0
  124. package/snippets/dropdown-menu.html +27 -0
  125. package/snippets/dropdown.html +9 -0
  126. package/snippets/dropzone.html +13 -0
  127. package/snippets/emoji-picker.html +51 -0
  128. package/snippets/empty-state.html +17 -0
  129. package/snippets/entity-card.html +26 -0
  130. package/snippets/expandable-table.html +26 -0
  131. package/snippets/fact-list.html +14 -0
  132. package/snippets/filter-counts.html +30 -0
  133. package/snippets/flip-card.html +22 -0
  134. package/snippets/form-field.html +6 -0
  135. package/snippets/greeting.html +22 -0
  136. package/snippets/heading.html +14 -0
  137. package/snippets/hover-card.html +18 -0
  138. package/snippets/image-zoom.html +30 -0
  139. package/snippets/input-group.html +10 -0
  140. package/snippets/input.html +5 -0
  141. package/snippets/item.html +26 -0
  142. package/snippets/kbd.html +13 -0
  143. package/snippets/label.html +9 -0
  144. package/snippets/language-select.html +26 -0
  145. package/snippets/launcher-card.html +14 -0
  146. package/snippets/lightbox.html +50 -0
  147. package/snippets/link-arrow.html +10 -0
  148. package/snippets/list-toolbar.html +33 -0
  149. package/snippets/marquee.html +15 -0
  150. package/snippets/media-card.html +28 -0
  151. package/snippets/media-row.html +21 -0
  152. package/snippets/media-thumb.html +12 -0
  153. package/snippets/menubar.html +30 -0
  154. package/snippets/message-thread.html +123 -0
  155. package/snippets/message.html +51 -0
  156. package/snippets/modal.html +15 -0
  157. package/snippets/native-select.html +15 -0
  158. package/snippets/navmenu.html +23 -0
  159. package/snippets/next-teaser.html +15 -0
  160. package/snippets/number-input.html +27 -0
  161. package/snippets/otp-input.html +9 -0
  162. package/snippets/page-header.html +24 -0
  163. package/snippets/pagination.html +6 -0
  164. package/snippets/password-input.html +24 -0
  165. package/snippets/password-requirements.html +30 -0
  166. package/snippets/phone-input.html +22 -0
  167. package/snippets/popover.html +15 -0
  168. package/snippets/price-card.html +34 -0
  169. package/snippets/product-mark.html +13 -0
  170. package/snippets/progress.html +4 -0
  171. package/snippets/project-card.html +23 -0
  172. package/snippets/prose.html +54 -0
  173. package/snippets/radio.html +17 -0
  174. package/snippets/recovery-codes.html +20 -0
  175. package/snippets/resizable.html +16 -0
  176. package/snippets/scroll-area.html +20 -0
  177. package/snippets/search.html +8 -0
  178. package/snippets/segmented.html +11 -0
  179. package/snippets/select.html +7 -0
  180. package/snippets/separator.html +15 -0
  181. package/snippets/severity-card.html +35 -0
  182. package/snippets/sheet.html +32 -0
  183. package/snippets/sidebar-docs.html +24 -0
  184. package/snippets/sidebar.html +55 -0
  185. package/snippets/site-footer.html +6 -0
  186. package/snippets/site-header.html +26 -0
  187. package/snippets/skeleton.html +4 -0
  188. package/snippets/slider.html +17 -0
  189. package/snippets/sortable-row.html +31 -0
  190. package/snippets/spinner.html +14 -0
  191. package/snippets/stack-card.html +17 -0
  192. package/snippets/stack-list.html +16 -0
  193. package/snippets/stat-card.html +24 -0
  194. package/snippets/stepper.html +13 -0
  195. package/snippets/switch.html +6 -0
  196. package/snippets/table-actions.html +39 -0
  197. package/snippets/table-compact.html +24 -0
  198. package/snippets/table.html +29 -0
  199. package/snippets/tabs.html +9 -0
  200. package/snippets/tag-input.html +17 -0
  201. package/snippets/terminal-log.html +23 -0
  202. package/snippets/textarea.html +3 -0
  203. package/snippets/theme-switch.html +6 -0
  204. package/snippets/time-input.html +26 -0
  205. package/snippets/timeline.html +28 -0
  206. package/snippets/toast-stack.html +10 -0
  207. package/snippets/toast.html +36 -0
  208. package/snippets/toc.html +13 -0
  209. package/snippets/toggle-field.html +20 -0
  210. package/snippets/toggle.html +22 -0
  211. package/snippets/tooltip.html +4 -0
  212. package/snippets/top-progress.html +12 -0
  213. package/snippets/tree.html +38 -0
  214. package/snippets/upgrade-banner.html +28 -0
  215. package/snippets/user.html +15 -0
  216. package/snippets/version-banner.html +15 -0
@@ -0,0 +1,26 @@
1
+ <!-- OmniaUI component: item - Item: list row with media, content and actions. Needs @birdycom/omniaui CSS.
2
+ Class API (modifiers are extra classes / attributes on the same element):
3
+ .oc-item: flex row (div, li or a). Variants: --outline, --muted, --sm, --interactive
4
+ .oc-item-media: left slot. Modifiers: --icon (boxed icon), --image
5
+ .oc-item-content: grows; holds .oc-item-title and .oc-item-description
6
+ .oc-item-actions: right slot (buttons)
7
+ .oc-item-header / .oc-item-footer: full-width rows above/below
8
+ .oc-item-group: vertical list of items; .oc-item-separator between them
9
+ -->
10
+ <div class="oc-item oc-item--outline">
11
+ <div class="oc-item-media oc-item-media--icon"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3 4 6v6c0 5 3.5 8 8 9 4.5-1 8-4 8-9V6z"/></svg></div>
12
+ <div class="oc-item-content">
13
+ <p class="oc-item-title">Two-factor authentication</p>
14
+ <p class="oc-item-description">Verify sign-ins with an authenticator app.</p>
15
+ </div>
16
+ <div class="oc-item-actions"><button class="oc-btn oc-btn--outline oc-btn--sm" type="button">Enable</button></div>
17
+ </div>
18
+
19
+ <div class="oc-item-group oc-item--outline" style="border:1px solid var(--oc-border);border-radius:var(--oc-radius);padding:.25rem">
20
+ <a class="oc-item oc-item--sm" href="#a"><div class="oc-item-media oc-item-media--icon"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 2h8l5 5v15H6z"/><path d="M14 2v5h5"/></svg></div><div class="oc-item-content"><p class="oc-item-title">invoice-2026-014.pdf</p><p class="oc-item-description">128 KB &middot; today</p></div><div class="oc-item-actions"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></div></a>
21
+ <hr class="oc-item-separator">
22
+ <a class="oc-item oc-item--sm" href="#b"><div class="oc-item-media oc-item-media--icon"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg></div><div class="oc-item-content"><p class="oc-item-title">Contracts</p><p class="oc-item-description">12 files</p></div><div class="oc-item-actions"><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg></div></a>
23
+ </div>
24
+ <div class="oc-item oc-item--muted">
25
+ <div class="oc-item-content"><p class="oc-item-title">Muted background, no media</p><p class="oc-item-description">Useful for inline notices and read-only rows.</p></div>
26
+ </div>
@@ -0,0 +1,13 @@
1
+ <!-- OmniaUI component: kbd - Keyboard key and shortcut group. Needs @birdycom/omniaui CSS.
2
+ Class API (modifiers are extra classes / attributes on the same element):
3
+ .oc-kbd: one key (use on <kbd>)
4
+ .oc-kbd-group: row of keys with a gap; .oc-kbd-sep for a '+' between them
5
+ -->
6
+ <p style="margin:0;display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;font-size:.875rem">
7
+ Open the command palette with
8
+ <span class="oc-kbd-group"><kbd class="oc-kbd">Ctrl</kbd><kbd class="oc-kbd">K</kbd></span>
9
+ or press <kbd class="oc-kbd">/</kbd> to search.
10
+ </p>
11
+
12
+ <button class="oc-btn oc-btn--outline" type="button">Save <span class="oc-kbd-group"><kbd class="oc-kbd">&#8984;</kbd><kbd class="oc-kbd">S</kbd></span></button>
13
+ <span class="oc-kbd-group"><kbd class="oc-kbd">Ctrl</kbd><span class="oc-kbd-sep">+</span><kbd class="oc-kbd">Shift</kbd><span class="oc-kbd-sep">+</span><kbd class="oc-kbd">P</kbd></span>
@@ -0,0 +1,9 @@
1
+ <!-- OmniaUI component: label - Form label: block, inline (around a control), required mark, muted. Needs @birdycom/omniaui CSS.
2
+ Class API (modifiers are extra classes / attributes on the same element):
3
+ .oc-label: block label above a control. Modifiers: --required (red asterisk), --inline (wraps a checkbox/radio), --muted (secondary text)
4
+ -->
5
+ <div class="oc-form-group">
6
+ <label class="oc-label oc-label--required" for="oc-label-email">E-mail</label>
7
+ <input class="oc-input" id="oc-label-email" type="email" placeholder="name@example.com">
8
+ </div>
9
+ <label class="oc-label oc-label--inline"><input class="oc-checkbox" type="checkbox"> Accept terms and conditions</label>
@@ -0,0 +1,26 @@
1
+ <!-- OmniaUI component: language-select - Compact language or currency switcher for a site header. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/language-select.html | Map: language pill (globe icon + select) -> currency pill (symbol + select)
3
+ Class API (modifiers are extra classes / attributes on the same element):
4
+ .oc-language-select: Pill frame around icon and select. Modifiers: --ghost (no frame), --currency (symbol in primary color).
5
+ .oc-language-select-icon: Leading icon (inline SVG) or symbol; aria-hidden="true"
6
+ .oc-language-select-control: The native <select> (give it an aria-label)
7
+ States: :focus-within (ring), disabled select (dimmed)
8
+ Behaviour: the app listens to "change" (navigate to /nl/..., set a cookie, or submit the form).
9
+ -->
10
+ <label class="oc-language-select">
11
+ <span class="oc-language-select-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18"/></svg></span>
12
+ <select class="oc-language-select-control" name="locale" aria-label="Language">
13
+ <option value="en" selected>English</option>
14
+ <option value="nl">Nederlands</option>
15
+ <option value="fr">Fran&ccedil;ais</option>
16
+ <option value="de">Deutsch</option>
17
+ </select>
18
+ </label>
19
+ <label class="oc-language-select oc-language-select--currency">
20
+ <span class="oc-language-select-icon" aria-hidden="true">&euro;</span>
21
+ <select class="oc-language-select-control" name="currency" aria-label="Currency">
22
+ <option value="EUR" selected>EUR</option>
23
+ <option value="USD">USD</option>
24
+ <option value="GBP">GBP</option>
25
+ </select>
26
+ </label>
@@ -0,0 +1,14 @@
1
+ <!-- OmniaUI component: launcher-card - Large clickable card with icon, title and description (module hub, settings sections). Needs @birdycom/omniaui CSS.
2
+ Class API (modifiers are extra classes / attributes on the same element):
3
+ .oc-launcher: Card (link or button). State: aria-current="true" / .is-active, aria-disabled="true".
4
+ .oc-launcher-icon: Tinted icon tile
5
+ .oc-launcher-title: Title (may contain a badge)
6
+ .oc-launcher-description: Muted description
7
+ .oc-launcher-footer: Optional footer text (e.g. why it is disabled)
8
+ Slots: icon, title (+ trailing badge), description, footer
9
+ -->
10
+ <a class="oc-launcher" href="#" style="max-width:20rem">
11
+ <span class="oc-launcher-icon" aria-hidden="true"><svg class="" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="8" r="4"/><path d="M2 21c0-4 3-6 7-6s7 2 7 6"/></svg></span>
12
+ <h3 class="oc-launcher-title">Contacts <span class="oc-badge oc-badge--success">Active</span></h3>
13
+ <p class="oc-launcher-description">Customers, suppliers and the people who work there.</p>
14
+ </a>
@@ -0,0 +1,50 @@
1
+ <!-- OmniaUI component: lightbox — Image viewer on a native <dialog> with prev / next, counter and caption. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/lightbox.html | Map: thumbnails -> dialog (bar, track of slides) -> optional script
3
+ Class API: .oc-lightbox-thumbs > button.oc-lightbox-thumb (command="show-modal" commandfor="<dialog id>") ·
4
+ dialog.oc-lightbox > .oc-lightbox__bar (.oc-lightbox__title, button.oc-lightbox__close) ·
5
+ .oc-lightbox__track (tabindex=0, autofocus) > figure.oc-lightbox__slide#id >
6
+ .oc-lightbox__media (img) · a.oc-lightbox__nav--prev / --next (href="#<slide id>") · figcaption.oc-lightbox__caption (.oc-lightbox__counter).
7
+ Modifiers: .oc-lightbox--single (hides nav + counter). Slots: any img / video / .oc-media-art in .oc-lightbox__media.
8
+ No JavaScript needed: the thumbnails open the dialog, links jump between slides, Esc / backdrop close it.
9
+ The optional script below only makes a thumbnail open the viewer at its own slide (data-oc-lightbox-slide).
10
+ -->
11
+ <div class="oc-lightbox-thumbs">
12
+ <button type="button" class="oc-lightbox-thumb" command="show-modal" commandfor="oc-lightbox-demo" data-oc-lightbox-slide="oc-lightbox-demo-1" aria-label="Open photo 1: Harbour at dawn"><span class="oc-media-art"></span></button>
13
+ <button type="button" class="oc-lightbox-thumb" command="show-modal" commandfor="oc-lightbox-demo" data-oc-lightbox-slide="oc-lightbox-demo-2" aria-label="Open photo 2: Forest trail"><span class="oc-media-art oc-media-art--4"></span></button>
14
+ <button type="button" class="oc-lightbox-thumb" command="show-modal" commandfor="oc-lightbox-demo" data-oc-lightbox-slide="oc-lightbox-demo-3" aria-label="Open photo 3: Desert road"><span class="oc-media-art oc-media-art--6"></span></button>
15
+ </div>
16
+
17
+ <dialog class="oc-lightbox" id="oc-lightbox-demo" closedby="any" aria-label="Photo viewer">
18
+ <div class="oc-lightbox__bar">
19
+ <h2 class="oc-lightbox__title">Holiday photos</h2>
20
+ <button type="button" class="oc-lightbox__close" command="close" commandfor="oc-lightbox-demo" aria-label="Close">&#10005;</button>
21
+ </div>
22
+ <div class="oc-lightbox__track" tabindex="0" autofocus aria-label="Photos, use the arrow keys">
23
+ <figure class="oc-lightbox__slide" id="oc-lightbox-demo-1">
24
+ <div class="oc-lightbox__media"><div class="oc-media-art" role="img" aria-label="Harbour at dawn"></div></div>
25
+ <a class="oc-lightbox__nav oc-lightbox__nav--prev" href="#oc-lightbox-demo-3" aria-label="Previous photo">&#8592;</a>
26
+ <a class="oc-lightbox__nav oc-lightbox__nav--next" href="#oc-lightbox-demo-2" aria-label="Next photo">&#8594;</a>
27
+ <figcaption class="oc-lightbox__caption"><span class="oc-lightbox__counter">1 / 3</span> Harbour at dawn</figcaption>
28
+ </figure>
29
+ <figure class="oc-lightbox__slide" id="oc-lightbox-demo-2">
30
+ <div class="oc-lightbox__media"><div class="oc-media-art oc-media-art--4" role="img" aria-label="Forest trail"></div></div>
31
+ <a class="oc-lightbox__nav oc-lightbox__nav--prev" href="#oc-lightbox-demo-1" aria-label="Previous photo">&#8592;</a>
32
+ <a class="oc-lightbox__nav oc-lightbox__nav--next" href="#oc-lightbox-demo-3" aria-label="Next photo">&#8594;</a>
33
+ <figcaption class="oc-lightbox__caption"><span class="oc-lightbox__counter">2 / 3</span> Forest trail</figcaption>
34
+ </figure>
35
+ <figure class="oc-lightbox__slide" id="oc-lightbox-demo-3">
36
+ <div class="oc-lightbox__media"><div class="oc-media-art oc-media-art--6" role="img" aria-label="Desert road"></div></div>
37
+ <a class="oc-lightbox__nav oc-lightbox__nav--prev" href="#oc-lightbox-demo-2" aria-label="Previous photo">&#8592;</a>
38
+ <a class="oc-lightbox__nav oc-lightbox__nav--next" href="#oc-lightbox-demo-1" aria-label="Next photo">&#8594;</a>
39
+ <figcaption class="oc-lightbox__caption"><span class="oc-lightbox__counter">3 / 3</span> Desert road</figcaption>
40
+ </figure>
41
+ </div>
42
+ </dialog>
43
+ <script>
44
+ // Optional: open the viewer at the clicked thumbnail. The "command" event fires before the dialog opens,
45
+ // so the jump waits one frame. Without this script the viewer opens at the first (or last viewed) photo.
46
+ document.addEventListener("command", (e) => {
47
+ const id = e.source?.dataset.ocLightboxSlide;
48
+ if (id) requestAnimationFrame(() => document.getElementById(id)?.scrollIntoView({ behavior: "instant", block: "nearest", inline: "center" }));
49
+ }, true);
50
+ </script>
@@ -0,0 +1,10 @@
1
+ <!-- OmniaUI component: link-arrow - Text link with an arrow that moves on hover and focus. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/link-arrow.html | Map: a.oc-link-arrow per variant
3
+ Class API (modifiers are extra classes / attributes on the same element):
4
+ .oc-link-arrow: Link with "→" after the text (brand color)
5
+ Modifiers: --back (← before the text), --external (↗), --accent (element accent), --muted, --sm, --lg
6
+ Slots: text
7
+ -->
8
+ <a class="oc-link-arrow" href="#more">Read the case study</a>
9
+ <a class="oc-link-arrow oc-link-arrow--back oc-link-arrow--muted" href="#back">All projects</a>
10
+ <a class="oc-link-arrow oc-link-arrow--external" href="#docs" rel="noopener">Documentation</a>
@@ -0,0 +1,33 @@
1
+ <!-- OmniaUI component: list-toolbar - Search, result count, cards / table view toggle and actions above a list; the toggle switches views with :has() - no JavaScript. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/list-toolbar.html | Map: scope -> toolbar (search, count, spacer, view toggle, actions) -> cards panel -> table panel
3
+ Class API (modifiers are extra classes / attributes on the same element):
4
+ .oc-list-toolbar: Toolbar row (wraps). Modifiers: --sticky (sticks to the top), --bordered (framed bar).
5
+ .oc-list-toolbar-search: Search area (holds an .oc-input type="search")
6
+ .oc-list-toolbar-count / -spacer / -actions: Result count, flexible gap, end buttons
7
+ .oc-list-toolbar-views: Radio group (role="radiogroup") for the view
8
+ .oc-list-toolbar-view: label + input[type=radio][value=cards|table|list] + icon; checked = active
9
+ .oc-list-toolbar-scope: Optional wrapper; shows only the .oc-list-toolbar-panel[data-view] that matches the checked radio
10
+ Slots: search, count, views, actions, panels
11
+ -->
12
+ <div class="oc-list-toolbar-scope">
13
+ <div class="oc-list-toolbar" role="toolbar" aria-label="Products">
14
+ <div class="oc-list-toolbar-search"><input class="oc-input" type="search" placeholder="Search products" aria-label="Search products"></div>
15
+ <span class="oc-list-toolbar-count">3 items</span>
16
+ <span class="oc-list-toolbar-spacer"></span>
17
+ <div class="oc-list-toolbar-views" role="radiogroup" aria-label="View">
18
+ <label class="oc-list-toolbar-view" title="Cards"><input type="radio" name="oc-list-view" value="cards" checked aria-label="Cards"><svg class="" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg></label>
19
+ <label class="oc-list-toolbar-view" title="Table"><input type="radio" name="oc-list-view" value="table" aria-label="Table"><svg class="" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 5h18M3 12h18M3 19h18"/></svg></label>
20
+ </div>
21
+ <div class="oc-list-toolbar-actions"><button class="oc-btn oc-btn--sm" type="button">New product</button></div>
22
+ </div>
23
+ <div class="oc-list-toolbar-panel" data-view="cards">
24
+ <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(10rem,1fr));gap:.75rem">
25
+ <div class="oc-card oc-card--sm"><div class="oc-card-content">Oak table</div></div>
26
+ <div class="oc-card oc-card--sm"><div class="oc-card-content">Linen chair</div></div>
27
+ <div class="oc-card oc-card--sm"><div class="oc-card-content">Brass lamp</div></div>
28
+ </div>
29
+ </div>
30
+ <div class="oc-list-toolbar-panel" data-view="table">
31
+ <table class="oc-table"><thead><tr><th>Product</th></tr></thead><tbody><tr><td>Oak table</td></tr><tr><td>Linen chair</td></tr><tr><td>Brass lamp</td></tr></tbody></table>
32
+ </div>
33
+ </div>
@@ -0,0 +1,15 @@
1
+ <!-- OmniaUI component: marquee - Endless scrolling strip of chips; pauses on hover and focus, static with reduced motion. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/marquee.html | Map: .oc-marquee -> .oc-marquee__track -> items + aria-hidden copies
3
+ Class API (modifiers are extra classes / attributes on the same element):
4
+ .oc-marquee: Clipping wrapper (aria-label describes the list). Modifiers: --reverse, --slow, --fast, --plain (text only), --no-fade
5
+ .oc-marquee__track: The moving row. Render the items twice; the second set gets aria-hidden="true"
6
+ .oc-marquee__item: One chip
7
+ --oc-marquee-duration: Custom speed (default 40s)
8
+ Slots: items (rendered twice for a seamless loop)
9
+ -->
10
+ <div class="oc-marquee" aria-label="Technologies">
11
+ <div class="oc-marquee__track">
12
+ <span class="oc-marquee__item">HTML</span><span class="oc-marquee__item">CSS</span><span class="oc-marquee__item">PHP</span><span class="oc-marquee__item">MySQL</span><span class="oc-marquee__item">TypeScript</span><span class="oc-marquee__item">Node.js</span>
13
+ <span class="oc-marquee__item" aria-hidden="true">HTML</span><span class="oc-marquee__item" aria-hidden="true">CSS</span><span class="oc-marquee__item" aria-hidden="true">PHP</span><span class="oc-marquee__item" aria-hidden="true">MySQL</span><span class="oc-marquee__item" aria-hidden="true">TypeScript</span><span class="oc-marquee__item" aria-hidden="true">Node.js</span>
14
+ </div>
15
+ </div>
@@ -0,0 +1,28 @@
1
+ <!-- OmniaUI component: media-card — Video card: thumb, avatar, two-line title, meta and an overflow menu. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/media-card.html | Map: card -> media thumb -> body (avatar, text, menu button) -> dropdown menu (popover)
3
+ Class API: article.oc-media-card > .oc-media-thumb (see media-thumb) · .oc-media-card__body >
4
+ .oc-media-card__avatar (img or initials) · .oc-media-card__text (h3.oc-media-card__title > a, p.oc-media-card__meta) ·
5
+ button.oc-media-card__menu (popovertarget → any .oc-dropdown-menu).
6
+ Modifiers: .oc-media-card--compact. The title link covers the whole card; meta links and the menu stay clickable.
7
+ -->
8
+ <article class="oc-media-card" style="max-inline-size:20rem">
9
+ <div class="oc-media-thumb">
10
+ <div class="oc-media-art oc-media-art--2"></div>
11
+ <span class="oc-media-thumb__duration">18:32</span>
12
+ <progress class="oc-media-thumb__progress" value="65" max="100" aria-label="Watched 65%">65%</progress>
13
+ </div>
14
+ <div class="oc-media-card__body">
15
+ <span class="oc-media-card__avatar" aria-hidden="true">NS</span>
16
+ <div class="oc-media-card__text">
17
+ <h3 class="oc-media-card__title"><a href="#">Building a cabin by the lake, part 3: the roof goes on</a></h3>
18
+ <p class="oc-media-card__meta"><a href="#">North Studio</a> · 24K views · 3 days ago</p>
19
+ </div>
20
+ <button type="button" class="oc-media-card__menu" popovertarget="oc-media-card-menu" aria-label="More actions">&#8942;</button>
21
+ </div>
22
+ <div class="oc-dropdown-menu" id="oc-media-card-menu" popover>
23
+ <a class="oc-dropdown-item" href="#">Save to playlist</a>
24
+ <a class="oc-dropdown-item" href="#">Share</a>
25
+ <div class="oc-dropdown-separator"></div>
26
+ <a class="oc-dropdown-item" href="#">Not interested</a>
27
+ </div>
28
+ </article>
@@ -0,0 +1,21 @@
1
+ <!-- OmniaUI component: media-row — Compact horizontal media item for up-next lists and numbered playlists. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/media-row.html | Map: list -> row (thumb, text, menu) -> numbered playlist variant
3
+ Class API: ul/ol.oc-media-row-list > li.oc-media-row > .oc-media-thumb (see media-thumb) ·
4
+ .oc-media-row__text (h3.oc-media-row__title > a, p.oc-media-row__meta) · button.oc-media-row__menu (optional).
5
+ Modifiers: .oc-media-row-list--numbered (CSS counter column). States: aria-current="true" (now playing).
6
+ The title link covers the row; the menu button stays clickable. No JavaScript.
7
+ -->
8
+ <ol class="oc-media-row-list oc-media-row-list--numbered" aria-label="Playlist: Cabin build" style="max-inline-size:26rem">
9
+ <li class="oc-media-row">
10
+ <div class="oc-media-thumb"><div class="oc-media-art"></div><span class="oc-media-thumb__duration">9:12</span><progress class="oc-media-thumb__progress" value="100" max="100" aria-label="Watched">100%</progress></div>
11
+ <div class="oc-media-row__text"><h3 class="oc-media-row__title"><a href="#">Part 1: clearing the site</a></h3><p class="oc-media-row__meta">North Studio</p></div>
12
+ </li>
13
+ <li class="oc-media-row" aria-current="true">
14
+ <div class="oc-media-thumb"><div class="oc-media-art oc-media-art--2"></div><span class="oc-media-thumb__duration">14:40</span><progress class="oc-media-thumb__progress" value="30" max="100" aria-label="Watched 30%">30%</progress></div>
15
+ <div class="oc-media-row__text"><h3 class="oc-media-row__title"><a href="#">Part 2: walls and windows</a></h3><p class="oc-media-row__meta">North Studio</p></div>
16
+ </li>
17
+ <li class="oc-media-row">
18
+ <div class="oc-media-thumb"><div class="oc-media-art oc-media-art--4"></div><span class="oc-media-thumb__duration">18:32</span></div>
19
+ <div class="oc-media-row__text"><h3 class="oc-media-row__title"><a href="#">Part 3: the roof goes on</a></h3><p class="oc-media-row__meta">North Studio</p></div>
20
+ </li>
21
+ </ol>
@@ -0,0 +1,12 @@
1
+ <!-- OmniaUI component: media-thumb — 16:9 video thumbnail with duration badge and watch-progress bar. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/media-thumb.html | Map: thumb link -> picture -> badge -> duration -> progress
3
+ Class API: a.oc-media-thumb (or div) > img / video / .oc-media-art · .oc-media-thumb__badge (top left, --live = red) ·
4
+ .oc-media-thumb__duration (bottom right) · progress.oc-media-thumb__progress (value / max = watched part).
5
+ Modifiers: .oc-media-thumb--square / --portrait. States: .is-watched (dimmed picture). No JavaScript.
6
+ -->
7
+ <a class="oc-media-thumb" href="#" style="inline-size:20rem" aria-label="Morning light in the dunes, 12 minutes 4 seconds">
8
+ <div class="oc-media-art oc-media-art--3"></div>
9
+ <span class="oc-media-thumb__badge">New</span>
10
+ <span class="oc-media-thumb__duration">12:04</span>
11
+ <progress class="oc-media-thumb__progress" value="40" max="100" aria-label="Watched 40%">40%</progress>
12
+ </a>
@@ -0,0 +1,30 @@
1
+ <!-- OmniaUI component: menubar - Menubar: horizontal menu bar built from popover dropdown menus. Needs @birdycom/omniaui CSS.
2
+ Class API (modifiers are extra classes / attributes on the same element):
3
+ .oc-menubar: the bar (role="menubar")
4
+ .oc-menubar-trigger: button with popovertarget
5
+ .oc-dropdown-menu[popover]: each menu (+ .oc-dropdown-item, -label, -separator, -shortcut, -sub)
6
+ -->
7
+ <div class="oc-menubar" role="menubar" aria-label="Application">
8
+ <button class="oc-menubar-trigger" type="button" role="menuitem" popovertarget="oc-mb-file" aria-haspopup="menu">File</button>
9
+ <button class="oc-menubar-trigger" type="button" role="menuitem" popovertarget="oc-mb-view" aria-haspopup="menu">View</button>
10
+ </div>
11
+ <div class="oc-dropdown-menu" id="oc-mb-file" popover role="menu">
12
+ <button class="oc-dropdown-item" type="button" role="menuitem">New tab <span class="oc-dropdown-shortcut">Ctrl T</span></button>
13
+ <button class="oc-dropdown-item" type="button" role="menuitem">New window <span class="oc-dropdown-shortcut">Ctrl N</span></button>
14
+ <div class="oc-dropdown-separator" role="separator"></div>
15
+ <div class="oc-dropdown-sub">
16
+ <button class="oc-dropdown-item" type="button" role="menuitem" aria-haspopup="menu">Share</button>
17
+ <div class="oc-dropdown-submenu" role="menu">
18
+ <button class="oc-dropdown-item" type="button" role="menuitem">E-mail link</button>
19
+ <button class="oc-dropdown-item" type="button" role="menuitem">Messages</button>
20
+ </div>
21
+ </div>
22
+ <div class="oc-dropdown-separator" role="separator"></div>
23
+ <button class="oc-dropdown-item" type="button" role="menuitem">Print <span class="oc-dropdown-shortcut">Ctrl P</span></button>
24
+ </div>
25
+ <div class="oc-dropdown-menu" id="oc-mb-view" popover role="menu">
26
+ <button class="oc-dropdown-item" type="button" role="menuitemcheckbox" aria-checked="true">Always show bookmarks</button>
27
+ <button class="oc-dropdown-item" type="button" role="menuitemcheckbox" aria-checked="false">Always show full URLs</button>
28
+ <div class="oc-dropdown-separator" role="separator"></div>
29
+ <button class="oc-dropdown-item" type="button" role="menuitem">Reload <span class="oc-dropdown-shortcut">Ctrl R</span></button>
30
+ </div>
@@ -0,0 +1,123 @@
1
+ <!-- OmniaUI component: message-thread — Scrolling conversation: header with subject, people, status and actions menu, messages that start at the bottom, composer and a sending overlay. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/message-thread.html | Map: thread -> header (heading, people, badge, menu) -> scroll (message list) -> footer (attachments + chat-composer) -> busy overlay -> optional script
3
+ Class API (modifiers are extra classes / attributes on the same element):
4
+ section.oc-message-thread: Grid frame, height from --oc-message-thread-height (32rem). Modifier: --plain (no wallpaper).
5
+ State: aria-busy="true" shows the sending overlay
6
+ .oc-message-thread__header > .oc-message-thread__heading (__title, __meta), .oc-avatar-group.oc-message-thread__people,
7
+ .oc-message-thread__actions > .oc-badge, button.oc-message-thread__icon-btn (popovertarget) + .oc-dropdown-menu[popover]
8
+ .oc-message-thread__scroll (tabindex="0", role="log"): Scroll area; column-reverse, so it opens at the newest message.
9
+ Holds one .oc-message-list (message component)
10
+ .oc-message-thread__footer: Bottom bar with an optional .oc-attachment-list and the chat-composer, or a .oc-message-thread__note
11
+ .oc-message-thread__busy (role="status"): Overlay with .oc-spinner, strong and span
12
+ Optional script (end of this snippet): appends what you type as an outgoing message after a short "Sending" state and
13
+ sends with Ctrl/Cmd + Enter. In an app, post the form and render the new message on the server instead.
14
+ -->
15
+ <section class="oc-message-thread" aria-labelledby="oc-thread-demo-title" style="inline-size:100%;max-inline-size:40rem">
16
+ <header class="oc-message-thread__header">
17
+ <div class="oc-message-thread__heading">
18
+ <h3 class="oc-message-thread__title" id="oc-thread-demo-title">Kitchen worktop: which finish?</h3>
19
+ <p class="oc-message-thread__meta"><strong>Villa Dewitte</strong> · Lisa Verbeke → Tom Peeters · 14 Oct 2026</p>
20
+ </div>
21
+ <div class="oc-avatar-group oc-message-thread__people" aria-label="2 participants"><span class="oc-avatar" title="Lisa Verbeke">LV</span><span class="oc-avatar" title="Tom Peeters">TP</span></div>
22
+ <div class="oc-message-thread__actions">
23
+ <span class="oc-badge oc-badge--destructive">Urgent</span>
24
+ <button type="button" class="oc-message-thread__icon-btn" popovertarget="oc-thread-demo-menu" aria-label="Conversation actions"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><circle cx="12" cy="5" r="1.8"/><circle cx="12" cy="12" r="1.8"/><circle cx="12" cy="19" r="1.8"/></svg></button>
25
+ <div class="oc-dropdown-menu" id="oc-thread-demo-menu" popover role="menu">
26
+ <button type="button" class="oc-dropdown-item" role="menuitem">Mark as unread</button>
27
+ <button type="button" class="oc-dropdown-item" role="menuitem">Move to archive</button>
28
+ <div class="oc-dropdown-separator" role="separator"></div>
29
+ <button type="button" class="oc-dropdown-item oc-dropdown-item--destructive" role="menuitem">Delete conversation</button>
30
+ </div>
31
+ </div>
32
+ </header>
33
+
34
+ <div class="oc-message-thread__scroll" tabindex="0" role="log" aria-label="Messages" aria-live="polite">
35
+ <div class="oc-message-list">
36
+ <div class="oc-message-divider"><span>Tuesday 14 October</span></div>
37
+ <article class="oc-message">
38
+ <span class="oc-avatar oc-message__avatar">LV</span>
39
+ <div class="oc-message__bubble">
40
+ <p class="oc-message__sender">Lisa Verbeke</p>
41
+ <div class="oc-message__text"><p>The supplier dropped off two worktop samples at the workshop. Photo attached.</p></div>
42
+ <div class="oc-message__attachments"><a class="oc-attachment-image" href="#samples.jpg"><span class="oc-media-art oc-media-art--6" role="img" aria-label="Two worktop samples on the workbench"></span><span class="oc-attachment-image__caption">samples.jpg · 2.1 MB</span></a></div>
43
+ <p class="oc-message__meta"><time datetime="2026-10-14T09:12">09:12</time></p>
44
+ </div>
45
+ </article>
46
+ <article class="oc-message oc-message--continued">
47
+ <span class="oc-avatar oc-message__avatar">LV</span>
48
+ <div class="oc-message__bubble">
49
+ <div class="oc-message__text"><p>Which one should we order: <strong>Calacatta matt</strong> or <strong>Nero Marquina polished</strong>? The cutting slot is Friday.</p></div>
50
+ <p class="oc-message__meta"><time datetime="2026-10-14T09:13">09:13</time></p>
51
+ </div>
52
+ </article>
53
+ <article class="oc-message oc-message--out">
54
+ <span class="oc-avatar oc-message__avatar">TP</span>
55
+ <div class="oc-message__bubble">
56
+ <div class="oc-message__text"><p>Calacatta matt, 20 mm. I'll send the updated quote to the client today.</p></div>
57
+ <div class="oc-message__attachments"><a class="oc-attachment oc-attachment--pdf" href="#quote.pdf" download><span class="oc-attachment__icon" aria-hidden="true">PDF</span><span class="oc-attachment__body"><span class="oc-attachment__name">Quote kitchen Dewitte v2.pdf</span><span class="oc-attachment__size">1.2 MB</span></span></a></div>
58
+ <p class="oc-message__meta"><time datetime="2026-10-14T09:20">09:20</time><span class="oc-message__status oc-message__status--read">Read</span></p>
59
+ </div>
60
+ <div class="oc-message__actions"><button type="button" class="oc-message__action oc-message__action--destructive" aria-label="Delete this message"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6"/></svg></button></div>
61
+ </article>
62
+ <div class="oc-message-divider"><span>Today</span></div>
63
+ <article class="oc-message">
64
+ <span class="oc-avatar oc-message__avatar">LV</span>
65
+ <div class="oc-message__bubble">
66
+ <p class="oc-message__sender">Lisa Verbeke</p>
67
+ <div class="oc-message__text"><p>Client signed. Can you confirm the edge profile before 16:00?</p></div>
68
+ <p class="oc-message__meta"><time datetime="2026-10-24T10:02">10:02</time></p>
69
+ </div>
70
+ </article>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="oc-message-thread__footer">
75
+ <form class="oc-chat-composer" action="#" aria-label="Reply">
76
+ <label class="oc-visually-hidden" for="oc-thread-demo-input">Message</label>
77
+ <textarea class="oc-chat-composer__input" id="oc-thread-demo-input" name="message" rows="1" placeholder="Type your reply…" required></textarea>
78
+ <div class="oc-chat-composer__bar">
79
+ <div class="oc-chat-composer__tools">
80
+ <button type="button" class="oc-chat-composer__tool" aria-label="Attach a file">&#128206;</button>
81
+ <button type="button" class="oc-chat-composer__tool" aria-label="Insert emoji">&#128578;</button>
82
+ </div>
83
+ <span class="oc-chat-composer__hint">Ctrl + Enter to send</span>
84
+ <button type="submit" class="oc-chat-composer__send" aria-label="Send">&#10148;</button>
85
+ </div>
86
+ </form>
87
+ </div>
88
+
89
+ <div class="oc-message-thread__busy" role="status">
90
+ <span class="oc-spinner oc-spinner--lg" aria-hidden="true"></span>
91
+ <strong>Sending message…</strong>
92
+ <span>Please wait, do not close this conversation.</span>
93
+ </div>
94
+ </section>
95
+ <script>
96
+ // Optional: append the typed message as an outgoing bubble (demo behaviour; an app posts the form instead).
97
+ document.addEventListener("submit", (e) => {
98
+ const form = e.target.closest(".oc-message-thread form.oc-chat-composer");
99
+ if (!form || e.defaultPrevented) return;
100
+ e.preventDefault();
101
+ const thread = form.closest(".oc-message-thread"), field = form.querySelector("textarea"), text = field.value.trim();
102
+ if (!text) return;
103
+ thread.setAttribute("aria-busy", "true");
104
+ setTimeout(() => {
105
+ const list = thread.querySelector(".oc-message-list"), last = list.lastElementChild;
106
+ const msg = document.createElement("article");
107
+ msg.className = "oc-message oc-message--out" + (last?.matches(".oc-message--out") ? " oc-message--continued" : "");
108
+ msg.innerHTML = '<span class="oc-avatar oc-message__avatar"></span><div class="oc-message__bubble"><div class="oc-message__text"><p></p></div><p class="oc-message__meta"><time></time><span class="oc-message__status">Sent</span></p></div>';
109
+ msg.querySelector(".oc-avatar").textContent = list.querySelector(".oc-message--out .oc-avatar")?.textContent || "Me";
110
+ msg.querySelector("p").textContent = text;
111
+ msg.querySelector("time").textContent = new Date().toTimeString().slice(0, 5);
112
+ list.append(msg);
113
+ field.value = "";
114
+ thread.removeAttribute("aria-busy");
115
+ field.focus();
116
+ }, 600);
117
+ });
118
+ document.addEventListener("keydown", (e) => {
119
+ if (e.defaultPrevented || e.key !== "Enter" || !(e.ctrlKey || e.metaKey) || !e.target.matches(".oc-message-thread .oc-chat-composer__input")) return;
120
+ e.preventDefault();
121
+ e.target.form.requestSubmit();
122
+ });
123
+ </script>
@@ -0,0 +1,51 @@
1
+ <!-- OmniaUI component: message — Chat bubble: incoming and outgoing, sender, avatar, time, read receipt, hover actions, grouping, reply quote and date divider. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/message.html | Map: list -> date divider -> incoming group (2) -> outgoing with quote + receipt -> system line
3
+ Class API (modifiers are extra classes / attributes on the same element):
4
+ .oc-message-list: Column of messages and dividers (role="log" when it updates live). Custom properties: --oc-message-in, --oc-message-out
5
+ article.oc-message: One row. Modifiers: --out (right, primary tint), --continued (follows a message of the same sender:
6
+ no name, the avatar moves to the last message of the group via :has())
7
+ .oc-avatar.oc-message__avatar: Avatar (avatar component); keep it on every message so the grouping can hide it
8
+ .oc-message__bubble > .oc-message__sender, a.oc-message__quote (strong + span), .oc-message__text (p, ul, ol, a, strong, code),
9
+ .oc-message__attachments (attachment component), .oc-message__meta > time + .oc-message__status (--read, --failed)
10
+ .oc-message__actions > button.oc-message__action (--destructive): Shown on hover and keyboard focus, always on touch screens
11
+ .oc-message-divider > span: Date divider. Modifier: --system (a centred notice without lines)
12
+ No JavaScript. Escape user text on the server; the markup only expects paragraphs, lists and links.
13
+ -->
14
+ <div class="oc-message-list" role="log" aria-label="Conversation with Lisa Verbeke" style="max-inline-size:36rem">
15
+ <div class="oc-message-divider"><span>Tuesday 14 October</span></div>
16
+
17
+ <article class="oc-message" aria-label="Lisa Verbeke, 09:12">
18
+ <span class="oc-avatar oc-message__avatar">LV</span>
19
+ <div class="oc-message__bubble">
20
+ <p class="oc-message__sender">Lisa Verbeke</p>
21
+ <div class="oc-message__text"><p>Morning Tom! The worktop supplier sent two samples for Villa Dewitte.</p></div>
22
+ <p class="oc-message__meta"><time datetime="2026-10-14T09:12">09:12</time></p>
23
+ </div>
24
+ </article>
25
+ <article class="oc-message oc-message--continued" id="oc-message-demo-question" aria-label="Lisa Verbeke, 09:13">
26
+ <span class="oc-avatar oc-message__avatar">LV</span>
27
+ <div class="oc-message__bubble">
28
+ <div class="oc-message__text">
29
+ <p>Which finish should we order?</p>
30
+ <ul><li><strong>Calacatta</strong> matt, 20 mm</li><li><strong>Nero Marquina</strong> polished, 12 mm</li></ul>
31
+ <p>Details are on the <a href="#supplier-sheet">supplier sheet</a>.</p>
32
+ </div>
33
+ <p class="oc-message__meta"><time datetime="2026-10-14T09:13">09:13</time></p>
34
+ </div>
35
+ </article>
36
+
37
+ <article class="oc-message oc-message--out" aria-label="You, 09:20">
38
+ <span class="oc-avatar oc-message__avatar">TP</span>
39
+ <div class="oc-message__bubble">
40
+ <a class="oc-message__quote" href="#oc-message-demo-question"><strong>Lisa Verbeke</strong><span>Which finish should we order? Calacatta matt, 20 mm or Nero Marquina polished, 12 mm</span></a>
41
+ <div class="oc-message__text"><p>Calacatta matt. The client wants the lighter look next to the oak fronts.</p></div>
42
+ <p class="oc-message__meta"><time datetime="2026-10-14T09:20">09:20</time><span class="oc-message__status oc-message__status--read"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12.5 7 17.5 18 6.5M13 16.5l1 1L22.5 9"/></svg>Read</span></p>
43
+ </div>
44
+ <div class="oc-message__actions">
45
+ <button type="button" class="oc-message__action" aria-label="Reply to this message"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 14 4 9l5-5M4 9h10a6 6 0 0 1 6 6v4"/></svg></button>
46
+ <button type="button" class="oc-message__action oc-message__action--destructive" aria-label="Delete this message"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4h8v2M19 6l-1 14H6L5 6M10 11v6M14 11v6"/></svg></button>
47
+ </div>
48
+ </article>
49
+
50
+ <div class="oc-message-divider oc-message-divider--system" role="note"><span>Lisa Verbeke marked this question as answered</span></div>
51
+ </div>
@@ -0,0 +1,15 @@
1
+ <!-- OmniaUI component: modal — Modal on <dialog> — opens with invoker commands, no JavaScript. Needs @birdycom/omniaui CSS. -->
2
+ <button class="oc-btn" command="show-modal" commandfor="oc-modal-demo">Open modal</button>
3
+
4
+ <dialog class="oc-modal" id="oc-modal-demo" closedby="any" aria-labelledby="oc-modal-demo-title">
5
+ <div class="oc-modal-content">
6
+ <div class="oc-modal-header">
7
+ <h2 class="oc-modal-title" id="oc-modal-demo-title">Are you sure?</h2>
8
+ <p class="oc-modal-description">This action cannot be undone.</p>
9
+ </div>
10
+ <div class="oc-modal-footer">
11
+ <button class="oc-btn oc-btn--outline" command="close" commandfor="oc-modal-demo">Cancel</button>
12
+ <button class="oc-btn oc-btn--destructive" command="close" commandfor="oc-modal-demo">Delete</button>
13
+ </div>
14
+ </div>
15
+ </dialog>
@@ -0,0 +1,15 @@
1
+ <!-- OmniaUI component: native-select - Native select: themed <select>; optional base-select picker styling. Needs @birdycom/omniaui CSS.
2
+ Class API (modifiers are extra classes / attributes on the same element):
3
+ select.oc-select: themed select (existing)
4
+ select.oc-select.oc-select--custom: also styles ::picker(select), options, checkmark where appearance: base-select is supported; elsewhere it looks like .oc-select
5
+ -->
6
+ <div class="oc-field" style="max-inline-size:16rem">
7
+ <label class="oc-field-label" for="oc-ns-fruit">Fruit</label>
8
+ <select class="oc-select oc-select--custom" id="oc-ns-fruit">
9
+ <option>Apple</option>
10
+ <option selected>Banana</option>
11
+ <option>Blueberry</option>
12
+ <option>Grapes</option>
13
+ <option>Pineapple</option>
14
+ </select>
15
+ </div>
@@ -0,0 +1,23 @@
1
+ <!-- OmniaUI component: navmenu - Horizontal navigation links with optional dropdown children. Needs @birdycom/omniaui CSS.
2
+ Class API (modifiers are extra classes / attributes on the same element):
3
+ .oc-navmenu: ul row of items
4
+ .oc-navmenu-link: Link or button. State: aria-current="page" or .is-active
5
+ .oc-navmenu-link--caret: Adds a chevron (trigger of a dropdown)
6
+ .oc-dropdown-menu[popover]: Child menu, from the dropdown component
7
+ -->
8
+ <nav aria-label="Module navigation">
9
+ <ul class="oc-navmenu">
10
+ <li><a class="oc-navmenu-link" href="#" aria-current="page">Overview</a></li>
11
+ <li><a class="oc-navmenu-link" href="#">Contacts</a></li>
12
+ <li>
13
+ <button class="oc-navmenu-link oc-navmenu-link--caret" type="button" popovertarget="oc-navmenu-sales">Sales</button>
14
+ <div class="oc-dropdown-menu" id="oc-navmenu-sales" popover>
15
+ <div class="oc-dropdown-label">Documents</div>
16
+ <a class="oc-dropdown-item" href="#">Quotes</a>
17
+ <a class="oc-dropdown-item" href="#">Orders</a>
18
+ <a class="oc-dropdown-item" href="#">Invoices</a>
19
+ </div>
20
+ </li>
21
+ <li><a class="oc-navmenu-link" href="#">Reports</a></li>
22
+ </ul>
23
+ </nav>
@@ -0,0 +1,15 @@
1
+ <!-- OmniaUI component: next-teaser - "Next product" teaser card with label, name, mark and an arrow tagline. Needs @birdycom/omniaui CSS.
2
+ File: packages/ui/snippets/next-teaser.html | Map: a.oc-next-teaser -> __label -> __name (mark + text) -> __tagline
3
+ Class API (modifiers are extra classes / attributes on the same element):
4
+ .oc-next-teaser: The card (an <a>). Modifier: --prev ("Previous" link, arrow left before the tagline)
5
+ .oc-next-teaser__label: Small uppercase label
6
+ .oc-next-teaser__name: Name; may start with an .oc-product-mark
7
+ .oc-next-teaser__tagline: Short line with a moving arrow
8
+ .oc-accent--*: Accent preset
9
+ Slots: label, name, mark, tagline
10
+ -->
11
+ <a class="oc-next-teaser oc-accent--violet" href="#next">
12
+ <span class="oc-next-teaser__label">Next product</span>
13
+ <span class="oc-next-teaser__name"><span class="oc-product-mark oc-product-mark--sm oc-accent--violet" aria-hidden="true">&#9673;</span>Acme Design</span>
14
+ <span class="oc-next-teaser__tagline">The design system behind our apps</span>
15
+ </a>