@abgov/nx-adsp 13.30.0 → 13.31.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/package.json +1 -1
- package/src/generators/vue-app/files/src/App.vue__tmpl__ +16 -3
- package/src/generators/vue-app/vue-app.spec.ts +19 -4
- package/src/generators/vue-components/files/src/lib/patterns/AppLayout.spec.ts__tmpl__ +32 -0
- package/src/generators/vue-components/files/src/lib/patterns/AppLayout.vue__tmpl__ +28 -21
- package/src/generators/vue-components/vue-components.spec.ts +1 -0
package/package.json
CHANGED
|
@@ -78,6 +78,13 @@ function onItemClick(item: { to?: string }) {
|
|
|
78
78
|
</AppLayout>
|
|
79
79
|
</AppSideMenu>
|
|
80
80
|
<% } else { %>
|
|
81
|
+
<!-- goa-one-column-layout owns the page's flex column, the sticky footer and
|
|
82
|
+
the <main> landmark. That <main> lives in the element's shadow DOM, so the
|
|
83
|
+
skip link targets an id'd wrapper in the slotted (light DOM) content
|
|
84
|
+
instead -- the wrapper is a plain div, so there is still exactly one main
|
|
85
|
+
landmark on the page. -->
|
|
86
|
+
<goa-one-column-layout>
|
|
87
|
+
<section slot="header">
|
|
81
88
|
<a class="skip-link" href="#main-content">Skip to main content</a>
|
|
82
89
|
<AppHeader heading="<%= projectName %>">
|
|
83
90
|
<template #utilities>
|
|
@@ -100,12 +107,18 @@ function onItemClick(item: { to?: string }) {
|
|
|
100
107
|
@sign-in="signInAgain"
|
|
101
108
|
@dismiss="session.dismiss"
|
|
102
109
|
/>
|
|
103
|
-
|
|
110
|
+
</section>
|
|
111
|
+
|
|
112
|
+
<div id="main-content">
|
|
104
113
|
<AppLayout :variant="contentWidth">
|
|
105
114
|
<RouterView />
|
|
106
115
|
</AppLayout>
|
|
107
|
-
</
|
|
108
|
-
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<section slot="footer">
|
|
119
|
+
<AppFooter />
|
|
120
|
+
</section>
|
|
121
|
+
</goa-one-column-layout>
|
|
109
122
|
<% } %>
|
|
110
123
|
</template>
|
|
111
124
|
|
|
@@ -120,10 +120,17 @@ describe('Vue App Generator', () => {
|
|
|
120
120
|
const layoutPath = 'libs/vue-components/src/lib/patterns/AppLayout.vue';
|
|
121
121
|
expect(host.exists(layoutPath)).toBeTruthy();
|
|
122
122
|
const layout = host.read(layoutPath).toString();
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
// The gutter is goa-page-block's job now: it supplies the centering, the
|
|
124
|
+
// max-width and a responsive horizontal gutter. The named variants stay the
|
|
125
|
+
// public API, mapped to the widths the element takes.
|
|
126
|
+
expect(layout).toContain('<goa-page-block');
|
|
127
|
+
expect(layout).toContain("form: '640px'");
|
|
128
|
+
expect(layout).toContain("page: '1000px'");
|
|
129
|
+
expect(layout).toContain("wide: '1200px'");
|
|
130
|
+
// Vertical padding is not part of goa-page-block, so this component keeps it
|
|
131
|
+
// -- the same thing GoA's own public-form reference does.
|
|
126
132
|
expect(layout).toContain('--goa-space');
|
|
133
|
+
expect(layout).toContain('padding-block');
|
|
127
134
|
// AppLayout must NOT own the skip-to-main-content landmark itself: it nests
|
|
128
135
|
// inside AppSideMenu for --layout=internal, which already provides one, and
|
|
129
136
|
// a second <main id="main-content"> would duplicate the landmark/id.
|
|
@@ -137,8 +144,16 @@ describe('Vue App Generator', () => {
|
|
|
137
144
|
const app = host.read('apps/test/src/App.vue').toString();
|
|
138
145
|
expect(app).toContain('AppLayout');
|
|
139
146
|
expect(app).not.toContain('main > section');
|
|
147
|
+
// The public shell is goa-one-column-layout, which owns the page's flex
|
|
148
|
+
// column, sticky footer and <main> landmark. That <main> is in its shadow
|
|
149
|
+
// DOM, so the skip link targets an id'd wrapper in the slotted content --
|
|
150
|
+
// a plain div, so the page still has exactly one main landmark.
|
|
151
|
+
expect(app).toContain('<goa-one-column-layout>');
|
|
152
|
+
expect(app).toContain('<section slot="header">');
|
|
153
|
+
expect(app).toContain('<section slot="footer">');
|
|
140
154
|
expect(app).toContain('href="#main-content"');
|
|
141
|
-
expect(app).toContain('id="main-content"');
|
|
155
|
+
expect(app).toContain('<div id="main-content">');
|
|
156
|
+
expect(app).not.toContain('<main id="main-content">');
|
|
142
157
|
});
|
|
143
158
|
|
|
144
159
|
it('provisions the shared GoA wrapper library and points the app at it', async () => {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import AppLayout from './AppLayout.vue';
|
|
4
|
+
|
|
5
|
+
// Note on how width is asserted: in this test environment goa-page-block is not
|
|
6
|
+
// a defined custom element, so Vue falls back to setting the width as an
|
|
7
|
+
// ATTRIBUTE. In a real browser the element defines the property, so Vue sets it
|
|
8
|
+
// as a property and getAttribute('width') returns null -- verified against a
|
|
9
|
+
// running app, where the element then applies --max-width correctly. These tests
|
|
10
|
+
// therefore guard the variant -> width mapping, which is the part that can
|
|
11
|
+
// regress; they are not evidence about attribute-vs-property binding.
|
|
12
|
+
describe('AppLayout', () => {
|
|
13
|
+
const widthFor = (variant?: 'page' | 'wide' | 'form') =>
|
|
14
|
+
mount(AppLayout, { props: variant ? { variant } : {} })
|
|
15
|
+
.find('goa-page-block')
|
|
16
|
+
.attributes('width');
|
|
17
|
+
|
|
18
|
+
it('delegates the content gutter to goa-page-block', () => {
|
|
19
|
+
expect(mount(AppLayout).find('goa-page-block').exists()).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('maps each variant to the width the element expects', () => {
|
|
23
|
+
expect(widthFor('form')).toBe('640px');
|
|
24
|
+
expect(widthFor()).toBe('1000px');
|
|
25
|
+
expect(widthFor('wide')).toBe('1200px');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders slotted content inside the block', () => {
|
|
29
|
+
const w = mount(AppLayout, { slots: { default: '<p>hello</p>' } });
|
|
30
|
+
expect(w.find('goa-page-block').html()).toContain('<p>hello</p>');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -14,36 +14,43 @@
|
|
|
14
14
|
// copy would duplicate the anchor screen readers/keyboard nav jump to. The
|
|
15
15
|
// top-level shell (App.vue for --layout=header, AppSideMenu for --layout=internal)
|
|
16
16
|
// owns that landmark instead.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
import { computed } from 'vue';
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(
|
|
20
|
+
defineProps<{ variant?: 'page' | 'wide' | 'form' }>(),
|
|
21
|
+
{ variant: 'page' },
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// goa-page-block takes a CSS dimension (or "full"); the variant names stay the
|
|
25
|
+
// public API so a view says what kind of page it is, not how wide it should be.
|
|
26
|
+
const WIDTHS = {
|
|
27
|
+
form: '640px',
|
|
28
|
+
page: '1000px',
|
|
29
|
+
wide: '1200px',
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
const maxWidth = computed(() => WIDTHS[props.variant]);
|
|
20
33
|
</script>
|
|
21
34
|
|
|
22
35
|
<template>
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
<goa-page-block :width="maxWidth">
|
|
37
|
+
<!-- goa-page-block supplies the centering, the max-width and a responsive
|
|
38
|
+
HORIZONTAL gutter, but no vertical padding -- GoA's own public-form
|
|
39
|
+
reference does the same thing, wrapping the block's children to add it. -->
|
|
40
|
+
<div class="app-layout-inset">
|
|
41
|
+
<slot />
|
|
42
|
+
</div>
|
|
43
|
+
</goa-page-block>
|
|
26
44
|
</template>
|
|
27
45
|
|
|
28
46
|
<style scoped>
|
|
29
|
-
.
|
|
30
|
-
|
|
31
|
-
.form-content {
|
|
32
|
-
margin-inline: auto;
|
|
33
|
-
box-sizing: border-box;
|
|
34
|
-
/* Design-token gutter; fallbacks apply only if tokens aren't loaded. */
|
|
35
|
-
padding: var(--goa-space-xl, 2.5rem) var(--goa-space-l, 1.5rem);
|
|
47
|
+
.app-layout-inset {
|
|
48
|
+
padding-block: var(--goa-space-xl, 2.5rem);
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
.form-content { max-width: 640px; }
|
|
39
|
-
.page-content { max-width: 1000px; }
|
|
40
|
-
.wide-content { max-width: 1200px; }
|
|
41
|
-
|
|
42
51
|
@media (max-width: 623.98px) {
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
.form-content {
|
|
46
|
-
padding: var(--goa-space-m, 1rem);
|
|
52
|
+
.app-layout-inset {
|
|
53
|
+
padding-block: var(--goa-space-m, 1rem);
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
</style>
|
|
@@ -171,6 +171,7 @@ describe('Vue Components Generator', () => {
|
|
|
171
171
|
'libs/vue-components/src/lib/primitives/GoabDatePicker.spec.ts',
|
|
172
172
|
'libs/vue-components/src/lib/patterns/WorkspaceTable.spec.ts',
|
|
173
173
|
'libs/vue-components/src/lib/patterns/Stepper.spec.ts',
|
|
174
|
+
'libs/vue-components/src/lib/patterns/AppLayout.spec.ts',
|
|
174
175
|
]) {
|
|
175
176
|
expect(host.exists(spec)).toBeTruthy();
|
|
176
177
|
}
|