feat: add form design
Some checks failed
Build / build (push) Successful in 4m14s
Build / deploy-stage (push) Failing after 1m52s
Build / switch-stage (push) Has been skipped

This commit is contained in:
2025-04-22 16:53:00 +02:00
parent 6214e262cc
commit 5ca9e98106
9 changed files with 381 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ baseVariants:
dependencies: dependencies:
- georgringer/news - georgringer/news
- typo3/redirects - typo3/redirects
- typo3/form
- cloonar-typo3/base - cloonar-typo3/base
languages: languages:
- -

72
package-lock.json generated
View File

@@ -1,12 +1,15 @@
{ {
"name": "lena-schilling-website", "name": "dialog-relations-website",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "lena-schilling-website", "name": "dialog-relations-website",
"version": "1.0.0", "version": "1.0.0",
"dependencies": {
"yaml-loader": "^0.8.1"
},
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"css-loader": "^6.7.3", "css-loader": "^6.7.3",
@@ -944,6 +947,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/big.js": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
"integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -1276,6 +1288,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.1", "version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
@@ -1837,6 +1858,12 @@
"@pkgjs/parseargs": "^0.11.0" "@pkgjs/parseargs": "^0.11.0"
} }
}, },
"node_modules/javascript-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"license": "MIT"
},
"node_modules/jest-worker": { "node_modules/jest-worker": {
"version": "27.5.1", "version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -1896,6 +1923,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/kind-of": { "node_modules/kind-of": {
"version": "6.0.3", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -1936,6 +1975,20 @@
"node": ">=6.11.5" "node": ">=6.11.5"
} }
}, },
"node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"license": "MIT",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
@@ -3632,7 +3685,6 @@
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
"integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==",
"dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
@@ -3640,6 +3692,20 @@
"engines": { "engines": {
"node": ">= 14" "node": ">= 14"
} }
},
"node_modules/yaml-loader": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/yaml-loader/-/yaml-loader-0.8.1.tgz",
"integrity": "sha512-BCEndnUoi3BaZmePkwGGe93txRxLgMhBa/gE725v1/GHnura8QvNs7c4+4C1yyhhKoj3Dg63M7IqhA++15j6ww==",
"license": "MIT",
"dependencies": {
"javascript-stringify": "^2.0.1",
"loader-utils": "^2.0.0",
"yaml": "^2.0.0"
},
"engines": {
"node": ">= 14"
}
} }
} }
} }

View File

@@ -1,5 +1,5 @@
{ {
"name": "lena-schilling-website", "name": "dialog-relations-website",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"build:css": "NODE_ENV=production webpack --config webpack.config.js --mode production", "build:css": "NODE_ENV=production webpack --config webpack.config.js --mode production",
@@ -16,5 +16,8 @@
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"webpack": "^5.88.0", "webpack": "^5.88.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
},
"dependencies": {
"yaml-loader": "^0.8.1"
} }
} }

View File

@@ -0,0 +1,15 @@
plugin.tx_form {
settings {
yamlConfigurations {
11 = EXT:base/Configuration/Ext/Form/Yaml/Setup.yaml
}
}
}
module.tx_form {
settings {
yamlConfigurations {
11 = EXT:base/Configuration/Ext/Form/Yaml/Setup.yaml
}
}
}

View File

