srcdev-nuxt-components 9.1.49 → 9.1.50

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 (220) hide show
  1. package/.claude/settings.json +2 -4
  2. package/.claude/settings.local.json +22 -2
  3. package/.claude/skills/components/card-core.md +205 -0
  4. package/.claude/skills/components/display-dialog.md +128 -0
  5. package/.claude/skills/components/page-hero-highlights.md +1 -1
  6. package/.claude/skills/components/page-row.md +157 -0
  7. package/.claude/skills/components/services-card-grid.md +4 -4
  8. package/.claude/skills/components/services-section-grid.md +4 -4
  9. package/.claude/skills/components/tab-navigation.md +222 -0
  10. package/.claude/skills/composable-dialog-controls.md +55 -43
  11. package/.claude/skills/index.md +5 -2
  12. package/app/components/01.atoms/card/CONSUMER-STYLING.md +175 -0
  13. package/app/components/01.atoms/card/CardCore.vue +48 -21
  14. package/app/components/01.atoms/display-dialog/CONSUMER-STYLING.md +163 -0
  15. package/app/components/01.atoms/display-dialog/DisplayDialog.vue +277 -0
  16. package/app/components/01.atoms/display-dialog/stories/DisplayDialog.stories.ts +177 -0
  17. package/app/components/01.atoms/display-dialog/tests/DisplayDialog.spec.ts +264 -0
  18. package/app/components/01.atoms/display-dialog/tests/__snapshots__/DisplayDialog.spec.ts.snap +18 -0
  19. package/app/components/01.atoms/page-row/CONSUMER-STYLING.md +90 -0
  20. package/app/components/01.atoms/page-row/PageRow.vue +115 -0
  21. package/app/components/01.atoms/page-row/stories/PageRow.stories.ts +311 -0
  22. package/app/components/01.atoms/page-row/tests/PageRow.spec.ts +191 -0
  23. package/app/components/02.molecules/navigation/tab-navigation/CONSUMER-STYLING.md +145 -0
  24. package/app/components/02.molecules/navigation/tab-navigation/TabNavigation.vue +21 -11
  25. package/app/components/02.molecules/navigation/tab-navigation/tests/__snapshots__/TabNavigation.spec.ts.snap +12 -6
  26. package/app/components/{forms/ui → 05.forms/form-field}/FormField.vue +12 -19
  27. package/app/components/{forms/ui → 05.forms/form-wrapper}/FormWrapper.vue +6 -6
  28. package/app/components/{forms → 05.forms}/input-button/InputButtonCore.vue +2 -0
  29. package/app/components/{forms → 05.forms}/input-button/stories/InputButtonCore.stories.ts +1 -1
  30. package/app/components/{forms → 05.forms}/input-text/InputTextCore.vue +73 -73
  31. package/app/components/{forms → 05.forms}/input-text/variants/InputTextAsNumberWithLabel.vue +29 -29
  32. package/app/components/carousels/CarouselBasic.vue +99 -99
  33. package/app/components/carousels/CarouselInfinite.vue +150 -150
  34. package/app/components/deep-expanding-menu/DeepExpandingMenu.vue +111 -111
  35. package/app/components/responsive-header/ResponsiveHeader.vue +250 -282
  36. package/app/components/test-storybook/TestStorybook.vue +16 -16
  37. package/app/composables/tests/useDialogControls.spec.ts +34 -64
  38. package/app/composables/useDialogControls.ts +19 -33
  39. package/app/composables/useFormControl.ts +0 -2
  40. package/app/layouts/default.vue +9 -7
  41. package/app/pages/auto-grid.vue +37 -20
  42. package/app/pages/banner-video.vue +2 -2
  43. package/app/pages/forms/examples/buttons/index.vue +189 -215
  44. package/app/pages/forms/examples/material/text-fields.vue +2 -2
  45. package/app/pages/grid-stack.vue +10 -10
  46. package/app/pages/index.vue +20 -20
  47. package/app/pages/samaritan.vue +4 -4
  48. package/app/pages/typography/hero-text.vue +6 -6
  49. package/app/pages/typography/index.vue +4 -4
  50. package/app/pages/typography/page-body.vue +4 -4
  51. package/app/pages/typography/page-heading.vue +4 -4
  52. package/app/pages/typography/page-link.vue +4 -4
  53. package/app/pages/ui/accordian.vue +4 -4
  54. package/app/pages/ui/anchor-scroll-tab-navigation.vue +77 -66
  55. package/app/pages/ui/anchor-scroll.vue +94 -70
  56. package/app/pages/ui/animated-svg-text.vue +4 -4
  57. package/app/pages/ui/block-decorators.vue +11 -11
  58. package/app/pages/ui/card-core.vue +9 -9
  59. package/app/pages/ui/carousel-basic.vue +4 -4
  60. package/app/pages/ui/carousel-flip.vue +2 -2
  61. package/app/pages/ui/carousel-infinite.vue +4 -4
  62. package/app/pages/ui/clipped-panels.vue +10 -10
  63. package/app/pages/ui/contact-section.vue +4 -4
  64. package/app/pages/ui/container-glow.vue +2 -3
  65. package/app/pages/ui/deep-expanding-menu.vue +138 -0
  66. package/app/pages/ui/display-avatar.vue +2 -2
  67. package/app/pages/ui/display-banner.vue +2 -2
  68. package/app/pages/ui/display-chip.vue +32 -25
  69. package/app/pages/ui/display-details.vue +6 -10
  70. package/app/pages/ui/display-dialog.vue +96 -131
  71. package/app/pages/ui/display-pill.vue +27 -33
  72. package/app/pages/ui/display-prompt.vue +4 -4
  73. package/app/pages/ui/display-toast.vue +2 -2
  74. package/app/pages/ui/expanding-panel.vue +12 -16
  75. package/app/pages/ui/glowing-border.vue +12 -12
  76. package/app/pages/ui/layout-grid-a.vue +6 -6
  77. package/app/pages/ui/layout-grid-b.vue +4 -4
  78. package/app/pages/ui/magnetic-navigation.vue +6 -6
  79. package/app/pages/ui/marquee-scroller.vue +2 -2
  80. package/app/pages/ui/mask-element.vue +2 -2
  81. package/app/pages/ui/masonry-columns.vue +4 -4
  82. package/app/pages/ui/masonry-grid-ordered-experiment.vue +4 -4
  83. package/app/pages/ui/masonry-grid-ordered.vue +4 -4
  84. package/app/pages/ui/masonry-grid-sorted.vue +4 -4
  85. package/app/pages/ui/masonry-grid.vue +4 -4
  86. package/app/pages/ui/navigation/navigation-horizontal.vue +12 -12
  87. package/app/pages/ui/navigation/tab-navigation.vue +493 -0
  88. package/app/pages/ui/page-row.vue +127 -0
  89. package/app/pages/ui/price-list.vue +4 -4
  90. package/app/pages/ui/profile-section.vue +4 -4
  91. package/app/pages/ui/qr-code/[componentName].vue +6 -6
  92. package/app/pages/ui/rotating-carousel.vue +16 -16
  93. package/app/pages/ui/scroll-reveal-image.vue +20 -20
  94. package/app/pages/ui/section-parallax.vue +7 -7
  95. package/app/pages/ui/services/colour-finder.vue +4 -4
  96. package/app/pages/ui/services/services-cards.vue +4 -4
  97. package/app/pages/ui/services/services-section/[slug].vue +4 -4
  98. package/app/pages/ui/services/services-sections-compact.vue +6 -6
  99. package/app/pages/ui/settings.vue +10 -10
  100. package/app/pages/ui/simple-grid.vue +2 -2
  101. package/app/pages/ui/slider-gallery.vue +5 -5
  102. package/app/pages/ui/tabs-y.vue +2 -3
  103. package/app/pages/ui/tabs.vue +6 -9
  104. package/app/pages/ui/tooltips.vue +9 -9
  105. package/app/pages/ui/wipe-away-vertical.vue +14 -14
  106. package/package.json +1 -1
  107. package/.claude/skills/components/layout-row.md +0 -239
  108. package/app/components/display-dialog/DisplayDialogCore.vue +0 -276
  109. package/app/components/display-dialog/variants/DisplayDialogConfirm.vue +0 -47
  110. package/app/components/display-dialog/variants/DisplayDialogScrollableContent.vue +0 -51
  111. package/app/components/forms/c12/utils.ts +0 -14
  112. package/app/components/layout-row/LayoutRow.vue +0 -171
  113. package/app/components/layout-row/stories/LayoutRow.stories.ts +0 -528
  114. package/app/pages/ui/layout-row.vue +0 -139
  115. /package/app/components/{forms → 05.forms}/form-errors/InputError.vue +0 -0
  116. /package/app/components/{forms → 05.forms}/form-errors/tests/InputError.spec.ts +0 -0
  117. /package/app/components/{forms → 05.forms}/form-fieldset/FormFieldset.vue +0 -0
  118. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts +0 -0
  119. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-chromium-darwin.png +0 -0
  120. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-firefox-darwin.png +0 -0
  121. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-secondary-webkit-darwin.png +0 -0
  122. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-chromium-darwin.png +0 -0
  123. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-firefox-darwin.png +0 -0
  124. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-icons-tertiary-webkit-darwin.png +0 -0
  125. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-chromium-darwin.png +0 -0
  126. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-firefox-darwin.png +0 -0
  127. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-primary-pending-webkit-darwin.png +0 -0
  128. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-chromium-darwin.png +0 -0
  129. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-firefox-darwin.png +0 -0
  130. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-secondary-pill-webkit-darwin.png +0 -0
  131. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-chromium-darwin.png +0 -0
  132. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-firefox-darwin.png +0 -0
  133. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/combo-tertiary-readonly-webkit-darwin.png +0 -0
  134. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-chromium-darwin.png +0 -0
  135. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-firefox-darwin.png +0 -0
  136. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-chromium-darwin.png +0 -0
  137. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-firefox-darwin.png +0 -0
  138. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-default-webkit-darwin.png +0 -0
  139. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-chromium-darwin.png +0 -0
  140. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-firefox-darwin.png +0 -0
  141. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-error-webkit-darwin.png +0 -0
  142. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-chromium-darwin.png +0 -0
  143. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-firefox-darwin.png +0 -0
  144. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-success-webkit-darwin.png +0 -0
  145. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-chromium-darwin.png +0 -0
  146. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-firefox-darwin.png +0 -0
  147. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-theme-warning-webkit-darwin.png +0 -0
  148. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-chromium-darwin.png +0 -0
  149. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-firefox-darwin.png +0 -0
  150. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-primary-webkit-darwin.png +0 -0
  151. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-chromium-darwin.png +0 -0
  152. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-firefox-darwin.png +0 -0
  153. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-secondary-webkit-darwin.png +0 -0
  154. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-chromium-darwin.png +0 -0
  155. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-firefox-darwin.png +0 -0
  156. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-variant-tertiary-webkit-darwin.png +0 -0
  157. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/default-webkit-darwin.png +0 -0
  158. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-chromium-darwin.png +0 -0
  159. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-firefox-darwin.png +0 -0
  160. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/nuxt-icon-only-component-webkit-darwin.png +0 -0
  161. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-chromium-darwin.png +0 -0
  162. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-firefox-darwin.png +0 -0
  163. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pending-webkit-darwin.png +0 -0
  164. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-chromium-darwin.png +0 -0
  165. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-firefox-darwin.png +0 -0
  166. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-pill-webkit-darwin.png +0 -0
  167. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-chromium-darwin.png +0 -0
  168. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-firefox-darwin.png +0 -0
  169. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/state-readonly-webkit-darwin.png +0 -0
  170. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-chromium-darwin.png +0 -0
  171. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-firefox-darwin.png +0 -0
  172. /package/app/components/{forms → 05.forms}/input-button/playwright/input-button-core.playwright.ts-snapshots/with-both-nuxt-icon-components-webkit-darwin.png +0 -0
  173. /package/app/components/{forms → 05.forms}/input-button/tests/InputButtonCore.spec.ts +0 -0
  174. /package/app/components/{forms → 05.forms}/input-button/tests/__snapshots__/InputButtonCore.spec.ts.snap +0 -0
  175. /package/app/components/{forms → 05.forms}/input-checkbox/MultipleCheckboxes.vue +0 -0
  176. /package/app/components/{forms → 05.forms}/input-checkbox/SingleCheckbox.vue +0 -0
  177. /package/app/components/{forms → 05.forms}/input-checkbox/tests/MultipleCheckboxes.spec.ts +0 -0
  178. /package/app/components/{forms → 05.forms}/input-checkbox/tests/data/tags.json +0 -0
  179. /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioButton.vue +0 -0
  180. /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioCore.vue +0 -0
  181. /package/app/components/{forms → 05.forms}/input-checkbox-radio/InputCheckboxRadioWithLabel.vue +0 -0
  182. /package/app/components/{forms → 05.forms}/input-checkbox-radio/tests/InputCheckboxRadioCore.spec.ts +0 -0
  183. /package/app/components/{forms → 05.forms}/input-copy/InputCopyCore.vue +0 -0
  184. /package/app/components/{forms → 05.forms}/input-copy/stories/InputCopyCore.stories.ts +0 -0
  185. /package/app/components/{forms → 05.forms}/input-copy/tests/InputCopyCore.spec.ts +0 -0
  186. /package/app/components/{forms → 05.forms}/input-copy/tests/__snapshots__/InputCopyCore.spec.ts.snap +0 -0
  187. /package/app/components/{forms → 05.forms}/input-description/InputDescription.vue +0 -0
  188. /package/app/components/{forms → 05.forms}/input-description/tests/InputDescription.spec.ts +0 -0
  189. /package/app/components/{forms → 05.forms}/input-label/InputLabel.vue +0 -0
  190. /package/app/components/{forms → 05.forms}/input-label/tests/InputLabel.spec.ts +0 -0
  191. /package/app/components/{forms → 05.forms}/input-number/InputNumberCore.vue +0 -0
  192. /package/app/components/{forms → 05.forms}/input-number/variants/InputNumberDefault.vue +0 -0
  193. /package/app/components/{forms → 05.forms}/input-radio/MultipleRadiobuttons.vue +0 -0
  194. /package/app/components/{forms → 05.forms}/input-radio/tests/MultipleRadioButtons.spec.ts +0 -0
  195. /package/app/components/{forms → 05.forms}/input-radio/tests/data/tags.json +0 -0
  196. /package/app/components/{forms → 05.forms}/input-range/InputRangeCore.vue +0 -0
  197. /package/app/components/{forms → 05.forms}/input-range/variants/InputRangeDefault.vue +0 -0
  198. /package/app/components/{forms → 05.forms}/input-range-fancy/InputRangeFancyCore.vue +0 -0
  199. /package/app/components/{forms → 05.forms}/input-range-fancy/InputRangeFancyWithLabel.vue +0 -0
  200. /package/app/components/{forms → 05.forms}/input-select/InputSelectCore.vue +0 -0
  201. /package/app/components/{forms → 05.forms}/input-select/variants/InputSelectWithLabel.vue +0 -0
  202. /package/app/components/{forms → 05.forms}/input-text/stories/InputPasswordWithLabel.stories.ts +0 -0
  203. /package/app/components/{forms → 05.forms}/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -0
  204. /package/app/components/{forms → 05.forms}/input-text/stories/InputTextCore.stories.ts +0 -0
  205. /package/app/components/{forms → 05.forms}/input-text/stories/InputTextWithLabel.stories.ts +0 -0
  206. /package/app/components/{forms → 05.forms}/input-text/variants/InputPasswordWithLabel.vue +0 -0
  207. /package/app/components/{forms → 05.forms}/input-text/variants/InputTextWithLabel.vue +0 -0
  208. /package/app/components/{forms → 05.forms}/input-textarea/InputTextareaCore.vue +0 -0
  209. /package/app/components/{forms → 05.forms}/input-textarea/stories/InputTextareaCore.stories.ts +0 -0
  210. /package/app/components/{forms → 05.forms}/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -0
  211. /package/app/components/{forms → 05.forms}/input-textarea/variants/InputTextareaWithLabel.vue +0 -0
  212. /package/app/components/{forms → 05.forms}/pending-effect/PendingEffect.vue +0 -0
  213. /package/app/components/{forms → 05.forms}/toggle-switch/ToggleSwitchCore.vue +0 -0
  214. /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -0
  215. /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -0
  216. /package/app/components/{forms → 05.forms}/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -0
  217. /package/app/components/{forms → 05.forms}/toggle-switch/variants/ToggleSwitchWithLabel.vue +0 -0
  218. /package/app/components/{forms → 05.forms}/toggle-switch/variants/ToggleSwitchWithLabelInline.vue +0 -0
  219. /package/app/components/{forms → 05.forms}/triple-toggle-switch/TripleToggleSwitchCore.vue +0 -0
  220. /package/app/{components/forms/c12/prop-validators/index.ts → utils/prop-validators.ts} +0 -0
