@semplice-studio/pa-design-system 0.1.1 → 0.3.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 (119) hide show
  1. package/README.md +85 -128
  2. package/dist/components/PaAccordion/PaAccordion.vue.d.ts +0 -1
  3. package/dist/components/PaAccordion/PaAccordion.vue.js +3 -5
  4. package/dist/components/PaAccordion/PaAccordion.vue.js.map +1 -1
  5. package/dist/components/PaAccordion/PaAccordion.vue2.js +24 -30
  6. package/dist/components/PaAccordion/PaAccordion.vue2.js.map +1 -1
  7. package/dist/components/PaBanner/PaBanner.vue.js +6 -6
  8. package/dist/components/PaBanner/PaBanner.vue2.js +26 -28
  9. package/dist/components/PaBanner/PaBanner.vue2.js.map +1 -1
  10. package/dist/components/PaCard/PaCard.vue.d.ts +17 -14
  11. package/dist/components/PaCard/PaCard.vue.js +2 -2
  12. package/dist/components/PaCard/PaCard.vue2.js +37 -42
  13. package/dist/components/PaCard/PaCard.vue2.js.map +1 -1
  14. package/dist/components/PaCheckbox/PaCheckbox.vue.d.ts +5 -6
  15. package/dist/components/PaCheckbox/PaCheckbox.vue.js +2 -4
  16. package/dist/components/PaCheckbox/PaCheckbox.vue.js.map +1 -1
  17. package/dist/components/PaCheckbox/PaCheckbox.vue2.js +20 -26
  18. package/dist/components/PaCheckbox/PaCheckbox.vue2.js.map +1 -1
  19. package/dist/components/PaCheckboxGroup/PaCheckboxGroup.vue.d.ts +5 -6
  20. package/dist/components/PaCheckboxGroup/PaCheckboxGroup.vue.js +2 -4
  21. package/dist/components/PaCheckboxGroup/PaCheckboxGroup.vue.js.map +1 -1
  22. package/dist/components/PaCheckboxGroup/PaCheckboxGroup.vue2.js +25 -31
  23. package/dist/components/PaCheckboxGroup/PaCheckboxGroup.vue2.js.map +1 -1
  24. package/dist/components/PaDatePicker/PaDatePicker.vue.d.ts +13 -26
  25. package/dist/components/PaDatePicker/PaDatePicker.vue.js +4 -6
  26. package/dist/components/PaDatePicker/PaDatePicker.vue.js.map +1 -1
  27. package/dist/components/PaDatePicker/PaDatePicker.vue2.js +53 -59
  28. package/dist/components/PaDatePicker/PaDatePicker.vue2.js.map +1 -1
  29. package/dist/components/PaDrawer/PaDrawer.vue.js +2 -2
  30. package/dist/components/PaDrawer/PaDrawer.vue2.js +37 -41
  31. package/dist/components/PaDrawer/PaDrawer.vue2.js.map +1 -1
  32. package/dist/components/PaFileUpload/PaFileUpload.vue.d.ts +0 -1
  33. package/dist/components/PaFileUpload/PaFileUpload.vue.js +3 -5
  34. package/dist/components/PaFileUpload/PaFileUpload.vue.js.map +1 -1
  35. package/dist/components/PaFileUpload/PaFileUpload.vue2.js +22 -28
  36. package/dist/components/PaFileUpload/PaFileUpload.vue2.js.map +1 -1
  37. package/dist/components/PaFooter/PaFooter.vue.js +1 -1
  38. package/dist/components/PaFooter/PaFooter.vue2.js +43 -48
  39. package/dist/components/PaFooter/PaFooter.vue2.js.map +1 -1
  40. package/dist/components/PaHeaderDesktop/PaHeaderDesktop.vue.js +1 -1
  41. package/dist/components/PaHeaderDesktop/PaHeaderDesktop.vue2.js +61 -66
  42. package/dist/components/PaHeaderDesktop/PaHeaderDesktop.vue2.js.map +1 -1
  43. package/dist/components/PaHeaderMobile/PaHeaderMobile.vue.js +2 -2
  44. package/dist/components/PaHeaderMobile/PaHeaderMobile.vue2.js +69 -74
  45. package/dist/components/PaHeaderMobile/PaHeaderMobile.vue2.js.map +1 -1
  46. package/dist/components/PaHero/PaHero.vue.d.ts +15 -12
  47. package/dist/components/PaHero/PaHero.vue.js +2 -2
  48. package/dist/components/PaHero/PaHero.vue2.js +36 -41
  49. package/dist/components/PaHero/PaHero.vue2.js.map +1 -1
  50. package/dist/components/PaInput/PaInput.vue.d.ts +15 -13
  51. package/dist/components/PaInput/PaInput.vue.js +4 -6
  52. package/dist/components/PaInput/PaInput.vue.js.map +1 -1
  53. package/dist/components/PaInput/PaInput.vue2.js +27 -33
  54. package/dist/components/PaInput/PaInput.vue2.js.map +1 -1
  55. package/dist/components/PaModal/PaModal.vue.d.ts +14 -11
  56. package/dist/components/PaModal/PaModal.vue.js +3 -3
  57. package/dist/components/PaModal/PaModal.vue2.js +41 -46
  58. package/dist/components/PaModal/PaModal.vue2.js.map +1 -1
  59. package/dist/components/PaMonthPicker/PaMonthPicker.vue.d.ts +13 -26
  60. package/dist/components/PaMonthPicker/PaMonthPicker.vue.js +2 -4
  61. package/dist/components/PaMonthPicker/PaMonthPicker.vue.js.map +1 -1
  62. package/dist/components/PaMonthPicker/PaMonthPicker.vue2.js +64 -70
  63. package/dist/components/PaMonthPicker/PaMonthPicker.vue2.js.map +1 -1
  64. package/dist/components/PaPagination/PaPagination.vue.js +2 -2
  65. package/dist/components/PaPagination/PaPagination.vue2.js +41 -46
  66. package/dist/components/PaPagination/PaPagination.vue2.js.map +1 -1
  67. package/dist/components/PaRadioGroup/PaRadioGroup.vue.d.ts +5 -6
  68. package/dist/components/PaRadioGroup/PaRadioGroup.vue.js +2 -4
  69. package/dist/components/PaRadioGroup/PaRadioGroup.vue.js.map +1 -1
  70. package/dist/components/PaRadioGroup/PaRadioGroup.vue2.js +22 -28
  71. package/dist/components/PaRadioGroup/PaRadioGroup.vue2.js.map +1 -1
  72. package/dist/components/PaRangePicker/PaRangePicker.vue.d.ts +0 -1
  73. package/dist/components/PaRangePicker/PaRangePicker.vue.js +4 -6
  74. package/dist/components/PaRangePicker/PaRangePicker.vue.js.map +1 -1
  75. package/dist/components/PaRangePicker/PaRangePicker.vue2.js +76 -82
  76. package/dist/components/PaRangePicker/PaRangePicker.vue2.js.map +1 -1
  77. package/dist/components/PaSearchBar/PaSearchBar.vue.d.ts +0 -1
  78. package/dist/components/PaSearchBar/PaSearchBar.vue.js +3 -5
  79. package/dist/components/PaSearchBar/PaSearchBar.vue.js.map +1 -1
  80. package/dist/components/PaSearchBar/PaSearchBar.vue2.js +55 -61
  81. package/dist/components/PaSearchBar/PaSearchBar.vue2.js.map +1 -1
  82. package/dist/components/PaSelect/PaSelect.vue.d.ts +5 -6
  83. package/dist/components/PaSelect/PaSelect.vue.js +4 -6
  84. package/dist/components/PaSelect/PaSelect.vue.js.map +1 -1
  85. package/dist/components/PaSelect/PaSelect.vue2.js +25 -31
  86. package/dist/components/PaSelect/PaSelect.vue2.js.map +1 -1
  87. package/dist/components/PaTabs/PaTabs.vue.js +1 -1
  88. package/dist/components/PaTabs/PaTabs.vue2.js +17 -19
  89. package/dist/components/PaTabs/PaTabs.vue2.js.map +1 -1
  90. package/dist/components/PaTextarea/PaTextarea.vue.d.ts +5 -6
  91. package/dist/components/PaTextarea/PaTextarea.vue.js +4 -6
  92. package/dist/components/PaTextarea/PaTextarea.vue.js.map +1 -1
  93. package/dist/components/PaTextarea/PaTextarea.vue2.js +16 -22
  94. package/dist/components/PaTextarea/PaTextarea.vue2.js.map +1 -1
  95. package/dist/components/PaTimePicker/PaTimePicker.vue.d.ts +0 -1
  96. package/dist/components/PaTimePicker/PaTimePicker.vue.js +2 -4
  97. package/dist/components/PaTimePicker/PaTimePicker.vue.js.map +1 -1
  98. package/dist/components/PaTimePicker/PaTimePicker.vue2.js +44 -50
  99. package/dist/components/PaTimePicker/PaTimePicker.vue2.js.map +1 -1
  100. package/dist/components/PaTitleBar/PaTitleBar.vue.d.ts +12 -9
  101. package/dist/components/PaTitleBar/PaTitleBar.vue.js +2 -2
  102. package/dist/components/PaTitleBar/PaTitleBar.vue2.js +34 -39
  103. package/dist/components/PaTitleBar/PaTitleBar.vue2.js.map +1 -1
  104. package/dist/components/PaToggle/PaToggle.vue.d.ts +0 -1
  105. package/dist/components/PaToggle/PaToggle.vue.js +2 -4
  106. package/dist/components/PaToggle/PaToggle.vue.js.map +1 -1
  107. package/dist/components/PaToggle/PaToggle.vue2.js +20 -26
  108. package/dist/components/PaToggle/PaToggle.vue2.js.map +1 -1
  109. package/dist/components/PaYearPicker/PaYearPicker.vue.d.ts +13 -26
  110. package/dist/components/PaYearPicker/PaYearPicker.vue.js +4 -6
  111. package/dist/components/PaYearPicker/PaYearPicker.vue.js.map +1 -1
  112. package/dist/components/PaYearPicker/PaYearPicker.vue2.js +70 -76
  113. package/dist/components/PaYearPicker/PaYearPicker.vue2.js.map +1 -1
  114. package/dist/style.css +1 -1
  115. package/docs/design-system.md +590 -0
  116. package/docs/layouts.md +297 -0
  117. package/docs/page-composition-examples.md +286 -0
  118. package/docs/public-surface.md +68 -0
  119. package/package.json +8 -4