@@ -0,0 +1,200 @@
TYPO3:
CMS:
Form:
prototypes:
standard:
formElementsDefinition:
Form:
renderingOptions:
templateVariant: version2
partialRootPaths:
20: 'EXT:base/Resources/Private/Extensions/Form/Partials/'
variants:
-
renderingOptions:
formNavigation:
btnNextClassAttribute: 'btn btn-primary'
btnPreviousClassAttribute: 'btn btn-outline btn-primary'
btnSubmitClassAttribute: 'btn btn-primary'
fieldProperties:
descriptionClassAttribute: 'form-text'
errorClassAttribute: 'peer [&_input]:input-error [&_[type=checkbox]]:checkbox-error [&_[type=file]]:file-input-error [&_[type=radio]]:radio-error [&_select]:select-error [&_textarea]:textarea-error'
errorMsgClassAttribute: 'label label-text-alt justify-start text-error peer-[.form-control]:-mt-4'
requiredMarkClassAttribute: 'required'
visuallyHiddenClassAttribute: 'sr-only'
SummaryPage:
variants:
-
renderingOptions:
listRowClassAttribute: 'grid grid-cols-2 gap-4'
# Form elements
AdvancedPassword:
variants:
-
properties:
fieldsetClassAttribute: 'form-control form-element-advancedpassword mb-4'
containerClassAttribute: 'form-control mb-4'
elementClassAttribute: 'input input-bordered'
confirmationClassAttribute: 'input input-bordered'
labelClassAttribute: 'label label-text justify-start'
renderFieldset: 0
Checkbox:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-checkbox mb-4'
elementClassAttribute: 'checkbox'
labelTextClassAttribute: 'label-text'
labelClassAttribute: 'label cursor-pointer justify-start gap-2'
ContentElement:
variants:
1:
properties:
outerContainerClassAttribute: 'mb-4 [&_.container]:max-w-none [&_.frame]:py-0'
CountrySelect:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-select mb-4'
elementClassAttribute: 'select select-bordered'
labelClassAttribute: 'label label-text justify-start'
Date:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-date mb-4'
elementClassAttribute: 'input input-bordered'
labelClassAttribute: 'label label-text justify-start'
DatePicker:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-date mb-4'
elementClassAttribute: 'input input-bordered'
labelClassAttribute: 'label label-text justify-start'
SingleSelect:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-select mb-4'
elementClassAttribute: 'select select-bordered'
labelClassAttribute: 'label label-text justify-start'
StaticText:
variants:
1:
properties:
containerClassAttribute: 'form-element-statictext mb-4 [&>p]:label-text'
Email:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-email mb-4'
elementClassAttribute: 'border border-primary block w-full p-2'
labelClassAttribute: 'label label-text font-bold justify-start block w-full mb-2'
Fieldset:
variants:
-
properties:
elementClassAttribute: 'form-element-fieldset mb-4 [&>legend]:font-bold'
FileUpload:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-fileupload mb-4'
elementClassAttribute: 'file-input file-input-bordered'
labelClassAttribute: 'label label-text justify-start'
ImageUpload:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-imageupload mb-4'
elementClassAttribute: 'file-input file-input-bordered'
labelClassAttribute: 'label label-text justify-start'
MultiCheckbox:
variants:
-
properties:
fieldsetClassAttribute: 'mb-4 [&>legend]:label [&>legend]:label-text [&>legend]:justify-start'
containerClassAttribute: 'form-control form-element-checkbox'
elementClassAttribute: 'checkbox'
labelTextClassAttribute: 'label-text'
labelClassAttribute: 'label cursor-pointer justify-start gap-2'
MultiSelect:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-multiselect mb-4'
elementClassAttribute: 'select select-bordered'
labelClassAttribute: 'label label-text justify-start'
Number:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-number mb-4'
elementClassAttribute: 'input input-bordered'
labelClassAttribute: 'label label-text justify-start'
Password:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-password mb-4'
elementClassAttribute: 'input input-bordered'
labelClassAttribute: 'label label-text justify-start'
RadioButton:
variants:
-
properties:
fieldsetClassAttribute: 'mb-4 [&>legend]:label [&>legend]:label-text [&>legend]:justify-start'
containerClassAttribute: 'form-control form-element-radio'
elementClassAttribute: 'radio'
labelTextClassAttribute: 'label-text'
labelClassAttribute: 'label cursor-pointer justify-start gap-2'
Telephone:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-phone mb-4'
elementClassAttribute: 'border border-primary block w-full p-2'
labelClassAttribute: 'label label-text font-bold justify-start block w-full mb-2'
Text:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-text mb-4'
elementClassAttribute: 'border border-primary block w-full p-2'
labelClassAttribute: 'label label-text font-bold justify-start block w-full mb-2'
Textarea:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-textarea mb-4'
elementClassAttribute: 'border border-primary block w-full p-2 h-24'
labelClassAttribute: 'label label-text font-bold justify-start block w-full mb-2'
Url:
variants:
-
properties:
containerClassAttribute: 'form-control form-element-url mb-4'
elementClassAttribute: 'border border-primary block w-full p-2'
labelClassAttribute: 'label label-text font-bold justify-start block w-full mb-2'
GridRow:
variants:
-
properties:
elementClassAttribute: 'form-element form-element-gridrow grid grid-cols-12 gap-8 md:gap-16'
gridColumnClassAutoConfiguration:
gridSize: 12
viewPorts:
xs:
classPattern: 'col-span-{@numbersOfColumnsToUse}'
sm:
classPattern: 'sm:col-span-{@numbersOfColumnsToUse}'
md:
classPattern: 'md:col-span-{@numbersOfColumnsToUse}'
lg:
classPattern: 'lg:col-span-{@numbersOfColumnsToUse}'
xl:
classPattern: 'xl:col-span-{@numbersOfColumnsToUse}'
xxl:
classPattern: 'xxl:col-span-{@numbersOfColumnsToUse}'

View File

