@asd20/ui 3.2.1032 → 3.2.1034

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/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "*.scss",
6
6
  "*.vue"
7
7
  ],
8
- "version": "3.2.1032",
8
+ "version": "3.2.1034",
9
9
  "private": false,
10
10
  "license": "MIT",
11
11
  "repository": {
@@ -19,23 +19,32 @@
19
19
  <div v-if="shortDescription" class="lead short-description">
20
20
  <span v-html="shortDescription"></span>
21
21
  <a v-if="detailLink" :href="detailLink">{{ detailLinkLabel }}</a>
22
- <span class="external-link" v-if="isAbsoluteUrl(detailLink)">
23
- <asd20-icon name="external" size="sm" />
24
- </span>
22
+ <div class="external-link" v-if="isExternalUrl(detailLink)">
23
+ <asd20-icon
24
+ name="external"
25
+ size="sm"
26
+ aria-hidden="true"
27
+ focusable="false"
28
+ />
29
+ </div>
25
30
  </div>
26
31
  <p
27
32
  v-if="longDescription"
28
33
  class="long-description"
29
34
  v-html="longDescription"
30
35
  />
31
- <!-- <div v-if="bodyContent" class="asd20-messaging__body-content">
36
+ <div v-if="bodyContent" class="asd20-messaging__body-content">
32
37
  <component :is="dynamicComponent"></component>
33
- </div> -->
34
- <div
38
+ </div>
39
+
40
+ <!-- use this in place of the dynamicComponent above for simpler/safer
41
+ body content rendering; it does prevent usage of Vue components like
42
+ nuxt-link during build -->
43
+ <!-- <div
35
44
  v-if="bodyContent"
36
45
  class="asd20-messaging__body-content"
37
46
  v-html="bodyContent"
38
- />
47
+ /> -->
39
48
 
40
49
  <div
41
50
  v-if="callsToAction && callsToAction.length > 0"
@@ -58,6 +67,148 @@
58
67
  import Asd20Button from '../Asd20Button'
59
68
  import Asd20Icon from '../Asd20Icon'
60
69
 
70
+ export default {
71
+ name: 'Asd20Messaging',
72
+ inheritAttrs: false,
73
+ components: { Asd20Button, Asd20Icon },
74
+ props: {
75
+ images: { type: Array, default: () => [] },
76
+ heading: { type: String, default: '' },
77
+ headingTagName: { type: String, default: 'h2' },
78
+ shortDescription: { type: String, default: '' },
79
+ longDescription: { type: String, default: '' },
80
+ bodyContent: { type: String, default: '' },
81
+ detailLink: { type: String, default: '' },
82
+ detailLinkLabel: { type: String, default: '' },
83
+ callsToAction: { type: Array, default: () => [] },
84
+ fullscreen: { type: Boolean, default: false },
85
+ padded: { type: Boolean, default: false },
86
+
87
+ /**
88
+ * If true, treat "www.asd20.org" the same as "asd20.org" for local/external checks.
89
+ * Leave true unless you specifically want "www." to be considered a separate subdomain.
90
+ */
91
+ treatWwwAsLocal: { type: Boolean, default: true },
92
+ },
93
+
94
+ data () {
95
+ return {
96
+ // SSR-safe: fill on client in mounted()
97
+ currentOrigin: '',
98
+ currentHost: '',
99
+ }
100
+ },
101
+
102
+ mounted () {
103
+ if (typeof window !== 'undefined' && window.location) {
104
+ this.currentOrigin = window.location.origin
105
+ this.currentHost = window.location.hostname
106
+ }
107
+ },
108
+
109
+ computed: {
110
+ hash() {
111
+ return this.heading.toLowerCase().split(' ').join('-')
112
+ },
113
+ classes() {
114
+ return {
115
+ 'asd20-messaging': true,
116
+ 'asd20-messaging--fullscreen': this.fullscreen,
117
+ 'asd20-messaging--padded': this.padded,
118
+ }
119
+ },
120
+ dynamicComponent() {
121
+ return {
122
+ template: `<div>${this.bodyContent}</div>`,
123
+ }
124
+ },
125
+ messageImage() {
126
+ const messageImages = Array.isArray(this.images) ? this.images : []
127
+ const coverImage = messageImages.find(i => i.isCover)
128
+ let messageImageUrl = null
129
+ if (coverImage) {
130
+ const coverImageFiles = coverImage.files || []
131
+ const coverImageFull = coverImageFiles.find(f => f.name === 'full')
132
+ if (coverImageFull && coverImageFull.filename && coverImageFull.filename.includes('headerimage')) {
133
+ messageImageUrl = ''
134
+ } else {
135
+ messageImageUrl = coverImageFull ? coverImageFull.url : (coverImage.url || '')
136
+ }
137
+ }
138
+ return messageImageUrl
139
+ },
140
+ messageImageAlt() {
141
+ const messageImages = Array.isArray(this.images) ? this.images : []
142
+ const coverImage = messageImages.find(i => i.isCover)
143
+ return coverImage?.metadata?.alt || ''
144
+ },
145
+ },
146
+
147
+ methods: {
148
+ normalizeHost (host) {
149
+ if (!host) return ''
150
+ const h = host.toLowerCase()
151
+ return this.treatWwwAsLocal ? h.replace(/^www\./, '') : h
152
+ },
153
+
154
+ isHttpLike (urlObj) {
155
+ // treat only http/https (and protocol-relative via base) as web links
156
+ return urlObj.protocol === 'http:' || urlObj.protocol === 'https:'
157
+ },
158
+
159
+ // "Local" means: relative URL or absolute URL whose hostname matches current hostname
160
+ isLocalUrl (href) {
161
+ if (!href || typeof href !== 'string') return true // nothing to show icon for
162
+ const s = href.trim()
163
+
164
+ // Always consider these "local" (no external icon)
165
+ if (s.startsWith('#') || s.startsWith('mailto:') || s.startsWith('tel:')) return true
166
+
167
+ // Build a URL object using currentOrigin as base so that
168
+ // - relative paths "/academics" resolve to current host
169
+ // - protocol-relative "//liberty.asd20.org" resolves correctly
170
+ // If currentOrigin is empty (SSR), use a dummy base.
171
+ const base = this.currentOrigin || 'http://example.local'
172
+ let u
173
+ try {
174
+ u = new URL(s, base)
175
+ } catch (e) {
176
+ // If it somehow can't be parsed, assume it's local (no icon)
177
+ return true
178
+ }
179
+
180
+ // Not http/https? treat as local (no icon)
181
+ if (!this.isHttpLike(u)) return true
182
+
183
+ // Relative URLs will have the same host as base; absolute will carry their own host.
184
+ const linkHost = this.normalizeHost(u.hostname)
185
+ const currHost = this.normalizeHost(this.currentHost || new URL(base).hostname)
186
+ return linkHost === currHost
187
+ },
188
+
189
+ // External if it's a web URL AND not local
190
+ isExternalUrl (href) {
191
+ if (!href || typeof href !== 'string') return false
192
+ const s = href.trim()
193
+ const base = this.currentOrigin || 'http://example.local'
194
+ let u
195
+ try {
196
+ u = new URL(s, base)
197
+ } catch (e) {
198
+ return false
199
+ }
200
+ if (!this.isHttpLike(u)) return false
201
+ return !this.isLocalUrl(s)
202
+ },
203
+ },
204
+ }
205
+ </script>
206
+
207
+
208
+ <!-- <script>
209
+ import Asd20Button from '../Asd20Button'
210
+ import Asd20Icon from '../Asd20Icon'
211
+
61
212
  export default {
62
213
  name: 'Asd20Messaging',
63
214
  inheritAttrs: false,
@@ -87,11 +238,11 @@ export default {
87
238
  'asd20-messaging--padded': this.padded,
88
239
  }
89
240
  },
90
- // dynamicComponent() {
91
- // return {
92
- // template: `<div>${this.bodyContent}</div>`,
93
- // }
94
- // },
241
+ dynamicComponent() {
242
+ return {
243
+ template: `<div>${this.bodyContent}</div>`,
244
+ }
245
+ },
95
246
  messageImage() {
96
247
  // Get full image
97
248
  const messageImages = Array.isArray(this.images) ? this.images : []
@@ -132,7 +283,7 @@ export default {
132
283
  },
133
284
  },
134
285
  }
