diff --git a/.github/workflows/components.yml b/.github/workflows/components.yml index 3c02114155..61af9c74fb 100644 --- a/.github/workflows/components.yml +++ b/.github/workflows/components.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 - name: Install Dependencies @@ -37,16 +37,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 with: version: 9.0.6 run_install: true - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' @@ -60,16 +60,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 env: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 with: version: 9.0.6 run_install: true - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 11920abb38..32ab756a2e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -11,14 +11,14 @@ jobs: Docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 run_install: false - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3675ee9a50..e006f5096a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,14 +4,14 @@ jobs: Test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 run_install: false - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' diff --git a/.github/workflows/nuxt.yml b/.github/workflows/nuxt.yml index ff14bf681b..69428ce77f 100644 --- a/.github/workflows/nuxt.yml +++ b/.github/workflows/nuxt.yml @@ -11,14 +11,14 @@ jobs: TestNuxt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 run_install: false - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' @@ -45,10 +45,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 diff --git a/.github/workflows/publish-canary.yml b/.github/workflows/publish-canary.yml index 78d7a87863..3fe959bac7 100644 --- a/.github/workflows/publish-canary.yml +++ b/.github/workflows/publish-canary.yml @@ -11,17 +11,17 @@ jobs: packages: write contents: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 run_install: false - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: registry-url: 'https://npm.pkg.github.com/' node-version: 20 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0d070a48c5..165faaed6a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,14 +10,14 @@ jobs: packages: write contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 run_install: false - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: registry-url: 'https://npm.pkg.github.com/' node-version: 20 diff --git a/.github/workflows/studio.yml b/.github/workflows/studio.yml index 841369eaca..9df644ed6b 100644 --- a/.github/workflows/studio.yml +++ b/.github/workflows/studio.yml @@ -51,7 +51,7 @@ jobs: with: version: 9.0.6 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: version: ${{ matrix.node }} cache: ${{ steps.pkgman.outputs.cache }} diff --git a/.github/workflows/vue.yml b/.github/workflows/vue.yml index 5da53ea05b..7cd8d2f0e8 100644 --- a/.github/workflows/vue.yml +++ b/.github/workflows/vue.yml @@ -11,16 +11,16 @@ jobs: Examples: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 run_install: false - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' diff --git a/.github/workflows/webcomponents.yml b/.github/workflows/webcomponents.yml index 34a72a3a78..81164c941b 100644 --- a/.github/workflows/webcomponents.yml +++ b/.github/workflows/webcomponents.yml @@ -11,16 +11,16 @@ jobs: Examples: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install pnpm - uses: pnpm/action-setup@v2 + uses: pnpm/action-setup@v3 with: version: 9.0.6 run_install: false - name: Install Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 20 cache: 'pnpm' diff --git a/examples/nuxt-app/cypress.config.mjs b/examples/nuxt-app/cypress.config.mjs index 27edbe8967..96355fdc5e 100644 --- a/examples/nuxt-app/cypress.config.mjs +++ b/examples/nuxt-app/cypress.config.mjs @@ -12,7 +12,6 @@ export default defineConfig({ baseUrl: 'http://localhost:3000', specPattern: '**/*.{feature,feature.ts}', supportFile: false, - supportFolder: './test', downloadsFolder: './test/downloads', fixturesFolder: './test/fixtures', videosFolder: './test/videos', diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/__test__/fixtures/webform.json b/examples/nuxt-app/layers/ripple-ui-forms-ext/__test__/fixtures/webform.json new file mode 100644 index 0000000000..8218186cf1 --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/__test__/fixtures/webform.json @@ -0,0 +1,155 @@ +{ + "title": "Webform", + "changed": "2024-06-07T17:15:09+10:00", + "created": "2024-05-02T14:42:13+10:00", + "type": "landing_page", + "nid": "45fa8751-74af-43ee-9cb9-8a8fdee7e593", + "_sectionId": "8888", + "sidebar": { + "contacts": [], + "relatedLinks": [], + "whatsNext": [], + "socialShareNetworks": ["Facebook", "X", "LinkedIn"], + "siteSectionNav": null + }, + "status": "published", + "topicTags": [ + { + "text": "Demo Topic", + "url": "/topic/demo-topic" + } + ], + "siteSection": { + "id": 8888, + "name": "Demo Site", + "siteOverrides": { + "showQuickExit": null, + "theme": {}, + "featureFlags": {} + } + }, + "meta": { + "url": "/webform", + "langcode": "en", + "description": "Testing the landing page webform mapping", + "additional": [ + { + "tag": "meta", + "attributes": { + "name": "title", + "content": "Webform | Single Digital Presence Content Management System" + } + }, + { + "tag": "link", + "attributes": { + "rel": "canonical", + "href": "http://content-sdp.docker.internal/webform" + } + }, + { + "tag": "meta", + "attributes": { + "property": "og:locale", + "content": "en-AU" + } + } + ], + "keywords": "", + "image": null + }, + "showContentRating": true, + "summary": "Testing the landing page webform mapping", + "showHeroAcknowledgement": false, + "showInPageNav": false, + "showHeroImageCaption": false, + "showTopicTags": true, + "inPageNavHeadingLevel": "h2", + "background": "default", + "header": { + "title": "Webform", + "summary": "", + "links": { + "title": "", + "items": [], + "more": null + }, + "backgroundImageCaption": "", + "theme": "default", + "logoImage": null, + "backgroundImage": null, + "cornerTop": null, + "cornerBottom": null, + "primaryAction": null, + "secondaryAction": null, + "secondaryActionLabel": "" + }, + "primaryCampaign": null, + "secondaryCampaign": null, + "headerComponents": [], + "bodyComponents": [ + { + "uuid": "986aca36-9a3a-408c-9455-c00396dcfb39", + "component": "TideLandingPageWebForm", + "id": 1795, + "title": "Input support", + "props": { + "title": "Input support", + "formId": "test_of_fields", + "hideFormOnSubmit": false, + "successMessageTitle": "Form submitted", + "successMessageHTML": "Thank you! Your response has been submitted.", + "errorMessageTitle": "Form not submitted", + "errorMessageHTML": "We are experiencing a server error. Please try again, otherwise contact us.", + "schema": [ + { + "$formkit": "RplFormText", + "key": "name", + "name": "name", + "label": "Supported", + "id": "test_of_fields_name", + "validation": [["length", 0, 255]], + "validationMessages": { + "required": "Name is required", + "accepted": "Name is required", + "length": "You can enter a maximum of 255 characters" + } + }, + { + "$formkit": "RplFormItem", + "key": "test", + "name": "test", + "label": "Extended", + "id": "test_of_fields_test", + "validation": [["length", 0, 255]], + "validationMessages": { + "required": "Item is required!", + "accepted": "Item is required", + "length": "You can enter a maximum of 255 characters" + } + }, + { + "$el": "div", + "attrs": { + "class": "rpl-form__outer rpl-form__input--unsupported" + }, + "children": ["\"signature\" is not yet supported"] + }, + { + "$formkit": "RplFormActions", + "key": "actions", + "name": "submit", + "variant": "filled", + "id": "test_of_fields_actions", + "displayResetButton": false, + "validation": [], + "validationMessages": { + "required": "Submit is required", + "accepted": "Submit is required" + } + } + ] + } + } + ] +} diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/__test__/forms-ext.feature b/examples/nuxt-app/layers/ripple-ui-forms-ext/__test__/forms-ext.feature new file mode 100644 index 0000000000..810148551e --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/__test__/forms-ext.feature @@ -0,0 +1,20 @@ +Feature: Forms, extended + + As a developer, I can add custom form inputs that are not provided in the core offering + + Background: + Given the mock server has started + And the page endpoint for path "/webform" returns fixture "../../layers/ripple-ui-forms-ext/__test__/fixtures/webform" with status 200 + And the site endpoint returns fixture "/site/reference" with status 200 + + @mockserver + Scenario: Custom input + Given I visit the page "/webform" + Then the landing page component "TideLandingPageWebForm" should exist + And a field labelled "Extended" should exist with the CSS class ".rpl-form__input--type-item" + + @mockserver + Scenario: Unsupported input + Given I visit the page "/webform" + Then the landing page component "TideLandingPageWebForm" should exist + And an input of type "signature" is not yet supported diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/app.config.ts b/examples/nuxt-app/layers/ripple-ui-forms-ext/app.config.ts new file mode 100644 index 0000000000..a596cbeb1f --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/app.config.ts @@ -0,0 +1,20 @@ +import { defineAppConfig } from '#imports' +import itemMapping from './mapping/item' + +export default defineAppConfig({ + ripple: { + customInputs: { + item: { + id: 'RplFormItem', + type: 'item', + formkitDefProps: { + type: 'input', + family: 'text', + props: ['placeholder', 'validationMeta', 'columnClasses', 'pii'], + forceTypeProp: 'text' + }, + mapping: itemMapping + } + } + } +}) diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/components/global/RplFormItem.vue b/examples/nuxt-app/layers/ripple-ui-forms-ext/components/global/RplFormItem.vue new file mode 100644 index 0000000000..3872e99a25 --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/components/global/RplFormItem.vue @@ -0,0 +1,165 @@ + + + + + + + diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/mapping/item.ts b/examples/nuxt-app/layers/ripple-ui-forms-ext/mapping/item.ts new file mode 100644 index 0000000000..9ac2320fb4 --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/mapping/item.ts @@ -0,0 +1,30 @@ +import type { TideWebformElement } from '@dpc-sdp/ripple-tide-webform/types' +import { + getValidationAndConditionals, + getInputIcons +} from '@dpc-sdp/ripple-tide-webform/mapping/utils' + +const logger = { + warn: (message: string, props: { label: string }) => { + console.warn(props.label, message) + } +} + +// Tide API mapping function +export default ( + fieldID: string, + field: TideWebformElement, + fieldKey: string +) => ({ + $formkit: 'RplFormItem', + key: fieldKey, + name: fieldKey, + label: field['#title'], + disabled: field['#disabled'], + placeholder: field['#placeholder'], + id: fieldID, + help: field['#description'] || field['#help_title'], + value: field['#default_value'], + ...getValidationAndConditionals(field, logger), + ...getInputIcons(field) +}) diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/nuxt.config.ts b/examples/nuxt-app/layers/ripple-ui-forms-ext/nuxt.config.ts new file mode 100644 index 0000000000..3a774af39d --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/nuxt.config.ts @@ -0,0 +1,9 @@ +import { defineNuxtConfig } from 'nuxt/config' + +export default defineNuxtConfig({ + hooks: { + 'build:done': async () => { + console.info('Added ripple-ui-forms-ext components') + } + } +}) diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/package.json b/examples/nuxt-app/layers/ripple-ui-forms-ext/package.json new file mode 100644 index 0000000000..66243bea10 --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/package.json @@ -0,0 +1,16 @@ +{ + "name": "@dpc-sdp/ripple-ui-forms-ext", + "description": "Custom form inputs", + "version": "2.10.1", + "license": "Apache-2.0", + "repository": "https://github.com/dpc-sdp/ripple-framework", + "main": "./nuxt.config.ts", + "type": "module", + "exports": { + ".": "./nuxt.config.ts" + }, + "dependencies": { + "@dpc-sdp/ripple-tide-webform": "workspace:*", + "@dpc-sdp/ripple-ui-forms": "workspace:*" + } +} diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/pages/_test/webform.vue b/examples/nuxt-app/layers/ripple-ui-forms-ext/pages/_test/webform.vue new file mode 100644 index 0000000000..f50c50471d --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/pages/_test/webform.vue @@ -0,0 +1,12 @@ + + + diff --git a/examples/nuxt-app/layers/ripple-ui-forms-ext/tsconfig.json b/examples/nuxt-app/layers/ripple-ui-forms-ext/tsconfig.json new file mode 100644 index 0000000000..99fbedd0b0 --- /dev/null +++ b/examples/nuxt-app/layers/ripple-ui-forms-ext/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./../../.nuxt/tsconfig.json" +} diff --git a/examples/nuxt-app/nuxt.config.ts b/examples/nuxt-app/nuxt.config.ts index 16c703c7e2..38c6b7625d 100644 --- a/examples/nuxt-app/nuxt.config.ts +++ b/examples/nuxt-app/nuxt.config.ts @@ -27,7 +27,8 @@ export default defineNuxtConfig({ '@dpc-sdp/ripple-tide-media', '@dpc-sdp/ripple-tide-news', '@dpc-sdp/ripple-tide-search', - '@dpc-sdp/ripple-tide-webform' + '@dpc-sdp/ripple-tide-webform', + './layers/ripple-ui-forms-ext' ], // Nuxt devtools sourcemap: true, diff --git a/packages/nuxt-ripple/app.config.ts b/packages/nuxt-ripple/app.config.ts index 83f9106604..c8d1f8c447 100644 --- a/packages/nuxt-ripple/app.config.ts +++ b/packages/nuxt-ripple/app.config.ts @@ -49,6 +49,20 @@ declare module '@nuxt/schema' { (map: any, results: any, location: any) => void > } + customInputs?: { + [key: string]: { + id: string + type: string + formkitDefProps: { + [key: string]: any + } + mapping: ( + fieldID: string, + field: any, + fieldKey: string + ) => Record + } + } } } } diff --git a/packages/ripple-test-utils/step_definitions/components/forms.ts b/packages/ripple-test-utils/step_definitions/components/forms.ts index 1155541e2b..a11c1b40ef 100644 --- a/packages/ripple-test-utils/step_definitions/components/forms.ts +++ b/packages/ripple-test-utils/step_definitions/components/forms.ts @@ -349,8 +349,25 @@ Then( } ) -// Open forms +// Extended +Then( + 'a field labelled {string} should exist with the CSS class {string}', + (label: string, css: string) => { + cy.get(css) + .closest('.rpl-form__outer') + .find('label.rpl-form-label') + .should('contain', label) + } +) + +// Unsupported +Then('an input of type {string} is not yet supported', (inputType: string) => { + cy.get('.rpl-form__outer.rpl-form__input--unsupported') + .contains(inputType) + .should('exist') +}) +// Open forms Then('there is an openforms embed with the url {string}', (url: string) => { cy.get(`iframe[src^="${url}"]`).should('exist') }) diff --git a/packages/ripple-tide-landing-page/components/global/TideLandingPage/WebForm.vue b/packages/ripple-tide-landing-page/components/global/TideLandingPage/WebForm.vue index cf27d87cdb..3b3f5a37da 100644 --- a/packages/ripple-tide-landing-page/components/global/TideLandingPage/WebForm.vue +++ b/packages/ripple-tide-landing-page/components/global/TideLandingPage/WebForm.vue @@ -41,6 +41,41 @@ watch( ) const submitted = computed(() => submissionState.value.status === 'success') + +import { FormKitPlugin, FormKitTypeDefinition } from '@formkit/core' +import { + createRplFormInput, + defaultRplFormInputProps, + inputLibrary, + rplFeatures +} from '@dpc-sdp/ripple-ui-forms' + +const appConfig = useAppConfig()?.ripple as { customInputs: any } +const customInputDefs = appConfig.customInputs || {} + +const customInputs: FormKitPlugin = () => {} +customInputs.library = (node: any) => { + Object.values(customInputDefs).forEach((item: any) => { + if (node.props.type === item.id) { + const def: FormKitTypeDefinition = { + schema: createRplFormInput({ + $cmp: item.id, + props: { + ...defaultRplFormInputProps, + type: item.type + } + }), + library: { + [item.id]: resolveComponent(item.id), + ...inputLibrary + }, + ...item.formkitDefProps, + features: rplFeatures + } + return node.define(def) + } + }) +}