@@ -1,4 +1,5 @@
@import './TypoScript/' @import './TypoScript/'
@import 'EXT:base/Configuration/Ext/Form/TypoScript/setup.typoscript'
# Override tx_news templates # Override tx_news templates
plugin.tx_news { plugin.tx_news {

View File

@@ -0,0 +1,11 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers" data-namespace-typo3-fluid="true">
<formvh:renderRenderable renderable="{element}">
<div class="{f:if(condition: element.properties.elementClassAttribute, then: '{element.properties.elementClassAttribute}')} grid-cols-{f:if(condition: '{element.properties.gridColumnClassAutoConfiguration.gridSize}', then: '{element.properties.gridColumnClassAutoConfiguration.gridSize}', else: '12')}">
<f:for each="{element.elements}" as="element">
<div class="{formvh:gridColumnClassAutoConfiguration(element: element)}">
<f:render partial="{element.templateName}" arguments="{element: element}" />
</div>
</f:for>
</div>
</formvh:renderRenderable>
</html>

62
safelist-loader.js Normal file
View File

@@ -0,0 +1,62 @@
// safelist-loader.js
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
/**
* Generate a Tailwind CSS safelist array from a YAML file.
* @param {string} yamlFilePath - Path to the YAML file, relative to the working directory.
* @returns {string[]} Array of unique class names.
*/
function generateSafelist(yamlFilePath) {
// Resolve the YAML file path
const absolutePath = path.resolve(process.cwd(), yamlFilePath);
const content = fs.readFileSync(absolutePath, 'utf8');
const doc = yaml.load(content);
const classes = new Set();
function walk(obj) {
if (Array.isArray(obj)) {
obj.forEach(walk);
} else if (obj && typeof obj === 'object') {
// Expand gridColumnClassAutoConfiguration placeholders
if (obj.gridColumnClassAutoConfiguration) {
const { gridSize, viewPorts } = obj.gridColumnClassAutoConfiguration;
const placeholder = /\{\@[\w]+\}/g;
if (typeof gridSize === 'number' && viewPorts) {
Object.values(viewPorts).forEach(vp => {
if (typeof vp.classPattern === 'string') {
for (let i = 1; i <= gridSize; i++) {
classes.add(vp.classPattern.replace(placeholder, i));
}
}
});
}
}
// Collect any *ClassAttribute strings
Object.entries(obj).forEach(([key, val]) => {
if (/Class(Attribute)?$/.test(key) && typeof val === 'string') {
val.split(/\s+/).forEach(c => {
if (c) classes.add(c);
});
} else {
walk(val);
}
});
}
}
walk(doc);
return Array.from(classes);
}
module.exports = generateSafelist;
// Usage in tailwind.config.js:
// const safelistLoader = require('./safelist-loader');
// module.exports = {
// // ... other config ...
// safelist: generateSafelist('packages/base/Configuration/Ext/Form/Yaml/Setup.yaml'),
// };

View File

@@ -1,27 +1,42 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
const defaultTheme = require('tailwindcss/defaultTheme') const defaultTheme = require('tailwindcss/defaultTheme')
const safelistLoader = require('./safelist-loader');
const safelist = safelistLoader('packages/base/Configuration/Ext/Form/Yaml/Setup.yaml');
const isProduction = process.env.NODE_ENV === 'production'; const isProduction = process.env.NODE_ENV === 'production';
const config = { const config = {
// Keep content defined always, but rely on safelist in dev // Keep content defined always, but rely on safelist in dev
content: [ content: [
"./packages/base/Configuration/**/*.yaml",
"./packages/base/Resources/Private/**/*.html", "./packages/base/Resources/Private/**/*.html",
"./packages/base/Resources/Private/**/*.js", "./packages/base/Resources/Private/**/*.js",
"./packages/base/ContentBlocks/ContentElements/**/*.html", "./packages/base/ContentBlocks/ContentElements/**/*.html",
"./public/typo3conf/ext/*/Resources/Private/**/*.html", "./public/typo3conf/ext/*/Resources/Private/**/*.html",
], ],
// Disable purging in dev by safelisting everything // Disable purging in dev by safelisting everything
safelist: isProduction ? [] : [{ pattern: /.*/ }], safelist: isProduction ? [
...safelist,
] : [
'md:col-span-6',
{
pattern: /col-span-\d+/,
variants: ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'] // Include other variants (sm, lg) if needed
},
{ pattern: /.*/ }
],
theme: { theme: {
extend: { extend: {
transitionProperty: { transitionProperty: {
'max-height': 'max-height' // Add max-height to transition properties 'max-height': 'max-height' // Add max-height to transition properties
}, },
fontFamily: { fontFamily: {
sans: ['Inter', ...defaultTheme.fontFamily.sans],
hajime: ['"Hajime Sans"', ...defaultTheme.fontFamily.sans], barlow: ['"Barlow"', ...defaultTheme.fontFamily.sans],
}, },
// borderColor: {
// primary: 'var(--color-primary)',
// },
colors: { colors: {
primary: 'var(--color-primary)', primary: 'var(--color-primary)',
yellow: '#F5AE07', yellow: '#F5AE07',