135
- </script>
286
+ </script> -->
136
287
 
137
288
  <style lang="scss" scoped>
138
289
  @import '../../../design/_variables.scss';
@@ -16,7 +16,7 @@ storiesOf('Organisms - Asd20AppHeader', module).add(
16
16
  () => ({
17
17
  ...wrapper,
18
18
  template: `
19
- <Asd20AppHeader title="App Name" icon="files" version="1.0.0">
19
+ <Asd20AppHeader title="App Name" icon="calendar" version="1.0.0">
20
20
  </Asd20AppHeader>`,
21
21
  }),
22
22
  { info }
@@ -1,31 +1,25 @@
1
1
  <template>
2
2
  <header class="asd20-app-header">
3
- <asd20-button
4
- v-show="!zoomed"
5
- class="back-button"
6
- icon="chevron"
7
- :icon-angle="-90"
8
- size="xs"
9
- :label="backLabel"
10
- transparent
11
- horizontal
12
- text-only
13
- :link="backLink"
14
- ></asd20-button>
15
- <h1>
16
- <div
17
- :class="
18
- zoomed
19
- ? 'asd20-app-header__title asd20-app-header__zoomed'
20
- : 'asd20-app-header__title'
21
- "
22
- >
3
+ <div class="asd20-app-header__title">
4
+ <asd20-button
5
+ class="back-button"
6
+ icon="chevron"
7
+ :icon-angle="-90"
8
+ size="xs"
9
+ :label="backLabel"
10
+ transparent
11
+ horizontal
12
+ text-only
13
+ :link="backLink"
14
+ ></asd20-button>
15
+ <div class="icon-and-title">
23
16
  <asd20-icon v-if="icon" :name="icon" size="md" />
24
- {{ title }}
17
+ <h1>{{ title }}</h1>
25
18
  </div>
26
- <asd20-district-logo link="https://www.asd20.org" />
27
- </h1>
28
- <!-- <span class="version" v-if="version && !zoomed" v-html="version"></span> -->
19
+ </div>
20
+
21
+ <asd20-district-logo link="https://www.asd20.org" />
22
+ <!-- <span class="version" v-if="version" v-html="version"></span> -->
29
23
  <slot />
30
24
  </header>
31
25
  </template>
@@ -45,18 +39,6 @@ export default {
45
39
  backLink: { type: String, default: 'https://www.asd20.org' },
46
40
  backLabel: { type: String, default: 'asd20.org' },
47
41
  },
48
- data: () => ({
49
- zoomed: false,
50
- }),
51
- mounted() {
52
- this.zoomed = window.innerHeight <= 500
53
- window.addEventListener('resize', this.handleResize)
54
- },
55
- methods: {
56
- handleResize() {
57
- this.zoomed = window.innerHeight <= 500
58
- },
59
- },
60
42
  }
