@asd20/ui-next 2.34.5 → 2.35.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ # [2.35.0](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.34.6...ui-next-v2.35.0) (2026-11-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * upgrade to node 24 / npm 11.19.1; add agents.md ([8ad319b](https://github.com/academydistrict20/asd20-ui-next/commit/8ad319bcb784c1856d4d1f05351a4e47d0b34ca0))
9
+
10
+ ## [2.34.6](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.34.5...ui-next-v2.34.6) (2026-11-02)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * update campus template with new styles ([7464df8](https://github.com/academydistrict20/asd20-ui-next/commit/7464df8de3d9979ab4a6387c572ae9f798cb4a4a))
16
+
3
17
  ## [2.34.5](https://github.com/academydistrict20/asd20-ui-next/compare/ui-next-v2.34.4...ui-next-v2.34.5) (2026-11-02)
4
18
 
5
19
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Shared Vue 3 UI package for Academy District 20 websites and web apps.
4
4
 
5
- Current local version: `2.13.3`
5
+ Current local version: `2.34.6`
6
6
 
7
7
  This package is the active shared presentation layer for the ASD20 web platform.
8
8
  It provides reusable components, public website templates, app-shell templates,
@@ -28,12 +28,22 @@ multi-repo contract, not as an app-local component folder.
28
28
 
29
29
  ## Runtime Contract
30
30
 
31
+ Development, builds, and releases use Node `24.22.2` and npm `11.19.1`
32
+ (bundled with that Node release). `.nvmrc`, `.node-version`, `packageManager`,
33
+ and the release workflow pin those versions. CI explicitly installs the pinned
34
+ npm version to keep local and release tooling aligned.
35
+
36
+ The published library keeps these consumer requirements:
37
+
31
38
  - Node `>=20`
32
39
  - npm `>=10`
33
40
  - Vue peer dependency `^3.0.0`
41
+
42
+ Development tools:
43
+
34
44
  - Demo and packed-consumer build system: Vite
35
45
  - Unit test runner: Vitest
36
- - Component development: Storybook 8 with its retained webpack 5 builder
46
+ - Component development: Storybook 10 with Vite
37
47
 
38
48
  ## Breadcrumb Navigation
39
49
 
@@ -102,7 +112,12 @@ package `exports` map.
102
112
  ## Local Development
103
113
 
104
114
  ```sh
105
- npm install
115
+ nvm install
116
+ nvm use
117
+ npm install --global npm@11.19.1
118
+ node --version # v24.22.2
119
+ npm --version # 11.19.1
120
+ npm ci
106
121
  npm run storybook
107
122
  npm test
108
123
  npm run build
@@ -153,8 +168,9 @@ Verify the packed consumer install path:
153
168
  npm run verify:release
154
169
  ```
155
170
 
156
- That command packs the staged package, installs it into a temporary Vue 3 Vite
157
- smoke app, imports the public
171
+ That command checks icon metadata, runs the unit tests and `verify:publish`,
172
+ then packs the staged package and extracts it into a temporary Vue 3 Vite
173
+ smoke app with dependencies linked from this checkout. It imports the public
158
174
  components/templates/icons/helpers/directives/styles, verifies the package's
159
175
  complete Vite development optimizer input set from a clean cache, and confirms
160
176
  the production consumer build succeeds with Vite's default CSS minifier.
@@ -168,6 +184,11 @@ npm run release
168
184
  The release flow publishes from `dist/package`, tags as `ui-next-v${version}`,
169
185
  and runs `npm run verify:release` before publishing.
170
186
 
187
+ The main-branch workflow publishes automatically. For local validation, run
188
+ `npm run lint`, `npm run verify:release`, and `npm run build-storybook`;
189
+ `verify:release` includes `verify:publish`. These checks do not publish.
190
+ A development-runtime upgrade alone does not require a new library release.
191
+
171
192
  ## Change Guidance
172
193
 
