@abgov/nx-adsp 13.6.1 → 13.7.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
CHANGED
|
@@ -31,7 +31,8 @@ executors read options from `project.json` and do not prompt.
|
|
|
31
31
|
| File | Purpose |
|
|
32
32
|
|------|---------|
|
|
33
33
|
| `src/main.ts` | Entry — registers Pinia, Router, and Keycloak plugin |
|
|
34
|
-
| `src/App.vue` | Shell — nav header with sign-in/out, hero banner, `<RouterView>` |
|
|
34
|
+
| `src/App.vue` | Shell — nav header with sign-in/out, hero banner, `<RouterView>` wrapped in `AppLayout` |
|
|
35
|
+
| `src/components/AppLayout.vue` | Shared content gutter (centered, max-width, token padding). Every view lands inside it — no per-view spacing to add. Width via route meta `layout` |
|
|
35
36
|
| `src/router/index.ts` | Routes — `/protected` guarded with `requiresAuth` meta |
|
|
36
37
|
| `src/views/HomeView.vue` | Public page — calls public and private APIs |
|
|
37
38
|
| `src/views/ProtectedView.vue` | Authenticated page — shows user info from token |
|
|
@@ -157,12 +158,17 @@ across every Vue app in this workspace — fix or extend a wrapper once in
|
|
|
157
158
|
|
|
158
159
|
## Adding a view
|
|
159
160
|
|
|
160
|
-
1. Create `src/views/MyFeatureView.vue` as a `<script setup>` SFC
|
|
161
|
+
1. Create `src/views/MyFeatureView.vue` as a `<script setup>` SFC. **Don't add your
|
|
162
|
+
own page margins/centering** — every view renders inside `AppLayout`'s gutter, so
|
|
163
|
+
any top-level tag (`<div>`, `<section>`, …) is already centered and padded.
|
|
161
164
|
2. Add the route to `src/router/index.ts`:
|
|
162
165
|
```typescript
|
|
163
166
|
{ path: '/my-feature', component: () => import('../views/MyFeatureView.vue') }
|
|
164
167
|
// For an authenticated route:
|
|
165
168
|
{ path: '/my-feature', component: () => import('../views/MyFeatureView.vue'), meta: { requiresAuth: true } }
|
|
169
|
+
// For a wider (data table) or narrower (form) view, set the layout width:
|
|
170
|
+
{ path: '/queue', component: () => import('../views/QueueView.vue'), meta: { layout: 'wide' } }
|
|
171
|
+
{ path: '/apply', component: () => import('../views/ApplyView.vue'), meta: { layout: 'form' } }
|
|
166
172
|
```
|
|
167
173
|
3. Add a nav link to `src/App.vue` if needed
|
|
168
174
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
2
3
|
import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
|
|
3
|
-
import { RouterView } from 'vue-router';
|
|
4
|
+
import { RouterView, useRoute } from 'vue-router';
|
|
5
|
+
import AppLayout from './components/AppLayout.vue';
|
|
4
6
|
|
|
5
7
|
// Keep the reactive instance — do NOT destructure. useKeycloak() returns a
|
|
6
8
|
// readonly(reactive()); destructuring snapshots each field at setup time, so
|
|
@@ -8,6 +10,13 @@ import { RouterView } from 'vue-router';
|
|
|
8
10
|
// `authenticated` would never flip. Access fields on `kc` to stay reactive.
|
|
9
11
|
const kc = useKeycloak();
|
|
10
12
|
|
|
13
|
+
// Content width per view, from route meta (default 'page'). Views opt into a
|
|
14
|
+
// different width with `meta: { layout: 'wide' | 'form' }` in the router.
|
|
15
|
+
const route = useRoute();
|
|
16
|
+
const layout = computed(
|
|
17
|
+
() => (route.meta.layout as 'page' | 'wide' | 'form' | undefined) ?? 'page'
|
|
18
|
+
);
|
|
19
|
+
|
|
11
20
|
function login() { kc.keycloak?.login(); }
|
|
12
21
|
function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin }); }
|
|
13
22
|
</script>
|
|
@@ -29,20 +38,11 @@ function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin })
|
|
|
29
38
|
</goa-button-group>
|
|
30
39
|
</goa-app-header>
|
|
31
40
|
<goa-hero-banner heading="<%= projectName %>" backgroundurl="/assets/banner.jpg" />
|
|
32
|
-
<
|
|
41
|
+
<AppLayout :variant="layout">
|
|
33
42
|
<RouterView />
|
|
34
|
-
</
|
|
43
|
+
</AppLayout>
|
|
35
44
|
</template>
|
|
36
45
|
|
|
37
46
|
<style>
|
|
38
47
|
body { margin: 0; }
|
|
39
|
-
main {
|
|
40
|
-
display: grid;
|
|
41
|
-
grid-template-columns: repeat(6, auto);
|
|
42
|
-
}
|
|
43
|
-
main > section {
|
|
44
|
-
grid-column: 2 / span 2;
|
|
45
|
-
margin-top: 40px;
|
|
46
|
-
margin-bottom: 40px;
|
|
47
|
-
}
|
|
48
48
|
</style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Shared page layout. App.vue wraps <RouterView> in this, so EVERY routed view
|
|
3
|
+
// gets the standard content gutter (centered, max-width, token-driven padding)
|
|
4
|
+
// regardless of its own top-level tag — there's no "wrap your view in <section>"
|
|
5
|
+
// convention to remember, and a plain <div> works fine.
|
|
6
|
+
//
|
|
7
|
+
// Width variant is chosen per-view via route meta `layout` (App.vue reads it):
|
|
8
|
+
// meta: { layout: 'form' } // single-column forms → 640px
|
|
9
|
+
// (default) // general content → 1000px
|
|
10
|
+
// meta: { layout: 'wide' } // data-heavy pages / tables → 1200px
|
|
11
|
+
withDefaults(defineProps<{ variant?: 'page' | 'wide' | 'form' }>(), {
|
|
12
|
+
variant: 'page',
|
|
13
|
+
});
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<main id="main-content">
|
|
18
|
+
<div :class="`${variant}-content`">
|
|
19
|
+
<slot />
|
|
20
|
+
</div>
|
|
21
|
+
</main>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
.page-content,
|
|
26
|
+
.wide-content,
|
|
27
|
+
.form-content {
|
|
28
|
+
margin-inline: auto;
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
/* Design-token gutter; fallbacks apply only if tokens aren't loaded. */
|
|
31
|
+
padding: var(--goa-space-xl, 2.5rem) var(--goa-space-l, 1.5rem);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.form-content { max-width: 640px; }
|
|
35
|
+
.page-content { max-width: 1000px; }
|
|
36
|
+
.wide-content { max-width: 1200px; }
|
|
37
|
+
|
|
38
|
+
@media (max-width: 623.98px) {
|
|
39
|
+
.page-content,
|
|
40
|
+
.wide-content,
|
|
41
|
+
.form-content {
|
|
42
|
+
padding: var(--goa-space-m, 1rem);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
</style>
|
|
@@ -72,6 +72,24 @@ describe('Vue App Generator', () => {
|
|
|
72
72
|
expect(agents).not.toContain('ui-components.alberta.ca');
|
|
73
73
|
}, 30000);
|
|
74
74
|
|
|
75
|
+
it('wraps views in a shared AppLayout gutter (not a bare tag selector)', async () => {
|
|
76
|
+
await generator(host, options);
|
|
77
|
+
|
|
78
|
+
// Shared layout component provides the content gutter for every view.
|
|
79
|
+
expect(host.exists('apps/test/src/components/AppLayout.vue')).toBeTruthy();
|
|
80
|
+
const layout = host.read('apps/test/src/components/AppLayout.vue').toString();
|
|
81
|
+
// Three named width variants, token-driven padding.
|
|
82
|
+
expect(layout).toContain('form-content');
|
|
83
|
+
expect(layout).toContain('wide-content');
|
|
84
|
+
expect(layout).toContain('--goa-space');
|
|
85
|
+
|
|
86
|
+
// App.vue uses AppLayout and no longer relies on the `main > section` gutter
|
|
87
|
+
// (which silently failed when a view's top-level tag wasn't <section>).
|
|
88
|
+
const app = host.read('apps/test/src/App.vue').toString();
|
|
89
|
+
expect(app).toContain('AppLayout');
|
|
90
|
+
expect(app).not.toContain('main > section');
|
|
91
|
+
});
|
|
92
|
+
|
|
75
93
|
it('provisions the shared GoA wrapper library and points the app at it', async () => {
|
|
76
94
|
await generator(host, options);
|
|
77
95
|
|