@abgov/nx-adsp 13.17.0 → 13.18.0-beta.1
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/react-app/files/AGENTS.md__tmpl__ +7 -6
- package/src/generators/react-app/react-app.spec.ts +14 -0
- package/src/generators/vue-app/files/AGENTS.md__tmpl__ +23 -6
- package/src/generators/vue-app/files/src/App.vue__tmpl__ +6 -10
- package/src/generators/vue-app/vue-app.js +3 -0
- package/src/generators/vue-app/vue-app.js.map +1 -1
- package/src/generators/vue-app/vue-app.spec.ts +38 -11
- package/src/generators/vue-components/files/AGENTS.md__tmpl__ +44 -14
- package/src/generators/vue-components/files/src/index.ts__tmpl__ +23 -12
- package/src/generators/vue-components/files/src/lib/patterns/AppFooter.vue__tmpl__ +14 -0
- package/src/generators/vue-components/files/src/lib/patterns/AppHeader.vue__tmpl__ +19 -0
- package/src/generators/{vue-app/files/src/components → vue-components/files/src/lib/patterns}/AppLayout.vue__tmpl__ +17 -0
- package/src/generators/vue-components/files/src/lib/patterns/SessionExpiredBanner.vue__tmpl__ +26 -0
- package/src/generators/vue-components/files/src/vue-components.spec.ts__tmpl__ +15 -4
- package/src/generators/vue-components/vue-components.spec.ts +14 -3
- /package/src/generators/vue-components/files/src/lib/{GoabButton.vue__tmpl__ → primitives/GoabButton.vue__tmpl__} +0 -0
- /package/src/generators/vue-components/files/src/lib/{GoabCheckbox.vue__tmpl__ → primitives/GoabCheckbox.vue__tmpl__} +0 -0
- /package/src/generators/vue-components/files/src/lib/{GoabDropdown.vue__tmpl__ → primitives/GoabDropdown.vue__tmpl__} +0 -0
- /package/src/generators/vue-components/files/src/lib/{GoabInput.vue__tmpl__ → primitives/GoabInput.vue__tmpl__} +0 -0
- /package/src/generators/vue-components/files/src/lib/{GoabModal.vue__tmpl__ → primitives/GoabModal.vue__tmpl__} +0 -0
- /package/src/generators/vue-components/files/src/lib/{GoabRadioGroup.vue__tmpl__ → primitives/GoabRadioGroup.vue__tmpl__} +0 -0
- /package/src/generators/vue-components/files/src/lib/{GoabTextarea.vue__tmpl__ → primitives/GoabTextarea.vue__tmpl__} +0 -0
package/package.json
CHANGED
|
@@ -64,14 +64,15 @@ from `@abgov/react-components` over those components:
|
|
|
64
64
|
import { GoabButton, GoabAppHeader, GoabButtonGroup } from '@abgov/react-components';
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
**Events**: GoA React components use `
|
|
68
|
-
|
|
67
|
+
**Events**: GoA React components use plain `onClick`/`onChange` handlers, not the
|
|
68
|
+
`onGoab*`/`e.detail` pattern some other GoA framework wrappers use. `onChange` receives its
|
|
69
|
+
detail object directly as the callback's sole argument — never wrapped in a synthetic event:
|
|
69
70
|
|
|
70
71
|
```tsx
|
|
71
|
-
<GoabButton
|
|
72
|
-
<GoabInput
|
|
73
|
-
<GoabDropdown
|
|
74
|
-
<GoabCheckbox
|
|
72
|
+
<GoabButton onClick={handleSave}>Save</GoabButton>
|
|
73
|
+
<GoabInput onChange={(detail) => setValue(detail.value)} />
|
|
74
|
+
<GoabDropdown onChange={(detail) => setSelected(detail.value)} />
|
|
75
|
+
<GoabCheckbox onChange={(detail) => setChecked(detail.checked)} />
|
|
75
76
|
```
|
|
76
77
|
|
|
77
78
|
Event `detail` shapes vary by component — check the component's gallery page or the
|
|
@@ -99,6 +99,20 @@ describe('React App Generator', () => {
|
|
|
99
99
|
expect(agents).not.toContain('ui-components.alberta.ca');
|
|
100
100
|
}, 30000);
|
|
101
101
|
|
|
102
|
+
it('AGENTS.md documents the real onClick/onChange event pattern, not the never-real onGoab*/e.detail one', async () => {
|
|
103
|
+
// @abgov/react-components has never exposed onGoab*-prefixed handlers or wrapped a
|
|
104
|
+
// callback's payload in e.detail -- onChange receives its detail object directly. Regression
|
|
105
|
+
// guard: this doc text drifted from the real API without the generated app code ever being
|
|
106
|
+
// affected (app.tsx has always used plain onClick), so nothing else would have caught it.
|
|
107
|
+
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
108
|
+
await generator(host, options);
|
|
109
|
+
const agents = host.read('apps/test/AGENTS.md').toString();
|
|
110
|
+
expect(agents).not.toContain('onGoabClick');
|
|
111
|
+
expect(agents).not.toContain('onGoabChange');
|
|
112
|
+
expect(agents).not.toMatch(/e\.detail\.(value|checked)/);
|
|
113
|
+
expect(agents).toContain('onChange={(detail) => setValue(detail.value)}');
|
|
114
|
+
}, 30000);
|
|
115
|
+
|
|
102
116
|
it('can add nginx proxy', async () => {
|
|
103
117
|
const host = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
|
104
118
|
await generator(host, {
|
|
@@ -31,8 +31,7 @@ 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 —
|
|
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` |
|
|
34
|
+
| `src/App.vue` | Shell — `AppHeader` with sign-in/out in its `utilities` slot, hero banner, `<RouterView>` wrapped in `AppLayout`, `AppFooter` |
|
|
36
35
|
| `src/router/index.ts` | Routes — `/protected` guarded with `requiresAuth` meta |
|
|
37
36
|
| `src/views/HomeView.vue` | Public page — calls public and private APIs |
|
|
38
37
|
| `src/views/ProtectedView.vue` | Authenticated page — shows user info from token |
|
|
@@ -151,10 +150,28 @@ the design system supports still works. For components without a wrapper, use th
|
|
|
151
150
|
across every Vue app in this workspace — fix or extend a wrapper once in
|
|
152
151
|
`libs/vue-components` (or run `nx g @abgov/nx-adsp:vue-components` to restore it).
|
|
153
152
|
|
|
154
|
-
> **Interim:** these wrappers are a
|
|
155
|
-
> `@abgov/vue-components`. When it lands,
|
|
156
|
-
> repoint imports at that package — the names/props are
|
|
157
|
-
> it's a scope swap.
|
|
153
|
+
> **Interim:** these wrappers (`libs/vue-components/src/lib/primitives/`) are a
|
|
154
|
+
> stopgap until GoA DS ships an official `@abgov/vue-components`. When it lands,
|
|
155
|
+
> delete `primitives/` and repoint imports at that package — the names/props are
|
|
156
|
+
> intended to match, so it's a scope swap. This does **not** apply to the
|
|
157
|
+
> `AppHeader`/`AppLayout`/`AppFooter`/`SessionExpiredBanner` shell components
|
|
158
|
+
> below — those live in the same library's `patterns/` folder and are permanent
|
|
159
|
+
> (app-shell composition has no equivalent in a design-system package).
|
|
160
|
+
|
|
161
|
+
### App shell components
|
|
162
|
+
|
|
163
|
+
`App.vue` is built from four shared components imported from
|
|
164
|
+
**`<%= goaImportPath %>`**, not hand-rolled markup:
|
|
165
|
+
|
|
166
|
+
| Component | Purpose |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `AppHeader` | `goa-microsite-header` + `goa-app-header`. Takes a `heading` prop; put account/sign-in actions in its `utilities` slot (a bare child with no slot is silently dropped by `goa-app-header`) |
|
|
169
|
+
| `AppLayout` | The content gutter every view renders inside (see the key files table). Also renders the skip-to-main-content link |
|
|
170
|
+
| `AppFooter` | `goa-app-footer` with the standard GoA nav/meta links (Services/Contact/Terms of Use, Privacy/Disclaimer/Accessibility) |
|
|
171
|
+
| `SessionExpiredBanner` | A `v-model:show` banner with `signIn`/`dismiss` emits — available in the shared library, not yet wired into this app's `App.vue`. Wiring it to a real Keycloak session-expiry signal needs an `onAuthLogout` callback added to `main.ts`'s Keycloak plugin config; do that before using it |
|
|
172
|
+
|
|
173
|
+
These are shared across every Vue app in this workspace — fix or extend one once
|
|
174
|
+
in `libs/vue-components/src/lib/patterns/`, the same way as the `Goab*` wrappers.
|
|
158
175
|
|
|
159
176
|
## Adding a view
|
|
160
177
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed } from 'vue';
|
|
3
3
|
import { useKeycloak } from '@dsb-norge/vue-keycloak-js';
|
|
4
4
|
import { RouterView, useRoute } from 'vue-router';
|
|
5
|
-
import AppLayout from '
|
|
5
|
+
import { AppHeader, AppLayout, AppFooter } from '<%= goaImportPath %>';
|
|
6
6
|
|
|
7
7
|
// Keep the reactive instance — do NOT destructure. useKeycloak() returns a
|
|
8
8
|
// readonly(reactive()); destructuring snapshots each field at setup time, so
|
|
@@ -22,13 +22,8 @@ function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin })
|
|
|
22
22
|
</script>
|
|
23
23
|
|
|
24
24
|
<template>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
<!-- goa-app-header only renders content placed in a named slot — the
|
|
28
|
-
"utilities" slot is where GoA's own design system docs put
|
|
29
|
-
account/sign-in actions (as opposed to "navigation" for nav links).
|
|
30
|
-
A bare child with no slot attribute renders nothing. -->
|
|
31
|
-
<div slot="utilities">
|
|
25
|
+
<AppHeader heading="<%= projectName %>">
|
|
26
|
+
<template #utilities>
|
|
32
27
|
<goa-button-group alignment="end">
|
|
33
28
|
<span v-if="kc.authenticated && kc.fullName">{{ kc.fullName }}</span>
|
|
34
29
|
<!-- Gate on !authenticated only, never on `ready`: with no load-time SSO
|
|
@@ -41,12 +36,13 @@ function logout() { kc.keycloak?.logout({ redirectUri: window.location.origin })
|
|
|
41
36
|
Sign out
|
|
42
37
|
</goa-button>
|
|
43
38
|
</goa-button-group>
|
|
44
|
-
</
|
|
45
|
-
</
|
|
39
|
+
</template>
|
|
40
|
+
</AppHeader>
|
|
46
41
|
<goa-hero-banner heading="<%= projectName %>" backgroundurl="/assets/banner.jpg" />
|
|
47
42
|
<AppLayout :variant="layout">
|
|
48
43
|
<RouterView />
|
|
49
44
|
</AppLayout>
|
|
45
|
+
<AppFooter />
|
|
50
46
|
</template>
|
|
51
47
|
|
|
52
48
|
<style>
|
|
@@ -122,6 +122,9 @@ function default_1(host, options) {
|
|
|
122
122
|
'src/app/App.spec.ts',
|
|
123
123
|
'src/app/NxWelcome.vue',
|
|
124
124
|
'src/components/HelloWorld.vue',
|
|
125
|
+
// AppLayout now lives in the shared vue-components lib (patterns/) —
|
|
126
|
+
// remove a stale copy from an app scaffolded before that move.
|
|
127
|
+
'src/components/AppLayout.vue',
|
|
125
128
|
'src/views/AboutView.vue',
|
|
126
129
|
'vite.config.mts',
|
|
127
130
|
]) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":";;AAsHA,
|
|
1
|
+
{"version":3,"file":"vue-app.js","sourceRoot":"","sources":["../../../../../../packages/nx-adsp/src/generators/vue-app/vue-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -116,15 +116,16 @@ describe('Vue App Generator', () => {
|
|
|
116
116
|
it('wraps views in a shared AppLayout gutter (not a bare tag selector)', async () => {
|
|
117
117
|
await generator(host, options);
|
|
118
118
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.toString();
|
|
119
|
+
// Layout is a pattern component in the shared lib, not copied into the app.
|
|
120
|
+
const layoutPath = 'libs/vue-components/src/lib/patterns/AppLayout.vue';
|
|
121
|
+
expect(host.exists(layoutPath)).toBeTruthy();
|
|
122
|
+
const layout = host.read(layoutPath).toString();
|
|
124
123
|
// Three named width variants, token-driven padding.
|
|
125
124
|
expect(layout).toContain('form-content');
|
|
126
125
|
expect(layout).toContain('wide-content');
|
|
127
126
|
expect(layout).toContain('--goa-space');
|
|
127
|
+
// Skip-to-main-content link for keyboard/screen-reader users.
|
|
128
|
+
expect(layout).toContain('href="#main-content"');
|
|
128
129
|
|
|
129
130
|
// App.vue uses AppLayout and no longer relies on the `main > section` gutter
|
|
130
131
|
// (which silently failed when a view's top-level tag wasn't <section>).
|
|
@@ -137,7 +138,7 @@ describe('Vue App Generator', () => {
|
|
|
137
138
|
await generator(host, options);
|
|
138
139
|
|
|
139
140
|
// Wrappers live in a shared workspace lib, not copied into the app.
|
|
140
|
-
const
|
|
141
|
+
const primitives = 'libs/vue-components/src/lib/primitives';
|
|
141
142
|
for (const name of [
|
|
142
143
|
'GoabInput',
|
|
143
144
|
'GoabTextarea',
|
|
@@ -147,12 +148,12 @@ describe('Vue App Generator', () => {
|
|
|
147
148
|
'GoabButton',
|
|
148
149
|
'GoabModal',
|
|
149
150
|
]) {
|
|
150
|
-
expect(host.exists(`${
|
|
151
|
+
expect(host.exists(`${primitives}/${name}.vue`)).toBeTruthy();
|
|
151
152
|
}
|
|
152
153
|
expect(host.exists('apps/test/src/components/goa')).toBeFalsy();
|
|
153
154
|
|
|
154
155
|
// Real v-model wiring: bind :value and read the new value off the GoA event.
|
|
155
|
-
const input = host.read(`${
|
|
156
|
+
const input = host.read(`${primitives}/GoabInput.vue`).toString();
|
|
156
157
|
expect(input).toContain('defineModel');
|
|
157
158
|
expect(input).toContain('.detail.value');
|
|
158
159
|
|
|
@@ -163,6 +164,25 @@ describe('Vue App Generator', () => {
|
|
|
163
164
|
expect(agents).toContain('/vue-components');
|
|
164
165
|
}, 30000);
|
|
165
166
|
|
|
167
|
+
it('provisions the shared app-shell pattern components and App.vue imports them', async () => {
|
|
168
|
+
await generator(host, options);
|
|
169
|
+
|
|
170
|
+
const patterns = 'libs/vue-components/src/lib/patterns';
|
|
171
|
+
for (const name of [
|
|
172
|
+
'AppLayout',
|
|
173
|
+
'AppHeader',
|
|
174
|
+
'AppFooter',
|
|
175
|
+
'SessionExpiredBanner',
|
|
176
|
+
]) {
|
|
177
|
+
expect(host.exists(`${patterns}/${name}.vue`)).toBeTruthy();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const app = host.read('apps/test/src/App.vue').toString();
|
|
181
|
+
expect(app).toContain("import { AppHeader, AppLayout, AppFooter } from '@proj/vue-components';");
|
|
182
|
+
expect(app).toContain('<AppHeader heading="test">');
|
|
183
|
+
expect(app).toContain('<AppFooter />');
|
|
184
|
+
}, 30000);
|
|
185
|
+
|
|
166
186
|
it('inits Keycloak with no hidden iframes so init never hangs', async () => {
|
|
167
187
|
await generator(host, options);
|
|
168
188
|
// Strip // comments — they legitimately reference the disabled iframe options
|
|
@@ -255,11 +275,18 @@ describe('Vue App Generator', () => {
|
|
|
255
275
|
// goa-app-header (v2) only renders content placed in a named slot — GoA's
|
|
256
276
|
// own design system docs put account/sign-in actions in "utilities" (vs.
|
|
257
277
|
// "navigation" for nav links). A bare child with no slot attribute
|
|
258
|
-
// renders nothing, silently dropping the sign-in button.
|
|
259
|
-
//
|
|
278
|
+
// renders nothing, silently dropping the sign-in button. AppHeader (the
|
|
279
|
+
// shared pattern component) owns the native `slot="utilities"` div; App.vue
|
|
280
|
+
// just feeds its named Vue slot. Regression guard for exactly that gap,
|
|
281
|
+
// now spanning both files since AppHeader was extracted out of App.vue.
|
|
260
282
|
await generator(host, options);
|
|
283
|
+
const appHeader = host
|
|
284
|
+
.read('libs/vue-components/src/lib/patterns/AppHeader.vue')
|
|
285
|
+
.toString();
|
|
286
|
+
expect(appHeader).toMatch(/<div[^>]*slot="utilities"[^>]*>[\s\S]*<slot name="utilities"/);
|
|
287
|
+
|
|
261
288
|
const app = host.read('apps/test/src/App.vue').toString();
|
|
262
|
-
expect(app).toMatch(/<
|
|
289
|
+
expect(app).toMatch(/<template #utilities>[\s\S]*<goa-button-group/);
|
|
263
290
|
}, 30000);
|
|
264
291
|
|
|
265
292
|
it('removes the @nx/vue demo scaffold and ships a passing App test', async () => {
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
# AGENTS.md — vue-components (GoA Design System Vue wrappers)
|
|
1
|
+
# AGENTS.md — vue-components (GoA Design System Vue wrappers + app shell)
|
|
2
2
|
|
|
3
|
-
A shared, workspace-local library
|
|
4
|
-
|
|
5
|
-
imports these instead of carrying its own copy. Generated by
|
|
3
|
+
A shared, workspace-local library with two distinct kinds of content — every Vue
|
|
4
|
+
app in this workspace imports both instead of carrying its own copy. Generated by
|
|
6
5
|
`@abgov/nx-adsp:vue-components`.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
| Folder | Contains | Lifespan |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `src/lib/primitives/` | Thin `v-model`/idiomatic-event wrappers over individual `goa-*` elements (`GoabInput`, `GoabButton`, …) | **Interim** — see below |
|
|
10
|
+
| `src/lib/patterns/` | Composite, app-shell components (`AppLayout`, `AppHeader`, `AppFooter`, `SessionExpiredBanner`) | **Permanent** |
|
|
11
|
+
|
|
12
|
+
> **⚠️ `primitives/` is interim — do not invest in it as permanent.** It exists
|
|
13
|
+
> only because GoA DS has not yet published an official Vue wrapper package. When
|
|
14
|
+
> `@abgov/vue-components` ships, delete `primitives/` and repoint imports at it —
|
|
11
15
|
> the component names and props are deliberately kept matching, so it should be a
|
|
12
16
|
> scope swap, not a rewrite. Don't add features here that would make that swap
|
|
13
17
|
> harder (see "Don't" below).
|
|
18
|
+
>
|
|
19
|
+
> **`patterns/` is not part of that swap.** App-shell composition (layout,
|
|
20
|
+
> header, footer, banners) has no equivalent in an official design-system
|
|
21
|
+
> package — a design system ships primitives, not your app's shell. Nothing in
|
|
22
|
+
> `patterns/` is deleted when `primitives/` is.
|
|
14
23
|
|
|
15
24
|
## What these wrappers do (and why they're needed)
|
|
16
25
|
|
|
@@ -45,7 +54,7 @@ Most value elements put the value on `detail.value`; some carry extra keys
|
|
|
45
54
|
(dropdown adds `label`, radio-group adds `labels`) you can ignore unless you need
|
|
46
55
|
them. Always confirm against the component's page — see below.
|
|
47
56
|
|
|
48
|
-
## Wrapping a new component
|
|
57
|
+
## Wrapping a new component (`primitives/`)
|
|
49
58
|
|
|
50
59
|
### 1. Find the event contract
|
|
51
60
|
|
|
@@ -129,14 +138,35 @@ Don't re-declare props you don't transform — let them fall through. `name` in
|
|
|
129
138
|
particular is required by most `goa-*` form elements (it's echoed in the event
|
|
130
139
|
detail); just leave it to fall through from the caller.
|
|
131
140
|
|
|
141
|
+
## Building a pattern component (`patterns/`)
|
|
142
|
+
|
|
143
|
+
A pattern component is app-shell composition — layout, header/footer chrome,
|
|
144
|
+
banners — not a single-element wrapper. Existing examples: `AppLayout`,
|
|
145
|
+
`AppHeader`, `AppFooter`, `SessionExpiredBanner`.
|
|
146
|
+
|
|
147
|
+
- It's fine to compose `primitives/` wrappers inside a pattern component (e.g.
|
|
148
|
+
`SessionExpiredBanner` uses `GoabButton`) — import them with a relative path
|
|
149
|
+
(`../primitives/GoabButton.vue`), not the package's own public import path.
|
|
150
|
+
- Stay presentational: accept data and behavior via props/`v-model`/`emit`, don't
|
|
151
|
+
fetch data, call routing APIs, or encode business rules. A pattern component
|
|
152
|
+
used by every app in the workspace can't assume any one app's routes or
|
|
153
|
+
domain — that belongs in the app that uses it (see `SessionExpiredBanner`'s own
|
|
154
|
+
comment for a live example: the component renders the banner, but which
|
|
155
|
+
Keycloak hook flips `show` is main.ts's job, in the consuming app).
|
|
156
|
+
- `export` it from `src/index.ts` under the "patterns" group, with a one-line
|
|
157
|
+
purpose comment in this file's app-shell components table (mirrored in each
|
|
158
|
+
consuming generator's own `AGENTS.md`, e.g. `vue-app/files/AGENTS.md__tmpl__`).
|
|
159
|
+
|
|
132
160
|
## Don't
|
|
133
161
|
|
|
134
|
-
- **Don't
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
162
|
+
- **Don't add new `goa-*` element wrappers to `patterns/`, or new composite
|
|
163
|
+
components to `primitives/`.** Single-element wrappers belong in
|
|
164
|
+
`primitives/` (interim, deleted on the `@abgov/vue-components` swap);
|
|
165
|
+
composites belong in `patterns/` (permanent). Putting one in the wrong folder
|
|
166
|
+
either blocks that swap or gets deleted by it.
|
|
167
|
+
- **Don't add app-specific logic** anywhere in this library (fetching, routing,
|
|
168
|
+
business rules) — it's shared across every app in the workspace. Keep every
|
|
169
|
+
component, wrapper or pattern, presentational.
|
|
140
170
|
|
|
141
171
|
## Testing
|
|
142
172
|
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
-
// GoA Design System Vue wrappers
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export { default as
|
|
1
|
+
// GoA Design System Vue wrappers, shared across every Vue app in this workspace.
|
|
2
|
+
//
|
|
3
|
+
// primitives/ — thin v-model/idiomatic-event wrappers over individual goa-*
|
|
4
|
+
// elements. INTERIM: once GoA DS publishes an official @abgov/vue-components,
|
|
5
|
+
// delete these files and repoint imports at it — names/props are kept
|
|
6
|
+
// matching, so it's a scope swap, not a rewrite.
|
|
7
|
+
//
|
|
8
|
+
// patterns/ — composite, app-shell components (layout, header, footer, ...).
|
|
9
|
+
// PERMANENT: these have no equivalent in an official design-system package
|
|
10
|
+
// (that's app-shell composition, not a design-system primitive) and are not
|
|
11
|
+
// deleted when primitives/ is.
|
|
12
|
+
export { default as GoabInput } from './lib/primitives/GoabInput.vue';
|
|
13
|
+
export { default as GoabTextarea } from './lib/primitives/GoabTextarea.vue';
|
|
14
|
+
export { default as GoabDropdown } from './lib/primitives/GoabDropdown.vue';
|
|
15
|
+
export { default as GoabCheckbox } from './lib/primitives/GoabCheckbox.vue';
|
|
16
|
+
export { default as GoabRadioGroup } from './lib/primitives/GoabRadioGroup.vue';
|
|
17
|
+
export { default as GoabButton } from './lib/primitives/GoabButton.vue';
|
|
18
|
+
export { default as GoabModal } from './lib/primitives/GoabModal.vue';
|
|
19
|
+
|
|
20
|
+
export { default as AppLayout } from './lib/patterns/AppLayout.vue';
|
|
21
|
+
export { default as AppHeader } from './lib/patterns/AppHeader.vue';
|
|
22
|
+
export { default as AppFooter } from './lib/patterns/AppFooter.vue';
|
|
23
|
+
export { default as SessionExpiredBanner } from './lib/patterns/SessionExpiredBanner.vue';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<goa-app-footer>
|
|
3
|
+
<goa-app-footer-nav-section slot="nav" heading="Resources">
|
|
4
|
+
<a href="https://alberta.ca/services.aspx">Services</a>
|
|
5
|
+
<a href="https://alberta.ca/contact.aspx">Contact</a>
|
|
6
|
+
<a href="https://alberta.ca/terms-of-use.aspx">Terms of Use</a>
|
|
7
|
+
</goa-app-footer-nav-section>
|
|
8
|
+
<goa-app-footer-meta-section slot="meta">
|
|
9
|
+
<a href="https://alberta.ca/security-and-privacy.aspx">Privacy</a>
|
|
10
|
+
<a href="https://alberta.ca/disclaimer.aspx">Disclaimer</a>
|
|
11
|
+
<a href="https://alberta.ca/accessibility.aspx">Accessibility</a>
|
|
12
|
+
</goa-app-footer-meta-section>
|
|
13
|
+
</goa-app-footer>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useSlots } from 'vue';
|
|
3
|
+
|
|
4
|
+
defineProps<{ heading: string }>();
|
|
5
|
+
const slots = useSlots();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<goa-microsite-header type="alpha" />
|
|
10
|
+
<goa-app-header url="/" :heading="heading">
|
|
11
|
+
<!-- goa-app-header only renders content placed in a named slot — the
|
|
12
|
+
"utilities" slot is where GoA's own design system docs put
|
|
13
|
+
account/sign-in actions (as opposed to "navigation" for nav links).
|
|
14
|
+
A bare child with no slot attribute renders nothing. -->
|
|
15
|
+
<div v-if="slots.utilities" slot="utilities">
|
|
16
|
+
<slot name="utilities" />
|
|
17
|
+
</div>
|
|
18
|
+
</goa-app-header>
|
|
19
|
+
</template>
|
|
@@ -14,6 +14,7 @@ withDefaults(defineProps<{ variant?: 'page' | 'wide' | 'form' }>(), {
|
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
16
|
<template>
|
|
17
|
+
<a class="skip-link" href="#main-content">Skip to main content</a>
|
|
17
18
|
<main id="main-content">
|
|
18
19
|
<div :class="`${variant}-content`">
|
|
19
20
|
<slot />
|
|
@@ -22,6 +23,22 @@ withDefaults(defineProps<{ variant?: 'page' | 'wide' | 'form' }>(), {
|
|
|
22
23
|
</template>
|
|
23
24
|
|
|
24
25
|
<style scoped>
|
|
26
|
+
/* Visually hidden until focused (e.g. via Tab from the top of the page) — lets
|
|
27
|
+
keyboard/screen-reader users jump past the header straight to the content. */
|
|
28
|
+
.skip-link {
|
|
29
|
+
position: absolute;
|
|
30
|
+
left: -9999px;
|
|
31
|
+
top: 0;
|
|
32
|
+
z-index: 100;
|
|
33
|
+
padding: var(--goa-space-s, 0.5rem) var(--goa-space-m, 1rem);
|
|
34
|
+
background: var(--goa-color-interactive-default, #0070c4);
|
|
35
|
+
color: #fff;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.skip-link:focus {
|
|
39
|
+
left: var(--goa-space-m, 1rem);
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
.page-content,
|
|
26
43
|
.wide-content,
|
|
27
44
|
.form-content {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Presentational only — not yet wired to a real Keycloak session-expiry signal
|
|
3
|
+
// by any generator. @dsb-norge/vue-keycloak-js has no reactive "expired" field
|
|
4
|
+
// on the useKeycloak() instance; the closest real hook is the `onAuthLogout`
|
|
5
|
+
// callback configured at plugin-install time in main.ts. Wiring that up is a
|
|
6
|
+
// separate change (new shared reactive state + main.ts plumbing); this
|
|
7
|
+
// component just renders the banner once some caller flips `show`.
|
|
8
|
+
import GoabButton from '../primitives/GoabButton.vue';
|
|
9
|
+
|
|
10
|
+
const show = defineModel<boolean>('show', { default: false });
|
|
11
|
+
const emit = defineEmits<{ signIn: []; dismiss: [] }>();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div v-if="show" class="session-expired-banner">
|
|
16
|
+
<goa-callout type="emergency" heading="Session expired">
|
|
17
|
+
<p>Your session has expired. Sign in again to continue.</p>
|
|
18
|
+
<goa-spacer vspacing="s" />
|
|
19
|
+
<goa-button-group gap="relaxed">
|
|
20
|
+
<GoabButton type="primary" @click="emit('signIn')">Sign in</GoabButton>
|
|
21
|
+
<GoabButton type="tertiary" @click="emit('dismiss')">Dismiss</GoabButton>
|
|
22
|
+
</goa-button-group>
|
|
23
|
+
</goa-callout>
|
|
24
|
+
<goa-spacer vspacing="m" />
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
2
|
import * as lib from './index';
|
|
3
3
|
|
|
4
|
-
// Smoke test: the barrel resolves and every wrapper SFC compiles under
|
|
5
|
-
// runner. It also gives the library a test target with real content,
|
|
6
|
-
// `nx test vue-components` doesn't fail on "no test files found".
|
|
4
|
+
// Smoke test: the barrel resolves and every wrapper/pattern SFC compiles under
|
|
5
|
+
// the test runner. It also gives the library a test target with real content,
|
|
6
|
+
// so `nx test vue-components` doesn't fail on "no test files found".
|
|
7
7
|
describe('vue-components', () => {
|
|
8
|
-
it('exports every GoA wrapper', () => {
|
|
8
|
+
it('exports every GoA primitive wrapper', () => {
|
|
9
9
|
for (const name of [
|
|
10
10
|
'GoabInput',
|
|
11
11
|
'GoabTextarea',
|
|
@@ -18,4 +18,15 @@ describe('vue-components', () => {
|
|
|
18
18
|
expect(lib[name as keyof typeof lib]).toBeTruthy();
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
|
+
|
|
22
|
+
it('exports every app-shell pattern component', () => {
|
|
23
|
+
for (const name of [
|
|
24
|
+
'AppLayout',
|
|
25
|
+
'AppHeader',
|
|
26
|
+
'AppFooter',
|
|
27
|
+
'SessionExpiredBanner',
|
|
28
|
+
]) {
|
|
29
|
+
expect(lib[name as keyof typeof lib]).toBeTruthy();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
21
32
|
});
|
|
@@ -23,7 +23,7 @@ describe('Vue Components Generator', () => {
|
|
|
23
23
|
const config = readProjectConfiguration(host, 'vue-components');
|
|
24
24
|
expect(config.root).toBe('libs/vue-components');
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const primitives = 'libs/vue-components/src/lib/primitives';
|
|
27
27
|
for (const name of [
|
|
28
28
|
'GoabInput',
|
|
29
29
|
'GoabTextarea',
|
|
@@ -33,10 +33,21 @@ describe('Vue Components Generator', () => {
|
|
|
33
33
|
'GoabButton',
|
|
34
34
|
'GoabModal',
|
|
35
35
|
]) {
|
|
36
|
-
expect(host.exists(`${
|
|
36
|
+
expect(host.exists(`${primitives}/${name}.vue`)).toBeTruthy();
|
|
37
|
+
}
|
|
38
|
+
// Permanent app-shell pattern components live alongside the interim wrappers.
|
|
39
|
+
const patterns = 'libs/vue-components/src/lib/patterns';
|
|
40
|
+
for (const name of [
|
|
41
|
+
'AppLayout',
|
|
42
|
+
'AppHeader',
|
|
43
|
+
'AppFooter',
|
|
44
|
+
'SessionExpiredBanner',
|
|
45
|
+
]) {
|
|
46
|
+
expect(host.exists(`${patterns}/${name}.vue`)).toBeTruthy();
|
|
37
47
|
}
|
|
38
48
|
const index = host.read('libs/vue-components/src/index.ts').toString();
|
|
39
49
|
expect(index).toContain('export { default as GoabInput }');
|
|
50
|
+
expect(index).toContain('export { default as AppLayout }');
|
|
40
51
|
expect(index).toContain('@abgov/vue-components'); // interim marker
|
|
41
52
|
|
|
42
53
|
// Ships a spec so the vitest test target isn't empty (vitest exits non-zero
|
|
@@ -79,7 +90,7 @@ describe('Vue Components Generator', () => {
|
|
|
79
90
|
await generator(host);
|
|
80
91
|
await expect(generator(host)).resolves.not.toThrow();
|
|
81
92
|
expect(
|
|
82
|
-
host.exists('libs/vue-components/src/lib/GoabInput.vue'),
|
|
93
|
+
host.exists('libs/vue-components/src/lib/primitives/GoabInput.vue'),
|
|
83
94
|
).toBeTruthy();
|
|
84
95
|
}, 30000);
|
|
85
96
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|