@@ -0,0 +1,264 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { nextTick } from "vue";
3
+ import { mountSuspended } from "@nuxt/test-utils/runtime";
4
+ import DisplayDialog from "../DisplayDialog.vue";
5
+
6
+ vi.mock("focus-trap-vue", () => ({
7
+ FocusTrap: {
8
+ name: "FocusTrap",
9
+ props: ["active", "escapeDeactivates", "clickOutsideDeactivates"],
10
+ emits: ["update:active", "deactivate"],
11
+ template: "<slot />",
12
+ },
13
+ }));
14
+
15
+ describe("DisplayDialog", () => {
16
+ // ─── Mount ────────────────────────────────────────────────────────────────
17
+
18
+ it("mounts without error", async () => {
19
+ const wrapper = await mountSuspended(DisplayDialog, {
20
+ props: { dataDialogId: "test" },
21
+ });
22
+ expect(wrapper.vm).toBeTruthy();
23
+ });
24
+
25
+ // ─── Snapshot ─────────────────────────────────────────────────────────────
26
+
27
+ it("renders correct HTML structure with all slots", async () => {
28
+ const wrapper = await mountSuspended(DisplayDialog, {
29
+ props: { dataDialogId: "test", modelValue: true },
30
+ slots: {
31
+ dialogTitle: "<p>Title</p>",
32
+ dialogContent: "<p>Content</p>",
33
+ actionButtonLeft: "<button>Cancel</button>",
34
+ actionButtonRight: "<button>Confirm</button>",
35
+ },
36
+ });
37
+ expect(wrapper.html()).toMatchSnapshot();
38
+ });
39
+
40
+ // ─── Root element ─────────────────────────────────────────────────────────
41
+
42
+ it("renders as <dialog>", async () => {
43
+ const wrapper = await mountSuspended(DisplayDialog, {
44
+ props: { dataDialogId: "test" },
45
+ });
46
+ expect(wrapper.element.tagName).toBe("DIALOG");
47
+ });
48
+
49
+ it("always has the display-dialog class", async () => {
50
+ const wrapper = await mountSuspended(DisplayDialog, {
51
+ props: { dataDialogId: "test" },
52
+ });
53
+ expect(wrapper.classes()).toContain("display-dialog");
54
+ });
55
+
56
+ // ─── data-dialog-id ───────────────────────────────────────────────────────
57
+
58
+ it("sets data-dialog-id attribute from prop", async () => {
59
+ const wrapper = await mountSuspended(DisplayDialog, {
60
+ props: { dataDialogId: "my-dialog" },
61
+ });
62
+ expect(wrapper.attributes("data-dialog-id")).toBe("my-dialog");
63
+ });
64
+
65
+ // ─── open attribute ───────────────────────────────────────────────────────
66
+
67
+ it("sets [open] attribute when modelValue is true", async () => {
68
+ const wrapper = await mountSuspended(DisplayDialog, {
69
+ props: { dataDialogId: "test", modelValue: true },
70
+ });
71
+ expect(wrapper.attributes("open")).toBeDefined();
72
+ });
73
+
74
+ it("omits [open] attribute when modelValue is false", async () => {
75
+ const wrapper = await mountSuspended(DisplayDialog, {
76
+ props: { dataDialogId: "test", modelValue: false },
77
+ });
78
+ expect(wrapper.attributes("open")).toBeUndefined();
79
+ });
80
+
81
+ // ─── role ─────────────────────────────────────────────────────────────────
82
+
83
+ it("has role='dialog' by default", async () => {
84
+ const wrapper = await mountSuspended(DisplayDialog, {
85
+ props: { dataDialogId: "test" },
86
+ });
87
+ expect(wrapper.attributes("role")).toBe("dialog");
88
+ });
89
+
90
+ it.each(["dialog", "modal", "confirm", "fullscreen"] as const)(
91
+ "has role='dialog' for variant='%s'",
92
+ async (variant) => {
93
+ const wrapper = await mountSuspended(DisplayDialog, {
94
+ props: { dataDialogId: "test", variant },
95
+ });
96
+ expect(wrapper.attributes("role")).toBe("dialog");
97
+ }
98
+ );
99
+
100
+ it("has role='alertdialog' for variant='alert'", async () => {
101
+ const wrapper = await mountSuspended(DisplayDialog, {
102
+ props: { dataDialogId: "test", variant: "alert" },
103
+ });
104
+ expect(wrapper.attributes("role")).toBe("alertdialog");
105
+ });
106
+
107
+ // ─── Variant class on .inner ──────────────────────────────────────────────
108
+
109
+ it("applies 'dialog' class to .inner by default", async () => {
110
+ const wrapper = await mountSuspended(DisplayDialog, {
111
+ props: { dataDialogId: "test" },
112
+ });
113
+ expect(wrapper.find(".inner").classes()).toContain("dialog");
114
+ });
115
+
116
+ it.each(["dialog", "modal", "confirm", "alert", "fullscreen"] as const)(
117
+ "applies '%s' class to .inner",
118
+ async (variant) => {
119
+ const wrapper = await mountSuspended(DisplayDialog, {
120
+ props: { dataDialogId: "test", variant },
121
+ });
122
+ expect(wrapper.find(".inner").classes()).toContain(variant);
123
+ }
124
+ );
125
+
126
+ // ─── Slots ────────────────────────────────────────────────────────────────
127
+
128
+ it("renders dialogTitle slot in .col-left", async () => {
129
+ const wrapper = await mountSuspended(DisplayDialog, {
130
+ props: { dataDialogId: "test" },
131
+ slots: { dialogTitle: "<p>My Title</p>" },
132
+ });
133
+ expect(wrapper.find(".col-left").text()).toBe("My Title");
134
+ });
135
+
136
+ it("omits .col-left when dialogTitle slot is not provided", async () => {
137
+ const wrapper = await mountSuspended(DisplayDialog, {
138
+ props: { dataDialogId: "test" },
139
+ });
140
+ expect(wrapper.find(".col-left").exists()).toBe(false);
141
+ });
142
+
143
+ it("renders dialogContent slot in .dialog-content", async () => {
144
+ const wrapper = await mountSuspended(DisplayDialog, {
145
+ props: { dataDialogId: "test" },
146
+ slots: { dialogContent: "<p>Content</p>" },
147
+ });
148
+ expect(wrapper.find(".dialog-content").text()).toBe("Content");
149
+ });
150
+
151
+ it("omits .dialog-content when dialogContent slot is not provided", async () => {
152
+ const wrapper = await mountSuspended(DisplayDialog, {
153
+ props: { dataDialogId: "test" },
154
+ });
155
+ expect(wrapper.find(".dialog-content").exists()).toBe(false);
156
+ });
157
+
158
+ it("renders .footer when actionButtonLeft slot is provided", async () => {
159
+ const wrapper = await mountSuspended(DisplayDialog, {
160
+ props: { dataDialogId: "test" },
161
+ slots: { actionButtonLeft: "<button>Cancel</button>" },
162
+ });
163
+ expect(wrapper.find(".footer").exists()).toBe(true);
164
+ });
165
+
166
+ it("renders .footer when actionButtonRight slot is provided", async () => {
167
+ const wrapper = await mountSuspended(DisplayDialog, {
168
+ props: { dataDialogId: "test" },
169
+ slots: { actionButtonRight: "<button>Confirm</button>" },
170
+ });
171
+ expect(wrapper.find(".footer").exists()).toBe(true);
172
+ });
173
+
174
+ it("omits .footer when neither action button slot is provided", async () => {
175
+ const wrapper = await mountSuspended(DisplayDialog, {
176
+ props: { dataDialogId: "test" },
177
+ });
178
+ expect(wrapper.find(".footer").exists()).toBe(false);
179
+ });
180
+
181
+ it("renders both action button slots inside .footer", async () => {
182
+ const wrapper = await mountSuspended(DisplayDialog, {
183
+ props: { dataDialogId: "test" },
184
+ slots: {
185
+ actionButtonLeft: "<button>Cancel</button>",
186
+ actionButtonRight: "<button>Confirm</button>",
187
+ },
188
+ });
189
+ expect(wrapper.find(".footer").text()).toContain("Cancel");
190
+ expect(wrapper.find(".footer").text()).toContain("Confirm");
191
+ });
192
+
193
+ // ─── allowContentScroll ───────────────────────────────────────────────────
194
+
195
+ it("does not apply allow-content-scroll class by default", async () => {
196
+ const wrapper = await mountSuspended(DisplayDialog, {
197
+ props: { dataDialogId: "test" },
198
+ slots: { dialogContent: "<p>Content</p>" },
199
+ });
200
+ expect(wrapper.find(".dialog-content").classes()).not.toContain("allow-content-scroll");
201
+ });
202
+
203
+ it("applies allow-content-scroll class when allowContentScroll=true", async () => {
204
+ const wrapper = await mountSuspended(DisplayDialog, {
205
+ props: { dataDialogId: "test", allowContentScroll: true },
206
+ slots: { dialogContent: "<p>Content</p>" },
207
+ });
208
+ expect(wrapper.find(".dialog-content").classes()).toContain("allow-content-scroll");
209
+ });
210
+
211
+ // ─── Close button ─────────────────────────────────────────────────────────
212
+
213
+ it("close button emits update:modelValue=false", async () => {
214
+ const wrapper = await mountSuspended(DisplayDialog, {
215
+ props: { dataDialogId: "test", modelValue: true },
216
+ });
217
+ await wrapper.find('[data-test-id="display-dialog-header-close"]').trigger("click");
218
+ expect(wrapper.emitted("update:modelValue")?.[0]).toEqual([false]);
219
+ });
220
+
221
+ // ─── styleClassPassthrough ────────────────────────────────────────────────
222
+
223
+ it("applies a styleClassPassthrough string", async () => {
224
+ const wrapper = await mountSuspended(DisplayDialog, {
225
+ props: { dataDialogId: "test", styleClassPassthrough: "my-class" },
226
+ });
227
+ expect(wrapper.classes()).toContain("my-class");
228
+ });
229
+
230
+ it("applies multiple styleClassPassthrough classes from an array", async () => {
231
+ const wrapper = await mountSuspended(DisplayDialog, {
232
+ props: { dataDialogId: "test", styleClassPassthrough: ["class-a", "class-b"] },
233
+ });
234
+ expect(wrapper.classes()).toContain("class-a");
235
+ expect(wrapper.classes()).toContain("class-b");
236
+ });
237
+
238
+ // ─── Body viewport lock ───────────────────────────────────────────────────
239
+
240
+ it("adds lock class to body on mount when lockViewport=true", async () => {
241
+ await mountSuspended(DisplayDialog, {
242
+ props: { dataDialogId: "test", lockViewport: true },
243
+ });
244
+ expect(document.body.classList.contains("lock")).toBe(true);
245
+ document.body.classList.remove("lock");
246
+ });
247
+
248
+ it("does not add lock class to body when lockViewport=false", async () => {
249
+ await mountSuspended(DisplayDialog, {
250
+ props: { dataDialogId: "test", lockViewport: false },
251
+ });
252
+ expect(document.body.classList.contains("lock")).toBe(false);
253
+ });
254
+
255
+ it("removes lock class from body when close button is clicked", async () => {
256
+ document.body.classList.add("lock");
257
+ const wrapper = await mountSuspended(DisplayDialog, {
258
+ props: { dataDialogId: "test", modelValue: true, lockViewport: true },
259
+ });
260
+ await wrapper.find('[data-test-id="display-dialog-header-close"]').trigger("click");
261
+ await nextTick();
262
+ expect(document.body.classList.contains("lock")).toBe(false);
263
+ });
264
+ });
@@ -0,0 +1,18 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`DisplayDialog > renders correct HTML structure with all slots 1`] = `
4
+ "<dialog class="display-dialog" role="dialog" align-dialog="center" justify-dialog="center" open="" data-dialog-id="test">
5
+ <div class="inner dialog">
6
+ <div class="header">
7
+ <div class="col-left">
8
+ <p>Title</p>
9
+ </div>
10
+ <div class="col-right"><button data-test-id="display-dialog-header-close" class="display-prompt-action"><span class="iconify i-bitcoin-icons:cross-filled icon" aria-hidden="true"></span><span class="sr-only">Close</span></button></div>
11
+ </div>
12
+ <div class="dialog-content">
13
+ <p>Content</p>
14
+ </div>
15
+ <div class="footer"><button>Cancel</button><button>Confirm</button></div>
16
+ </div>
17
+ </dialog>"
18
+ `;
@@ -0,0 +1,90 @@
1
+ # PageRow — Consumer Styling Guide
2
+
3
+ ## Public token API
4
+
5
+ All `--page-row-*` tokens are the stable override surface. Set them at any scope (global, page,
6
+ or instance) without touching the component itself.
7
+
8
+ | Token | Default | Controls |
9
+ |---|---|---|
10
+ | `--page-row-minimum-content-padding` | `1rem` | Minimum gutter on each side at narrow viewports |
11
+ | `--page-row-popout-max-width` | `1400px` | Maximum width of the `popout` column track |
12
+ | `--page-row-content-max-width` | `1064px` | Maximum width of the `content` column track |
13
+ | `--page-row-inset-content-max-width` | `840px` | Maximum width of the `inset-content` column track |
14
+
15
+ ---
16
+
17
+ ## Global theming — app-level CSS file
18
+
19
+ Create `assets/styles/setup/07.components/page-row.css` in the consuming app and set tokens on
20
+ `:root`. These values apply to every `PageRow` instance across the site.
21
+
22
+ ```css
23
+ /* assets/styles/setup/07.components/page-row.css */
24
+ :root {
25
+ --page-row-minimum-content-padding: 1.6rem;
26
+ --page-row-popout-max-width: 1280px;
27
+ --page-row-content-max-width: 960px;
28
+ --page-row-inset-content-max-width: 720px;
29
+ }
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Page-scoped overrides
35
+
36
+ Override track widths for a specific page by scoping tokens under the page wrapper. No `:deep()`
37
+ is required (component styles are unscoped).
38
+
39
+ ```css
40
+ /* In the consuming page's unscoped <style> block */
41
+ .landing-page {
42
+ .page-row {
43
+ --page-row-content-max-width: 800px;
44
+ --page-row-minimum-content-padding: 2.4rem;
45
+ }
46
+ }
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Per-instance overrides via inline style
52
+
53
+ ```vue
54
+ <PageRow
55
+ variant="content"
56
+ style="--page-row-content-max-width: 720px;"
57
+ >
58
+ ...
59
+ </PageRow>
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Per-instance overrides via styleClassPassthrough
65
+
66
+ ```vue
67
+ <PageRow variant="content" :style-class-passthrough="['narrow']">
68
+ ...
69
+ </PageRow>
70
+ ```
71
+
72
+ ```css
73
+ .page-row {
74
+ &.narrow {
75
+ --page-row-content-max-width: 720px;
76
+ --page-row-minimum-content-padding: 2rem;
77
+ }
78
+ }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Notes
84
+
85
+ - Track widths are derived from the four tokens using `calc()` — changing one token shifts all
86
+ related tracks proportionally.
87
+ - `--page-row-minimum-content-padding` controls how close content gets to the viewport edge on
88
+ narrow screens; it also sets the outer `full` gutter minimum.
89
+ - The component applies no padding, margin, or background — those are always set by the consuming
90
+ app on the PageRow element or its children.
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ :id
5
+ class="page-row"
6
+ :class="[elementClasses, variant]"
7
+ :data-align="align || undefined"
8
+ :aria-labelledby="ariaLabelledby"
9
+ >
10
+ <slot :heading-id="headingId"></slot>
11
+ </component>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ interface Props {
16
+ tag?: "div" | "section" | "article" | "header" | "footer" | "main" | "nav";
17
+ variant?: "full" | "popout" | "content" | "inset-content";
18
+ align?: "start" | "end";
19
+ id?: string | null;
20
+ styleClassPassthrough?: string | string[];
21
+ }
22
+
23
+ const props = withDefaults(defineProps<Props>(), {
24
+ tag: "div",
25
+ variant: "content",
26
+ align: undefined,
27
+ id: null,
28
+ styleClassPassthrough: () => [],
29
+ });
30
+
31
+ const { elementClasses } = useStyleClassPassthrough(props.styleClassPassthrough);
32
+ const { headingId, ariaLabelledby } = useAriaLabelledById(props.tag);
33
+ </script>
34
+
35
+ <style lang="css">
36
+ @layer components {
37
+ /*
38
+ * Page layout grid — https://layout-breakouts-builder.vercel.app
39
+ * Each .page-row is both a CSS grid container and a grid item when nested.
40
+ * Direct children default to the content column; nest a .page-row.{variant}
41
+ * to break out to a wider or narrower track.
42
+ */
43
+ .page-row {
44
+ --_minimum-content-padding: var(--page-row-minimum-content-padding, 1rem);
45
+ --_popout-max-width: var(--page-row-popout-max-width, 1400px);
46
+ --_content-max-width: var(--page-row-content-max-width, 1064px);
47
+ --_inset-content-max-width: var(--page-row-inset-content-max-width, 840px);
48
+
49
+ --full: minmax(var(--_minimum-content-padding), 1fr);
50
+ --popout: minmax(0, calc((var(--_popout-max-width) - var(--_content-max-width)) * 0.5));
51
+ --content: minmax(0, calc((var(--_content-max-width) - var(--_inset-content-max-width)) * 0.5));
52
+ --inset-content: min(var(--_inset-content-max-width), 100% - var(--_minimum-content-padding) * 2);
53
+
54
+ display: grid;
55
+ grid-template-columns:
56
+ [full-start]
57
+ var(--full)
58
+ [popout-start]
59
+ var(--popout)
60
+ [content-start]
61
+ var(--content)
62
+ [inset-content-start]
63
+ var(--inset-content)
64
+ [inset-content-end]
65
+ var(--content)
66
+ [content-end]
67
+ var(--popout)
68
+ [popout-end]
69
+ var(--full)
70
+ [full-end];
71
+
72
+ container-type: inline-size;
73
+ }
74
+
75
+ /* All direct children default to the content column */
76
+ .page-row > * {
77
+ grid-column: content;
78
+ }
79
+
80
+ /*
81
+ * Variant propagation: a page-row's variant also sets the default column
82
+ * for its direct non-PageRow children. This allows variant="full" to make
83
+ * its own children span full width, not just control placement when nested.
84
+ * Specificity (0,2,0) — nested .page-row variant overrides below win at (0,3,0).
85
+ */
86
+ .page-row.full > * { grid-column: full; }
87
+ .page-row.popout > * { grid-column: popout; }
88
+ .page-row.inset-content > * { grid-column: inset-content; }
89
+
90
+ /* Nested page-rows: symmetric placement by variant class */
91
+ .page-row > .page-row {
92
+ &.full { grid-column: full; }
93
+ &.popout { grid-column: popout; }
94
+ &.content { grid-column: content; }
95
+ &.inset-content { grid-column: inset-content; }
96
+ }
97
+
98
+ /*
99
+ * align="start": bleeds to the left (full-start) edge, stops at the variant's end line.
100
+ * align="end": starts at the variant's start line, bleeds to the right (full-end) edge.
101
+ * Specificity (0,4,0) beats the symmetric rules above at (0,3,0).
102
+ */
103
+ .page-row > .page-row[data-align="start"] {
104
+ &.popout { grid-column: full-start / popout-end; }
105
+ &.content { grid-column: full-start / content-end; }
106
+ &.inset-content { grid-column: full-start / inset-content-end; }
107
+ }
108
+
109
+ .page-row > .page-row[data-align="end"] {
110
+ &.popout { grid-column: popout-start / full-end; }
111
+ &.content { grid-column: content-start / full-end; }
112
+ &.inset-content { grid-column: inset-content-start / full-end; }
113
+ }
114
+ }
115
+ </style>