@@ -0,0 +1,297 @@
1
+ # Layout primitives — composing pages with `@semplice-studio/pa-design-system`
2
+
3
+ > **Audience:** developers building Vue / Nuxt consumer apps on top of `@semplice-studio/pa-design-system`. This guide documents the `.pa-*` utility-class layer shipped in **v1.35** alongside the existing token vocabulary.
4
+
5
+ ## TL;DR
6
+
7
+ Import the bundled stylesheet **once** in your app entry. You get tokens + layout classes through a single import:
8
+
9
+ ```ts
10
+ // app.vue / main.ts / layouts/default.vue
11
+ import '@semplice-studio/pa-design-system/tokens.css'
12
+ ```
13
+
14
+ Compose pages from three layers:
15
+
16
+ 1. **Tokens** — `var(--pa-color-*)`, `var(--pa-space-*)`, `var(--pa-font-*)` etc. (see [`design-system.md`](./design-system.md) §3).
17
+ 2. **Layout classes** — `.pa-page-frame`, `.pa-container`, `.pa-section`, `.pa-grid-3` etc. (this document, §1–§6).
18
+ 3. **Components** — `<PaButton>`, `<PaHero>`, `<PaCard>` etc. (see [`design-system.md`](./design-system.md) §4 component registry).
19
+
20
+ Page chrome (frame / container / sections / grids) → use a class. Visual choices (color / spacing / radius) → use a token. Interactive behavior (button, modal, drawer) → use a component.
21
+
22
+ ## 1. The page chrome
23
+
24
+ Every page starts with the same three-layer chrome: an outer **frame** clipping the design at 1280 px, a centered **container** capped at 1168 px, and (optionally) a **narrow** band for centered content like auth cards or article bodies.
25
+
26
+ ```vue
27
+ <template>
28
+ <div class="pa-page-frame">
29
+ <PaHeaderDesktop … />
30
+
31
+ <main id="main">
32
+ <div class="pa-container">
33
+ <PaHero … />
34
+ </div>
35
+
36
+ <section class="pa-container pa-section">
37
+ <!-- regular content section -->
38
+ </section>
39
+
40
+ <section class="pa-section pa-section--tint">
41
+ <div class="pa-container">
42
+ <!-- band content -->
43
+ </div>
44
+ </section>
45
+ </main>
46
+
47
+ <PaFooter … />
48
+ </div>
49
+ </template>
50
+ ```
51
+
52
+ | Class | Max-width | Use |
53
+ |---|---|---|
54
+ | `.pa-page-frame` | 1280 px | Wraps the entire page. White background. |
55
+ | `.pa-container` | 1168 px | Centered content column. 16 px side gutter. |
56
+ | `.pa-narrow` | 720 px | Narrow band for CTAs, banners, search inputs. |
57
+ | `.pa-narrow--xs` | 480 px | Auth card width. |
58
+ | `.pa-narrow--prose` | 880 px | Long-form article body / FAQ. |
59
+
60
+ ### Frame vs container vs narrow
61
+
62
+ - **Frame** is the *page* boundary. Background, max-width, centered.
63
+ - **Container** is the *column* inside the frame. It gives content a consistent 1168 px reading column and a uniform 16 px gutter.
64
+ - **Narrow** is for centered content that should not span the full container width — auth cards, banners, prose articles.
65
+
66
+ A common pattern: full-bleed gradient or tint band as the section, container inside for content alignment, narrow inside the container for centered focus content (see § 2).
67
+
68
+ ## 2. Sections and bands
69
+
70
+ Sections give pages their vertical rhythm. A `.pa-section` is one band: 32 / 16 padding default, with `--lg` modifier for the more spacious 48 / 16 default. Add a single `--variant` modifier for the band background:
71
+
72
+ | Class | Background |
73
+ |---|---|
74
+ | `.pa-section` (no variant) | none |
75
+ | `.pa-section--tint` | `--pa-color-bg-primary-light-40` (soft blue tint) |
76
+ | `.pa-section--dark` | `--pa-color-bg-primary-dark-120` + white text |
77
+ | `.pa-section--gradient` | 135° from `--pa-color-bg-primary-dark-110` to `--pa-color-primary` + white text |
78
+
79
+ ```vue
80
+ <!-- Full-bleed tint band with constrained content -->
81
+ <section class="pa-section pa-section--tint pa-section--lg">
82
+ <div class="pa-container">
83
+ <PaTitleBar title="In evidenza" variant="light" />
84
+ <div class="pa-grid-3">
85
+ <PaCard … />
86
+ </div>
87
+ </div>
88
+ </section>
89
+ ```
90
+
91
+ ```vue
92
+ <!-- Gradient hero / CTA band -->
93
+ <section class="pa-section pa-section--gradient pa-section--lg">
94
+ <div class="pa-narrow">
95
+ <h2>Pronto a digitalizzare il tuo Ente?</h2>
96
+ <PaButton variant="primary">Richiedi demo</PaButton>
97
+ </div>
98
+ </section>
99
+ ```
100
+
101
+ **Vertical rhythm rule of thumb:** use `.pa-section` (32 / 16) for regular bands and `.pa-section--lg` (48 / 16) for hero-band-like sections (top of page, conversion-focused CTAs, banded backgrounds). Reach for `--pa-space-2xl-plus` (64 px) only at the bottom of the page for the closing-section breathing room.
102
+
103
+ ## 3. Grids and stacks
104
+
105
+ Two families of layout helpers: **grids** (2D, equal columns) and **stacks** (1D flex column with token-spaced gap).
106
+
107
+ ### Grids
108
+
109
+ ```vue
110
+ <div class="pa-grid-3">
111
+ <PaCard v-for="…" />
112
+ </div>
113
+ ```
114
+
115
+ | Class | Layout |
116
+ |---|---|
117
+ | `.pa-grid-2` / `-3` / `-4` / `-6` | N equal `minmax(0, 1fr)` columns + `--pa-space-sm` gap |
118
+ | `.pa-split` | Two equal columns + `--pa-space-xl` gap (use for prominent 1-1 feature pairs) |
119
+ | `.pa-layout-sidebar` (default `--md`) | Main column + 320 px sidebar + `--pa-space-2xl` gap |
120
+ | `.pa-layout-sidebar--sm` | Sidebar narrows to 280 px (article-page pattern) |
121
+ | `.pa-field-full-row` | Grid utility — child spans `grid-column: 1 / -1`. Use inside `.pa-grid-2` form layouts. |
122
+
123
+ ### Stacks
124
+
125
+ A stack is a `flex; flex-direction: column; gap: var(--pa-space-N)`. Use stacks instead of stacking `margin-top` on children.
126
+
127
+ | Class | Gap |
128
+ |---|---|
129
+ | `.pa-stack-xs` | `--pa-space-xs` (8) |
130
+ | `.pa-stack-sm` | `--pa-space-sm` (16) |
131
+ | `.pa-stack-md` | `--pa-space-md` (24) |
132
+ | `.pa-stack-lg` | `--pa-space-lg` (32) |
133
+ | `.pa-stack-xl` | `--pa-space-xl` (40) |
134
+
135
+ ```vue
136
+ <div class="pa-stack-sm">
137
+ <PaInput … />
138
+ <PaInput … />
139
+ <PaSelect … />
140
+ </div>
141
+ ```
142
+
143
+ ## 4. Surfaces
144
+
145
+ | Class | Visual |
146
+ |---|---|
147
+ | `.pa-panel` | White background, 1 px `--pa-color-border-subtle` border, smooth radius, 32 px padding. Use for form cards, content panels, contact cards. |
148
+ | `.pa-tint-card` | Light-tint background, smooth radius, 24 px padding. Use for sidebars, signature blocks, info callouts. |
149
+ | `.pa-tint-card--sticky` | Adds `position: sticky; top: var(--pa-space-md)`. Use for article-page table-of-contents sidebars. |
150
+
151
+ ```vue
152
+ <!-- Form panel -->
153
+ <form class="pa-panel" @submit.prevent="…">
154
+ <div class="pa-grid-2">
155
+ <PaInput … />
156
+ </div>
157
+ <div class="pa-form-action-bar">
158
+ <PaButton variant="ghost">Annulla</PaButton>
159
+ <PaButton variant="primary">Invia</PaButton>
160
+ </div>
161
+ </form>
162
+
163
+ <!-- Sticky article sidebar -->
164
+ <aside>
165
+ <div class="pa-tint-card pa-tint-card--sticky">
166
+
167
+ </div>
168
+ </aside>
169
+ ```
170
+
171
+ ### Dividers
172
+
173
+ `.pa-divider` is a 1 px horizontal rule using `--pa-color-border-subtle`. `.pa-divider--text` is the same rule with a centered text label ("oppure", "or", "—"):
174
+
175
+ ```vue
176
+ <hr class="pa-divider" />
177
+
178
+ <div class="pa-divider--text">oppure</div>
179
+ ```
180
+
181
+ ### Form chrome
182
+
183
+ `.pa-form-action-bar` is the standard right-aligned action footer for forms. Includes a subtle top border + token-spaced padding.
184
+
185
+ ```vue
186
+ <form class="pa-panel">
187
+
188
+ <div class="pa-form-action-bar">
189
+ <PaButton variant="ghost">Annulla</PaButton>
190
+ <PaButton variant="primary" type="submit">Invia</PaButton>
191
+ </div>
192
+ </form>
193
+ ```
194
+
195
+ ## 5. Typography helpers
196
+
197
+ | Class | Effect |
198
+ |---|---|
199
+ | `.pa-eyebrow` | Uppercase section label — 13 px / weight 600 / `letter-spacing 0.04em` / muted color. Pattern repeated above every section heading. |
200
+ | `.pa-eyebrow--sm` | 12 px variant for tighter contexts (info-strip cells). |
201
+
202
+ ```vue
203
+ <p class="pa-eyebrow">Sezione del sito</p>
204
+ <h2>Sezione</h2>
205
+ ```
206
+
207
+ ## 6. Compositional helpers
208
+
209
+ Multi-element patterns recurring across content-heavy pages. Each helper ships as a single class — no need to re-derive the CSS in every consumer project.
210
+
211
+ | Class | Use |
212
+ |---|---|
213
+ | `.pa-prose` | Wraps article-body content. Scopes typography for nested `h2` / `h3` / `p` / `ul` / `ol` / `figure` / `figcaption`. `scroll-margin-top` on headings supports anchor-TOC navigation. |
214
+ | `.pa-blockquote` | Left-bordered tint blockquote. Italic. |
215
+ | `.pa-stat` + `.pa-stat__value` + `.pa-stat__label` | Big-number stat block. Value consumes the h1 token group; label consumes `--pa-font-small-*` + `--pa-color-text-muted`. |
216
+ | `.pa-info-strip` | Bordered horizontal cell row with vertical dividers between children. Children flex equally. |
217
+ | `.pa-map-placeholder` | 320 px tall gradient placeholder block. (See **Known gaps** below.) |
218
+ | `.pa-article-meta` | Article-header meta row. Flex-wrap, `--pa-space-sm` gap, `--pa-font-small-*` typography, muted color, bottom rule. |
219
+ | `.pa-byline` | Avatar + name signature block. |
220
+ | `.pa-tag-cloud` | Flex-wrap row + `--pa-space-xs` gap. Use for tag / chip / pill groups. |
221
+ | `.pa-icon-circle-sm` / `-md` / `-lg` / `-xl` | 28 / 40 / 48 / 64 px circular icon containers with `border-radius: 50%`. |
222
+
223
+ ### Prose example
224
+
225
+ ```vue
226
+ <article class="pa-container">
227
+ <div class="pa-prose">
228
+ <h2 id="sintesi">In sintesi</h2>
229
+ <p>…</p>
230
+
231
+ <blockquote class="pa-blockquote">«…».</blockquote>
232
+
233
+ <h2 id="dettagli">Dettagli</h2>
234
+ <ul>
235
+ <li>…</li>
236
+ </ul>
237
+ </div>
238
+ </article>
239
+ ```
240
+
241
+ ### Info-strip example
242
+
243
+ ```vue
244
+ <div class="pa-info-strip">
245
+ <div>
246
+ <p class="pa-eyebrow pa-eyebrow--sm">Data</p>
247
+ <div>2 giugno 2026</div>
248
+ </div>
249
+ <div>
250
+ <p class="pa-eyebrow pa-eyebrow--sm">Orario</p>
251
+ <div>10:00 – 18:00</div>
252
+ </div>
253
+ </div>
254
+ ```
255
+
256
+ ### Stat example
257
+
258
+ ```vue
259
+ <section class="pa-section pa-section--dark pa-section--lg">
260
+ <div class="pa-container">
261
+ <div class="pa-grid-4">
262
+ <div v-for="s in stats" class="pa-stat">
263
+ <div class="pa-stat__value">{{ s.value }}</div>
264
+ <div class="pa-stat__label">{{ s.label }}</div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+ </section>
269
+ ```
270
+
271
+ ## 7. What is **not** in this layer
272
+
273
+ The `.pa-*` utility-class layer is **layout primitives only** — page chrome, sections, grids, stacks, common surfaces, and a handful of compositional patterns. Reach for a `Pa*` **component** when you need:
274
+
275
+ - Interactive behavior — `PaButton`, `PaModal`, `PaDrawer`, `PaSearchBar`, `PaTabs`, `PaCarousel`, `PaAccordion`, `PaPagination`.
276
+ - Composed page-level chrome — `PaHeaderDesktop`, `PaHeaderMobile`, `PaFooter`, `PaHero`, `PaTitleBar`, `PaCard`, `PaBreadcrumb`, `PaStepProgressBar`.
277
+ - Form fields — `PaInput`, `PaSelect`, `PaTextarea`, `PaCheckbox`, `PaRadioGroup`, `PaFileUpload`, `PaDatePicker`, `PaRangePicker`, `PaTimePicker`, `PaMonthPicker`, `PaYearPicker`.
278
+ - Status surfaces — `PaTag`, `PaChip`, `PaToggle`, `PaBanner`, `PaTable`, `PaProgressBar`, `PaImage`.
279
+
280
+ Full component registry: [`design-system.md`](./design-system.md) §4.
281
+
282
+ ## 8. Known gaps
283
+
284
+ Two literals remain inline in `layout.css` because there is no matching token yet:
285
+
286
+ - **Icon-circle dimensions** — `.pa-icon-circle-sm/-md/-lg/-xl` use literal 28 / 40 / 48 / 64 px. A `--pa-size-icon-*` token group is intentionally deferred until a wider sizing scale lands.
287
+ - **Map-placeholder gradient stops** — `.pa-map-placeholder` uses `linear-gradient(135deg, #E0E8F1 0%, #C6D5E5 100%)` inline. A `--pa-gradient-placeholder-light` token is deferred for the same reason — there is currently only one consumer and no broader gradient vocabulary to extend.
288
+
289
+ Both gaps are flagged in the source file. When a future release adds the missing tokens, the literals will be swapped out.
290
+
291
+ ## 9. Reference
292
+
293
+ Every shipped class has a live preview under **design system → Layout** in Histoire (`pnpm dev`). Use it as the canonical visual reference.
294
+
295
+ For the historical context behind v1.35 — what the templates looked like before, which patterns were duplicated where, why the token gaps were filled the way they were — see [`docs/templates-audit.md`](../templates-audit.md).
296
+
297
+ For consumer-facing copy-paste references of how full pages compose, browse the 11 templates under `src/templates/*/` in Histoire (`pages` group): they're refactored to demonstrate the layer, with zero inline hex / px literals where tokens exist.
@@ -0,0 +1,286 @@
1
+ # Page composition examples — @semplice-studio/pa-design-system
2
+
3
+ > Distilled from the 11 reference page templates in `src/templates/` (LandingPage, SearchListPage, DashboardPage, MultiStepFormPage, ArticleDetailPage, EventDetailPage, ContactPage, LoginPage, MarketingPage, OfficeDetailPage, ServiceInfoPage). Read this before composing a page; see [`layouts.md`](layouts.md) for the full `.pa-*` utility reference and [`public-surface.md`](public-surface.md) for component APIs.
4
+ >
5
+ > Conventions in the snippets: imports use the published package; classes **not** prefixed `pa-` are page-local (define them in your page's scoped styles).
6
+
7
+ ## The canonical page skeleton
8
+
9
+ Every page follows the same frame: `.pa-page-frame` → header → `<main id="main">` → footer. Sections inside `main` are either a plain `.pa-container` (1168 px column) or a full-bleed `.pa-section` band with a `.pa-container` inside (for tinted/dark/gradient backgrounds).
10
+
11
+ ```vue
12
+ <script setup lang="ts">
13
+ import {
14
+ PaHeaderDesktop, PaHero, PaCarousel, PaCard, PaButton, PaFooter,
15
+ } from '@semplice-studio/pa-design-system'
16
+ </script>
17
+
18
+ <template>
19
+ <div class="pa-page-frame">
20
+ <PaHeaderDesktop :logged-in="loggedIn" :main-nav="mainNav"
21
+ :languages="languages" current-language="ita" />
22
+
23
+ <main id="main">
24
+ <!-- plain constrained section -->
25
+ <div class="pa-container">
26
+ <PaHero title="Benvenuti nel portale del Comune"
27
+ subtitle="Servizi e informazioni per cittadini, imprese e visitatori" />
28
+ </div>
29
+
30
+ <!-- constrained section with heading + rhythm -->
31
+ <section class="pa-container pa-section pa-section--lg">
32
+ <h2 class="pa-section-title">In Evidenza</h2>
33
+ <PaCarousel :items-per-page="3" label="Servizi in evidenza">
34
+ <PaCard v-for="(h, i) in highlights" :key="i" typology="portrait" v-bind="h" />
35
+ </PaCarousel>
36
+ </section>
37
+
38
+ <!-- full-bleed tinted band, content constrained inside -->
39
+ <section class="pa-section pa-section--tint pa-section--lg">
40
+ <div class="pa-container"><!-- … --></div>
41
+ </section>
42
+ </main>
43
+
44
+ <PaFooter :sitemap="footerSitemap" :address="footerAddress"
45
+ :socials="footerSocials" copyright="© 2026 Comune. Tutti i diritti riservati." />
46
+ </div>
47
+ </template>
48
+ ```
49
+
50
+ Widths: `.pa-page-frame` 1280 px outer · `.pa-container` 1168 px + 16 px gutters · `.pa-narrow` 720 px (`--xs` 480, `--prose` 880) · `.pa-section` 32 px vertical padding (`--lg` 48 px).
51
+
52
+ ## Section patterns
53
+
54
+ ### Hero with CTA (Landing, ServiceInfo, Event, Marketing…)
55
+
56
+ ```vue
57
+ <div class="pa-container">
58
+ <PaHero title="Carta d'identità elettronica" subtitle="Documento di riconoscimento"
59
+ body="La Carta d'Identità Elettronica (CIE)…"
60
+ :image-src="heroImage" image-alt="Documento d'identità elettronica"
61
+ image-ratio="3:2" image-position="right">
62
+ <template #cta>
63
+ <PaButton variant="primary">
64
+ <template #icon-left><PaIcon name="calendar-blank" size="sm" /></template>
65
+ Prenota appuntamento
66
+ </PaButton>
67
+ <PaButton variant="secondary">Maggiori informazioni</PaButton>
68
+ </template>
69
+ </PaHero>
70
+ </div>
71
+ ```
72
+
73
+ ### Card grid / carousel (Landing, SearchList, Dashboard)
74
+
75
+ Static grid → `.pa-grid-3` (or `-2`/`-4`) of `PaCard`. Scrollable strip → `PaCarousel` wrapping the same cards (see skeleton above). Card footer takes the action:
76
+
77
+ ```vue
78
+ <div class="pa-grid-3">
79
+ <PaCard v-for="(svc, i) in services" :key="i" typology="portrait"
80
+ :title="svc.title" :body="svc.body"
81
+ :category="svc.category" :category-icon="svc.icon"
82
+ :image-src="svc.image" image-ratio="3:2">
83
+ <template #footer><PaButton variant="tertiary" size="sm">Scopri</PaButton></template>
84
+ </PaCard>
85
+ </div>
86
+ ```
87
+
88
+ ### Tabbed content (Dashboard, Landing, Marketing)
89
+
90
+ ```vue
91
+ <PaTabs :tabs="tabs" v-model:active-id="activeTab" label="Sezioni dell'area personale">
92
+ <template #panel-pratiche>
93
+ <PaTable :columns="praticheColumns" :rows="praticheRows" />
94
+ <PaPagination v-model:current-page="currentPage" :total-items="totalItems" />
95
+ </template>
96
+ <template #panel-documenti>
97
+ <div class="pa-grid-3"><PaCard /><!-- … --></div>
98
+ </template>
99
+ </PaTabs>
100
+ ```
101
+
102
+ ### Accordion FAQ / collapsible sections (Event, Marketing, ServiceInfo)
103
+
104
+ ```vue
105
+ <section class="pa-container pa-section">
106
+ <h2 class="pa-section-title">Programma della giornata</h2>
107
+ <PaAccordionGroup v-model="openIds" :multiple="true">
108
+ <PaAccordion id="giorno-1" title="Mattina — Cerimonia ufficiale" icon="clock">
109
+ <p><strong>10:00</strong> — Alzabandiera in Piazza del Municipio</p>
110
+ </PaAccordion>
111
+ <!-- one PaAccordion per entry -->
112
+ </PaAccordionGroup>
113
+ </section>
114
+ ```
115
+
116
+ ### Form panel (Contact, MultiStepForm)
117
+
118
+ Form fields in a `.pa-grid-2`; full-width fields wrapped in `.pa-field-full-row`; actions in `.pa-form-action-bar`; feedback via `PaBanner` at the top:
119
+
120
+ ```vue
121
+ <form class="pa-panel" @submit.prevent="submitForm">
122
+ <PaBanner v-if="submitted" variant="success" title="Messaggio inviato" />
123
+
124
+ <div class="pa-grid-2">
125
+ <PaInput v-model="nome" label="Nome e cognome" required />
126
+ <PaInput v-model="email" type="email" label="Email" required />
127
+ <PaSelect v-model="argomento" :options="argomentoOptions" label="Argomento" required />
128
+ <div class="pa-field-full-row">
129
+ <PaTextarea v-model="messaggio" label="Messaggio" :rows="5" required />
130
+ </div>
131
+ <div class="pa-field-full-row">
132
+ <PaCheckbox v-model="privacy" label="Ho letto e accetto l'informativa privacy" required />
133
+ </div>
134
+ </div>
135
+
136
+ <div class="pa-form-action-bar">
137
+ <PaButton variant="ghost">Annulla</PaButton>
138
+ <PaButton variant="primary" type="submit">Invia messaggio</PaButton>
139
+ </div>
140
+ </form>
141
+ ```
142
+
143
+ ### Multi-step form (MultiStepForm)
144
+
145
+ `PaStepProgressBar` above the form panel; back/draft/next in a split footer:
146
+
147
+ ```vue
148
+ <div class="pa-container">
149
+ <h1 class="pa-page-title">Richiesta certificato di residenza</h1>
150
+ <PaStepProgressBar :steps="steps" orientation="horizontal" label="Avanzamento richiesta" />
151
+ <form class="pa-panel" @submit.prevent="confirmOpen = true"><!-- pa-grid-2 fields … --></form>
152
+ </div>
153
+ ```
154
+
155
+ ### Search hero + results + pagination (SearchList)
156
+
157
+ Gradient band with the search bar, filters via `PaDrawer`, active filters as a `.pa-tag-cloud` of chips:
158
+
159
+ ```vue
160
+ <div class="pa-section pa-section--gradient pa-section--lg">
161
+ <div class="pa-container">
162
+ <h1>Servizi</h1>
163
+ <div class="pa-narrow">
164
+ <PaSearchBar v-model:value="searchValue" placeholder="Es. carta d'identità…" />
165
+ </div>
166
+ </div>
167
+ </div>
168
+
169
+ <section class="pa-container pa-section">
170
+ <PaButton variant="secondary" @click="drawerOpen = true">
171
+ <template #icon-left><PaIcon name="funnel" size="sm" /></template>
172
+ Filtri ({{ activeChips.length }})
173
+ </PaButton>
174
+ <div class="pa-tag-cloud">
175
+ <PaChip v-for="label in activeChips" :key="label" variant="dismissible" @close="remove(label)">
176
+ {{ label }}
177
+ </PaChip>
178
+ </div>
179
+
180
+ <div class="pa-grid-3"><!-- PaCard results --></div>
181
+ <PaPagination v-model:current-page="currentPage" :total-items="totalItems" />
182
+ </section>
183
+ ```
184
+
185
+ ### Metadata info strip (EventDetail)
186
+
187
+ ```vue
188
+ <div class="pa-info-strip">
189
+ <div v-for="item in infoStrip" :key="item.label" class="info-cell">
190
+ <PaIcon :name="item.icon" size="lg" />
191
+ <div>
192
+ <p class="pa-eyebrow pa-eyebrow--sm">{{ item.label }}</p>
193
+ <p>{{ item.value }}</p>
194
+ </div>
195
+ </div>
196
+ </div>
197
+ ```
198
+
199
+ ### Article with sticky TOC sidebar (ArticleDetail)
200
+
201
+ ```vue
202
+ <div class="pa-layout-sidebar pa-layout-sidebar--sm pa-layout-sidebar--left">
203
+ <div class="pa-prose">
204
+ <h2 id="sintesi">In sintesi</h2>
205
+ <p>…</p>
206
+ <figure>
207
+ <PaImage :src="inlineImage" alt="…" ratio="16:9" />
208
+ <figcaption>…</figcaption>
209
+ </figure>
210
+ <blockquote class="pa-blockquote">
211
+ <p>…</p>
212
+ <cite>— Sindaco, intervento in Consiglio</cite>
213
+ </blockquote>
214
+ </div>
215
+
216
+ <aside>
217
+ <div class="pa-tint-card pa-tint-card--sticky">
218
+ <p class="pa-eyebrow">In questa pagina</p>
219
+ <a v-for="s in sections" :key="s.id" :href="`#${s.id}`">{{ s.label }}</a>
220
+ </div>
221
+ </aside>
222
+ </div>
223
+ ```
224
+
225
+ ### Centered auth card (Login)
226
+
227
+ ```vue
228
+ <main id="main">
229
+ <div class="login-card pa-narrow pa-narrow--xs">
230
+ <div class="pa-icon-circle-xl"><PaIcon name="lock-key" size="lg" /></div>
231
+ <h1>Accedi all'area personale</h1>
232
+ <div class="pa-stack-xs"><!-- one button per identity provider --></div>
233
+ <div class="pa-divider pa-divider--text">oppure</div>
234
+ <!-- credentials form -->
235
+ </div>
236
+ </main>
237
+ ```
238
+
239
+ ## Header/footer data shapes
240
+
241
+ ```ts
242
+ import type {
243
+ PaHeaderNavItem, PaHeaderLanguage, PaFooterColumn, PaFooterAddress, PaFooterSocial,
244
+ } from '@semplice-studio/pa-design-system'
245
+
246
+ const mainNav: PaHeaderNavItem[] = [
247
+ { label: 'Servizi', href: '/servizi' }, // leaf → plain link
248
+ { label: 'Amministrazione', children: [ // branch → mega menu
249
+ { label: 'Organi di governo', children: [{ label: 'Sindaco', href: '#' }] },
250
+ ]},
251
+ ]
252
+ const languages: PaHeaderLanguage[] = [{ code: 'ita', label: 'ITA' }, { code: 'eng', label: 'ENG' }]
253
+
254
+ const footerSitemap: PaFooterColumn[] = [
255
+ { heading: 'Amministrazione', links: [{ label: 'Uffici', href: '#' }] },
256
+ ]
257
+ const footerAddress: PaFooterAddress = {
258
+ title: 'Sede legale',
259
+ lines: ['Piazza del Municipio, 1 — 00100 Roma', 'P.IVA 00000000000'],
260
+ }
261
+ const footerSocials: PaFooterSocial[] = [{ icon: 'facebook-logo', href: '#', label: 'Facebook' }]
262
+ ```
263
+
264
+ Logged-in header chrome goes through slots: `#user` (avatar chip), `#utility-links` (bare `<a>` strip), `#utility-icons` (bare icon `<button>`s — chrome is applied by the header).
265
+
266
+ ## State wiring cheat-sheet
267
+
268
+ ```ts
269
+ const activeTab = ref('pratiche') // PaTabs v-model:active-id
270
+ const openIds = ref<string[]>(['intro']) // PaAccordionGroup v-model (+ :multiple)
271
+ const currentPage = ref(1) // PaPagination v-model:current-page
272
+ const carouselPage = ref(0) // PaCarousel v-model:current-page (0-indexed)
273
+ const drawerOpen = ref(false) // PaDrawer v-model:open
274
+ const modalOpen = ref(false) // PaModal v-model:open
275
+ const allegato = ref<File | null>(null) // PaFileUpload v-model
276
+ ```
277
+
278
+ ## Page-level conventions
279
+
280
+ - `<main id="main">` always — the headers' skip link targets `#main`.
281
+ - Heading discipline: one `<h1>` per page (`.pa-page-title` or PaHero's title); sections use `<h2 class="pa-section-title">`; anchor `id`s on `<h2>` for TOC links.
282
+ - `.pa-eyebrow` as the small uppercase pre-label above section headings.
283
+ - Breadcrumb sits in its own `.pa-container` strip directly under the header.
284
+ - `PaTag` = read-only badge; `PaChip` = interactive filter/selection.
285
+ - `label` props on `PaTabs`/`PaCarousel`/`PaPagination`, `aria-label` on icon-only buttons.
286
+ - Section rhythm comes from `.pa-section`/`--lg` + `.pa-stack-*`; avoid ad-hoc margins between siblings.
@@ -0,0 +1,68 @@
1
+ # Public surface — @semplice-studio/pa-design-system
2
+
3
+ > One row per export — update after every release. Full prop signatures live in each component's `.d.ts` (shipped in `dist/`); detailed behavioral contracts and deferred-feature notes from v1.x live in this file's git history (pre-2026-06-06).
4
+
5
+ Every component exports from the barrel **and** from `./components/<Name>`, together with its `*Props` / option types.
6
+
7
+ Stylesheets: `./tokens.css` (design tokens + bundled Inter/Roboto Mono via `@font-face` — no separate font loading needed), `./layout.css` (`.pa-*` layout utilities; auto-imported by tokens.css), `./style.css` (component styles).
8
+
9
+ **`anatomy*`** in the API column = the shared form-field prop set: `label`, `helperText`, `error`, `valid`, `required`, `disabled`, `readonly`, `id`, `name` (error message wins over helper).
10
+
11
+ ## Atoms
12
+
13
+ | Export | Contract | API |
14
+ |---|---|---|
15
+ | `PaButton` | brand button; icon-only square mode needs `aria-label` | props: `variant: primary\|secondary\|tertiary\|accent\|ghost\|error\|success`, `size: sm\|lg`, `disabled`, `type`, `iconOnly` · slots: default, `icon-left`, `icon-right` · emits: `click` |
16
+ | `PaInput` | text field; password toggle; clearable; native attr forwarding | props: `type: text\|email\|tel\|url\|password\|number\|date`, `size: sm\|lg`, `clearable`, anatomy* · v-model: `string` · slots: `icon-prefix`, `icon-suffix` |
17
+ | `PaTextarea` | multiline mirror of PaInput; char counter when `maxlength` set | props: `rows`, anatomy* · v-model: `string` |
18
+ | `PaIcon` | curated Phosphor registry (130 names); decorative unless `title` set | props: `name: PaIconName`, `size: sm\|md\|lg`, `title` |
19
+ | `PaSelect` | native `<select>` with PaInput anatomy | props: `options: PaSelectOption[]` (per-option `disabled`), `size: sm\|lg`, `placeholder`, anatomy* · v-model: `string` |
20
+ | `PaCheckbox` | single checkbox; also the row primitive of PaCheckboxGroup | props: `indeterminate`, `value`, anatomy* · v-model: `boolean` |
21
+ | `PaChip` | pill — dismissible or toggleable | props: `variant: dismissible\|selectable`, `icon`, `disabled` · v-model: `boolean` (selectable) · emits: `close` (dismissible) |
22
+ | `PaTag` | static status badge; non-interactive (use PaChip for that) | props: `variant: default\|info\|alert\|warning\|success\|default-dark`, `icon` |
23
+ | `PaToggle` | on/off switch (`role="switch"`) | props: anatomy*, `value` · v-model: `boolean` |
24
+ | `PaAccordion` | disclosure; group-aware under PaAccordionGroup | props: `title`, `icon`, `defaultOpen`, `id` · v-model: `boolean` · slots: default (body), `title` |
25
+ | `PaFab` | floating action button; positioning is consumer's | props: `icon: PaIconName` (required), `variant: primary\|inverse`, `size: sm\|md`, `extended`, `label`, `ariaLabel` · click via attrs fall-through |
26
+ | `PaImage` | ratio-locked image (cover crop); empty-state placeholder | props: `src`, `alt` (required; `''` = decorative), `ratio: 1:1\|4:3\|16:9\|3:2\|2:3\|3:4\|9:16`, `ratioCustom` |
27
+ | `PaProgressBar` | determinate or indeterminate; presentational only | props: `value: 0–100` (clamped), `label`, `showPercentage`, `indeterminate` |
28
+ | `PaTab` | valid only inside `PaTabs`; renders its panel when active | props: `id` (required), `label`, `icon`, `disabled` · slot: default (panel body) |
29
+ | `PaDrawerInfoRow` | key-value row for drawer bodies | props: `variant: header\|text\|text-multiline\|link\|number\|date` (required), `label`, `value`, `href` (link) · slots: `label`, `value` |
30
+
31
+ ## Composed
32
+
33
+ | Export | Contract | API |
34
+ |---|---|---|
35
+ | `PaRadioGroup` | single-select radio group | props: `options: PaRadioGroupOption[]` (per-option `disabled`), anatomy* · v-model: `string` |
36
+ | `PaCheckboxGroup` | multi-select; option-array order preserved | props: `options: PaCheckboxGroupOption[]`, anatomy* · v-model: `string[]` |
37
+ | `PaBreadcrumb` | nav trail; auto-truncates beyond `maxItems` | props: `items: PaBreadcrumbItem[]` (`icon?` per item), `maxItems: 3`, `separator: '/'`, `label` · emits: `select({ index, item, event })` — `preventDefault()` for SPA routing |
38
+ | `PaFileUpload` | single-file picker (PaButton + dismissible PaChip); consumer handles upload | props: `accept`, `maxSize` (bytes), `buttonLabel`, `chipLabel: (file) => string`, anatomy* · v-model: `File \| null` · emits: `invalid({ file, reason })` |
39
+ | `PaAccordionGroup` | coordinates PaAccordion children | props: `multiple` · v-model: `string \| null` (single-open) or `string[]` (multiple) · slot: default (PaAccordion children) |
40
+ | `PaImageGrid` | CSS grid of PaImage children; settings flow down via inject | props: `columns: 3`, `ratio`, `gap` · slot: default (PaImage children) |
41
+ | `PaStepProgressBar` | wizard steps; statuses derived from index, per-step override wins | props: `steps: { label, status?: done\|current\|upcoming\|error }[]`, `currentStep`, `orientation: horizontal\|vertical`, `label` · emits: `select({ index, item, status, event })` (done/current/error only) |
42
+ | `PaBanner` | advisory banner; auto `role`/`aria-live` per variant | props: `variant: info\|info-dark\|success\|warning\|error`, `title` (required), `label` · slots: default (body), `action` |
43
+ | `PaTabs` | tab strip; full ARIA + keyboard; array or composition API | props: `tabs: { id, label, icon?, disabled? }[]`, `variant: line\|background`, `label` · v-model: `activeId: string` · slots: `panel-<id>` (array API) or nested `<PaTab>` children |
44
+ | `PaPagination` | numbered strip or items-per-page bar | props: `variant: cursor-number\|items-page`, `totalItems` (required), `pageSizeOptions: [10,25,50,100]`, `disabled`, label props · v-model: `currentPage`, `pageSize` |
45
+ | `PaHero` | page hero (text + PaImage); slot wins over prop | props: `title`, `titleLevel: 1–6`, `subtitle`, `body`, `imageSrc`, `imageAlt`, `imageRatio: 3:2`, `imagePosition: left\|right` · slots: `breadcrumb`, `label`, `title`, `subtitle`, `body`, `cta` |
46
+ | `PaTitleBar` | section title bar; mobile kebab dropdown | props: `title` (required), `titleLevel: 1–6`, `subtitle`, `variant: dark\|light`, `menuItems: { label, onClick?, disabled? }[]`, `menuLabel` · v-model: `menuOpen: boolean` · slot: `actions` |
47
+ | `PaCard` | content card; slot wins over prop | props: `typology: landscape\|portrait\|process`, `variant: light\|dark`, `title`, `titleLevel`, `subtitle`, `body`, `signature`, `date`, `category`, `categoryIcon`, `imageSrc`/`imageAlt`/`imageRatio` · slots: `title`, `subtitle`, `body`, `signature`, `header-leading`, `header-trailing`, `footer` |
48
+ | `PaModal` | teleported dialog; focus trap + scroll lock; Esc/backdrop close | props: `title`, `titleLevel`, `subtitle`, `leadingIcon`, `showClose`, `closeLabel`, `size: sm\|md\|lg` · v-model: `open: boolean` · slots: default (body), `footer` |
49
+ | `PaTable` | native table; opt-in row selection | props: `columns: { key, label, align?, width? }[]`, `rows`, `label`, `selectable`, `rowId: (row, i) => string`, `selectionLabel` · v-model: `selectedRowIds: string[]` · slots: `cell-<key>` (scoped: `{ row, value, index }`) |
50
+ | `PaCarousel` | paginated slide strip over arbitrary children; scroll-snap; page-indexed | props: `itemsPerPage: 1\|2\|3`, `showChevrons`, `showDots`, label props · v-model: `currentPage: number` (0-indexed) · slot: default (slides) |
51
+ | `PaSearchBar` | search input + button; optional combobox dropdown (consumer pre-filters) | props: `placeholder`, `debounceMs: 0`, `options: { value, disabled? }[]`, `searchLabel`, `clearLabel`, `label`, `helperText`, `disabled` · v-model: `value: string` · emits: `search(value)`, `clear`, `select(option)` |
52
+ | `PaDrawer` | teleported slide-out panel; focus trap | props: `title`, `titleLevel`, `showClose`, `closeLabel`, `position: right\|left\|top\|bottom`, `size: sm\|md\|lg` · v-model: `open: boolean` · slots: default (body), `footer` (sticky CTA bar) |
53
+ | `PaFooter` | page footer: back-to-top + sitemap + contact + privacy bands, each omitted when its data is absent | props: `backToTop: boolean \| { label?, ariaLabel? }`, `sitemap: PaFooterColumn[]`, `address: { title?, lines }`, `socials: { icon, href, label }[]`, `copyright`, `showRepublicEmblem` · slots: `logo`, `sitemap`, `address`, `socials`, `contact-extra`, `privacy-logo`, `copyright` · emits: `back-to-top` (preventable) |
54
+ | `PaHeaderMobile` | mobile header bar + full-screen drill-down menu | props: `loggedIn`, `primaryNav`/`secondaryNav: PaHeaderNavItem[]`, `languages: PaHeaderLanguage[]`, `currentLanguage`, `skipLink`, `teleportTarget`, copy props · v-model: `menuOpen: boolean` · slots: `logo`, `actions`, `user` · emits: `navigate`, `language-change` (both preventable), `search-click`, `user-click` |
55
+ | `PaHeaderDesktop` | desktop header: utility row + PaSearchBar + nav with 3-column mega menu | props: `loggedIn`, `mainNav: PaHeaderNavItem[]`, `languages`, `currentLanguage`, `currentHref`, `searchEnabled` + search props, `loginLabel`/`loginHref`, `skipLink`, copy props · v-model: `menuOpen: string \| null` · slots: `utility-links`, `utility-icons`, `language`, `user`, `search`, `mega-column-0/1/2` (scoped) · emits: `navigate` (preventable), `language-change`, `search` |
56
+ | `PaDatePicker` | single-day picker (v-calendar); popover or inline | props: `display: popover\|inline`, `placeholder`, `min`/`max: Date`, `disabledDates: (d) => boolean`, anatomy* · v-model: `Date \| null` · slot: `suffix` |
57
+ | `PaRangePicker` | day-range picker (v-calendar); two-click commit, auto start ≤ end | props: same as PaDatePicker · v-model: `PaRangePickerValue ({ start, end }) \| null` · slot: `suffix` |
58
+ | `PaMonthPicker` | month-grid picker; value = 1st of month | props: same chrome, `disabledMonths: (d) => boolean` · v-model: `Date \| null` · slot: `suffix` |
59
+ | `PaYearPicker` | 12-year-grid picker; value = Jan 1 | props: same chrome, `disabledYears: (d) => boolean` · v-model: `Date \| null` · slot: `suffix` |
60
+ | `PaTimePicker` | HH:MM spinner with wrap + step-skip of disabled times | props: `display`, `placeholder`, `min`/`max: PaTimePickerValue`, `disabledTimes: (t) => boolean`, anatomy* · v-model: `PaTimePickerValue ({ hours, minutes }) \| null` · slot: `suffix` |
61
+
62
+ ## Cross-cutting conventions
63
+
64
+ - **Form anatomy is shared** (`anatomy*` above): every form-ish component mirrors PaInput's field chrome; the error message wins over the helper text.
65
+ - **Date/time pickers**: Italian locale hardcoded; all values normalized to **local** midnight (never UTC); with `name` set, a hidden input serializes for native form posts (`yyyy-mm-dd`, `yyyy-mm-dd/yyyy-mm-dd`, `yyyy-mm`, `yyyy`, `HH:mm`).
66
+ - **Italian default labels throughout** (`'Chiudi'`, `'Ricerca'`, `'Torna su'`, …) — all overridable via props for other locales.
67
+ - **Responsive collapse uses container queries**, not viewport media queries — components reflow inside narrow sidebars on wide screens.
68
+ - **Router integration**: nav-emitting components (`PaBreadcrumb`, headers, footer) emit preventable payloads — call `preventDefault()` and route with your SPA router.