61
43
  </script>
62
44
 
@@ -67,84 +49,63 @@ export default {
67
49
  position: relative;
68
50
  z-index: 100;
69
51
  display: flex;
70
- align-items: center;
52
+ align-items: flex-start;
71
53
  flex-shrink: 0;
72
54
  background: var(--color__primary);
73
55
  color: white;
74
- // background: white;
75
- flex-wrap: wrap;
56
+ flex-wrap: nowrap;
76
57
  border-bottom: 1px solid var(--color__tertiary);
77
-
78
58
  &__title {
79
- display: flex;
80
- align-items: center;
81
- flex-shrink: 0;
82
- padding: 0 0 space(0.5) 0;
83
- // width: 100vw;
84
- // border-bottom: 1px solid var(--color__tertiary);
85
- }
86
- &__zoomed,
87
- .asd20-district-logo {
88
- padding: 0 !important;
89
- }
90
-
91
- h1 {
92
- margin: 0;
93
- font-size: 1.5rem;
94
- color: var(--color__on-primary);
95
- flex-grow: 1;
96
- width: 100%;
97
- border-bottom: 1px solid var(--color__tertiary);
98
- display: flex;
99
- align-items: center;
100
- font-family: var(--website-typography__font-family-headlines);
101
- .asd20-icon {
102
- margin: 0 space(0.5);
103
- --line-color: white;
59
+ .icon-and-title {
60
+ display: flex;
61
+ align-items: center;
62
+ flex-shrink: 0;
63
+ padding-bottom: 0.5rem;
64
+ h1 {
65
+ margin: 0;
66
+ font-size: 1.5rem;
67
+ color: var(--color__on-primary);
68
+ flex-grow: 1;
69
+ width: 100%;
70
+ // border-bottom: 1px solid var(--color__tertiary);
71
+ display: flex;
72
+ align-items: center;
73
+ font-family: var(--website-typography__font-family-headlines);
74
+ }
75
+ .asd20-icon {
76
+ margin: 0 space(0.5);
77
+ --line-color: white;
78
+ }
104
79
  }
105
80
  }
106
- .version {
107
- position: absolute;
108
- top: space(3);
109
- right: space(-0.45);
110
- display: block;
111
- font-size: 0.75rem;
112
- font-family: var(--website-typography__font-family-headlines);
113
- color: lightgray;
114
- text-transform: uppercase;
115
- margin-left: space(0.5);
116
- margin-right: space(0.5);
117
- order: 2;
118
- }
119
81
 
120
82
  .asd20-district-logo {
121
83
  margin-left: auto;
122
84
  margin-right: space(1);
123
- height: space(1.5);
124
- padding: space(0.5) 0;
125
- --fill-one: #fff;
126
- --fill-two: #ccc;
127
- // --fill-one: #{asd20-color('brand-blue')};
128
- // --fill-two: #{asd20-color('brand-gray')};
129
- }
130
-
131
- .asd20-button {
132
- margin-left: auto;
133
- margin-right: space(0.5);
134
- order: 3;
135
- border-radius: 0;
85
+ height: 1.5rem;
86
+ padding: 0.75rem 0 0 0;
87
+ &::v-deep svg {
88
+ fill: #fff;
89
+ .district {
90
+ fill: #ccc;
91
+ }
92
+ }
136
93
  }
137
94
 
138
- .asd20-button + .asd20-button {
139
- margin-left: 0;
140
- }
141
- .asd20-button:not(.back-button) {
142
- border-left: 1px solid var(--color__tertiary);
143
- }
95
+ // .version {
96
+ // position: absolute;
97
+ // top: space(3);
98
+ // right: space(-0.45);
99
+ // display: block;
100
+ // font-size: 0.75rem;
101
+ // font-family: var(--website-typography__font-family-headlines);
102
+ // color: lightgray;
103
+ // text-transform: uppercase;
104
+ // margin-left: space(0.5);
105
+ // margin-right: space(0.5);
106
+ // order: 2;
107
+ // }
144
108
 
145
- .asd20-button.asd20-button--transparent {
146
- margin-right: 0;
147
- }
148
109
  .back-button {
149
110
  // width: 100vw;
150
111
  background: var(--color__primary);
@@ -161,38 +122,12 @@ export default {
161
122
  }
162
123
  }
163
124
  }
164
-
165
- @media (max-width: 767px) {
166
- .asd20-app-header::v-deep .asd20-district-logo {
167
- display: none;
168
- }
125
+ @media (min-width: 768px) {
169
126
  .asd20-app-header {
170
- h1 .version {
171
- display: none;
172
- }
173
- .asd20-button:not(.back-button) {
174
- &::v-deep .asd20-button__label {
175
- display: none;
176
- }
177
- }
178
- .asd20-app-header__title {
179
- padding: space(0.125) 0;
180
- &::v-deep .asd20-icon {
181
- margin-right: space(0.125);
182
- }
183
- }
184
- }
185
- }
186
-
187
- @media (max-width: 480px) {
188
- .asd20-app-header {
189
- &__title {
190
- display: flex;
191
- align-items: center;
192
- flex-shrink: 0;
193
- padding: space(0.5) 0;
194
- width: 100vw;
195
- border-bottom: 1px solid var(--color__tertiary);
127
+ align-items: flex-end;
128
+ .asd20-district-logo {
129
+ padding: 0 0 0.25rem 0;
130
+ height: space(1.5);
196
131
  }
197
132
  }
198
133
  }
@@ -56,15 +56,6 @@
56
56
  </transition>
57
57
 
58
58
  <transition name="slide">
59
- <!-- <asd20-site-menu
60
- ref="siteMenu"
61
- :class="zoomed ? 'zoomed' : ''"
62
- v-show="menuOpen"
63
- :active="menuOpen"
64
- @update:active="$emit('update:menuOpen', $event)"
65
- :sections="navigation"
66
- v-scroll-lock="menuOpen"
67
- /> -->
68
59
  <asd20-site-menu
69
60
  ref="siteMenu"
70
61
  :class="zoomed ? 'zoomed' : ''"