diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d3e9edc2..08f9efb3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -50,7 +50,7 @@ storybook:test: if [[ $exit_code -ne 0 && -d "__snapshots__/__diff_output__" ]]; then echo -e "\e[0Ksection_start:`date +%s`:glpa_summary\r\e[0KHeader of the summary" echo "Storybook tests failed." - echo "Check for rendering differences at http://gitlab.com/code0-tech/development/pictor/-/jobs/$CI_JOB_ID/artifacts/browse/__snapshots__/__diff_output__/" + echo "Check for rendering differences at https://gitlab.com/code0-tech/development/pictor/-/jobs/$CI_JOB_ID/artifacts/browse/__snapshots__/__diff_output__/" echo "If the changes are intended, update the snapshots by adding the 'regenerate-snapshots' label." echo -e "\e[0Ksection_end:`date +%s`:glpa_summary\r\e[0K" fi diff --git a/__snapshots__/form--checkbox-chromium.png b/__snapshots__/form--checkbox-chromium.png new file mode 100644 index 00000000..bf104927 Binary files /dev/null and b/__snapshots__/form--checkbox-chromium.png differ diff --git a/__snapshots__/form--checkbox-firefox.png b/__snapshots__/form--checkbox-firefox.png new file mode 100644 index 00000000..321b967d Binary files /dev/null and b/__snapshots__/form--checkbox-firefox.png differ diff --git a/__snapshots__/form--checkbox-webkit.png b/__snapshots__/form--checkbox-webkit.png new file mode 100644 index 00000000..779fb70b Binary files /dev/null and b/__snapshots__/form--checkbox-webkit.png differ diff --git a/__snapshots__/form--login-chromium.png b/__snapshots__/form--login-chromium.png new file mode 100644 index 00000000..4996c24d Binary files /dev/null and b/__snapshots__/form--login-chromium.png differ diff --git a/__snapshots__/form--login-firefox.png b/__snapshots__/form--login-firefox.png new file mode 100644 index 00000000..056bde96 Binary files /dev/null and b/__snapshots__/form--login-firefox.png differ diff --git a/__snapshots__/form--login-webkit.png b/__snapshots__/form--login-webkit.png new file mode 100644 index 00000000..df4cb3a0 Binary files /dev/null and b/__snapshots__/form--login-webkit.png differ diff --git a/__snapshots__/form--number-chromium.png b/__snapshots__/form--number-chromium.png new file mode 100644 index 00000000..1305d9e8 Binary files /dev/null and b/__snapshots__/form--number-chromium.png differ diff --git a/__snapshots__/form--number-firefox.png b/__snapshots__/form--number-firefox.png new file mode 100644 index 00000000..f7ff5d5d Binary files /dev/null and b/__snapshots__/form--number-firefox.png differ diff --git a/__snapshots__/form--number-webkit.png b/__snapshots__/form--number-webkit.png new file mode 100644 index 00000000..37b99078 Binary files /dev/null and b/__snapshots__/form--number-webkit.png differ diff --git a/__snapshots__/form--radio-card-chromium.png b/__snapshots__/form--radio-card-chromium.png new file mode 100644 index 00000000..f3973d07 Binary files /dev/null and b/__snapshots__/form--radio-card-chromium.png differ diff --git a/__snapshots__/form--radio-card-firefox.png b/__snapshots__/form--radio-card-firefox.png new file mode 100644 index 00000000..5cf11cc2 Binary files /dev/null and b/__snapshots__/form--radio-card-firefox.png differ diff --git a/__snapshots__/form--radio-card-webkit.png b/__snapshots__/form--radio-card-webkit.png new file mode 100644 index 00000000..ee9b82ef Binary files /dev/null and b/__snapshots__/form--radio-card-webkit.png differ diff --git a/__snapshots__/form--radio-example-chromium.png b/__snapshots__/form--radio-example-chromium.png new file mode 100644 index 00000000..8a2546bb Binary files /dev/null and b/__snapshots__/form--radio-example-chromium.png differ diff --git a/__snapshots__/form--radio-example-firefox.png b/__snapshots__/form--radio-example-firefox.png new file mode 100644 index 00000000..e6aa51e5 Binary files /dev/null and b/__snapshots__/form--radio-example-firefox.png differ diff --git a/__snapshots__/form--radio-example-webkit.png b/__snapshots__/form--radio-example-webkit.png new file mode 100644 index 00000000..a8ed8dea Binary files /dev/null and b/__snapshots__/form--radio-example-webkit.png differ diff --git a/__snapshots__/form--radio-without-input-chromium.png b/__snapshots__/form--radio-without-input-chromium.png new file mode 100644 index 00000000..0195184b Binary files /dev/null and b/__snapshots__/form--radio-without-input-chromium.png differ diff --git a/__snapshots__/form--radio-without-input-firefox.png b/__snapshots__/form--radio-without-input-firefox.png new file mode 100644 index 00000000..d3c17869 Binary files /dev/null and b/__snapshots__/form--radio-without-input-firefox.png differ diff --git a/__snapshots__/form--radio-without-input-webkit.png b/__snapshots__/form--radio-without-input-webkit.png new file mode 100644 index 00000000..c2fefde8 Binary files /dev/null and b/__snapshots__/form--radio-without-input-webkit.png differ diff --git a/__snapshots__/form--switch-chromium.png b/__snapshots__/form--switch-chromium.png new file mode 100644 index 00000000..58096cb8 Binary files /dev/null and b/__snapshots__/form--switch-chromium.png differ diff --git a/__snapshots__/form--switch-firefox.png b/__snapshots__/form--switch-firefox.png new file mode 100644 index 00000000..360d90d8 Binary files /dev/null and b/__snapshots__/form--switch-firefox.png differ diff --git a/__snapshots__/form--switch-webkit.png b/__snapshots__/form--switch-webkit.png new file mode 100644 index 00000000..a51c3ed4 Binary files /dev/null and b/__snapshots__/form--switch-webkit.png differ diff --git a/__snapshots__/form--website-chromium.png b/__snapshots__/form--website-chromium.png new file mode 100644 index 00000000..8eee1dfe Binary files /dev/null and b/__snapshots__/form--website-chromium.png differ diff --git a/__snapshots__/form--website-firefox.png b/__snapshots__/form--website-firefox.png new file mode 100644 index 00000000..266cb507 Binary files /dev/null and b/__snapshots__/form--website-firefox.png differ diff --git a/__snapshots__/form--website-webkit.png b/__snapshots__/form--website-webkit.png new file mode 100644 index 00000000..1c47907b Binary files /dev/null and b/__snapshots__/form--website-webkit.png differ diff --git a/package-lock.json b/package-lock.json index 850e61e2..9b69f582 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2091,6 +2091,24 @@ "resolved": "https://registry.npmjs.org/@daybrush/utils/-/utils-1.13.0.tgz", "integrity": "sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -9847,10 +9865,11 @@ "dev": true }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -10047,9 +10066,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -11156,17 +11175,18 @@ "dev": true }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dev": true, + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -17073,9 +17093,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -17091,10 +17111,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -19350,10 +19371,11 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -20504,33 +20526,35 @@ } }, "node_modules/vite": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz", - "integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==", + "version": "5.4.10", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", + "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -20548,6 +20572,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -20559,21 +20586,440 @@ } } }, - "node_modules/vite/node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", "peer": true, "bin": { - "rollup": "dist/bin/rollup" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" + "node": ">=12" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/wait-on": { diff --git a/src/components/card/Card.stories.tsx b/src/components/card/Card.stories.tsx index 1d15bff2..4111bd87 100644 --- a/src/components/card/Card.stories.tsx +++ b/src/components/card/Card.stories.tsx @@ -91,6 +91,5 @@ export const CardNews: CardStory = { color: "secondary", outline: false, gradient: true, - gradientPosition: "top-right" } } \ No newline at end of file diff --git a/src/components/d-screen/DScreen.stories.tsx b/src/components/d-screen/DScreen.stories.tsx index 1f11c04b..9b3f470c 100644 --- a/src/components/d-screen/DScreen.stories.tsx +++ b/src/components/d-screen/DScreen.stories.tsx @@ -1,7 +1,6 @@ import {Meta} from "@storybook/react"; import React from "react"; import DScreen from "./DScreen"; -import Badge from "../badge/Badge"; import { IconBrandAdobe, IconDatabase, diff --git a/src/components/dropdown/Dropdown.stories.tsx b/src/components/dropdown/Dropdown.stories.tsx index ff502cf2..40147ab1 100644 --- a/src/components/dropdown/Dropdown.stories.tsx +++ b/src/components/dropdown/Dropdown.stories.tsx @@ -2,9 +2,8 @@ import {Meta} from "@storybook/react"; import React from "react"; import Dropdown from "./Dropdown"; import Button from "../button/Button"; -import {IconAbc, IconSearch} from "@tabler/icons-react"; +import {IconAbc} from "@tabler/icons-react"; import ButtonGroup from "../button-group/ButtonGroup"; -import Input from "../input/Input"; const meta: Meta = { title: "Dropdown", @@ -30,11 +29,6 @@ export const Dropdowns = () => { - - - - - Item 1 diff --git a/src/components/form/CheckboxInput.tsx b/src/components/form/CheckboxInput.tsx new file mode 100644 index 00000000..71038bf9 --- /dev/null +++ b/src/components/form/CheckboxInput.tsx @@ -0,0 +1,36 @@ +import React, {RefObject} from "react"; +import Input, {InputProps} from "./Input"; + + +interface CheckboxInputProps extends Omit, "wrapperComponent" | "type" | "left" | "leftType"> { + text?: string +} + +const CheckboxInput: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + text, + value, + initialValue, + ...rest + } = props + + + return + +}) + +export default CheckboxInput \ No newline at end of file diff --git a/src/components/form/EmailInput.tsx b/src/components/form/EmailInput.tsx new file mode 100644 index 00000000..d0b4b7e4 --- /dev/null +++ b/src/components/form/EmailInput.tsx @@ -0,0 +1,46 @@ +import React, {RefObject} from "react"; +import Input, {InputProps, setElementKey} from "./Input"; +import {IconX} from "@tabler/icons-react"; +import Button from "../button/Button"; + +/** + * This regex is based on the validation behind the type="email" validation of html. + * This can be used as the validation for email input + */ +const EMAIL_REGEX = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/ + +interface EmailInputProps extends Omit, "wrapperComponent" | "type"> { + //defaults to false + clearable?: boolean +} + +export const emailValidation = (email: string) => EMAIL_REGEX.test(email) + +const EmailInput: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + clearable = false, + right, + ...rest + } = props + + const toClearable = () => { + if (ref.current) setElementKey(ref.current, "value", "", "change") + } + + const rightAction = [right] + clearable && rightAction.push() + + + return + +}) + +export default EmailInput \ No newline at end of file diff --git a/src/components/form/Input.stories.tsx b/src/components/form/Input.stories.tsx new file mode 100644 index 00000000..932db229 --- /dev/null +++ b/src/components/form/Input.stories.tsx @@ -0,0 +1,354 @@ +import React from "react"; +import useForm from "./useForm"; +import Input from "./Input"; +import Card from "../card/Card"; +import Button from "../button/Button"; +import {IconKey, IconLogin, IconMail} from "@tabler/icons-react"; +import Text from "../Text/Text"; +import PasswordInput from "./PasswordInput"; +import TextInput from "./TextInput"; +import EmailInput, {emailValidation} from "./EmailInput"; +import NumberInput from "./NumberInput"; +import RadioInput from "./radio/RadioInput"; +import CheckboxInput from "./CheckboxInput"; +import RadioGroup from "./radio/RadioGroup"; +import RadioButton from "./radio/RadioButton"; +import CardSection from "../card/CardSection"; +import Flex from "../flex/Flex"; +import SwitchInput from "./SwitchInput"; + +export default { + title: "Form" +} + +export const Login = () => { + + const [inputs, validate] = useForm({ + initialValues: { + email: "nicoq@", + password: null, + checkbox: true + }, + validate: { + email: (value) => { + if (!value) return "Email is required" + if (!emailValidation(value)) return "Please provide a valid email" + return null + }, + password: (value) => { + if (!value) return "Password is required" + return null + } + }, + onSubmit: (values) => { + console.log(values) + } + }) + + return + Login +
+ + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et + dolore magna aliquyam erat, sed diam voluptua. + +
+
+ } + {...inputs.getInputProps("email")} + /> +
+ } + {...inputs.getInputProps("password")} + /> +
+
+ + +
+ + +
+ +} + +export const Website = () => { + + return + + + +} + +export const Number = () => { + + return + + + +} + +export const RadioExample = () => { + + const [inputs, validate] = useForm({ + initialValues: { + test: "dynamic" + }, + validate: { + test: (value) => { + if (!value) return "Error" + return null + } + }, + onSubmit: (values) => { + console.log(values) + } + }) + + + return
+ + + + +
+
+ + +
+
+
+} + + +export const Checkbox = () => { + + const [inputs, validate] = useForm({ + initialValues: { + test: null + }, + validate: { + test: (value) => { + if (!value) return "Error" + return null + } + }, + onSubmit: (values) => { + console.log(values) + } + }) + + + return
+ + +
+
+ + +
+
+
+ +} + +export const RadioCard = () => { + const [inputs, validate] = useForm({ + initialValues: { + test: "dynamic" + }, + validate: { + test: (value) => { + if (!value) return "Error" + return null + } + }, + onSubmit: (values) => { + console.log(values) + } + }) + return + {({activeRadio, setActiveRadio}) => { + return <> + setActiveRadio("dynamic")} borderColor={activeRadio == "dynamic" ? "info" : "primary"} color={"secondary"}> + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut + + +
+ setActiveRadio("hybrid")} borderColor={activeRadio == "hybrid" ? "info" : "primary"} color={"secondary"}> + + + + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut + + +
+
+ +
+ + }} +
+} + +export const RadioWithoutInput = () => { + const [inputs, validate] = useForm({ + initialValues: { + test: "dynamic" + }, + validate: { + test: (value) => { + if (!value) return "Error" + return null + } + }, + onSubmit: (values) => { + console.log(values) + } + }) + return + {({activeRadio, setActiveRadio}) => { + return <> + + setActiveRadio("dynamic")} maw={200} borderColor={activeRadio == "dynamic" ? "info" : "primary"} color={"secondary"}> + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut + + + setActiveRadio("hybrid")} maw={200} borderColor={activeRadio == "hybrid" ? "info" : "primary"} color={"secondary"}> + + Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt + ut + + + + +
+
+ +
+ + }} +
+} + + +export const Switch = () => { + + const [inputs, validate] = useForm({ + initialValues: { + test: null + }, + validate: { + test: (value) => { + if (!value) return "Error" + return null + } + }, + onSubmit: (values) => { + console.log(values) + } + }) + + + return
+ + +
+
+ + +
+
+
+ +} diff --git a/src/components/form/Input.style.scss b/src/components/form/Input.style.scss new file mode 100644 index 00000000..20cffc30 --- /dev/null +++ b/src/components/form/Input.style.scss @@ -0,0 +1,273 @@ +@import "../../styles/helpers"; + +.input { + + $padding: $xs; + + @include box(true, $primary); + display: flex; + z-index: 1; + align-items: stretch; + cursor: pointer; + + + &:has(&__control:focus) { + border: 1px solid borderColor($primary, true); + } + + &--not-valid { + border: 1px solid borderColor($error, true); + } + + &__left, &__right { + display: flex; + align-items: stretch; + + &--action { + padding: 0; + } + + &--placeholder { + @include box(false, $secondary); + padding: $padding; + } + + } + + &__left { + &--icon { + align-items: center; + padding: $padding 0 $padding $padding; + } + } + + &__right { + &--icon { + align-items: center; + padding: $padding $padding $padding 0; + } + } + + &__control { + background: none; + border: none; + outline: none; + padding: $padding $padding; + flex: 1; + box-shadow: none; + font-size: $sm; + color: rgba($white, .5); + } + + + &__label { + text-transform: uppercase; + color: rgba($white, .5); + font-size: $xs; + //margin: $padding 0; + display: block; + } + + &__description { + color: rgba($white, .5); + font-size: $sm; + margin: $padding/2 0 $padding; + display: block; + } + + &__message { + @include box(false, $error); + border-top-right-radius: 0; + border-top-left-radius: 0; + padding: $padding * 2 $padding $padding; + top: -$padding; + z-index: 0; + display: flex; + align-items: center; + font-size: $xs; + gap: $padding; + + > svg { + width: $xs; + height: $xs; + } + } + +} + +.radio-input, .radio-button { + + $padding: $xxs; + $size: 0.9rem; + + background:transparent !important; + border-radius: 0 !important; + border: none !important; + align-items: center; + box-sizing: border-box; + padding: $padding 0; + gap: $xxs; + + .input__control { + + position: relative; + flex: none; + box-sizing: border-box; + padding: 0; + margin: 1px 0; + width: $size; + height: $size; + aspect-ratio: 1/1; + + appearance: none; + outline: none; + + background-color: transparent; + border: 1px solid borderColor($secondary, true); + border-radius: 50%; + + &:checked { + border-color: borderColor($info, true); + + &:before { + content: ""; + position: absolute; + left: 50%; + aspect-ratio: 1/1; + border-radius: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: $size/2; + height: $size/2; + background: backgroundColor($info); + } + } + + } +} + +.radio-button { + padding: 0; + display: inline-flex; +} + +.checkbox-input { + + $padding: $xxs; + $size: 0.9rem; + + background: backgroundColor($secondary) !important; + border-radius: 0 !important; + border: none !important; + align-items: center; + box-sizing: border-box; + gap: $xxs; + padding: $padding 0; + + .input__control { + + position: relative; + flex: none; + box-sizing: border-box; + padding: 0; + margin: 1px 0; + width: $size; + height: $size; + + appearance: none; + outline: none; + + background-color: transparent; + border: 1px solid borderColor($secondary, true); + border-radius: calc($borderRadius / 3); + + &:checked { + border-color: borderColor($info, true); + background: backgroundColor($info); + + &:before { + content: ""; + position: absolute; + left: 50%; + top: 50%; + transform: translateX(-50%) translateY(-50%); + width: $size/2; + height: $size/2; + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + background: rgba($white, .5); + } + } + + } +} + +.switch-input { + + $padding: $xxs; + $size: 0.9rem; + background: transparent !important; + display: inline-flex; + align-items: center; + padding: 0; + border: none !important; + + .input__control { + + position: relative; + box-sizing: border-box; + padding: 0; + width: $size; + height: $size; + + appearance: none; + outline: none; + + background-color: transparent; + border-radius: 50%; + + &:checked{ + + &:after { + -webkit-transform: translateX(22.5px); + -ms-transform: translateX(22.5px); + transform: translateX(22.5px); + -webkit-transition: .4s; + transition: .4s; + } + + &:before { + background-color: backgroundColor($info); + -webkit-transition: .4s; + transition: .4s; + } + } + + &:after { + width: $size; + height: $size; + background-color: $white; + border-radius: 50%; + position: absolute; + content: ""; + -webkit-transition: .4s; + transition: .4s; + } + + &:before { + background: backgroundColor($primary); + position: absolute; + border-radius: 50rem; + width: $size * 2.5 + $padding; + height: $size + $padding; + top: -1 * $padding/2; + left: -1 * $padding/2; + content: ""; + z-index: -1; + -webkit-transition: .4s; + transition: .4s; + } + + + } + +} \ No newline at end of file diff --git a/src/components/form/Input.tsx b/src/components/form/Input.tsx new file mode 100644 index 00000000..6713ddfc --- /dev/null +++ b/src/components/form/Input.tsx @@ -0,0 +1,95 @@ +import {Code0Component} from "../../utils/types"; +import React, {LegacyRef, RefObject, useEffect} from "react"; +import {ValidationProps} from "./useForm"; +import {mergeCode0Props} from "../../utils/utils"; +import "./Input.style.scss" +import InputLabel from "./InputLabel"; +import InputDescription from "./InputDescription"; +import InputMessage from "./InputMessage"; + +type Code0Input = Omit, "defaultValue">, "left">, "right">, "label"> + +export interface InputProps extends Code0Input, ValidationProps { + + wrapperComponent?: Code0Component + right?: React.ReactNode | React.ReactElement | React.ReactElement[] + left?: React.ReactNode | React.ReactElement | React.ReactElement[] + leftType?: "action" | "placeholder" | "icon" + rightType?: "action" | "placeholder" | "icon" + label?: React.ReactNode | React.ReactElement + description?: React.ReactNode | React.ReactElement +} + +export const setElementKey = (element: HTMLElement, key: string, value: any, event: string) => { + const valueSetter = Object.getOwnPropertyDescriptor(element, key)?.set; + const prototype = Object.getPrototypeOf(element); + const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, key)?.set; + + if (valueSetter && valueSetter !== prototypeValueSetter) { + prototypeValueSetter?.call(element, value); + } else { + valueSetter?.call(element, value); + } + + element.dispatchEvent(new Event(event, { bubbles: true })); +} + + +const Input: React.ForwardRefExoticComponent> = React.forwardRef((props: InputProps, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + wrapperComponent = {}, + label, + description, + disabled = false, + left, + right, + leftType = "icon", + rightType = "action", + formValidation = { + valid: true, + notValidMessage: null, + setValue: null + }, + ...rest + } = props + + useEffect(() => { + if (!ref) return + if (!ref.current) return + if (!formValidation) return + if (!formValidation.setValue) return + + // @ts-ignore + ref.current.addEventListener("change", ev => formValidation.setValue(rest.type != "checkbox" ? ev.target.value : ev.target.checked)) + }, [ref]) + + + return <> + + {!!label ? : null} + {!!description ? : null} + +
+ + {!!left ?
+ {left} +
: null} + + | undefined} {...mergeCode0Props("input__control", rest)}/> + + {!!right ?
+ {right} +
: null} + +
+ + {!formValidation?.valid && formValidation?.notValidMessage ? + : null} + +}) + + +export default Input \ No newline at end of file diff --git a/src/components/form/InputDescription.tsx b/src/components/form/InputDescription.tsx new file mode 100644 index 00000000..46d0ad9c --- /dev/null +++ b/src/components/form/InputDescription.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +export interface InputDescriptionProps { + children: React.ReactNode | React.ReactElement +} + +const InputDescription: React.FC = (props) => { + + const {children} = props + + return + {children} + + +} + +export default InputDescription \ No newline at end of file diff --git a/src/components/form/InputLabel.tsx b/src/components/form/InputLabel.tsx new file mode 100644 index 00000000..4fee9f7a --- /dev/null +++ b/src/components/form/InputLabel.tsx @@ -0,0 +1,17 @@ +import React from "react"; + +export interface InputLabelProps { + children: React.ReactNode | React.ReactElement +} + +const InputLabel: React.FC = (props) => { + + const {children} = props + + return + +} + +export default InputLabel \ No newline at end of file diff --git a/src/components/form/InputMessage.tsx b/src/components/form/InputMessage.tsx new file mode 100644 index 00000000..8d6d54fb --- /dev/null +++ b/src/components/form/InputMessage.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import {IconExclamationCircle} from "@tabler/icons-react"; + +export interface InputMessageProps { + children: string +} + +const InputMessage: React.FC = (props) => { + + const {children} = props + + return + + {children} + + +} + +export default InputMessage \ No newline at end of file diff --git a/src/components/form/NumberInput.tsx b/src/components/form/NumberInput.tsx new file mode 100644 index 00000000..08d0896e --- /dev/null +++ b/src/components/form/NumberInput.tsx @@ -0,0 +1,45 @@ +import React, {RefObject} from "react"; +import Input, {InputProps} from "./Input"; +import {IconMinus, IconPlus} from "@tabler/icons-react"; +import Button from "../button/Button"; + + +interface NumberInputProps extends Omit, "wrapperComponent" | "type" | "left" | "right" | "leftType" | "rightType"> { + +} + +const NumberInput: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + step = 1, + ...rest + } = props + + const countUp = () => { + if (ref.current) { + if (!ref.current.value) ref.current.value = "0" + ref.current.value = (Number.parseInt(ref.current.value) + (step as number)).toString() + } + } + + const countDown = () => { + if (ref.current) { + if (!ref.current.value) ref.current.value = "0" + ref.current.value = (Number.parseInt(ref.current.value) - (step as number)).toString() + } + } + + return } + left={} + leftType={"action"} + type={"number"} + ref={ref} + {...rest} + /> + +}) + +export default NumberInput \ No newline at end of file diff --git a/src/components/form/PasswordInput.tsx b/src/components/form/PasswordInput.tsx new file mode 100644 index 00000000..ca7e0934 --- /dev/null +++ b/src/components/form/PasswordInput.tsx @@ -0,0 +1,47 @@ +import React, {RefObject} from "react"; +import Input, {InputProps, setElementKey} from "./Input"; +import {IconEye, IconX} from "@tabler/icons-react"; +import Button from "../button/Button"; + +interface PasswordInputProps extends Omit, "wrapperComponent" | "type"> { + clearable?: boolean, + visible?: boolean +} + + +const PasswordInput: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + clearable = true, + visible = true, + right, + ...rest + } = props + + const toClearable = () => { + if (ref.current) setElementKey(ref.current, "value", "", "change") + } + + const toVisible = () => { + if (ref.current && ref.current.type == "password") ref.current.type = "text" + else if (ref.current && ref.current.type == "text") ref.current.type = "password" + } + + const rightAction = [right] + visible && rightAction.push() + clearable && rightAction.push() + + + return + +}) + +export default PasswordInput \ No newline at end of file diff --git a/src/components/form/SwitchInput.tsx b/src/components/form/SwitchInput.tsx new file mode 100644 index 00000000..7b9cbd2e --- /dev/null +++ b/src/components/form/SwitchInput.tsx @@ -0,0 +1,35 @@ +import React, {RefObject} from "react"; +import Input, {InputProps} from "./Input"; + + +interface SwitchInputProps extends Omit, "wrapperComponent" | "type" | "left" | "leftType"> { + +} + +const SwitchInput: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + value, + initialValue, + ...rest + } = props + + + return + +}) + +export default SwitchInput \ No newline at end of file diff --git a/src/components/form/TextInput.tsx b/src/components/form/TextInput.tsx new file mode 100644 index 00000000..25895702 --- /dev/null +++ b/src/components/form/TextInput.tsx @@ -0,0 +1,39 @@ +import React, {RefObject} from "react"; +import Input, {InputProps, setElementKey} from "./Input"; +import {IconX} from "@tabler/icons-react"; +import Button from "../button/Button"; + + +interface TextInputProps extends Omit, "wrapperComponent" | "type"> { + //defaults to false + clearable?: boolean +} + +const TextInput: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + clearable = false, + right, + ...rest + } = props + + const toClearable = () => { + if (ref.current) setElementKey(ref.current, "value", "", "change") + } + + const rightAction = [right] + clearable && rightAction.push() + + + return + +}) + +export default TextInput \ No newline at end of file diff --git a/src/components/form/radio/RadioButton.tsx b/src/components/form/radio/RadioButton.tsx new file mode 100644 index 00000000..b27d94f9 --- /dev/null +++ b/src/components/form/radio/RadioButton.tsx @@ -0,0 +1,42 @@ +import React, {RefObject, useEffect} from "react"; +import Input from "../Input"; +import {Code0Component} from "../../../utils/types"; +import {useRadioGroup} from "./RadioGroup"; + + +interface RadioButtonProps extends Code0Component { +} + +const RadioButton: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + const {value, ...rest} = props + + const radioGroup = useRadioGroup() + ref = ref || React.useRef(null) + + //update store of radio group + useEffect(() => { + if (!ref || !ref.current || !radioGroup) return + ref.current.addEventListener("change", ev => { + radioGroup.setActiveRadio(String(value)) + }) + }, [ref, radioGroup]) + + return + +}) + +export default RadioButton \ No newline at end of file diff --git a/src/components/form/radio/RadioGroup.tsx b/src/components/form/radio/RadioGroup.tsx new file mode 100644 index 00000000..01705de3 --- /dev/null +++ b/src/components/form/radio/RadioGroup.tsx @@ -0,0 +1,61 @@ +"use client" + +import React, {useMemo} from "react"; +import {ValidationProps} from "../useForm"; +import {InputProps} from "../Input"; + +export interface RadioGroupContextProps { + validation?: ValidationProps + activeRadio: string | null + setActiveRadio: (radio: string) => void +} + +export interface RadioGroupExoticProps { + activeRadio: string | null + setActiveRadio: (radio: string) => void +} + +export interface RadioGroupProps extends Omit, "wrapperComponent" | "type" | "left" | "leftType" | "right" | "rightType" | "children"> { + children: React.ReactElement[] | React.FC +} + +const RadioGroupContext = React.createContext(null) + +export const useRadioGroup = () => React.useContext(RadioGroupContext) + +const RadioGroup: React.FC = (props) => { + + const { + children, + formValidation, + initialValue = null, + required, + label, + description + } = props + const [radioStore, setRadioStore] = React.useState(initialValue) + + const setActiveRadio = (radio: string) => { + formValidation?.setValue(radio) + setRadioStore(radio) + } + + const child = typeof children === "function" ? useMemo(() => children({activeRadio: radioStore, setActiveRadio}), [radioStore]) : children + + return + {/** TODO: add label and description **/} + {child} + {/** TODO: add error handling **/} + + +} + +export default RadioGroup \ No newline at end of file diff --git a/src/components/form/radio/RadioInput.tsx b/src/components/form/radio/RadioInput.tsx new file mode 100644 index 00000000..db0819da --- /dev/null +++ b/src/components/form/radio/RadioInput.tsx @@ -0,0 +1,36 @@ +import React, {RefObject} from "react"; +import Input, {InputProps} from "../Input"; + + +interface RadioInputProps extends Omit, "wrapperComponent" | "type" | "left" | "leftType"> { + text?: string +} + +const RadioInput: React.ForwardRefExoticComponent = React.forwardRef((props, ref: RefObject) => { + + ref = ref || React.useRef(null) + + const { + text, + value, + initialValue, + ...rest + } = props + + + return + +}) + +export default RadioInput \ No newline at end of file diff --git a/src/components/form/useForm.ts b/src/components/form/useForm.ts new file mode 100644 index 00000000..d96370d8 --- /dev/null +++ b/src/components/form/useForm.ts @@ -0,0 +1,101 @@ +"use client" + +import {useCallback, useMemo, useState} from "react"; + +export type Validations = Partial<{ + [Key in keyof Values]: (value: Values[Key]) => string | null; +}> + +export interface FormValidationProps { + initialValues: Values + validate?: Validations, + onSubmit?: (values: Values) => void +} + +export interface ValidationProps { + initialValue?: Value | null + required?: boolean + formValidation?: { + setValue: (value: string) => void + valid?: boolean + notValidMessage?: string | null + } +} + +export type ValidationsProps = Partial<{ + [Key in keyof Values]: ValidationProps +}> + +export type FormValidationReturn = [IValidation, () => void] + +export interface IValidation { + getInputProps(key: Key): ValidationProps +} + +class Validation implements IValidation { + + private readonly changeValue: (key: string, value: any) => void + private readonly initialRender: boolean + private readonly currentValues: Values + private readonly currentValidations?: Validations + + constructor(changeValue: (key: string, value: any) => void, values: Values, validations: Validations, initial: boolean) { + this.changeValue = changeValue + this.currentValues = values + this.currentValidations = validations + this.initialRender = initial + } + + public getInputProps(key: Key): ValidationProps { + + const currentValue = ((this.currentValues[key]) || null)!! + const currentName = key as string + const currentFc = !!this.currentValidations && !!this.currentValidations[key] ? this.currentValidations[key] : (value: typeof currentValue) => null + const message = !this.initialRender ? currentFc(currentValue) : null + + return { + initialValue: currentValue, + formValidation: { + setValue: (value: string) => { + this.changeValue(currentName, value) + }, + ...(!this.initialRender ? { + notValidMessage: message, + valid: message === null ? true : !message, + } : { + valid: true, + }) + }, + ...(!!this.currentValidations && !!this.currentValidations[key] ? {required: true} : {}) + } + } +} + +const useForm = = Record>(props: FormValidationProps): FormValidationReturn => { + + const {initialValues, validate = {}, onSubmit} = props + const [values, setValues] = useState(initialValues) + const changeValue = (key: keyof Values, value: any) => setValues(prevState => { + prevState[key] = value + return prevState + }) + const initialInputProps = useMemo(() => new Validation(changeValue, initialValues, validate, true), []) + const [inputProps, setInputProps] = useState>(initialInputProps) + + const validateFunction = useCallback(() => { + + let inputProps: IValidation = new Validation(changeValue, values, validate, false) + + setInputProps(() => inputProps) + if (onSubmit) onSubmit(values as Values) + + }, []) + + return [ + inputProps, + validateFunction + ] + +} + +export default useForm diff --git a/src/components/input/Input.stories.tsx b/src/components/input/Input.stories.tsx deleted file mode 100644 index f6d88529..00000000 --- a/src/components/input/Input.stories.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from "react"; -import Input from "./Input"; -import {IconMail} from "@tabler/icons-react"; - -export default { - title: "Input", - component: Input -}; - -export const Mail = () => - e-mail - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam - - - -export const Disabled = () => - - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam - - -export const NotValid = () => - - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor - invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam - - -export const Valid = () => - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor - invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. - - Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam - \ No newline at end of file diff --git a/src/components/input/Input.style.scss b/src/components/input/Input.style.scss deleted file mode 100644 index 1ed8d7ff..00000000 --- a/src/components/input/Input.style.scss +++ /dev/null @@ -1,122 +0,0 @@ -@import "../../styles/helpers"; - -.input { - margin-bottom: 1rem; - - &:last-child, &:last-of-type { - margin-bottom: 0; - } - - @include disabled; - - &__control { - display: flex; - align-items: center; - margin-bottom: .5rem; - - @include box(); - } - - &__label { - color: rgba($white, .5); - font-size: .75rem; - font-family: Ubuntu, sans-serif; - margin-bottom: .5rem; - display: block; - text-transform: uppercase; - } - - &__field { - position: relative; - display: inline-block; - background: none; - width: 100%; - color: rgba($white, .5); - font-family: Ubuntu, sans-serif; - border: none; - outline: none; - padding: .75rem .5rem; - box-shadow: none; - font-size: 1rem; - - &::placeholder { - color: rgba($white, .25); - } - } - - &__icon { - display: flex; - width: 1.25rem; - height: 1.25rem; - justify-content: center; - align-items: center; - aspect-ratio: 50/50; - margin-left: .5rem; - pointer-events: none; - - > * { - width: 1.25rem; - height: 1.25rem; - } - - } - - &__desc { - padding-left: 1rem; - color: rgba($white, .5); - font-size: .75rem; - font-family: Ubuntu, sans-serif; - margin: .5rem 0; - - &:before { - //tabler info icon as an svg component - content: url('data:image/svg+xml; utf8, '); - height: .75rem; - width: .75rem; - padding: .25rem 0; - margin-right: .25rem; - } - } - - &__message { - display: none; - position: relative; - color: rgba($white, .5); - border-radius: $borderRadius; - font-size: .75rem; - z-index: -1; - font-family: Ubuntu, sans-serif; - - > p { - margin: 0; - padding: .5rem .5rem .5rem 1rem; - - &:before { - //tabler info icon as an svg component - content: url('data:image/svg+xml; utf8, '); - height: .75rem; - width: .75rem; - padding: .25rem 0; - margin-right: .25rem; - } - } - } - - &--not-valid { - - .input__message { - display: block; - background: rgba($error, .1); - - } - } - - &--valid { - - .input__message { - display: block; - background: rgba($success, .1); - - } - } -} \ No newline at end of file diff --git a/src/components/input/Input.tsx b/src/components/input/Input.tsx deleted file mode 100644 index 00ac30f6..00000000 --- a/src/components/input/Input.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import React, {DetailedHTMLProps, InputHTMLAttributes, ReactElement, ReactNode, RefAttributes} from "react"; -import "./Input.style.scss" -import {TablerIcon} from "@tabler/icons-react"; - - -export type InputChildType = InputControlType | InputDescType | InputLabelType - -export interface InputType { - children: ReactElement | ReactElement[] - //defaults to false - disabled?: boolean - //defaults to undefined - valid?: boolean | undefined -} - - -const Input: React.FC = (props: InputType) => { - - const {disabled= false, children, valid} = props - const childNodes = React.Children.toArray(children).map(value => { - if (React.isValidElement(value) && value.type == InputControl) - return React.cloneElement(value as ReactElement, { - "aria-disabled": disabled, - "disabled": disabled - }) - return value - }) - - return
- {childNodes} -
-} - -export type InputControlTypesType = "email" - | "date" - | "datetime-local" - | "hidden" - | "number" - | "password" - | "search" - | "tel" - | "text" - | "time" - | "url" - | "week" - -// params / props declaration for InputComponent -export interface InputControlType extends DetailedHTMLProps, HTMLInputElement> { - placeholder: string - //TODO: allow both as non required and make sure this does not result in errors inside the component - children?: ReactElement[] | ReactElement - //defaults to false - disabled?: boolean - //default is text type - type?: InputControlTypesType -} - -/** - * Component to create the input. Manages the icon and the success / failure message. - * Extends the normal HTMLInputElement to allow further adjustments. - * - * @example - * {@link InputControlIcon} {@link InputControlMessage} - * - * @since 0.1.0-pre-alpha - * @author Nico Sammito - */ -const InputControl: React.ForwardRefExoticComponent & RefAttributes> = React.forwardRef((props, ref) => { - - const {type, placeholder, children, disabled, ...args} = props - const childNodes = children && !Array.isArray(children) ? Array.of(children) : children; - const icon = childNodes ? childNodes.find(child => child.type == InputControlIcon) : null - const message = childNodes ? childNodes.find(child => child.type == InputControlMessage) : null - - const onFocusParam = args.onFocus - const onBlurParam = args.onBlur - - const onFocus = ((event: React.FocusEvent) => { - if (event.target.parentElement) - event.target.parentElement.classList.add("input__control--active") - if (onFocusParam) onFocusParam(event) - }) - const onBlur = ((event: React.FocusEvent) => { - if (event.target.parentElement) - event.target.parentElement.classList.remove("input__control--active") - if (onBlurParam) onBlurParam(event) - }) - - args.onFocus = onFocus - args.onBlur = onBlur - - return <> -
- {icon ?? null} - { - (event.target as HTMLInputElement).focus() - }}/> -
- {message ?? null} - -}) - -export type InputControlMessageType = { - children: string -} - -const InputControlMessage: React.FC = ({children}) => { - return

{children}

-} - -export type InputControlIconType = { - children: ReactElement -} - -const InputControlIcon: React.FC = ({children}) => { - return - {children} - -} - -export type InputLabelType = { - children: string -} - -const InputLabel: React.FC = ({children}) => { - return -} - - -export type InputDescType = { - children: string -} - -const InputDesc: React.FC = ({children}) => { - return

{children}

-} - -export default Object.assign(Input, { - Desc: InputDesc, - Label: InputLabel, - Control: Object.assign(InputControl, { - Message: InputControlMessage, - Icon: InputControlIcon - }) -}); \ No newline at end of file diff --git a/src/components/quote/Quote.stories.tsx b/src/components/quote/Quote.stories.tsx index d3c6a0a0..5db7aadb 100644 --- a/src/components/quote/Quote.stories.tsx +++ b/src/components/quote/Quote.stories.tsx @@ -51,7 +51,6 @@ export const QuoteWithLogo: QuoteStory = { gradient: true, borderColor: "secondary", firstGradientColor: "secondary", - gradientPosition: "bottom-left", inlineBorder: true } } @@ -74,7 +73,6 @@ export const QuoteWithoutLogo: QuoteStory = { borderColor: "secondary", firstGradientColor: "secondary", gradient: true, - gradientPosition: "bottom-left", inlineBorder: true } } diff --git a/src/components/tooltip/Tooltip.tsx b/src/components/tooltip/Tooltip.tsx index 721ec3ec..230c9587 100644 --- a/src/components/tooltip/Tooltip.tsx +++ b/src/components/tooltip/Tooltip.tsx @@ -1,4 +1,4 @@ -import React, {cloneElement, ReactElement, ReactNode} from "react"; +import React, {ReactElement, ReactNode} from "react"; import {getChild, getPositionAroundTarget} from "../../utils/utils"; import "./Tooltip.style.scss" diff --git a/src/index.ts b/src/index.ts index dc6e29d4..f282a727 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,6 @@ export { default as Col} from "./components/col/Col" export { default as Container} from "./components/container/Container" export { default as Dropdown} from "./components/dropdown/Dropdown" export { default as Text} from "./components/Text/Text" -export { default as Input } from "./components/input/Input"; export { default as ListGroup} from "./components/list-group/ListGroup" export { default as Menu} from "./components/menu/Menu" export { default as Popover} from "./components/popover/Popover" diff --git a/src/styles/_helpers.scss b/src/styles/_helpers.scss index c3f6750f..9818bac5 100644 --- a/src/styles/_helpers.scss +++ b/src/styles/_helpers.scss @@ -21,7 +21,7 @@ outline: none; } - &:active { + &:active, &--active, &:focus, &:focus-visible { border: 1px solid borderColor($color, true); } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 076fe32f..9c517248 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,4 @@ -import React, {CSSProperties, ReactNode, useState} from "react"; +import React, {CSSProperties, ReactNode} from "react"; import mergeProps from "merge-props"; import {Code0Component, Code0ComponentProps} from "./types";