173
194
  The [PDFService implementation overview](docs/projects/pdf-service/README.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asd20/ui-next",
3
- "version": "2.34.5",
3
+ "version": "2.35.0",
4
4
  "private": false,
5
5
  "description": "ASD20 UI component library for Vue 3.",
6
6
  "license": "MIT",
@@ -1,78 +1,64 @@
1
1
  <template>
2
- <header
3
- v-scroll-track.window
4
- class="asd20-campus-header"
5
- :class="classes"
6
- role="banner"
7
- >
2
+ <header class="asd20-campus-header" :class="classes" role="banner">
8
3
  <div class="asd20-campus-header__top">
9
- <!-- Use v-show here so breakpoint-driven visibility changes do not alter the hydrated structure. -->
10
- <asd20-district-logo
11
- v-show="
12
- organization.title === 'Academy District 20' &&
13
- mq !== 'sm' &&
14
- mq !== 'md'
15
- "
16
- ></asd20-district-logo>
17
- <asd20-organization-picker
18
- v-show="organization.title !== 'Academy District 20'"
19
- :organization-options="organizationOptions"
20
- :organization="organization"
21
- ></asd20-organization-picker>
4
+ <template v-if="organization">
5
+ <asd20-district-logo
6
+ v-if="organization.title === 'Academy District 20'"
7
+ />
8
+ <asd20-organization-picker
9
+ v-else
10
+ :organization-options="organizationOptions"
11
+ :organization="organization"
12
+ />
13
+ </template>
22
14
  <slot name="top" />
23
15
  </div>
24
16
 
25
17
  <div class="asd20-campus-header__content">
26
- <div class="heading-container">
27
- <div
28
- v-if="$slots.notification"
29
- class="asd20-campus-header__heading-combo"
30
- >
31
- <slot name="notification" />
32
- </div>
33
- <h2
34
- v-if="heading"
35
- v-html="heading"
36
- />
37
- </div>
38
18
  <div
39
- v-if="lead"
40
- class="asd20-campus-header__lead"
19
+ v-if="$slots.notification"
20
+ class="asd20-campus-header__notifications"
41
21
  >
42
- <span v-html="lead"></span>
22
+ <slot name="notification" />
43
23
  </div>
24
+ <h1 v-if="heading" v-html="heading" />
25
+ <div v-if="lead" class="asd20-campus-header__lead" v-html="lead" />
44
26
  </div>
45
27
 
46
- <div class="asd20-campus-header__image">
47
- <div
48
- v-for="(image, index) in campusImages"
49
- :key="index"
50
- >
51
- <img
52
- :src="image.url"
53
- :alt="image.alt"
54
- />
55
- </div>
56
- </div>
57
-
58
- <div class="call-to-action-container">
59
- <div
60
- v-if="callsToAction.length > 0"
61
- class="asd20-campus-header__call-to-action"
62
- >
63
- <asd20-button
64
- v-for="(cta, index) in callsToAction"
28
+ <div v-if="campusPanels.length" class="asd20-campus-header__schools">
29
+ <div class="asd20-campus-header__panels">
30
+ <div
31
+ v-for="(panel, index) in campusPanels"
65
32
  :key="index"
66
- :size="['sm', 'md'].indexOf(mq) > -1 ? 'sm' : 'lg'"
67
- :label="cta.label"
68
- :icon="cta.icon"
69
- :link="cta.url"
70
- :tracking-code="cta.trackingCode"
71
- :public-form-id="cta.publicFormId"
72
- reversed
73
- horizontal
74
- centered
75
- />
33
+ class="asd20-campus-header__school"
34
+ >
35
+ <div
36
+ class="asd20-campus-header__photo"
37
+ :class="{ 'asd20-campus-header__photo--empty': !panel.image.url }"
38
+ >
39
+ <img
40
+ v-if="panel.image.url"
41
+ :src="panel.image.url"
42
+ :alt="panel.image.alt"
43
+ :width="panel.image.width"
44
+ :height="panel.image.height"
45
+ decoding="async"
46
+ @error="onImageError(panel.image.url)"
47
+ />
48
+ </div>
49
+ <asd20-button
50
+ v-if="panel.cta"
51
+ class="asd20-campus-header__school-link"
52
+ :label="panel.cta.label"
53
+ :icon="panel.cta.icon || 'chevron'"
54
+ :icon-angle="panel.cta.icon ? 0 : 90"
55
+ :link="panel.cta.url"
56
+ :tracking-code="panel.cta.trackingCode"
57
+ :public-form-id="panel.cta.publicFormId"
58
+ horizontal
59
+ opposite
60
+ />
61
+ </div>
76
62
  </div>
77
63
  </div>
78
64
  <slot />
@@ -80,13 +66,9 @@
80
66
  </template>
81
67
 
82
68
  <script>
83
- import scrollTrack from '../../../directives/scroll-track'
84
- import responsiveBreakpointMixin from '../../../mixins/responsiveBreakpointMixin'
85
69
  import Asd20Button from '../../atoms/Asd20Button'
86
- import Asd20DistrictLogo from '../../../components/atoms/Asd20DistrictLogo'
87
- import Asd20OrganizationPicker from '../../../components/organisms/Asd20OrganizationPicker'
88
- import organizationTitle from '../../../helpers/organizationTitle'
89
- import organizationSubtitle from '../../../helpers/organizationSubtitle'
70
+ import Asd20DistrictLogo from '../../atoms/Asd20DistrictLogo'
71
+ import Asd20OrganizationPicker from '../Asd20OrganizationPicker'
90
72
 
91
73
  export default {
92
74
  name: 'Asd20CampusHeader',
@@ -95,8 +77,6 @@ export default {
95
77
  Asd20DistrictLogo,
96
78
  Asd20OrganizationPicker,
97
79
  },
98
- directives: { scrollTrack },
99
- mixins: [responsiveBreakpointMixin],
100
80
  props: {
101
81
  organization: { type: Object, default: null },
102
82
  organizationOptions: { type: Array, default: () => [] },
@@ -110,34 +90,65 @@ export default {
110
90
  detailLinkLabel: { type: String, default: '' },
111
91
  images: { type: Array, default: () => [] },
112
92
  },
93
+ data: () => ({ failedImageUrls: [] }),
113
94
  computed: {
114
95
  classes() {
115
96
  return {
116
97
  'asd20-campus-header--fullscreen': this.fullscreen,
117
- 'asd20-campus-header--has-cta': this.callsToAction
118
- ? this.callsToAction.length > 0
119
- : false,
98
+ 'asd20-campus-header--has-cta': this.callsToAction?.length > 0,
120
99
  }
121
100
  },
122
- isDetailPage() {
123
- if (typeof window === 'undefined') return false
124
- return (
125
- window.location.pathname.split('/').pop() ===
126
- this.detailLink.split('/').pop()
101
+ campusImages() {
102
+ const images = this.images?.length
103
+ ? this.images
104
+ : this.imageUrl
105
+ ? [{ url: this.imageUrl, alt: this.imageCaption }]
106
+ : []
107
+
108
+ // Keep each image's position, even when its files are missing, so the
109
+ // elementary/middle/high links never move to a different school's photo.
110
+ return images.map(image => {
111
+ const files = Array.isArray(image?.files)
112
+ ? image.files.filter(file => file?.url)
113
+ : []
114
+ const candidates = [
115
+ files.find(file => file.name === 'full'),
116
+ ...files,
117
+ image?.url ? image : null,
118
+ ]
119
+ const file = candidates.find(
120
+ file => file?.url && !this.failedImageUrls.includes(file.url)
121
+ )
122
+ return {
123
+ url: file?.url || '',
124
+ alt: image?.metadata?.alt || image?.alt || '',
125
+ width: file?.width,
126
+ height: file?.height,
127
+ }
128
+ })
129
+ },
130
+ campusPanels() {
131
+ const callsToAction = this.callsToAction || []
132
+ return Array.from(
133
+ { length: Math.max(this.campusImages.length, callsToAction.length) },
134
+ (_, index) => ({
135
+ image: this.campusImages[index] || { url: '', alt: '' },
136
+ cta: callsToAction[index],
137
+ })
127
138
  )
128
139
  },
129
- campusImages() {
130
- let images = this.images || []
131
- return images.map(i => ({
132
- url: (i.files.find(f => f.name === 'full') || i.files[0]).url,
133
- alt: i.metadata.alt,
134
- }))
140
+ },
141
+ watch: {
142
+ images() {
143
+ this.failedImageUrls = []
135
144
  },
136
- mapOrganizationTitle() {
137
- return organizationTitle(this.organization.title)
145
+ imageUrl() {
146
+ this.failedImageUrls = []
138
147
  },
139
- mapOrganizationSubtitle() {
140
- return organizationSubtitle(this.organization.title)
148
+ },
149
+ methods: {
150
+ onImageError(url) {
151
+ if (!this.failedImageUrls.includes(url)) this.failedImageUrls.push(url)
141
152
  },
142
153
  },
143
154
  }
@@ -148,281 +159,173 @@ export default {
148
159
 
149
160
  .asd20-campus-header {
150
161
  position: relative;
151
- display: flex;
152
162
  flex-shrink: 0;
153
- flex-direction: column;
154
- box-sizing: border-box;
155
- margin: space(1);
156
- .heading-container {
157
- margin-bottom: space(1);
158
- }
159
- &::after {
160
- content: '';
161
- display: block;
162
- position: absolute;
163
- top: space(3);
164
- bottom: space(0);
165
- right: space(0);
166
- left: space(0);
167
- background: var(--website-header__background-color)
168
- var(--website-header__background-style);
169
- z-index: -2;
170
- border-radius: var(--website-shape__radius-l);
171
- border-bottom-left-radius: 0;
172
- border-bottom-right-radius: 0;
163
+ background-color: var(--website-page__background-color, #fff);
164
+
165
+ &__top,
166
+ &__content,
167
+ &__panels {
168
+ box-sizing: border-box;
169
+ width: min(100%, 90rem);
170
+ margin-inline: auto;
171
+ padding-inline: clamp(1rem, 4vw, 4rem);
173
172
  }
174
- &__top {
175
- --fill-one: rgba(255, 255, 255, 1);
176
- --fill-two: rgba(255, 255, 255, 0.625);
177
173
 
174
+ &__top {
178
175
  position: relative;
179
176
  z-index: 101;
180
- background: white;
181
177
  display: flex;
182
- justify-content: space-between;
178
+ flex-wrap: wrap;
183
179
  align-items: center;
184
- flex-direction: column;
185
- align-items: flex-start;
186
- padding: space(0.25);
187
- .asd20-logo {
188
- margin: 0 space(1) space(1) space(1);
189
- }
180
+ justify-content: space-between;
181
+ gap: 1rem;
182
+ padding-block: 1.25rem;
190
183
  }
191
184
 
192
- h2 {
185
+ &__content {
193
186
  position: relative;
194
- color: var(--website-header__title-color);
195
- @include asd20-font(
196
- 1.5,
197
- var(--website-typography__font-family-headlines),
198
- 1.2,
199
- 700
200
- );
201
- margin: space(1) space(1) space(1) space(1);
202
- z-index: 1;
203
- }
204
- &__heading-combo {
205
- margin: space(2) space(1) 0 0;
206
- order: 2;
187
+ padding-block: clamp(1.5rem, 4vw, 4rem) clamp(1.5rem, 3vw, 2.75rem);
207
188
  }
208
189
 
209
- &__lead {
210
- position: relative;
211
- z-index: 1;
212
- color: var(--website-header__title-color);
213
- @include asd20-font(1, var(--website-typography__font-family-headlines));
214
- margin: space(-0.5) space(2) space(1) space(1);
215
- a {
216
- color: var(--website-header__link-color);
217
- }
190
+ h1 {
191
+ max-width: 22ch;
192
+ margin: 0;
193
+ color: var(--color__primary);
194
+ font-family: var(--website-typography__font-family-headlines);
195
+ font-size: clamp(2rem, 4.5vw, 4rem);
196
+ font-weight: 700;
197
+ line-height: 1.1;
198
+ letter-spacing: -0.035em;
199
+ text-wrap: balance;
218
200
  }
219
201
 
220
- &__call-to-action {
221
- margin: 0 space(2) space(-0.5) space(1);
222
- z-index: 1;
223
- display: flex;
224
- flex-direction: column;
225
- .asd20-button {
226
- flex-grow: 1;
227
- width: 100%;
228
- margin-bottom: space(1);
229
- /* max-width: 25vw;*/
230
- font-size: 0.875rem !important;
231
- background-color: var(
232
- --website-header__button-background-color,
233
- var(--color__accent)
234
- );
235
- color: var(
236
- --website-header__button-foreground-color,
237
- var(--color__accent-t100)
238
- );
239
- &:hover,
240
- &:focus {
241
- background-color: var(
242
- --website-header__button-hover-background-color,
243
- var(--color__accent-s10)
244
- );
245
- color: var(
246
- --website-header__button-hover-foreground-color,
247
- var(--color__accent-t100)
248
- );
249
- }
250
-
251
- &:not(:last-child) {
252
- margin-right: space(0.5);
253
- }
202
+ &__lead {
203
+ max-width: 62ch;
204
+ margin-top: 1.25rem;
205
+ color: var(--website-page__foreground-color, #333);
206
+ font-family: var(--website-typography__font-family-body);
207
+ font-size: clamp(1rem, 1.4vw, 1.2rem);
208
+ line-height: 1.65;
209
+ :deep(a) {
210
+ color: var(--color__primary);
211
+ text-decoration: underline;
254
212
  }
255
213
  }
256
214
 
257
- /* Hide images on mobile*/
258
- &__image {
259
- display: none;
260
- }
261
-
262
- /* Add a bit more space at the bottom*/
263
- /* if cta is present*/
264
- &--has-cta {
265
- margin-bottom: space(1);
266
- &::after {
267
- bottom: space(-1);
215
+ &__notifications {
216
+ margin-bottom: 1.5rem;
217
+ &:empty {
218
+ display: none;
268
219
  }
269
220
  }
270
- }
271
221
 
272
- $min: 3rem;
273
- $max: 4rem;
274
- @media (min-width: 667px) {
275
- .asd20-campus-header {
276
- &__top {
277
- flex-direction: row;
278
- justify-content: space-between;
279
- padding: 0 space(1);
280
- align-items: center !important;
281
- }
282
- }
283
- }
222
+ &__schools {
223
+ position: relative;
224
+ isolation: isolate;
225
+ padding-bottom: clamp(2rem, 4vw, 4rem);
284
226
 
285
- @media (min-width: 1024px) {
286
- .asd20-campus-header {
287
- display: grid;
288
- grid-template-columns: space(3) repeat(5, 1fr space(3)) 1fr space(3);
289
- grid-template-rows: repeat(6, space(1)) 0.6fr repeat(17, space(1));
290
- margin-top: 0;
291
- margin-bottom: 0;
292
- .heading-container {
293
- display: flex;
294
- justify-content: space-between;
295
- margin-bottom: space(1);
296
- }
227
+ // Keep the configured campus artwork in a visible, contained layer.
228
+ // It is decorative: school names and CTAs sit on solid surfaces.
297
229
  &::before {
298
230
  content: '';
299
- display: block;
300
- position: relative;
301
- top: auto;
302
- bottom: auto;
303
- left: auto;
304
- right: auto;
305
- margin-left: 0;
231
+ position: absolute;
232
+ z-index: -1;
233
+ inset: 4rem 0 0;
306
234
  background-color: var(
307
- --website-header__button-background-color,
308
- var(--color__accent)
235
+ --website-header__background-color,
236
+ var(--color__primary)
309
237
  );
310
- opacity: 0.9;
311
- z-index: 2;
312
- grid-column: 2 / 13;
313
- grid-row: 23 / 25;
314
- }
315
- &::after {
316
- top: space(6);
317
- }
318
-
319
- /* Provide space between logo and breadcrumb*/
320
- /* TODO: Revisit for school logo*/
321
- & :deep(.asd20-logo + .asd20-breadcrumb) {
322
- margin-top: space(2);
238
+ background: var(--website-header__background-color, var(--color__primary))
239
+ var(--website-header__background-style, none);
323
240
  }
241
+ }
324
242
 
325
- &__top {
326
- margin: 0;
327
- padding: space(1) space(3) space(1) space(3);
328
- grid-column: 1/-1;
329
- grid-row: 1/7;
330
- background: transparent;
331
- justify-content: space-between;
332
- .asd20-logo {
333
- margin: 0;
334
- }
335
- }
243
+ &__panels {
244
+ display: grid;
245
+ grid-template-columns: minmax(0, 1fr);
246
+ gap: 1.25rem;
247
+ }
336
248
 
337
- &__content {
338
- position: relative;
339
- grid-column: 2 / 13;
340
- grid-row: 7 / 9;
249
+ &__school {
250
+ display: flex;
251
+ flex-direction: column;
252
+ min-width: 0;
253
+ background: var(--website-page__background-color, #fff);
254
+ border-radius: var(--website-shape__radius-l, 1rem);
255
+ box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 12%);
256
+ // Leave the link's focus outline visible outside the panel.
257
+ &:focus-within {
258
+ outline: 3px solid var(--color__primary);
259
+ outline-offset: 4px;
341
260
  }
261
+ }
342
262
 
343
- h2 {
344
- margin: space(2) 0 space(1) 0;
263
+ &__photo {
264
+ overflow: hidden;
265
+ aspect-ratio: 4 / 3;
266
+ border-radius: var(--website-shape__radius-l, 1rem)
267
+ var(--website-shape__radius-l, 1rem) 0 0;
268
+ background: var(--color__primary-t90, #eee);
269
+ img {
270
+ display: block;
271
+ width: 100%;
272
+ height: 100%;
273
+ object-fit: cover;
274
+ object-position: center;
345
275
  }
346
- &__heading-combo {
347
- margin: space(2) 0 0 0;
348
- /* order: 2;*/
276
+ &--empty {
277
+ background: linear-gradient(
278
+ 135deg,
279
+ var(--color__primary-t90),
280
+ var(--color__primary-t70)
281
+ );
349
282
  }
283
+ }
350
284
 
351
- &__lead {
352
- /* Make it closer to the h1*/
353
- margin: space(-0.5) 0 0 0;
354
- }
355
- .call-to-action-container {
356
- grid-column: 2 / 13;
357
- grid-row-start: 24;
358
- z-index: 2;
285
+ &__school-link {
286
+ box-sizing: border-box;
287
+ flex: 1;
288
+ width: 100%;
289
+ min-height: 4.5rem;
290
+ margin: 0;
291
+ padding: 1.25rem;
292
+ border: 0;
293
+ border-top: 4px solid var(--color__primary);
294
+ border-radius: 0 0 var(--website-shape__radius-l, 1rem)
295
+ var(--website-shape__radius-l, 1rem);
296
+ background: var(--website-page__background-color, #fff);
297
+ color: var(--color__primary);
298
+ box-shadow: none;
299
+ // Override the shared button's fluid font size, which uses !important.
300
+ font-size: 1rem !important;
301
+ line-height: 1.4;
302
+ text-align: left;
303
+ justify-content: space-between;
304
+ gap: 0.75rem;
305
+ :deep(.asd20-button__label) {
359
306
  margin: 0;
360
- height: min-content;
361
- align-self: end;
362
- justify-content: space-between;
307
+ overflow-wrap: anywhere;
363
308
  }
364
-
365
- &__call-to-action {
366
- z-index: 2;
367
- margin: 0;
368
- height: min-content;
369
- align-self: end;
370
- display: flex;
371
- flex-direction: row;
372
- & :deep(.asd20-button) {
373
- margin: 0 !important;
374
- text-align: left !important;
375
- min-height: space(1);
376
- font-size: 1rem !important;
377
- background: rgba(255, 255, 255, 0);
378
- max-width: inherit;
379
- z-index: 3;
380
- border-radius: 0;
381
- width: 10vw;
382
- }
383
- & :deep(.asd20-button:hover) {
384
- background: rgba(255, 255, 255, 0) !important;
385
- }
309
+ :deep(.asd20-icon) {
310
+ flex-shrink: 0;
311
+ color: inherit;
386
312
  }
387
-
388
- &__lead + &__call-to-action {
389
- margin-top: 0;
313
+ &:hover,
314
+ &:focus-visible {
315
+ background: var(--color__primary);
316
+ color: var(--color__on-primary, #fff);
317
+ box-shadow: none;
390
318
  }
391
-
392
- &__image {
393
- display: flex;
394
- position: relative;
395
- bottom: auto;
396
- left: auto;
397
- right: auto;
398
- top: auto;
399
- grid-column: 1 / 14;
400
- grid-row: 13 / 26;
401
- z-index: 1;
402
- height: auto;
403
- margin: 0;
404
- padding: 0;
405
- transform: translate3d(0, calc(-10% * var(--scroll-progress)), 0);
406
- border-radius: var(--website-shape__radius-l);
407
- overflow: hidden;
408
- &__image_container {
409
- display: flex;
410
- }
411
- div {
412
- border-right: 5px solid var(--website-header__background-color);
413
- }
414
- div:last-child {
415
- border-right: none;
416
- }
417
-
418
- img {
419
- display: block;
420
- width: 100%;
421
- height: 100%;
422
- object-fit: cover;
423
- object-position: center;
424
- }
319
+ &:focus-visible {
320
+ outline: 3px solid currentColor;
321
+ outline-offset: -6px;
425
322
  }
426
323
  }
427
324
  }
325
+
326
+ @media (min-width: 768px) {
327
+ .asd20-campus-header__panels {
328
+ grid-template-columns: repeat(3, minmax(0, 1fr));
329
+ }
330
+ }
428
331
  </style>
@@ -275,7 +275,7 @@ export default {
275
275
 
276
276
  if (!isDistrict && hasExteriorPhoto) {
277
277
  return {
278
- '--school-exterior-photo-url': `url(${this.organization.exteriorPhotoUrl})`,
278
+ '--school-exterior-photo-url': `url(${JSON.stringify(this.organization.exteriorPhotoUrl)})`,
279
279
  }
280
280
  }
281
281
  return null
@@ -1,25 +1,20 @@
1
1
  <template>
2
2
  <div class="asd20-template-campus">
3
- <!-- Skip Nav -->
4
3
  <asd20-skip-to></asd20-skip-to>
5
-
6
- <!-- Banner Notifications -->
7
4
  <asd20-notification-group
8
5
  :notifications="activeNotificationsByType.banner"
9
6
  group-type="banner"
10
7
  @dismiss="$emit('dismiss-notification', $event)"
11
8
  @toggle-all="$emit('toggle-all')"
12
9
  ></asd20-notification-group>
13
-
14
- <!-- Page Header -->
15
10
  <asd20-campus-header
16
- v-show="pageHeaderContent"
11
+ v-if="pageHeaderContent"
17
12
  v-bind="pageHeaderContent"
18
13
  id="campus-header"
19
14
  :organization="organization"
20
15
  :organization-options="organizationOptions"
21
16
  :language-code="languageCode"
22
- :images="firstMessage.images"
17
+ :images="firstMessage.images || []"
23
18
  :breadcrumb-links="breadcrumbLinks"
24
19
  @update:languageCode="$emit('update:languageCode', $event)"
25
20
  >
@@ -57,8 +52,13 @@
57
52
  </a>
58
53
  </div>
59
54
  </template>
60
- <!-- Floating Notifications -->
61
- <template #notification>
55
+ <template
56
+ v-if="
57
+ activeNotificationsByType.floating.length ||
58
+ totalDismissedNotifications
59
+ "
60
+ #notification
61
+ >
62
62
  <asd20-notification-group
63
63
  class="asd20-notification-group--floating"
64
64
  :notifications="activeNotificationsByType.floating"
@@ -68,78 +68,38 @@
68
68
  @toggle-all="$emit('toggle-all')"
69
69
  ></asd20-notification-group>
70
70
  </template>
71
-
72
- <!-- <template #top><asd20-breadcrumb :links="breadcrumbLinks"></asd20-breadcrumb></template> -->
73
71
  </asd20-campus-header>
74
-
75
- <!-- Site Navigation -->
76
- <!-- <asd20-site-navigation
77
- :navigation="navigation"
78
- :action-items="actionItems"
79
- @update:menuOpen="$emit('update:menuOpen', $event)"
80
- :menu-open="menuOpen"
81
- @update:searchOpen="$emit('update:searchOpen', $event)"
82
- :search-open="searchOpen"
83
- :organization="organization"
84
- :organization-options="organizationOptions"
85
- >
86
- </asd20-site-navigation> -->
87
-
88
- <!-- Page Content -->
89
- <asd20-page-content
90
- :primary-messages="wayFindingPrimaryMessages"
91
- :secondary-messages="secondaryMessages"
92
- omit-body-content
93
- grid
94
- >
95
- <!-- Inline-Notifications -->
72
+ <main id="main" tabindex="-1">
96
73
  <asd20-notification-group
97
74
  :notifications="activeNotificationsByType.inline"
98
75
  group-type="inline"
99
76
  @dismiss="$emit('dismiss-notification', $event)"
100
77
  @toggle-all="$emit('toggle-all')"
101
- ></asd20-notification-group>
102
-
103
- <slot></slot>
104
- </asd20-page-content>
105
-
106
- <asd20-feeds-section
107
- :announcements="announcements"
108
- :announcements-feed-props="announcementsFeedProps"
109
- :stories="stories"
110
- :stories-feed-props="storiesFeedProps"
111
- :events="events"
112
- :events-feed-props="eventsFeedProps"
113
- :organization="organization"
114
- :organization-options="organizationOptions"
115
- @announcements-in-view="$emit('announcements-in-view')"
116
- @stories-in-view="$emit('stories-in-view')"
117
- @events-in-view="$emit('events-in-view')"
118
- ></asd20-feeds-section>
119
-
120
- <!-- Bottom Widgets -->
121
- <!-- <asd20-widgets-section
122
- v-if="
123
- widgetsSectionProps.footer ||
124
- widgetsSectionProps.sidebar ||
125
- $slots.widgets
126
- "
127
- v-bind="{ ...widgetsSectionProps.footer, ...widgetsSectionProps.sidebar }"
128
- @events-in-view="$emit('events-in-view')"
129
- @files-in-view="$emit('files-in-view')"
130
- @people-in-view="$emit('people-in-view')"
131
- full
132
- ><slot name="widgets"
133
- /></asd20-widgets-section> -->
134
-
135
- <!-- <slot name="before-footer" /> -->
136
-
137
- <!-- Quick Links -->
138
- <!-- <template #before-footer><asd20-quicklinks-menu :quick-links="quickLinks">
139
- <slot name="quicklinks-menu" />
140
- </asd20-quicklinks-menu></template> -->
141
-
142
- <!-- Footer -->
78
+ />
79
+ <asd20-page-content
80
+ tag="section"
81
+ :primary-messages="wayFindingPrimaryMessages"
82
+ :secondary-messages="secondaryMessages"
83
+ omit-body-content
84
+ grid
85
+ >
86
+ <slot></slot>
87
+ </asd20-page-content>
88
+
89
+ <asd20-feeds-section
90
+ :announcements="announcements"
91
+ :announcements-feed-props="announcementsFeedProps"
92
+ :stories="stories"
93
+ :stories-feed-props="storiesFeedProps"
94
+ :events="events"
95
+ :events-feed-props="eventsFeedProps"
96
+ :organization="organization"
97
+ :organization-options="organizationOptions"
98
+ @announcements-in-view="$emit('announcements-in-view')"
99
+ @stories-in-view="$emit('stories-in-view')"
100
+ @events-in-view="$emit('events-in-view')"
101
+ ></asd20-feeds-section>
102
+ </main>
143
103
  <asd20-page-footer
144
104
  :organization="organization"
145
105
  :social-links="socialLinks"
@@ -153,24 +113,14 @@
153
113
  </template>
154
114
 
155
115
  <script>
156
- // Components
157
116
  import Asd20SkipTo from '../../../components/atoms/Asd20SkipTo'
158
117
  import Asd20CampusHeader from '../../../components/organisms/Asd20CampusHeader'
159
-
160
- // import Asd20SiteNavigation from '../../../components/organisms/Asd20SiteNavigation'
161
118
  import Asd20PageContent from '../../../components/organisms/Asd20PageContent'
162
-
163
- // import Asd20WidgetsSection from '../../../components/organisms/Asd20WidgetsSection'
164
119
  import Asd20FeedsSection from '../../../components/organisms/Asd20FeedsSection'
165
- // import Asd20MediaSection from '../../../components/organisms/Asd20MediaSection'
166
-
167
120
  import Asd20PageFooter from '../../../components/organisms/Asd20PageFooter'
168
- // import Asd20QuicklinksMenu from '../../../components/organisms/Asd20QuicklinksMenu'
169
121
  import Asd20NotificationGroup from '../../../components/organisms/Asd20NotificationGroup'
170
122
  import { navigateInternalHref } from '../../../helpers/linkPolicy'
171
123
 
172
-
173
- // Mixins
174
124
  import pageTemplateMixin from '../../../mixins/pageTemplateMixin'
175
125
 
176
126
  export default {
@@ -180,25 +130,22 @@ export default {
180
130
  Asd20SkipTo,
181
131
  Asd20CampusHeader,
182
132
  Asd20PageFooter,
183
- // Asd20SiteNavigation,
184
- // Asd20WidgetsSection,
185
133
  Asd20FeedsSection,
186
134
  Asd20NotificationGroup,
187
- // Asd20QuicklinksMenu,
188
- // Asd20MediaSection,
189
135
  },
190
136
  mixins: [pageTemplateMixin],
191
137
  computed: {
138
+ pageHeaderContent() {
139
+ if (!this.firstMessage) return null
140
+ return {
141
+ heading: this.firstMessage.title || this.firstMessage.heading || '',
142
+ lead:
143
+ this.firstMessage.shortDescription || this.firstMessage.summary || '',
144
+ callsToAction: this.firstMessage.callsToAction || [],
145
+ }
146
+ },
192
147
  wayFindingPrimaryMessages() {
193
- // wayfinding page
194
- return (
195
- this.messages
196
- // Remove the first message, limit
197
- .slice(
198
- 1,
199
- Math.min(this.messages.length, this.primaryMessageLimit + 1)
200
- )
201
- )
148
+ return this.messages.slice(1, this.primaryMessageLimit + 1)
202
149
  },
203
150
  },
204
151
  methods: {
@@ -216,68 +163,48 @@ export default {
216
163
 
217
164
  <style lang="scss">
218
165
  @use '../../../design/template-stack' as *;
166
+
219
167
  .asd20-template-campus {
220
168
  @include typography;
221
169
  @include template;
222
170
  display: flex;
223
171
  flex-direction: column;
224
172
  flex-grow: 1;
225
- flex-shrink: 0;
226
- overflow: hidden;
227
- margin-top: space(2.25);
228
- margin-top: 0;
229
- #campus-header .logoContainer {
230
- display: none;
231
- }
232
- }
233
-
234
- @media (min-width: 1024px) {
235
- .asd20-template-campus {
236
- @include template-desktop;
237
- // margin-left: space(3);
238
- margin-top: 0;
239
- #campus-header .logoContainer {
240
- display: flex;
241
- .optionalLogo {
242
- max-width: 12rem;
243
- max-height: 6rem;
244
- margin-left: space(2);
245
- }
173
+ min-width: 0;
174
+ overflow: clip;
175
+ background: var(--website-page__background-color, #fff);
176
+
177
+ .logoContainer {
178
+ display: flex;
179
+ align-items: center;
180
+ flex-wrap: wrap;
181
+ gap: 1rem;
182
+ a {
183
+ display: inline-flex;
246
184
  }
247
- .asd20-page-content {
248
- margin-top: space(1);
185
+ .optionalLogo {
186
+ display: block;
187
+ max-width: 8rem;
188
+ max-height: 3.5rem;
189
+ object-fit: contain;
249
190
  }
250
191
  }
251
192
 
252
- .asd20-template-campus #campus-header .asd20-campus-header__image {
253
- grid-column: 1 / -1;
254
- justify-self: center;
255
- width: 90%;
256
- display: grid;
257
- grid-template-columns: repeat(3, minmax(0, 1fr));
258
- gap: 0;
259
- }
260
-
261
- .asd20-template-campus #campus-header .asd20-campus-header__image > div {
193
+ > main {
262
194
  min-width: 0;
263
- height: 100%;
264
- border-right: 5px solid var(--website-header__background-color);
265
195
  }
266
-
267
- .asd20-template-campus
268
- #campus-header
269
- .asd20-campus-header__image
270
- > div:last-child {
271
- border-right: none;
196
+ .asd20-page-content {
197
+ margin-block: clamp(1.5rem, 3vw, 3rem);
272
198
  }
199
+ }
273
200
 
274
- .asd20-template-campus #campus-header .asd20-campus-header__image > div img {
275
- width: 100%;
276
- height: 100%;
277
- aspect-ratio: 4 / 3;
278
- object-fit: cover;
279
- object-position: center;
280
- display: block;
201
+ @media (min-width: 1024px) {
202
+ .asd20-template-campus {
203
+ @include template-desktop;
204
+ .logoContainer .optionalLogo {
205
+ max-width: 12rem;
206
+ max-height: 5rem;
207
+ }
281
208
  }
282
209
  }
283
210
  </style>