diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 47162ec3af..c6d0fafa25 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -101,7 +101,7 @@ jobs: run: | if grep -q "igz-controls/" build.log; then echo -e "\033[31mModule not found: Error: Can't resolve 'igz-controls....'.\033[0m" - echo -e "\033[33mPlease check for changes in the 'dashboard-react-controls' repository.\033[0m" + echo -e "\033[33mPlease check for missing files in 'ui/src/igz-controls/'.\033[0m" echo "::set-output name=igz-controls-import-error::failure" echo "::error::Module not found: Error: Can't resolve 'igz-controls" else diff --git a/eslint.config.mjs b/eslint.config.mjs index d476aa233d..ac544bb3e0 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -4,13 +4,14 @@ import js from '@eslint/js' import react from 'eslint-plugin-react' import reactHooks from 'eslint-plugin-react-hooks' import eslintPluginImport from 'eslint-plugin-import' +import tseslint from 'typescript-eslint' export default [ { ignores: ['dist'] }, js.configs.recommended, eslintConfigPrettier, { - files: ['**/*.{js,jsx,ts,tsx}'], + files: ['**/*.{js,jsx}'], languageOptions: { ecmaVersion: 2021, globals: { @@ -49,9 +50,49 @@ export default [ } }, { - files: ["**/*.test.jsx"], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2021, + globals: { + ...globals.browser, + ...globals.jest, + ...globals.node + }, + parser: tseslint.parser, + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + }, + plugins: { + react: react, + 'react-hooks': reactHooks, + '@typescript-eslint': tseslint.plugin + }, + settings: { + react: { + version: 'detect' + } + }, + rules: { + ...reactHooks.configs.recommended.rules, + ...react.configs.recommended.rules, + 'react/react-in-jsx-scope': 'off', + 'react/no-unescaped-entities': 'off', + 'react/prop-types': 'off', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': process.env.NODE_ENV === 'production' ? 2 : 1, + 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 1, + 'no-console': process.env.NODE_ENV === 'production' ? 2 : 1, + quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }], + semi: ['error', 'never'] + } + }, + { + files: ['**/*.test.jsx'], rules: { - "import/named": "off" + 'import/named': 'off' } } ] diff --git a/jsconfig.json b/jsconfig.json index 7e84976282..b271628265 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -5,8 +5,11 @@ "@/*": [ "src/nextGenComponents/*" ], + "@igz-controls/*": [ + "src/igz-controls/nextGenComponents/*" + ], "igz-controls/*": [ - "node_modules/iguazio.dashboard-react-controls/dist/*" + "src/igz-controls/*" ] } } diff --git a/package-lock.json b/package-lock.json old mode 100755 new mode 100644 index 4713301561..840231758f --- a/package-lock.json +++ b/package-lock.json @@ -11,13 +11,20 @@ "dependencies": { "@dagrejs/dagre": "^1.1.5", "@monaco-editor/react": "^4.7.0", + "@radix-ui/react-checkbox": "^1.3.7", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-popover": "^1.1.19", + "@radix-ui/react-scroll-area": "^1.2.14", + "@radix-ui/react-select": "^2.3.3", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.17", "@radix-ui/react-tooltip": "^1.2.8", "@reduxjs/toolkit": "^1.9.5", + "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.14.6", "axios": "^1.18.0", "bfj": "^9.1.3", "camelcase": "^6.3.0", @@ -28,6 +35,7 @@ "clsx": "^2.1.1", "concurrently": "^6.4.2", "cronstrue": "^2.49.0", + "date-fns": "^4.4.0", "dotenv": "^10.0.0", "dotenv-expand": "^5.1.0", "file-saver": "^2.0.5", @@ -35,7 +43,6 @@ "final-form-arrays": "^3.1.0", "fs-extra": "^10.0.0", "identity-obj-proxy": "^3.0.0", - "iguazio.dashboard-react-controls": "^3.2.26", "is-wsl": "^1.1.0", "js-base64": "^2.6.4", "js-yaml": "^4.1.0", @@ -48,6 +55,7 @@ "prop-types": "^15.8.1", "qs": "^6.15.2", "react": "^18.2.0", + "react-day-picker": "^10.0.1", "react-dom": "^18.2.0", "react-final-form": "^6.5.9", "react-final-form-arrays": "^3.1.4", @@ -67,7 +75,8 @@ "semver": "^7.5.4", "tailwind-merge": "^3.4.0", "text-mask-addons": "^3.8.0", - "uuid": "^14.0.0" + "uuid": "^14.0.0", + "zustand": "^5.0.14" }, "devDependencies": { "@babel/core": "^7.16.0", @@ -145,6 +154,7 @@ "stylelint-scss": "^3.17.2", "tailwindcss": "^3.4.18", "tailwindcss-animate": "^1.0.7", + "typescript-eslint": "^8.64.0", "url-loader": "4.1.1", "vite": "^6.4.3", "vite-plugin-commonjs": "^0.10.4", @@ -195,9 +205,9 @@ } }, "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -219,9 +229,9 @@ } }, "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -5716,9 +5726,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -5728,7 +5738,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -5753,9 +5763,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -5808,31 +5818,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.6" + "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -5840,9 +5850,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@humanfs/core": { @@ -6516,9 +6526,9 @@ } }, "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "optional": true, @@ -6549,9 +6559,9 @@ "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", - "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", + "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { @@ -6607,61 +6617,17 @@ } } }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.15.tgz", - "integrity": "sha512-8A1zibu5skAQ+UVbaeNH5hVMibiFCRJzgMuM14LTWGttnTZKQL9jwYnhAbHRuxrtCqPXa4JvvnVUq1pTNgyZYw==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.16.tgz", + "integrity": "sha512-opfXRe6nnzyGmCDPx+l1Aqo/RbqWtQal2FnsBqF9hhePp6j0LsRoBaRxcMOlTv+uYTJVtWYZKg9t9wTe+BA/ZA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", + "@radix-ui/primitive": "1.1.5", "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-context": "1.2.0", "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-presence": "1.1.7", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-controllable-state": "1.2.3", "@radix-ui/react-use-layout-effect": "1.1.2" @@ -6682,13 +6648,13 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.11.tgz", - "integrity": "sha512-djW9+zeg137KQdlPtmE8xnaD+K2rcXXMWFrSg0hsmYZ6HRbdTA7tDHFgpaW9+huWVEu0RCabL+985T4TA0BE7g==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", + "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-context": "1.2.0", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0" }, @@ -6723,9 +6689,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", - "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", + "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6738,20 +6704,20 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.18.tgz", - "integrity": "sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.19.tgz", + "integrity": "sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", + "@radix-ui/primitive": "1.1.5", "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.14", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.11", + "@radix-ui/react-focus-scope": "1.1.12", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-presence": "1.1.7", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", @@ -6789,12 +6755,12 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.14.tgz", - "integrity": "sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", + "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", + "@radix-ui/primitive": "1.1.5", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2", @@ -6816,16 +6782,16 @@ } }, "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.19.tgz", - "integrity": "sha512-HZccBkbK0LOi8nYKIp5jll/zIRW0cCOmG6WWyqsSpmXCU+ZlcBbTqIwlBvPCu886C5RVu6c/kHV7xSP8IgYNHw==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.20.tgz", + "integrity": "sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", + "@radix-ui/primitive": "1.1.5", "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-context": "1.2.0", "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-menu": "2.1.19", + "@radix-ui/react-menu": "2.1.20", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-controllable-state": "1.2.3" }, @@ -6860,9 +6826,9 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.11.tgz", - "integrity": "sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", + "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", @@ -6903,25 +6869,25 @@ } }, "node_modules/@radix-ui/react-menu": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.19.tgz", - "integrity": "sha512-Mht9BVd1AIsNFVQr4KG3bIK7XQn5IXF0TL/2ObsrzOdc1loaly/+kBDL5roSCYn8j8XZkvpOD0WYLz2FQtH1Eg==", + "version": "2.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.20.tgz", + "integrity": "sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-context": "1.2.0", "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.14", + "@radix-ui/react-dismissable-layer": "1.1.15", "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.11", + "@radix-ui/react-focus-scope": "1.1.12", "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.2", + "@radix-ui/react-popper": "1.3.3", "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-presence": "1.1.7", "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.14", + "@radix-ui/react-roving-focus": "1.1.15", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-callback-ref": "1.1.2", "aria-hidden": "^1.2.4", @@ -6979,80 +6945,7 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", - "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-effect-event": "0.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", - "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "node_modules/@radix-ui/react-popper": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz", "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==", @@ -7084,61 +6977,6 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.2.tgz", - "integrity": "sha512-3QXNeMkdshed1MR3LNoiCirBywRFPkD8ETJa/HlPuLwSajaQixf2ro+isoDNJlGABg9ug41XuZpINZJIle4XWg==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-portal": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.13.tgz", @@ -7164,9 +7002,9 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", - "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", + "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" @@ -7210,20 +7048,22 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.14.tgz", - "integrity": "sha512-8Qcnx9447tx/aCBgw6Jenfqg4Skq+vqab9mCBmuGNipIS5YXvL275wbKEu7+ICYHIlAPgCduUMJH1XOYewKF6Q==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", + "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.11", + "@radix-ui/primitive": "1.1.5", + "@radix-ui/react-collection": "1.1.12", "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-context": "1.2.0", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", @@ -7271,50 +7111,6 @@ } } }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-select": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.3.tgz", @@ -7359,160 +7155,6 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-collection": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", - "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.15.tgz", - "integrity": "sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-effect-event": "0.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.12.tgz", - "integrity": "sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-popper": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.3.tgz", - "integrity": "sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.11", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-separator": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.11.tgz", @@ -7531,146 +7173,43 @@ "@types/react": { "optional": true }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", - "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.17", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.17.tgz", - "integrity": "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.7", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-roving-focus": "1.1.15", - "@radix-ui/react-use-controllable-state": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.5.tgz", - "integrity": "sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-collection": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.12.tgz", - "integrity": "sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.2.0", - "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-slot": "1.3.0" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-context": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.0.tgz", - "integrity": "sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { + "@types/react-dom": { "optional": true } } }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-presence": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.7.tgz", - "integrity": "sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==", + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { "optional": true - }, - "@types/react-dom": { - "optional": true } } }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.15.tgz", - "integrity": "sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==", + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.17.tgz", + "integrity": "sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.5", - "@radix-ui/react-collection": "1.1.12", - "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.2.0", "@radix-ui/react-direction": "1.1.2", "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.7", "@radix-ui/react-primitive": "2.1.7", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-is-hydrated": "0.1.1", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-roving-focus": "1.1.15", + "@radix-ui/react-use-controllable-state": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -7688,19 +7227,19 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.11.tgz", - "integrity": "sha512-8XZ6Py3y3W2nEzAUGCN5cfVKaUi+CVApcz1d6lrNVVf2hvYEixMRkq8k9ggPKnQUpRRuOV5avt8uvxViH2jLwA==", + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.12.tgz", + "integrity": "sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", + "@radix-ui/primitive": "1.1.5", "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.14", + "@radix-ui/react-context": "1.2.0", + "@radix-ui/react-dismissable-layer": "1.1.15", "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.2", + "@radix-ui/react-popper": "1.3.3", "@radix-ui/react-portal": "1.1.13", - "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-presence": "1.1.7", "@radix-ui/react-primitive": "2.1.7", "@radix-ui/react-slot": "1.3.0", "@radix-ui/react-use-controllable-state": "1.2.3", @@ -8604,9 +8143,9 @@ "license": "MIT" }, "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.12.tgz", + "integrity": "sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==", "dev": true, "license": "MIT" }, @@ -9666,15 +9205,6 @@ "@swc/counter": "^0.1.3" } }, - "node_modules/@tabby_ai/hijri-converter": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@tabby_ai/hijri-converter/-/hijri-converter-1.0.5.tgz", - "integrity": "sha512-r5bClKrcIusDoo049dSL8CawnHR6mRdDwhlQuIgZRNty68q0x8k3Lf1BtPAMxRf/GgnHBnIO4ujd3+GQdLWzxQ==", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@tanstack/react-table": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", @@ -9696,12 +9226,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.14.5", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.5.tgz", - "integrity": "sha512-4EKRXh7zBLkbKbFmG3AUVkircuHd+7OdT1pocJSepxtfBd3qnrJgJ5rtPkRYyo9fmyVb2+pI2xPy5oYvMLQy6A==", + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.14.6.tgz", + "integrity": "sha512-4+Uq8m0/gzO4kMCHUEpTtGX1RnONK0C+g88b2ltwPMWUBiaVarBuWKoPJaz7gj1cKCVRAdyu+U8GcKhwCc2beA==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.17.3" + "@tanstack/virtual-core": "3.17.4" }, "funding": { "type": "github", @@ -9726,9 +9256,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.3.tgz", - "integrity": "sha512-8Np/TFELpI0ySuJoVmjvOrQYXH/8sTX0Biv9szhFhY39xOdAAY+smrMxjxOum/ux3eM8MUJQsEJ0/R0UpvC8dw==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.4.tgz", + "integrity": "sha512-nGm5KteqxasUdThLc2izl6dHUqLv0LQj7Nuyo5gYalTPf/U8a9ermvsl7reT+6ioBW1l8WfpP/mcU338nLXpqw==", "license": "MIT", "funding": { "type": "github", @@ -10104,9 +9634,9 @@ "license": "MIT" }, "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", "license": "MIT" }, "node_modules/@types/d3-scale": { @@ -10305,9 +9835,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", - "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", "dev": true, "license": "MIT", "dependencies": { @@ -10399,6 +9929,249 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -10445,16 +10218,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -10473,13 +10246,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.9", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -10510,9 +10283,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -10523,13 +10296,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -10537,14 +10310,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -10553,9 +10326,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -10563,13 +10336,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -10800,9 +10573,9 @@ "peer": true }, "node_modules/@zip.js/zip.js": { - "version": "2.8.26", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.26.tgz", - "integrity": "sha512-RQ4h9F6DOiHxpdocUDrOl6xBM+yOtz+LkUol47AVWcfebGBDpZ7w7Xvz9PS24JgXvLGiXXzSAfdCdVy1tPlaFA==", + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.8.31.tgz", + "integrity": "sha512-2NLmow9ax/5IdBbJKxNQp3Ur9mNxmgLfN2Yp/FKNh1ZIGs3OYkiC3AIUfIZouTPSgeW5+F1bzTAZAyD2Y4ZfFA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -11321,9 +11094,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", - "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.4.tgz", + "integrity": "sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==", "dev": true, "funding": [ { @@ -11341,8 +11114,8 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.4", - "caniuse-lite": "^1.0.30001799", + "browserslist": "^4.28.6", + "caniuse-lite": "^1.0.30001806", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -11824,9 +11597,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.40", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", - "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -11940,9 +11713,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.6", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", + "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==", "dev": true, "license": "MIT", "dependencies": { @@ -12022,9 +11795,9 @@ "peer": true }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -12042,10 +11815,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -12201,9 +11974,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001800", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", - "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -13187,9 +12960,9 @@ } }, "node_modules/cssstyle/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -13550,12 +13323,6 @@ "url": "https://github.com/sponsors/kossnocorp" } }, - "node_modules/date-fns-jalali": { - "version": "4.1.0-0", - "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz", - "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -13940,9 +13707,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", "peer": true, "optionalDependencies": { @@ -14025,9 +13792,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.384", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.384.tgz", - "integrity": "sha512-g6KAKY1vkYsADvSPWvdJsuYT0ixdcu6lUtD9P/wJKGBEDlZVXh2AX42j1mPqqaQPDluWjara9ziQ7xqAeXCt5A==", + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", "dev": true, "license": "ISC" }, @@ -14067,9 +13834,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.24.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz", - "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==", + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", "dev": true, "license": "MIT", "peer": true, @@ -14243,9 +14010,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz", - "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -14446,9 +14213,9 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", "dependencies": { @@ -14457,8 +14224,8 @@ "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -14565,9 +14332,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", - "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -16420,9 +16187,9 @@ } }, "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -16647,59 +16414,6 @@ "dev": true, "license": "ISC" }, - "node_modules/iguazio.dashboard-react-controls": { - "version": "3.2.26", - "resolved": "https://registry.npmjs.org/iguazio.dashboard-react-controls/-/iguazio.dashboard-react-controls-3.2.26.tgz", - "integrity": "sha512-S4mriqVP4EsTf0iC6R0yJ++sMNA4/BbOlxe8rDwVUPpghi8XDeXZrn515xesIhPiVHjkgaccf70Qg0X4Zb1Rcg==", - "dependencies": { - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@tanstack/react-table": "^8.21.3", - "@tanstack/react-virtual": "^3.13.19", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "lucide-react": "^0.552.0", - "react-day-picker": "^9.13.0", - "tailwind-merge": "^3.3.1", - "zustand": "^5.0.8" - }, - "peerDependencies": { - "@reduxjs/toolkit": "*", - "classnames": "*", - "final-form": "*", - "final-form-arrays": "*", - "lodash": "*", - "moment": "*", - "prop-types": "*", - "react": "*", - "react-dom": "*", - "react-final-form": "*", - "react-final-form-arrays": "*", - "react-modal-promise": "*", - "react-redux": "*", - "react-router-dom": "*", - "react-transition-group": "*" - } - }, - "node_modules/iguazio.dashboard-react-controls/node_modules/lucide-react": { - "version": "0.552.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.552.0.tgz", - "integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -18202,9 +17916,9 @@ } }, "node_modules/lucide-react": { - "version": "1.23.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.23.0.tgz", - "integrity": "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz", + "integrity": "sha512-YT6mBD8lGKkg4nM39enlm94/sfJIiW0YKUT60fBy4YK8tai31ylg1VhGNWxkpSKHo9UagfnZqwIff3HTDQwXeA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -18896,9 +18610,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -19058,9 +18772,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, "license": "MIT", "engines": { @@ -19675,9 +19389,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", - "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { @@ -19921,9 +19635,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "funding": [ { "type": "opencollective", @@ -21639,9 +21353,9 @@ } }, "node_modules/prettier": { - "version": "3.9.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz", - "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz", + "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==", "dev": true, "license": "MIT", "bin": { @@ -22027,15 +21741,13 @@ "license": "MIT" }, "node_modules/react-day-picker": { - "version": "9.14.0", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.14.0.tgz", - "integrity": "sha512-tBaoDWjPwe0M5pGrum4H0SR6Lyk+BO9oHnp9JbKpGKW2mlraNPgP9BMfsg5pWpwrssARmeqk7YBl2oXutZTaHA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-10.0.1.tgz", + "integrity": "sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==", "license": "MIT", "dependencies": { "@date-fns/tz": "^1.4.1", - "@tabby_ai/hijri-converter": "1.0.5", - "date-fns": "^4.1.0", - "date-fns-jalali": "4.1.0-0" + "date-fns": "^4.1.0" }, "engines": { "node": ">=18" @@ -22045,7 +21757,13 @@ "url": "https://github.com/sponsors/gpbl" }, "peerDependencies": { + "@types/react": ">=16.8.0", "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/react-dev-utils": { @@ -22363,9 +22081,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.0.tgz", - "integrity": "sha512-t/Gp57qSCxGQ52ckhz+8lM7dnuymeU95TEzl2U203qEbGkSLHrtm7US2/ANzq/zOlja3CwPTAfCDuh1unv9mfw==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.2.tgz", + "integrity": "sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==", "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -22511,9 +22229,9 @@ } }, "node_modules/read-package-up/node_modules/type-fest": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", - "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { @@ -22662,9 +22380,9 @@ } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", - "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz", + "integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==", "dev": true, "license": "(MIT OR CC0-1.0)", "dependencies": { @@ -23513,9 +23231,9 @@ "license": "MIT" }, "node_modules/selenium-webdriver": { - "version": "4.45.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.45.0.tgz", - "integrity": "sha512-Cb2nqvJiwXVOtRTCYHX9D1FJR5+Ls7aL3Nev0t6n4CpXsQ//YGiiUmSCbvTDDeLtbV85SZ46qmLab4SIYKXWRw==", + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.46.0.tgz", + "integrity": "sha512-UlTkgnx9y+bf3QxFsrgUyMqC2oy1Yf+qcOs7xIC/XfsUPv/ow7Sicx6SJ7AeOn2/Z+xF1M8SLqyn983wipI82w==", "dev": true, "funding": [ { @@ -23721,9 +23439,9 @@ } }, "node_modules/shell-quote": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", - "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", "dev": true, "license": "MIT", "engines": { @@ -24105,9 +23823,9 @@ } }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, @@ -24755,9 +24473,9 @@ "dev": true }, "node_modules/svgo": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.2.tgz", - "integrity": "sha512-TyzE4NVGLUFy+H/Uy4N6c3G0HEeprsVfge6Lmq+0FdQQ/zqoVYB62IsBZORsiL+o96s6ff/V6/3UQo/C0cgCAA==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.3.tgz", + "integrity": "sha512-5EZD0pafXX6PphdwOGCiVLDSaV1xyuQao2blHajHLsPxr07q4mmEjdtXEWgG07ae2mIz8Ex2CDXNCTiXhy3Khw==", "dev": true, "license": "MIT", "dependencies": { @@ -25026,9 +24744,9 @@ "license": "MIT" }, "node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "dev": true, "license": "BSD-2-Clause", "peer": true, @@ -25207,9 +24925,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -25230,22 +24948,22 @@ } }, "node_modules/tldts": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.5.tgz", - "integrity": "sha512-RfEzKWcq5fHUOFq7J3rl3Oz6ylKGtcHqUznzj4EcXsxLSIjJcvpbXAQtWGeJQ0xKnimR5e0Cn+cn9TssfMzm+g==", + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.9.tgz", + "integrity": "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.4.5" + "tldts-core": "^7.4.9" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.5.tgz", - "integrity": "sha512-pGrwzZDvPwKe+7NNUqAunb6rqTfynr0VOUhCMdqbu5xlvNiszsAJygRzwvpVycdzejlbpY+SWJOn+s75Og7FEA==", + "version": "7.4.9", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.9.tgz", + "integrity": "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg==", "dev": true, "license": "MIT" }, @@ -25307,9 +25025,9 @@ } }, "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -25375,6 +25093,19 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "license": "MIT" }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-dedent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", @@ -25577,6 +25308,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.64.0.tgz", + "integrity": "sha512-0qg+pDNMnqYzqH9AnNK+39tejHvsShUOUUoRUgtnTGE7QuMZhiFDnozq8nHJVq+Wae6NMLKNWLg5WmkcC/ndyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.64.0", + "@typescript-eslint/parser": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -26253,9 +26008,9 @@ } }, "node_modules/vite-plugin-svgr/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -26284,9 +26039,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -26297,19 +26052,19 @@ } }, "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -26337,12 +26092,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -26387,16 +26142,16 @@ } }, "node_modules/vitest/node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -26454,9 +26209,9 @@ } }, "node_modules/webpack": { - "version": "5.108.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.3.tgz", - "integrity": "sha512-hOpaCHmQVVY66IVTjofnH14IgSdmod2aquSGHGuYig/OIdWge01Hk2Wt988DZcwXumFUT4+FvJY5N+ikl8o/ww==", + "version": "5.108.4", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", + "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", "dev": true, "license": "MIT", "peer": true, @@ -26501,9 +26256,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", - "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", "dev": true, "license": "MIT", "peer": true, @@ -26579,9 +26334,9 @@ } }, "node_modules/webpack/node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT", "peer": true @@ -26879,9 +26634,9 @@ } }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 3724b8e2ba..148b31b581 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,21 @@ "dependencies": { "@dagrejs/dagre": "^1.1.5", "@monaco-editor/react": "^4.7.0", + "@radix-ui/react-checkbox": "^1.3.7", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-popover": "^1.1.19", + "@radix-ui/react-scroll-area": "^1.2.14", + "@radix-ui/react-select": "^2.3.3", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.17", "@radix-ui/react-tooltip": "^1.2.8", "@reduxjs/toolkit": "^1.9.5", "axios": "^1.18.0", + "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.14.6", "bfj": "^9.1.3", "camelcase": "^6.3.0", "chart.js": "^4.4.2", @@ -23,6 +30,7 @@ "clsx": "^2.1.1", "concurrently": "^6.4.2", "cronstrue": "^2.49.0", + "date-fns": "^4.4.0", "dotenv": "^10.0.0", "dotenv-expand": "^5.1.0", "file-saver": "^2.0.5", @@ -30,7 +38,6 @@ "final-form-arrays": "^3.1.0", "fs-extra": "^10.0.0", "identity-obj-proxy": "^3.0.0", - "iguazio.dashboard-react-controls": "^3.2.26", "is-wsl": "^1.1.0", "js-base64": "^2.6.4", "js-yaml": "^4.1.0", @@ -43,6 +50,7 @@ "prop-types": "^15.8.1", "qs": "^6.15.2", "react": "^18.2.0", + "react-day-picker": "^10.0.1", "react-dom": "^18.2.0", "react-final-form": "^6.5.9", "react-final-form-arrays": "^3.1.4", @@ -62,7 +70,8 @@ "semver": "^7.5.4", "tailwind-merge": "^3.4.0", "text-mask-addons": "^3.8.0", - "uuid": "^14.0.0" + "uuid": "^14.0.0", + "zustand": "^5.0.14" }, "overrides": { "uuid": "^14.0.0", @@ -100,9 +109,7 @@ "report": "node tests/report.js", "test:regression": "npm run test:ui && npm run report", "start:regression": "concurrently \"npm:mock-server\" \"npm:start\" \"npm:test:regression\"", - "ui-steps": "cross-env BABEL_ENV=test; cross-env NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js", - "nli": "npm link iguazio.dashboard-react-controls", - "nui": "npm unlink iguazio.dashboard-react-controls" + "ui-steps": "cross-env BABEL_ENV=test; cross-env NODE_ENV=test; npx -p @babel/core -p @babel/node babel-node --presets @babel/preset-env scripts/collectUITestsSteps.js" }, "devDependencies": { "@babel/core": "^7.16.0", @@ -180,6 +187,7 @@ "stylelint-scss": "^3.17.2", "tailwindcss": "^3.4.18", "tailwindcss-animate": "^1.0.7", + "typescript-eslint": "^8.64.0", "url-loader": "4.1.1", "vite": "^6.4.3", "vite-plugin-commonjs": "^0.10.4", diff --git a/src/App.jsx b/src/App.jsx index 494a38c9fa..363cfdb056 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -417,7 +417,7 @@ const App = () => { ) return ( -
+
{isHeaderShown &&
}
diff --git a/src/igz-controls/components/ActionsMenu/ActionsMenu.jsx b/src/igz-controls/components/ActionsMenu/ActionsMenu.jsx new file mode 100644 index 0000000000..6a4d80c9d8 --- /dev/null +++ b/src/igz-controls/components/ActionsMenu/ActionsMenu.jsx @@ -0,0 +1,194 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useCallback, useEffect, useRef, useState } from 'react' +import PropTypes from 'prop-types' +import { isEmpty } from 'lodash' +import classnames from 'classnames' + +import ActionsMenuItem from '../../elements/ActionsMenuItem/ActionsMenuItem' +import PopUpDialog from '../PopUpDialog/PopUpDialog' +import RoundedIcon from '../RoundedIcon/RoundedIcon' + +import { ACTIONS_MENU } from '../../types' + +import ActionMenuIcon from '../../images/elipsis.svg?react' + +import './actionsMenu.scss' + +const ActionsMenu = ({ + dataItem = {}, + menu, + menuPosition = '', + time = 100, + withQuickActions = false +}) => { + const [[actionMenu, quickActions], setActionMenuContent] = useState(menu) + const [isIconDisplayed, setIsIconDisplayed] = useState(false) + const [isShowMenu, setIsShowMenu] = useState(false) + const actionMenuRef = useRef() + const actionMenuBtnRef = useRef() + const dropDownMenuRef = useRef() + const mainActionsWrapperRef = useRef() + + let idTimeout = null + + const actionMenuClassNames = classnames( + 'actions-menu__container', + withQuickActions && + (actionMenu.length > 0 || quickActions.length > 1) && + 'actions-menu__container_extended', + isShowMenu && 'actions-menu__container-active' + ) + + const clickHandler = useCallback( + event => { + if (!event.target.closest('.actions-menu-button')) { + setIsShowMenu(false) + } + }, + [setIsShowMenu] + ) + + const scrollHandler = useCallback( + event => { + if (!event.target.closest('.actions-menu__body')) { + setIsShowMenu(false) + } + }, + [setIsShowMenu] + ) + + const onMouseOut = () => { + if (isShowMenu) { + idTimeout = setTimeout(() => { + setIsShowMenu(false) + }, time) + } + } + + const handleMouseOver = event => { + if (mainActionsWrapperRef.current?.contains(event.target)) { + setIsShowMenu(false) + } + + if (idTimeout) clearTimeout(idTimeout) + } + + useEffect(() => { + if (!isEmpty(dataItem)) { + setActionMenuContent(typeof menu === 'function' ? menu(dataItem, menuPosition) : menu) + } + }, [dataItem, menu, menuPosition]) + + useEffect(() => { + setIsIconDisplayed(actionMenu?.some(menuItem => menuItem.icon)) + }, [actionMenu]) + + useEffect(() => { + window.addEventListener('click', clickHandler) + window.addEventListener('scroll', scrollHandler, true) + + return () => { + window.removeEventListener('click', clickHandler) + window.removeEventListener('scroll', scrollHandler, true) + } + }, [clickHandler, scrollHandler]) + + return ( +
+ {withQuickActions && ( +
+ {quickActions.map( + mainAction => + !mainAction.hidden && ( + mainAction.onClick(dataItem)} + tooltipText={mainAction.label} + key={mainAction.label} + > + {mainAction.icon} + + ) + )} +
+ )} + {actionMenu.length > 0 && ( +
+ { + setIsShowMenu(prevValue => !prevValue) + }} + ref={actionMenuBtnRef} + tooltipText="More actions" + > + + + {isShowMenu && ( + +
    + {actionMenu.map( + (menuItem, idx) => + !menuItem.hidden && ( + + ) + )} +
+
+ )} +
+ )} +
+ ) +} + +ActionsMenu.propTypes = { + dataItem: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), + menu: ACTIONS_MENU.isRequired, + menuPosition: PropTypes.string, + time: PropTypes.number, + withQuickActions: PropTypes.bool +} + +export default ActionsMenu diff --git a/src/igz-controls/components/ActionsMenu/actionsMenu.scss b/src/igz-controls/components/ActionsMenu/actionsMenu.scss new file mode 100644 index 0000000000..b5024b3361 --- /dev/null +++ b/src/igz-controls/components/ActionsMenu/actionsMenu.scss @@ -0,0 +1,60 @@ +@use '../../scss/colors'; + +.actions-menu { + position: relative; + + &__container { + position: relative; + display: none; + + &_extended { + position: absolute; + right: 0; + display: none; + align-items: center; + justify-content: center; + background-color: colors.$ghostWhite; + height: 100%; + + &:before { + content: ''; + width: 30px; + height: 100%; + position: absolute; + display: block; + left: -30px; + background: linear-gradient(90deg, rgba(255, 255, 255, 0) 0%, rgba(245, 247, 255, 1) 100%); + } + + .actions-menu { + padding: 0 5px 0 0; + } + } + + &-active { + display: flex; + } + } + + &__main-actions-wrapper { + display: flex; + align-items: center; + justify-content: center; + } + + &__body { + min-width: 150px; + max-width: 250px; + + .pop-up-dialog { + width: 100%; + padding: 0; + } + } + + &__list { + list-style-type: none; + margin: 0; + padding: 0; + } +} diff --git a/src/igz-controls/components/Backdrop/Backdrop.jsx b/src/igz-controls/components/Backdrop/Backdrop.jsx new file mode 100644 index 0000000000..02fcbb8f77 --- /dev/null +++ b/src/igz-controls/components/Backdrop/Backdrop.jsx @@ -0,0 +1,46 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useRef } from 'react' +import PropTypes from 'prop-types' +import { CSSTransition } from 'react-transition-group' + +import './Backdrop.scss' + +const Backdrop = ({ duration = 300, show = false, onClose = null }) => { + const nodeRef = useRef(null) + + return ( + +
+
+ ) +} + +Backdrop.propTypes = { + duration: PropTypes.number, + onClose: PropTypes.func, + show: PropTypes.bool +} + +export default Backdrop diff --git a/src/igz-controls/components/Backdrop/Backdrop.scss b/src/igz-controls/components/Backdrop/Backdrop.scss new file mode 100644 index 0000000000..036a91b12e --- /dev/null +++ b/src/igz-controls/components/Backdrop/Backdrop.scss @@ -0,0 +1,32 @@ +@use '../../scss/colors'; + +.backdrop { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: colors.$black; + z-index: 9; + + &-transition { + &-enter { + opacity: 0; + } + + &-enter-active, + &-enter-done { + opacity: 0.5; + transition: opacity 0.3s ease-in-out; + } + + &-exit { + opacity: 0.5; + } + + &-exit-active { + opacity: 0; + transition: opacity 0.3s ease-in-out; + } + } +} diff --git a/src/igz-controls/components/BlockerSpy/BlockerSpy.jsx b/src/igz-controls/components/BlockerSpy/BlockerSpy.jsx new file mode 100644 index 0000000000..db1028d710 --- /dev/null +++ b/src/igz-controls/components/BlockerSpy/BlockerSpy.jsx @@ -0,0 +1,39 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useEffect } from 'react' +import PropTypes from 'prop-types' +import { useBlocker } from 'react-router-dom' + +const BlockerSpy = ({ setBlocker, shouldBlock }) => { + const blocker = useBlocker(shouldBlock) + + useEffect(() => { + setBlocker(blocker) + }, [setBlocker, blocker]) + + return <> +} + +BlockerSpy.propTypes = { + setBlocker: PropTypes.func.isRequired, + shouldBlock: PropTypes.func.isRequired +} + +export default BlockerSpy diff --git a/src/igz-controls/components/Button/Button.jsx b/src/igz-controls/components/Button/Button.jsx new file mode 100644 index 0000000000..542eda8a51 --- /dev/null +++ b/src/igz-controls/components/Button/Button.jsx @@ -0,0 +1,73 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { forwardRef } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' + +import Tooltip from '../Tooltip/Tooltip' +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' + +import { BUTTON_VARIANTS, DENSITY } from '../../types' +import { TERTIARY_BUTTON } from '../../constants' + +import './Button.scss' + +let Button = ( + { + className = '', + density = 'normal', + icon = null, + iconPosition = 'left', + id = 'btn', + label = 'Button', + tooltip = '', + variant = TERTIARY_BUTTON, + ...restProps + }, + ref +) => { + const buttonClassName = classNames('btn', `btn-${variant}`, `btn-${density}`, className) + + return ( + + ) +} + +Button = forwardRef(Button) + +Button.displayName = 'Button' + +Button.propTypes = { + className: PropTypes.string, + density: DENSITY, + icon: PropTypes.element, + iconPosition: PropTypes.oneOf(['left', 'right']), + id: PropTypes.string, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), + tooltip: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), + variant: BUTTON_VARIANTS +} + +export default Button diff --git a/src/igz-controls/components/Button/Button.scss b/src/igz-controls/components/Button/Button.scss new file mode 100644 index 0000000000..0f199c6308 --- /dev/null +++ b/src/igz-controls/components/Button/Button.scss @@ -0,0 +1,171 @@ +@use '../../scss/variables'; +@use '../../scss/colors'; +@use '../../scss/borders'; + +.btn { + display: flex; + align-items: center; + justify-content: center; + min-width: 90px; + height: variables.$fieldNormal; + padding: 0 16px; + color: colors.$white; + font-weight: 500; + font-size: 0.875rem; + font-style: normal; + border: borders.$transparentBorder; + border-radius: variables.$mainBorderRadius; + + &-dense { + height: variables.$fieldDense; + } + + &-normal { + height: variables.$fieldNormal; + } + + &-medium { + height: variables.$fieldMedium; + } + + &-chunky { + height: variables.$fieldChunky; + } + + svg { + & > * { + fill: currentColor; + } + } + + &:focus { + border-color: rgba(colors.$black, 0.4); + } + + &:active { + border-color: rgba(colors.$black, 0.4); + } + + &:disabled { + color: colors.$spunPearl; + background: colors.$white; + cursor: not-allowed; + + svg { + & > * { + fill: colors.$alto; + } + } + } + + :not(:last-child) { + margin-right: 10px; + } + + :last-child { + margin: 0; + } + + &-secondary { + background: colors.$brightTurquoise; + + &:hover:not(:disabled) { + background: colors.$javaLight; + } + + &:active:not(:disabled) { + background: colors.$mountainMeadow; + } + + &:disabled { + border: 1px solid colors.$brightTurquoise; + } + } + + &-tertiary { + color: colors.$primary; + background: colors.$white; + border: borders.$primaryBorder; + + svg { + & > * { + fill: colors.$primary; + } + } + + &:hover:not(:disabled) { + background: colors.$alabaster; + } + + &:active:not(:disabled) { + background: colors.$mercury; + } + + &:disabled { + border: borders.$primaryBorder; + } + } + + &-primary { + color: colors.$white; + background: colors.$malibu; + + &:hover:not(:disabled) { + background: colors.$cornflowerBlue; + } + + &:active:not(:disabled) { + background: colors.$indigo; + } + + &:disabled { + border: 1px solid colors.$malibu; + } + } + + &-danger { + color: colors.$white; + background: colors.$amaranth; + + &:hover:not(:disabled) { + background: colors.$ceriseRed; + } + + &:active:not(:disabled) { + background: colors.$maroonFlash; + } + + &:disabled { + border: 1px solid colors.$amaranth; + } + } + + &-label { + color: colors.$primary; + background: transparent; + border: 0; + + svg { + & > * { + fill: colors.$primary; + } + } + + &:focus:not(:disabled) { + border-color: transparent; + } + + &:hover:not(:disabled) { + color: colors.$topaz; + } + + &:active:not(:disabled) { + color: colors.$black; + border-color: transparent; + } + + &:disabled { + border-color: transparent; + } + } +} diff --git a/src/igz-controls/components/ConfirmDialog/ConfirmDialog.jsx b/src/igz-controls/components/ConfirmDialog/ConfirmDialog.jsx new file mode 100644 index 0000000000..5f2a7dda9d --- /dev/null +++ b/src/igz-controls/components/ConfirmDialog/ConfirmDialog.jsx @@ -0,0 +1,115 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +import Button from '../Button/Button' +import PopUpDialog from '../PopUpDialog/PopUpDialog' + +import { + CONFIRM_DIALOG_CANCEL_BUTTON, + CONFIRM_DIALOG_MESSAGE, + CONFIRM_DIALOG_SUBMIT_BUTTON +} from '../../types' + +import './confirmDialog.scss' + +const ConfirmDialog = ({ + cancelButton = null, + children = null, + className = '', + closePopUp = null, + confirmButton = null, + customPosition = {}, + header = '', + isOpen = false, + message = '', + messageOnly = false, + onResolve = null +}) => { + const messageClassNames = classnames( + 'confirm-dialog__message', + messageOnly && 'confirm-dialog__message-only' + ) + + const handleCancelDialog = event => { + onResolve && onResolve() + cancelButton.handler && cancelButton.handler(event) + } + + const handleCloseDialog = event => { + onResolve && onResolve() + closePopUp && closePopUp(event) + } + + const handleConfirmDialog = event => { + onResolve && onResolve() + confirmButton.handler && confirmButton.handler(event) + } + + return ( + isOpen && ( + +
+ {message &&
{message}
} + {children &&
{children}
} +
+ {cancelButton && ( +
+
+
+ ) + ) +} + +ConfirmDialog.propTypes = { + cancelButton: CONFIRM_DIALOG_CANCEL_BUTTON, + children: PropTypes.node, + className: PropTypes.string, + closePopUp: PropTypes.func, + confirmButton: CONFIRM_DIALOG_SUBMIT_BUTTON, + customPosition: PropTypes.object, + header: PropTypes.string, + isOpen: PropTypes.bool, + message: CONFIRM_DIALOG_MESSAGE, + messageOnly: PropTypes.bool, + onResolve: PropTypes.func +} + +export default ConfirmDialog diff --git a/src/igz-controls/components/ConfirmDialog/confirmDialog.scss b/src/igz-controls/components/ConfirmDialog/confirmDialog.scss new file mode 100644 index 0000000000..f8c5683b93 --- /dev/null +++ b/src/igz-controls/components/ConfirmDialog/confirmDialog.scss @@ -0,0 +1,24 @@ +@use '../../scss/colors'; + +.confirm-dialog { + color: colors.$primary; + + &__message { + font-size: 15px; + line-height: 24px; + + &-only { + font-size: 22px; + } + } + + &__btn-container { + display: flex; + justify-content: flex-end; + margin-top: 20px; + } + + &__body { + margin: 20px 0; + } +} diff --git a/src/igz-controls/components/CopyToClipboard/CopyToClipboard.jsx b/src/igz-controls/components/CopyToClipboard/CopyToClipboard.jsx new file mode 100644 index 0000000000..32c8bfbe85 --- /dev/null +++ b/src/igz-controls/components/CopyToClipboard/CopyToClipboard.jsx @@ -0,0 +1,87 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import PropTypes from 'prop-types' +import { useMemo } from 'react' +import { useDispatch } from 'react-redux' + +import RoundedIcon from '../RoundedIcon/RoundedIcon' +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' +import Tooltip from '../Tooltip/Tooltip' + +import { setNotification } from '../../reducers/notificationReducer' +import { showErrorNotification } from '../../utils/notification.util' + +import Copy from '../../images/copy-to-clipboard-icon.svg?react' + +const CopyToClipboard = ({ + children = null, + className = '', + disabled = false, + textToCopy = '', + tooltipText +}) => { + const dispatch = useDispatch() + const copyIsDisabled = useMemo(() => disabled || !textToCopy, [disabled, textToCopy]) + + const copyToClipboard = textToCopy => { + navigator.clipboard + .writeText(textToCopy) + .then(() => { + dispatch( + setNotification({ + status: 200, + id: Math.random(), + message: 'Copied to clipboard successfully' + }) + ) + }) + .catch(error => { + showErrorNotification(dispatch, error, '', 'Copy to clipboard failed') + }) + } + + return ( +
+ {children ? ( + } textShow> + copyToClipboard(textToCopy)}>{children} + + ) : ( + copyToClipboard(textToCopy)} + disabled={copyIsDisabled} + > + + + )} +
+ ) +} + +CopyToClipboard.propTypes = { + children: PropTypes.oneOfType([PropTypes.string, PropTypes.array, PropTypes.element]), + className: PropTypes.string, + disabled: PropTypes.bool, + textToCopy: PropTypes.string, + tooltipText: PropTypes.string.isRequired +} + +export default CopyToClipboard diff --git a/src/igz-controls/components/ErrorMessage/ErrorMessage.jsx b/src/igz-controls/components/ErrorMessage/ErrorMessage.jsx new file mode 100644 index 0000000000..a48ed3295e --- /dev/null +++ b/src/igz-controls/components/ErrorMessage/ErrorMessage.jsx @@ -0,0 +1,56 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React from 'react' +import PropTypes from 'prop-types' + +import Tooltip from '../Tooltip/Tooltip' +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' + +import UnsuccessAlert from '../../images/unsuccess_alert.svg?react' +import Close from '../../images/close.svg?react' + +import './errorMessage.scss' + +const ErrorMessage = ({ closeError = null, message }) => { + return ( +
+
+
+ +
+
{message}
+
+ {closeError && ( + + )} +
+ ) +} + +ErrorMessage.propTypes = { + closeError: PropTypes.func, + message: PropTypes.string.isRequired +} + +export default ErrorMessage diff --git a/src/igz-controls/components/ErrorMessage/errorMessage.scss b/src/igz-controls/components/ErrorMessage/errorMessage.scss new file mode 100644 index 0000000000..9e026f4937 --- /dev/null +++ b/src/igz-controls/components/ErrorMessage/errorMessage.scss @@ -0,0 +1,30 @@ +@use '../../scss/borders'; +@use '../../scss/colors'; + +.error { + display: flex; + justify-content: space-between; + padding: 10px 14px; + color: colors.$amaranth; + background-color: rgba(colors.$amaranth, 0.1); + border: borders.$errorBorder; + + &__data { + display: flex; + align-items: center; + } + + &__message { + margin-right: 10px; + word-break: break-word; + } + + &__icon { + width: 22px; + height: 22px; + margin-right: 10px; + padding: 5px; + background-color: colors.$burntSienna; + border-radius: 50%; + } +} diff --git a/src/igz-controls/components/FormCheckBox/FormCheckBox.jsx b/src/igz-controls/components/FormCheckBox/FormCheckBox.jsx new file mode 100644 index 0000000000..88fc1e5a1e --- /dev/null +++ b/src/igz-controls/components/FormCheckBox/FormCheckBox.jsx @@ -0,0 +1,77 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useRef } from 'react' +import PropTypes from 'prop-types' +import { Field } from 'react-final-form' +import classNames from 'classnames' + +import './formCheckBox.scss' + +let FormCheckBox = ({ + children = null, + className = '', + highlightLabel = false, + label = '', + name, + readOnly = false, + ...inputProps +}) => { + const formFieldClassNames = classNames( + 'form-field-checkbox', + readOnly && 'form-field-checkbox_readonly', + className + ) + const labelClassNames = classNames(highlightLabel && 'highlighted') + const inputRef = useRef(null) + + return ( + + {({ input }) => { + return ( +
+ + +
+ ) + }} +
+ ) +} + +FormCheckBox.propTypes = { + children: PropTypes.node, + className: PropTypes.string, + highlightLabel: PropTypes.bool, + label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), + name: PropTypes.string.isRequired, + readOnly: PropTypes.bool +} + +FormCheckBox = React.memo(FormCheckBox) + +export default FormCheckBox diff --git a/src/igz-controls/components/FormCheckBox/formCheckBox.scss b/src/igz-controls/components/FormCheckBox/formCheckBox.scss new file mode 100644 index 0000000000..007efbd604 --- /dev/null +++ b/src/igz-controls/components/FormCheckBox/formCheckBox.scss @@ -0,0 +1,91 @@ +@use '../../scss/colors'; +@use '../../scss/mixins'; + +.form-field-checkbox { + display: inline-flex; + align-items: center; + justify-content: flex-start; + color: colors.$primary; + + &_readonly { + @include mixins.radioCheckReadonly; + } + + input[type='checkbox'] { + flex: 0 0 18px; + width: 18px; + height: 18px; + border-radius: 4px; + transition: background 0.2s ease-in-out; + + @include mixins.radioCheckField; + + &::before { + content: ''; + display: block; + position: absolute; + top: 1px; + left: 5px; + width: 6px; + height: 11px; + border-style: solid; + border-color: colors.$white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); + } + + &:checked { + background: currentColor; + + &:hover { + background: currentColor; + + &:disabled { + background: currentColor; + } + } + } + + &:disabled { + &:hover { + background: colors.$white; + } + } + + &:not(:disabled):checked { + ~ label { + &.highlighted { + color: colors.$white; + background-color: colors.$malibu; + } + } + + &:hover { + ~ label { + &.highlighted { + background-color: colors.$cornflowerBlue; + } + } + } + } + + ~ label { + &.highlighted { + background-color: colors.$mischka; + font-size: 12px; + font-weight: bold; + margin-left: 10px; + padding: 4px 8px; + border-radius: 4px; + } + } + + &:not(:disabled):hover { + ~ label { + &.highlighted { + background-color: colors.$iron; + } + } + } + } +} diff --git a/src/igz-controls/components/FormChipCell/FormChip/FormChip.jsx b/src/igz-controls/components/FormChipCell/FormChip/FormChip.jsx new file mode 100644 index 0000000000..8ab1539e8a --- /dev/null +++ b/src/igz-controls/components/FormChipCell/FormChip/FormChip.jsx @@ -0,0 +1,113 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useLayoutEffect, forwardRef } from 'react' +import PropTypes from 'prop-types' + +import NewChipForm from '../NewChipForm/NewChipForm' + +import { CHIP_OPTIONS } from '../../../types' + +import './formChip.scss' + +let FormChip = ( + { + chip, + chipIndex, + chipSizeIsRecalculated, + setChipSizeIsRecalculated, + chipOptions = { + background: 'purple', + boldValue: false, + borderRadius: 'primary', + borderColor: 'transparent', + density: 'dense', + font: 'purple' + }, + editConfig, + handleEditChip, + handleRemoveChip, + handleToEditMode, + isDeletable = false, + isEditable = false, + keyName = '', + meta, + setChipsSizes, + setEditConfig, + validationRules = {}, + valueName = '' + }, + ref +) => { + const chipRef = React.useRef() + useLayoutEffect(() => { + if (chipRef.current && setChipsSizes && chipSizeIsRecalculated) { + setChipsSizes(state => ({ + ...state, + [chipIndex]: chipRef.current?.getBoundingClientRect?.()?.width ?? 50 + })) + } + }, [chipIndex, chipSizeIsRecalculated, setChipsSizes]) + + return ( +
handleToEditMode(event, chipIndex, keyName)} ref={chipRef}> + +
+ ) +} + +FormChip = forwardRef(FormChip) + +FormChip.displayName = 'FormChip' + +FormChip.propTypes = { + chip: PropTypes.object.isRequired, + chipSizeIsRecalculated: PropTypes.bool.isRequired, + setChipSizeIsRecalculated: PropTypes.func.isRequired, + chipIndex: PropTypes.number.isRequired, + chipOptions: CHIP_OPTIONS, + editConfig: PropTypes.object.isRequired, + handleEditChip: PropTypes.func.isRequired, + handleRemoveChip: PropTypes.func.isRequired, + handleToEditMode: PropTypes.func.isRequired, + isDeletable: PropTypes.bool, + isEditable: PropTypes.bool, + keyName: PropTypes.string, + meta: PropTypes.object.isRequired, + setChipsSizes: PropTypes.func.isRequired, + setEditConfig: PropTypes.func.isRequired, + validationRules: PropTypes.object, + valueName: PropTypes.string +} + +export default FormChip diff --git a/src/igz-controls/components/FormChipCell/FormChip/formChip.scss b/src/igz-controls/components/FormChipCell/FormChip/formChip.scss new file mode 100644 index 0000000000..61e67e5026 --- /dev/null +++ b/src/igz-controls/components/FormChipCell/FormChip/formChip.scss @@ -0,0 +1,71 @@ +@use '../../../scss/mixins'; + +.chip { + position: relative; + margin: 2px 8px 2px 0; + padding: 4px 8px; + font-size: 14px; + line-height: 16px; + visibility: hidden; + cursor: default; + + &_visible { + visibility: visible; + } + + &_invisible { + visibility: hidden; + height: 30px; + } + + &__content { + display: flex; + align-items: center; + + &-item { + flex: 1 1 50%; + max-width: fit-content; + align-self: flex-start; + } + } + + &__delimiter { + display: flex; + align-items: center; + margin: 0 4px; + } + + &__value { + min-width: 10px; + } + + &.editable { + cursor: pointer; + } + + &.chips_button { + padding: 8px 7px; + width: max-content; + } + + &-background { + @include mixins.chipBackground(false); + } + + &-border { + @include mixins.chipBorder(); + } + + &-density { + @include mixins.chipDensity(false, false); + } + + &-font { + @include mixins.chipsFont(Chip); + } + + &-value_bold { + font-weight: 700; + font-size: 15px; + } +} diff --git a/src/igz-controls/components/FormChipCell/FormChipCell.jsx b/src/igz-controls/components/FormChipCell/FormChipCell.jsx new file mode 100644 index 0000000000..c2553fa20f --- /dev/null +++ b/src/igz-controls/components/FormChipCell/FormChipCell.jsx @@ -0,0 +1,425 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useState, useCallback, useMemo } from 'react' +import lodash, { get, isEmpty, set, isNil } from 'lodash' +import classnames from 'classnames' +import PropTypes from 'prop-types' + +import FormChipCellView from './FormChipCellView' + +import { CHIP_OPTIONS, VISIBLE_CHIPS_MAX_LENGTH } from '../../types' +import { CLICK, TAB, TAB_SHIFT } from '../../constants' +import { areArraysEqual } from '../../utils/common.util' +import { checkPatternsValidity } from '../../utils/validation.util' +import { generateChipsList } from '../../utils/generateChipsList.util' +import { uniquenessError } from './formChipCell.util' +import { useChipCell } from '../../hooks' + +import './formChipCell.scss' + +let FormChipCell = ({ + chipOptions = { + background: 'purple', + boldValue: false, + borderRadius: 'primary', + borderColor: 'transparent', + density: 'dense', + font: 'purple' + }, + className = '', + children, + delimiter = null, + formState, + initialValues, + isDeletable = false, + isEditable = false, + label = null, + name, + onClick = () => {}, + onExitEditModeCallback = null, + shortChips = false, + validationRules = {}, + validator = null, + visibleChipsMaxLength = null, + withInitialParentWidth = false +}) => { + const chipsClassName = classnames('chips', className) + const [chipSizeIsRecalculated, setChipSizeIsRecalculated] = useState(false) + const { + chipsCellRef, + chipsWrapperRef, + handleShowElements, + hiddenChipsCounterRef, + hiddenChipsPopUpRef, + setChipsSizes, + setShowHiddenChips, + showChips, + showHiddenChips, + visibleChipsCount + } = useChipCell(isEditable, visibleChipsMaxLength, withInitialParentWidth) + + const [editConfig, setEditConfig] = useState({ + chipIndex: null, + isEdit: false, + isKeyFocused: false, + isValueFocused: false, + isNewChip: false + }) + + let chips = useMemo(() => { + return isEditable || visibleChipsMaxLength === 'all' + ? { + visibleChips: get(formState.values, name), + hiddenChips: [] + } + : generateChipsList( + get(formState.values, name), + visibleChipsMaxLength ? visibleChipsMaxLength : visibleChipsCount + ) + }, [visibleChipsMaxLength, isEditable, visibleChipsCount, formState.values, name]) + + const checkChipsList = useCallback( + currentChipsList => { + if (areArraysEqual(get(initialValues, name), currentChipsList, ['id'])) { + set(formState.initialValues, name, currentChipsList) + } + + formState.form.mutators.setFieldState(name, { modified: true }) + formState.form.mutators.setFieldState(name, { touched: true }) + }, + [initialValues, name, formState] + ) + + const handleAddNewChip = useCallback( + (event, fields) => { + const fieldsLength = fields.value?.length || 0 + + if (!editConfig.isEdit && !editConfig.chipIndex) { + formState.form.mutators.push(name, { + id: fieldsLength + new Date(), + key: '', + value: '', + delimiter: delimiter + }) + } + + if (showHiddenChips) { + setShowHiddenChips(false) + } + + setEditConfig({ + chipIndex: fieldsLength, + isEdit: true, + isKeyFocused: true, + isValueFocused: false, + isNewChip: true + }) + + event && event.preventDefault() + }, + [ + editConfig.isEdit, + editConfig.chipIndex, + showHiddenChips, + formState.form.mutators, + name, + delimiter, + setShowHiddenChips + ] + ) + + const handleRemoveChip = useCallback( + (event, fields, chipIndex, isOutsideClick = false) => { + checkChipsList( + lodash + .chain(formState) + .get(['values', name]) + .filter((_, index) => index !== chipIndex) + .value() + ) + + if (fields.length === 1) { + formState.form.change(name, []) + } else { + fields.remove(chipIndex) + } + + onExitEditModeCallback && onExitEditModeCallback() + event && !isOutsideClick && event.stopPropagation() + }, + [checkChipsList, formState, name, onExitEditModeCallback] + ) + + const handleEditChip = useCallback( + (event, fields, nameEvent, isOutsideClick) => { + const { key, value } = fields.value[editConfig.chipIndex] + const isChipNotEmpty = !!(key?.trim() && value?.trim()) + + if (nameEvent === CLICK) { + if (!isChipNotEmpty) { + handleRemoveChip(event, fields, editConfig.chipIndex, isOutsideClick) + } + + setEditConfig({ + chipIndex: null, + isEdit: false, + isKeyFocused: false, + isValueFocused: false, + isNewChip: false + }) + isChipNotEmpty && onExitEditModeCallback && onExitEditModeCallback() + } else if (nameEvent === TAB) { + if (!isChipNotEmpty) { + handleRemoveChip(event, fields, editConfig.chipIndex) + } + + setEditConfig(prevState => { + const lastChipSelected = prevState.chipIndex + 1 > fields.value.length - 1 + + isChipNotEmpty && lastChipSelected && onExitEditModeCallback && onExitEditModeCallback() + + return { + chipIndex: lastChipSelected ? null : prevState.chipIndex + 1, + isEdit: !lastChipSelected, + isKeyFocused: !lastChipSelected, + isValueFocused: false, + isNewChip: false + } + }) + } else if (nameEvent === TAB_SHIFT) { + if (!isChipNotEmpty) { + handleRemoveChip(event, fields, editConfig.chipIndex) + } + + setEditConfig(prevState => { + const firstChipIsSelected = prevState.chipIndex === 0 + + isChipNotEmpty && + firstChipIsSelected && + onExitEditModeCallback && + onExitEditModeCallback() + + return { + chipIndex: firstChipIsSelected ? null : prevState.chipIndex - 1, + isEdit: !firstChipIsSelected, + isKeyFocused: false, + isValueFocused: !firstChipIsSelected, + isNewChip: false + } + }) + } + + checkChipsList(get(formState.values, name)) + + if ( + (editConfig.chipIndex > 0 && editConfig.chipIndex < fields.value.length - 1) || + (fields.value.length > 1 && editConfig.chipIndex === 0 && nameEvent !== TAB_SHIFT) || + (fields.value.length > 1 && + editConfig.chipIndex === fields.value.length - 1 && + nameEvent !== TAB) + ) { + event && event.preventDefault() + } + }, + [ + editConfig.chipIndex, + checkChipsList, + formState.values, + name, + onExitEditModeCallback, + handleRemoveChip + ] + ) + + const handleToEditMode = useCallback( + (event, chipIndex, keyName) => { + if (isEditable) { + const { clientX: pointerCoordinateX, clientY: pointerCoordinateY } = event + let isKeyClicked = false + const isClickedInsideInputElement = ( + pointerCoordinateX, + pointerCoordinateY, + inputElement + ) => { + if (inputElement) { + const { + top: topPosition, + left: leftPosition, + right: rightPosition, + bottom: bottomPosition + } = inputElement.getBoundingClientRect() + if (pointerCoordinateX > rightPosition || pointerCoordinateX < leftPosition) + return false + if (pointerCoordinateY > bottomPosition || pointerCoordinateY < topPosition) + return false + + return true + } + } + event.stopPropagation() + + if (event.target.nodeName !== 'INPUT') { + if (event.target.firstElementChild) { + isKeyClicked = isClickedInsideInputElement( + pointerCoordinateX, + pointerCoordinateY, + event.target.firstElementChild + ) + } + } else { + isKeyClicked = event.target.name === keyName + } + + setEditConfig(preState => ({ + ...preState, + chipIndex, + isEdit: true, + isKeyFocused: isKeyClicked, + isValueFocused: !isKeyClicked + })) + } + + onClick && onClick() + }, + [isEditable, onClick] + ) + + const validateFields = fieldsArray => { + if (!fieldsArray) return null + + let errorData = [] + + const uniquenessValidator = (newValue, idx) => { + return !fieldsArray.some(({ key }, index) => { + return newValue === key && index !== idx + }) + } + + if (!isEmpty(validationRules)) { + errorData = fieldsArray.map(chip => { + const [keyValidation, valueValidation] = validateChip(chip) + + if (keyValidation && valueValidation) return { key: keyValidation, value: valueValidation } + + if (keyValidation) return { key: keyValidation } + + if (valueValidation) return { value: valueValidation } + + return null + }) + } + + // uniqueness + fieldsArray.forEach((chip, index) => { + const isUnique = uniquenessValidator(chip.key, index) + + if (!isUnique) { + if (get(errorData, [index, 'key'], false)) { + errorData.at(index).key.push(uniquenessError) + } else { + set(errorData, [index, 'key'], [uniquenessError]) + } + } + }) + + if (isEmpty(errorData) && validator) { + errorData = validator(fieldsArray) + } + + if (errorData.every(label => isNil(label))) { + return null + } + + return errorData + } + + const validateChip = ({ key, value, disabled }) => { + const validateField = (value, field) => { + const [newRules, isValidField] = checkPatternsValidity( + validationRules[field].filter(rule => rule.pattern), + value + ) + + if (isValidField) return null + + const invalidRules = newRules.filter(rule => !rule.isValid) + + return invalidRules.map(rule => ({ name: rule.name, label: rule.label })) + } + + return disabled ? [null, null] : [validateField(key, 'key'), validateField(value, 'value')] + } + + return ( +
+ {label &&
{label}
} +
+ + {children} + +
+
+ ) +} + +FormChipCell.propTypes = { + chipOptions: CHIP_OPTIONS, + children: PropTypes.node, + className: PropTypes.string, + delimiter: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), + formState: PropTypes.object.isRequired, + initialValues: PropTypes.object.isRequired, + isDeletable: PropTypes.bool, + isEditable: PropTypes.bool, + label: PropTypes.string, + name: PropTypes.string.isRequired, + onClick: PropTypes.func, + onExitEditModeCallback: PropTypes.func, + shortChips: PropTypes.bool, + validationRules: PropTypes.object, + validator: PropTypes.func, + visibleChipsMaxLength: VISIBLE_CHIPS_MAX_LENGTH, + withInitialParentWidth: PropTypes.bool +} + +FormChipCell = React.memo(FormChipCell) + +export default FormChipCell diff --git a/src/igz-controls/components/FormChipCell/FormChipCellView.jsx b/src/igz-controls/components/FormChipCell/FormChipCellView.jsx new file mode 100644 index 0000000000..8eb592b430 --- /dev/null +++ b/src/igz-controls/components/FormChipCell/FormChipCellView.jsx @@ -0,0 +1,245 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { forwardRef } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import { FieldArray } from 'react-final-form-arrays' +import { isEmpty } from 'lodash' + +import FormChip from './FormChip/FormChip' +import HiddenChipsBlock from './HiddenChipsBlock/HiddenChipsBlock' +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' +import Tooltip from '../Tooltip/Tooltip' + +import { CHIP_OPTIONS, VISIBLE_CHIPS_MAX_LENGTH } from '../../types' +import { isEveryObjectValueEmpty } from '../../utils/common.util' +import { uniquenessError } from './formChipCell.util' + +import Add from '../../images/add.svg?react' + +let FormChipCellView = ( + { + chipOptions = { + background: 'purple', + boldValue: false, + borderRadius: 'primary', + borderColor: 'transparent', + density: 'dense', + font: 'purple' + }, + chipSizeIsRecalculated, + setChipSizeIsRecalculated, + children, + chips, + editConfig, + handleAddNewChip, + handleEditChip, + handleRemoveChip, + handleShowElements, + handleToEditMode, + isDeletable = false, + isEditable = false, + name, + setChipsSizes, + setEditConfig, + shortChips = false, + showChips, + showHiddenChips, + validateFields, + validationRules = {}, + visibleChipsMaxLength = null + }, + { chipsCellRef, chipsWrapperRef, hiddenChipsCounterRef, hiddenChipsPopUpRef } +) => { + const buttonAddClassNames = classnames( + 'button-add', + chipOptions.background && `button-add-background_${chipOptions.background}`, + chipOptions.borderColor && `button-add-border_${chipOptions.borderColor}`, + chipOptions.font && `button-add-font_${chipOptions.font}`, + chipOptions.density && `button-add-density_${chipOptions.density}` + ) + const wrapperClassNames = classnames( + 'chips-wrapper', + isEditable && 'fixed-max-width', + chips.visibleChips?.length > 0 && !chipSizeIsRecalculated && 'chip_invisible', + visibleChipsMaxLength === 'all' && 'chips-wrapper_all-visible' + ) + const chipClassNames = classnames( + 'chip', + 'chip__content', + isEditable && 'data-ellipsis', + shortChips && 'chip_short', + chips.hiddenChips && 'chip_hidden', + chipOptions.density && `chip-density_${chipOptions.density}`, + chipOptions.borderRadius && `chip-border_${chipOptions.borderRadius}`, + chipOptions.background && `chip-background_${chipOptions.background}`, + chipOptions.borderColor && `chip-border_${chipOptions.borderColor}`, + chipOptions.font && `chip-font_${chipOptions.font}`, + isEditable && 'editable', + (showChips || isEditable) && 'chip_visible' + ) + + return ( + + {({ fields, meta }) => { + let newValidationRules = { ...validationRules } + + if ( + !isEmpty(validationRules) && + validationRules.key.every(rule => rule.name !== uniquenessError.name) + ) { + newValidationRules = { + ...validationRules, + key: [...validationRules.key, uniquenessError] + } + } + + return ( + (isEditable || !isEveryObjectValueEmpty(fields)) && ( +
+
+ {fields.map((contentItem, index) => { + const chipData = fields.value[index] + + return ( + index < chips.visibleChips?.length && ( +
+ +
+ ) + ) + })} + +
+ {chips.hiddenChips.length > 0 && showHiddenChips && ( + + )} + {chips.hiddenChipsNumber && ( + + {chips.hiddenChipsNumber} + + )} +
+ + {isEditable && ( + + )} + {children} +
+
+ ) + ) + }} +
+ ) +} + +FormChipCellView = forwardRef(FormChipCellView) + +FormChipCellView.displayName = 'FormChipCellView' + +FormChipCellView.propTypes = { + chipOptions: CHIP_OPTIONS, + chipSizeIsRecalculated: PropTypes.bool.isRequired, + setChipSizeIsRecalculated: PropTypes.func.isRequired, + children: PropTypes.node, + chips: PropTypes.object.isRequired, + editConfig: PropTypes.object.isRequired, + formState: PropTypes.object.isRequired, + handleAddNewChip: PropTypes.func.isRequired, + handleEditChip: PropTypes.func.isRequired, + handleRemoveChip: PropTypes.func.isRequired, + handleShowElements: PropTypes.func.isRequired, + handleToEditMode: PropTypes.func.isRequired, + isDeletable: PropTypes.bool, + isEditable: PropTypes.bool, + name: PropTypes.string.isRequired, + setChipsSizes: PropTypes.func.isRequired, + setEditConfig: PropTypes.func.isRequired, + shortChips: PropTypes.bool, + showChips: PropTypes.bool.isRequired, + showHiddenChips: PropTypes.bool.isRequired, + validateFields: PropTypes.func.isRequired, + validationRules: PropTypes.object, + visibleChipsMaxLength: VISIBLE_CHIPS_MAX_LENGTH +} + +export default FormChipCellView diff --git a/src/igz-controls/components/FormChipCell/HiddenChipsBlock/HiddenChipsBlock.jsx b/src/igz-controls/components/FormChipCell/HiddenChipsBlock/HiddenChipsBlock.jsx new file mode 100644 index 0000000000..cf1f3eadaf --- /dev/null +++ b/src/igz-controls/components/FormChipCell/HiddenChipsBlock/HiddenChipsBlock.jsx @@ -0,0 +1,117 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { forwardRef, useEffect } from 'react' +import { createPortal } from 'react-dom' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +import Tooltip from '../../Tooltip/Tooltip' +import TextTooltipTemplate from '../../TooltipTemplate/TextTooltipTemplate' + +import { CHIP_OPTIONS } from '../../../types' +import { useHiddenChipsBlock } from '../../../hooks' + +let HiddenChipsBlock = ( + { chipClassNames, chipOptions, chips, handleShowElements, textOverflowEllipsis = false }, + { hiddenChipsCounterRef, hiddenChipsPopUpRef } +) => { + const { hiddenChipsBlockClassNames } = useHiddenChipsBlock( + hiddenChipsCounterRef, + hiddenChipsPopUpRef + ) + + const chipLabelClassNames = classnames('chip__label', textOverflowEllipsis && 'data-ellipsis') + const chipValueClassNames = classnames( + 'chip__value', + textOverflowEllipsis && 'data-ellipsis', + chipOptions.boldValue && 'chip-value_bold' + ) + + const generateChipData = chip => { + return chip.isKeyOnly + ? chip.key + : `${chip.key}${chip.delimiter ? chip.delimiter : ':'} ${chip.value}` + } + + useEffect(() => { + if (chips.length === 0) { + handleShowElements() + } + }) + + return createPortal( +
event.stopPropagation()} + > +
+ {chips?.map(element => { + return ( + + {element.key} + {!element.isKeyOnly && ( + <> + {element.delimiter} + {element.value} + + )} + + ) : ( + generateChipData(element) + ) + } + /> + } + > +
+ {element.key &&
{element.key}
} + {element.value && ( + <> +
{element.delimiter ?? ':'}
+
{element.value}
+ + )} +
+
+ ) + })} +
+
, + document.getElementById('overlay_container') + ) +} + +HiddenChipsBlock = forwardRef(HiddenChipsBlock) + +HiddenChipsBlock.displayName = 'HiddenChipsBlock' + +HiddenChipsBlock.propTypes = { + chipClassNames: PropTypes.string.isRequired, + chipOptions: CHIP_OPTIONS.isRequired, + chips: PropTypes.array.isRequired, + handleShowElements: PropTypes.func.isRequired, + textOverflowEllipsis: PropTypes.bool +} + +export default HiddenChipsBlock diff --git a/src/igz-controls/components/FormChipCell/NewChipForm/NewChipForm.jsx b/src/igz-controls/components/FormChipCell/NewChipForm/NewChipForm.jsx new file mode 100644 index 0000000000..2648ef9493 --- /dev/null +++ b/src/igz-controls/components/FormChipCell/NewChipForm/NewChipForm.jsx @@ -0,0 +1,505 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { + useState, + useCallback, + useEffect, + useLayoutEffect, + useMemo, + forwardRef +} from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import { isEmpty, get, isNil, throttle } from 'lodash' + +import NewChipInput from '../NewChipInput/NewChipInput' +import OptionsMenu from '../../../elements/OptionsMenu/OptionsMenu' +import ValidationTemplate from '../../../elements/ValidationTemplate/ValidationTemplate' + +import { CHIP_OPTIONS } from '../../../types' +import { CLICK, TAB, TAB_SHIFT } from '../../../constants' +import { getTextWidth } from '../formChipCell.util' +import { getTransitionEndEventName } from '../../../utils/common.util' + +import Close from '../../../images/close.svg?react' + +import './newChipForm.scss' + +const defaultProps = { + rules: {} +} + +let NewChipForm = ( + { + chip, + chipIndex, + chipOptions, + className = '', + editConfig, + handleRemoveChip, + isDeletable, + isEditable, + keyName, + meta, + onChange, + setChipSizeIsRecalculated, + setEditConfig, + validationRules: rules = defaultProps.rules, + valueName + }, + ref +) => { + const [chipData, setChipData] = useState({ + isKeyOnly: chip.isKeyOnly, + key: chip.key, + value: chip.value, + keyFieldWidth: 0, + valueFieldWidth: 0 + }) + const [selectedInput, setSelectedInput] = useState('key') + const [validationRules, setValidationRules] = useState(rules) + const [showValidationRules, setShowValidationRules] = useState(false) + + const { background, borderColor, borderRadius, density, font } = chipOptions + const minWidthInput = useMemo(() => { + return isEditable ? 25 : 20 + }, [isEditable]) + const minWidthValueInput = useMemo(() => { + return isEditable ? 35 : 20 + }, [isEditable]) + const transitionEndEventName = useMemo(() => getTransitionEndEventName(), []) + + const refInputKey = React.useRef({}) + const refInputValue = React.useRef({}) + const refInputContainer = React.useRef() + const validationRulesRef = React.useRef() + + const labelKeyClassName = classnames( + className, + !editConfig.isKeyFocused && 'item_edited', + !isEmpty(get(meta, ['error', chipIndex, 'key'], [])) && + !isEmpty(chipData.key) && + !chip.disabled && + isEditable && + 'item_edited_invalid' + ) + const labelContainerClassName = classnames( + 'edit-chip-container', + background && `edit-chip-container-background_${background}`, + borderColor && `edit-chip-container-border_${borderColor}`, + font && `edit-chip-container-font_${font}`, + density && `edit-chip-container-density_${density}`, + borderRadius && `edit-chip-container-border_${borderRadius}`, + (editConfig.isEdit || editConfig.isNewChip) && 'edit-chip-container_edited', + isEditable && chip.disabled && 'edit-chip-container_disabled edit-chip-container-font_disabled' + ) + const labelValueClassName = classnames( + 'input-label-value', + !editConfig.isValueFocused && 'item_edited', + !isEmpty(get(meta, ['error', chipIndex, 'value'], [])) && + !isEmpty(chipData.value) && + isEditable && + 'item_edited_invalid' + ) + + const closeButtonClass = classnames( + 'item-icon-close', + !chip.disabled && + ((editConfig.chipIndex === chipIndex && isEditable) || (!isDeletable && !isEditable)) && + 'item-icon-close invisible', + !isEditable && !isDeletable && 'item-icon-close hidden' + ) + + const resizeChip = useCallback(() => { + if (refInputKey.current) { + const currentWidthKeyInput = getTextWidth(refInputKey.current) + 1 + const currentWidthValueInput = getTextWidth(refInputValue.current) + 1 + const maxWidthInput = ref.current?.clientWidth - 50 + const keyEllipsis = currentWidthKeyInput >= maxWidthInput / 2 + const valueEllipsis = currentWidthValueInput >= maxWidthInput / 2 + let keyFieldWidth = null + let valueFieldWidth = null + + if (keyEllipsis && valueEllipsis) { + keyFieldWidth = valueFieldWidth = maxWidthInput / 2 + } else if (keyEllipsis) { + valueFieldWidth = !chipData.value ? minWidthValueInput : currentWidthValueInput + + const remainingPlace = maxWidthInput - valueFieldWidth + + keyFieldWidth = + remainingPlace > currentWidthKeyInput ? currentWidthKeyInput : remainingPlace + } else if (valueEllipsis) { + keyFieldWidth = !chipData.key ? minWidthInput : currentWidthKeyInput + + const remainingPlace = maxWidthInput - keyFieldWidth + + valueFieldWidth = + remainingPlace > currentWidthValueInput ? currentWidthValueInput : remainingPlace + } else { + keyFieldWidth = + !chipData.key || currentWidthKeyInput <= minWidthInput + ? minWidthInput + : currentWidthKeyInput + valueFieldWidth = + !chipData.value || currentWidthValueInput <= minWidthValueInput + ? minWidthValueInput + : currentWidthValueInput + } + + refInputKey.current.style.width = `${keyFieldWidth}px` + + if (!isEmpty(refInputValue.current)) { + refInputValue.current.style.width = `${valueFieldWidth}px` + } + + setChipData(prevState => ({ + ...prevState, + keyFieldWidth, + valueFieldWidth + })) + setChipSizeIsRecalculated(true) + } + }, [ + chipData.key, + chipData.value, + minWidthInput, + minWidthValueInput, + ref, + setChipSizeIsRecalculated + ]) + + useEffect(() => { + if (!ref.current) return + + const element = ref.current + const observer = new ResizeObserver(resizeChip) + + observer.observe(element) + + return () => observer.unobserve(element) + }, [ref, resizeChip]) + + useEffect(() => { + const resizeChipDebounced = throttle(resizeChip, 500) + + if (isEditable) { + window.addEventListener('resize', resizeChipDebounced) + window.addEventListener(transitionEndEventName, resizeChipDebounced) + + return () => { + window.removeEventListener('resize', resizeChipDebounced) + window.removeEventListener(transitionEndEventName, resizeChipDebounced) + } + } + }, [isEditable, resizeChip, transitionEndEventName]) + + useLayoutEffect(() => { + if (!chipData.keyFieldWidth && !chipData.valueFieldWidth) { + resizeChip() + } + }, [chipData.keyFieldWidth, chipData.valueFieldWidth, resizeChip]) + + const outsideClick = useCallback( + (event, forceOutsideClick) => { + if (editConfig.chipIndex === chipIndex) { + const elementPath = event.path ?? event.composedPath?.() + + if (!elementPath.includes(refInputContainer.current) || forceOutsideClick) { + onChange(event, CLICK, true) + window.getSelection().removeAllRanges() + document.activeElement.blur() + } else { + event.stopPropagation() + } + } + }, + [onChange, refInputContainer, chipIndex, editConfig.chipIndex] + ) + + const handleScroll = useCallback( + event => { + if (validationRulesRef?.current && !validationRulesRef.current.contains(event.target)) { + setShowValidationRules(false) + outsideClick(event, true) + } + }, + [outsideClick] + ) + + useEffect(() => { + if (showValidationRules) { + window.addEventListener('scroll', handleScroll, true) + } + return () => { + window.removeEventListener('scroll', handleScroll, true) + } + }, [handleScroll, showValidationRules]) + + useEffect(() => { + if (editConfig.chipIndex === chipIndex) { + if (editConfig.isKeyFocused) { + refInputKey.current.focus() + } else if (editConfig.isValueFocused) { + refInputValue.current.focus() + } + } + }, [ + editConfig.isKeyFocused, + editConfig.isValueFocused, + refInputKey, + refInputValue, + chipIndex, + editConfig.chipIndex + ]) + + useEffect(() => { + if (showValidationRules) { + window.addEventListener('scroll', handleScroll, true) + } + return () => { + window.removeEventListener('scroll', handleScroll, true) + } + }, [handleScroll, showValidationRules]) + + useEffect(() => { + if (editConfig.isEdit) { + document.addEventListener('click', outsideClick, true) + + return () => { + document.removeEventListener('click', outsideClick, true) + } + } + }, [outsideClick, editConfig.isEdit]) + + const focusChip = useCallback( + event => { + if (editConfig.chipIndex === chipIndex && isEditable) { + if (!event.shiftKey && event.key === TAB && editConfig.isValueFocused) { + return onChange(event, TAB) + } else if (event.shiftKey && event.key === TAB && editConfig.isKeyFocused) { + return onChange(event, TAB_SHIFT) + } + } + event.stopPropagation() + }, + [editConfig, onChange, chipIndex, isEditable] + ) + + const handleOnFocus = useCallback( + event => { + const isKeyFocused = event.target.name === keyName + + if (editConfig.chipIndex === chipIndex) { + if (isKeyFocused) { + refInputKey.current.selectionStart = refInputKey.current.selectionEnd + + setEditConfig(prevConfig => ({ + ...prevConfig, + isKeyFocused: true, + isValueFocused: false + })) + } else { + refInputValue.current.selectionStart = refInputValue.current.selectionEnd + + setEditConfig(prevConfig => ({ + ...prevConfig, + isKeyFocused: false, + isValueFocused: true + })) + } + + event && event.stopPropagation() + } else if (isNil(editConfig.chipIndex)) { + if (isKeyFocused) { + refInputKey.current.selectionStart = refInputKey.current.selectionEnd + } else { + refInputValue.current.selectionStart = refInputValue.current.selectionEnd + } + setEditConfig({ + chipIndex, + isEdit: true, + isKeyFocused: isKeyFocused, + isValueFocused: !isKeyFocused + }) + } + }, + [keyName, refInputKey, refInputValue, setEditConfig, editConfig.chipIndex, chipIndex] + ) + + const handleOnChange = useCallback( + event => { + const maxWidthInput = ref.current?.clientWidth - 50 + + event.preventDefault() + + if (event.target.name === keyName) { + const currentWidthKeyInput = getTextWidth(refInputKey.current) + + setChipData(prevState => ({ + ...prevState, + key: refInputKey.current.value, + keyFieldWidth: + refInputKey.current.value.length <= 1 + ? minWidthInput + : currentWidthKeyInput >= maxWidthInput + ? maxWidthInput + : currentWidthKeyInput > minWidthInput + ? currentWidthKeyInput + 2 + : minWidthInput + })) + } else { + const currentWidthValueInput = getTextWidth(refInputValue.current) + + setChipData(prevState => ({ + ...prevState, + value: refInputValue.current.value, + valueFieldWidth: + refInputValue.current.value?.length <= 1 + ? minWidthValueInput + : currentWidthValueInput >= maxWidthInput + ? maxWidthInput + : currentWidthValueInput > minWidthValueInput + ? currentWidthValueInput + 2 + : minWidthValueInput + })) + } + }, + [keyName, minWidthInput, ref, minWidthValueInput] + ) + + useLayoutEffect(() => { + if (editConfig.chipIndex === chipIndex) { + setSelectedInput(editConfig.isKeyFocused ? 'key' : editConfig.isValueFocused ? 'value' : null) + } + }, [editConfig.isKeyFocused, editConfig.isValueFocused, editConfig.chipIndex, chipIndex]) + + useEffect(() => { + if (meta.valid && showValidationRules) { + setShowValidationRules(false) + } + }, [meta.valid, showValidationRules]) + + useEffect(() => { + if (meta.error) { + setValidationRules(prevState => { + return { + ...prevState, + [selectedInput]: prevState[selectedInput]?.map(rule => { + return { + ...rule, + isValid: isEmpty(get(meta, ['error', editConfig.chipIndex, selectedInput], [])) + ? true + : !meta.error[editConfig.chipIndex][selectedInput].some( + err => err && err.name === rule.name + ) + } + }) + } + }) + + !showValidationRules && setShowValidationRules(true) + } + }, [meta, showValidationRules, selectedInput, editConfig.chipIndex]) + + const getValidationRules = useCallback(() => { + return validationRules[selectedInput]?.map(({ isValid = false, label, name }) => { + return + }) + }, [selectedInput, validationRules]) + + return ( +
!chip.disabled && editConfig.isEdit && focusChip(event)} + ref={refInputContainer} + > + + {!chipData.isKeyOnly &&
:
} + {!chipData.isKeyOnly && ( + + )} + + + + {!chip.disabled && + (editConfig.isKeyFocused ? !isEmpty(chipData.key) : !isEmpty(chipData.value)) && + editConfig.chipIndex === chipIndex && + !isEmpty(get(meta, ['error', editConfig.chipIndex, selectedInput], [])) && ( + + {getValidationRules()} + + )} +
+ ) +} + +NewChipForm = forwardRef(NewChipForm) + +NewChipForm.displayName = 'NewChipForm' + +NewChipForm.propTypes = { + chip: PropTypes.object.isRequired, + chipIndex: PropTypes.number.isRequired, + chipOptions: CHIP_OPTIONS.isRequired, + className: PropTypes.string, + editConfig: PropTypes.object.isRequired, + handleRemoveChip: PropTypes.func.isRequired, + isDeletable: PropTypes.bool.isRequired, + isEditable: PropTypes.bool.isRequired, + keyName: PropTypes.string.isRequired, + meta: PropTypes.object.isRequired, + onChange: PropTypes.func.isRequired, + setChipSizeIsRecalculated: PropTypes.func.isRequired, + setEditConfig: PropTypes.func.isRequired, + validationRules: PropTypes.object, + valueName: PropTypes.string.isRequired +} + +export default NewChipForm diff --git a/src/igz-controls/components/FormChipCell/NewChipForm/newChipForm.scss b/src/igz-controls/components/FormChipCell/NewChipForm/newChipForm.scss new file mode 100644 index 0000000000..ac105fab87 --- /dev/null +++ b/src/igz-controls/components/FormChipCell/NewChipForm/newChipForm.scss @@ -0,0 +1,80 @@ +@use '../../../scss/colors'; +@use '../../../scss/mixins'; + +.edit-chip { + &-container { + display: inline-flex; + max-width: 100%; + margin: 2px 0 2px 0; + padding: 0 8px; + font-size: 14px; + line-height: 22px; + + input { + display: flex; + padding: 0; + font-size: 14px; + background-color: transparent; + border: none; + + &[disabled] { + pointer-events: none; + } + + &.item_edited { + &_invalid { + color: colors.$amaranth; + } + } + + &.input-label-key, + &.input-label-value { + &::placeholder { + color: colors.$topaz; + } + } + } + + &-background { + @include mixins.chipBackground(false); + } + + &-border { + @include mixins.chipBorder(); + } + + &-density { + @include mixins.chipDensity(true, false); + } + + &-font { + @include mixins.chipsFont(EditableChip); + } + + button.item-icon-close { + display: flex; + align-items: center; + justify-content: center; + + &.hidden { + display: none; + } + + &.invisible { + visibility: hidden; + } + + svg { + transform: scale(0.7); + } + } + + &_disabled { + cursor: not-allowed; + } + } + + &-separator { + margin-right: 5px; + } +} diff --git a/src/igz-controls/components/FormChipCell/NewChipInput/NewChipInput.jsx b/src/igz-controls/components/FormChipCell/NewChipInput/NewChipInput.jsx new file mode 100644 index 0000000000..e0f529d913 --- /dev/null +++ b/src/igz-controls/components/FormChipCell/NewChipInput/NewChipInput.jsx @@ -0,0 +1,64 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { forwardRef } from 'react' +import PropTypes from 'prop-types' +import { Field, useField } from 'react-final-form' + +let NewChipInput = ({ name, onChange, onFocus, ...inputProps }, ref) => { + const { input } = useField(name) + + const handleInputChange = event => { + input.onChange(event) + onChange(event) + } + + const handleInputFocus = event => { + input.onFocus(event) + onFocus(event) + } + + return ( + + {({ input }) => ( + + )} + + ) +} + +NewChipInput = forwardRef(NewChipInput) + +NewChipInput.displayName = 'NewChipInput' + +NewChipInput.propTypes = { + name: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func.isRequired +} + +export default NewChipInput diff --git a/src/igz-controls/components/FormChipCell/formChipCell.scss b/src/igz-controls/components/FormChipCell/formChipCell.scss new file mode 100644 index 0000000000..c526ac0ecd --- /dev/null +++ b/src/igz-controls/components/FormChipCell/formChipCell.scss @@ -0,0 +1,72 @@ +@use '../../scss/mixins'; + +.chips { + @include mixins.inputSelectField; + + & { + height: auto; + min-width: 0; + } + + &__wrapper { + padding: 12px 16px; + } + + &-wrapper { + display: flex; + flex-flow: row; + align-items: center; + overflow: hidden; + + &.chips-wrapper_all-visible { + flex-wrap: wrap; + } + } + + &-cell { + display: flex; + flex: 1; + align-items: center; + max-width: 100%; + + .fixed-max-width { + max-width: 100%; + } + + .chip { + &-block { + position: relative; + max-width: 100%; + margin-right: calc(#{var(--chipBlockMarginRight)}); + } + } + + .button-add { + display: flex; + align-items: center; + justify-content: center; + margin: 2px 0 2px 0; + border-radius: 32px; + + &-background { + @include mixins.chipBackground(true); + } + + &_border { + @include mixins.chipBorder(); + } + + &-density { + @include mixins.chipDensity(false, true); + } + + &-font { + @include mixins.chipsFont(ButtonAddChip); + } + } + } + + input:disabled { + cursor: default; + } +} diff --git a/src/igz-controls/components/FormChipCell/formChipCell.util.js b/src/igz-controls/components/FormChipCell/formChipCell.util.js new file mode 100644 index 0000000000..d7854322ba --- /dev/null +++ b/src/igz-controls/components/FormChipCell/formChipCell.util.js @@ -0,0 +1,48 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +export const uniquenessError = { name: 'uniqueness', label: 'Key must be unique' } + +export const getTextWidth = elementWithText => { + if (!elementWithText) { + return 0 + } + const hiddenElementId = 'chips-hidden-element' + let hiddenElement = document.getElementById(hiddenElementId) + + if (!hiddenElement) { + hiddenElement = document.createElement('span') + const styles = { + position: 'absolute', + left: '-10000px', + top: 'auto', + visibility: 'hidden' + } + + for (const [styleName, styleValue] of Object.entries(styles)) { + hiddenElement.style[styleName] = styleValue + } + + hiddenElement.style.font = window.getComputedStyle(elementWithText).font + hiddenElement.id = hiddenElementId + hiddenElement.tabIndex = -1 + document.body.append(hiddenElement) + } + + hiddenElement.textContent = elementWithText.value + + return hiddenElement.offsetWidth ?? 0 +} diff --git a/src/igz-controls/components/FormCombobox/FormCombobox.jsx b/src/igz-controls/components/FormCombobox/FormCombobox.jsx new file mode 100644 index 0000000000..dd8aa97f86 --- /dev/null +++ b/src/igz-controls/components/FormCombobox/FormCombobox.jsx @@ -0,0 +1,514 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useCallback, useEffect, useRef, useState } from 'react' +import { Field, useField } from 'react-final-form' +import { isEmpty } from 'lodash' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +import OptionsMenu from '../../elements/OptionsMenu/OptionsMenu' +import ValidationTemplate from '../../elements/ValidationTemplate/ValidationTemplate' +import PopUpDialog from '../PopUpDialog/PopUpDialog' +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' +import Tooltip from '../Tooltip/Tooltip' + +import { checkPatternsValidity } from '../../utils/validation.util' +import { useDetectOutsideClick } from '../../hooks' +import { COMBOBOX_SELECT_OPTIONS, COMBOBOX_SUGGESTION_LIST, DENSITY } from '../../types' + +import Arrow from '../../images/arrow.svg?react' +import SearchIcon from '../../images/search.svg?react' +import WarningIcon from '../../images/warning.svg?react' +import ExclamationMarkIcon from '../../images/exclamation-mark.svg?react' + +import './formCombobox.scss' + +const FormCombobox = ({ + comboboxClassName = '', + density = 'normal', + disabled = false, + hideSearchInput = false, + inputDefaultValue = '', + inputPlaceholder = '', + invalidText = 'Invalid', + label = '', + maxSuggestedMatches = 1, + name, + onBlur = null, + onChange = null, + onFocus = null, + required = false, + rules = [], + selectDefaultValue = { + label: '', + id: '', + className: '' + }, + selectOptions, + selectPlaceholder = '', + suggestionList = [], + validator = null, + withoutBorder = false +}) => { + const { input, meta } = useField(name) + const [inputValue, setInputValue] = useState(inputDefaultValue) + const [selectValue, setSelectValue] = useState(selectDefaultValue) + const [dropdownStyle, setDropdownStyle] = useState({ + left: '0px' + }) + const [showSelectDropdown, setShowSelectDropdown] = useState(false) + const [showSuggestionList, setShowSuggestionList] = useState(false) + const [dropdownList, setDropdownList] = useState(suggestionList) + const [searchIsFocused, setSearchIsFocused] = useState(false) + const [isInvalid, setIsInvalid] = useState(false) + const [validationRules, setValidationRules] = useState(rules) + const [showValidationRules, setShowValidationRules] = useState(false) + const comboboxRef = useRef() + const selectRef = useRef() + const inputRef = useRef() + const suggestionListRef = useRef() + useDetectOutsideClick(comboboxRef, () => setShowValidationRules(false)) + + const labelClassNames = classnames('form-field__label', disabled && 'form-field__label-disabled') + const inputClassNames = classnames( + 'form-field-combobox__input', + selectValue.id.length === 0 && 'form-field-combobox__input_hidden' + ) + + useEffect(() => { + setValidationRules(prevState => + prevState.map(rule => ({ + ...rule, + isValid: + !meta.error || !Array.isArray(meta.error) + ? true + : !meta.error.some(err => err.name === rule.name) + })) + ) + }, [meta.error]) + + useEffect(() => { + if (!searchIsFocused) { + if (JSON.stringify(dropdownList) !== JSON.stringify(suggestionList)) { + setDropdownList(suggestionList) + } + } + }, [dropdownList, suggestionList, searchIsFocused]) + + useEffect(() => { + setIsInvalid( + meta.invalid && (meta.validating || meta.modified || (meta.submitFailed && meta.touched)) + ) + }, [meta.invalid, meta.modified, meta.submitFailed, meta.touched, meta.validating]) + + const handleOutsideClick = useCallback( + event => { + if ( + comboboxRef.current && + !comboboxRef.current.contains(event.target) && + suggestionListRef.current && + !suggestionListRef.current.contains(event.target) + ) { + setSearchIsFocused(false) + setShowSelectDropdown(false) + setShowSuggestionList(false) + input.onBlur(new Event('blur')) + onBlur && onBlur(input.value) + } + }, + [input, onBlur] + ) + + const handleScroll = event => { + if (comboboxRef.current && comboboxRef.current.contains(event.target)) return + + if ( + !event.target.closest('.pop-up-dialog') && + !event.target.classList.contains('form-field-combobox') + ) { + setShowValidationRules(false) + setShowSelectDropdown(false) + setShowSuggestionList(false) + inputRef.current.blur() + } + } + + useEffect(() => { + window.addEventListener('click', handleOutsideClick) + + return () => { + window.removeEventListener('click', handleOutsideClick) + } + }, [handleOutsideClick]) + + useEffect(() => { + if (showValidationRules || showSelectDropdown || showSuggestionList) { + window.addEventListener('scroll', handleScroll, true) + } + return () => { + window.removeEventListener('scroll', handleScroll, true) + } + }, [showSelectDropdown, showSuggestionList, showValidationRules]) + + const getValidationRules = () => { + return validationRules.map(({ isValid = false, label, name }) => { + return + }) + } + + const handleInputChange = event => { + const target = event.target + + setDropdownStyle({ + left: `${target.selectionStart < 30 ? target.selectionStart : 30}ch` + }) + + if (searchIsFocused) { + setSearchIsFocused(false) + } + + setInputValue(target.value) + input.onChange(`${selectValue.id}${target.value}`) + onChange && onChange(selectValue.id, target.value) + + if (dropdownList.length > 0) { + setShowSuggestionList(true) + } + } + + const handleSelectOptionClick = selectedOption => { + if (selectedOption.id !== selectValue.id) { + setSelectValue(selectedOption) + input.onChange(selectedOption.id) + setInputValue('') + onChange && onChange(selectedOption.id) + setShowSelectDropdown(false) + inputRef.current.disabled = false + inputRef.current.focus() + } else { + setShowSelectDropdown(false) + inputRef.current.disabled = false + inputRef.current.focus() + } + } + + const handleSuggestionListOptionClick = option => { + const inputValueItems = inputValue.split('/') + const valueIndex = inputValueItems.length - 1 + let formattedValue = option.customDelimiter + ? inputValueItems[valueIndex].replace(new RegExp(`${option.customDelimiter}.*`), '') + + option.id + : option.id + + if (inputValueItems.length <= maxSuggestedMatches - 1) formattedValue += '/' + + inputValueItems[valueIndex] = formattedValue + + if (searchIsFocused) { + setSearchIsFocused(false) + } + + if (inputValueItems.join('/') !== inputValue) { + setInputValue(inputValueItems.join('/')) + input.onChange(`${selectValue.id}${inputValueItems.join('/')}`) + onChange && onChange(selectValue.id, inputValueItems.join('/')) + } + + setShowSuggestionList(false) + inputRef.current.focus() + setDropdownStyle({ + left: `${inputRef.current.selectionStart < 30 ? inputRef.current.selectionStart : 30}ch` + }) + } + + const inputOnFocus = event => { + onFocus && onFocus() + input.onFocus(new Event('focus')) + + if (showSelectDropdown) { + setShowSelectDropdown(false) + } + + // browser need some time to calculate cursor position after onFocus fired + if (!inputRef.current.selectionStart) { + setTimeout(() => { + setDropdownStyle({ + left: `${event.target.selectionStart < 30 ? event.target.selectionStart : 30}ch` + }) + setShowSuggestionList(true) + }) + } else { + setShowSuggestionList(true) + } + } + + const suggestionListSearchChange = event => { + event.persist() + setDropdownList(() => + suggestionList.filter(option => { + return option.id.startsWith(event.target.value) + }) + ) + } + + const toggleSelect = useCallback(() => { + if (showSelectDropdown) { + setShowSelectDropdown(false) + input.onBlur(new Event('blur')) + onBlur && onBlur(input.value) + } else { + setShowSuggestionList(false) + setShowValidationRules(false) + setDropdownStyle({ + left: '0px' + }) + setShowSelectDropdown(true) + input.onFocus(new Event('focus')) + onFocus && onFocus(input.value) + } + }, [input, onBlur, onFocus, showSelectDropdown]) + + const validateField = (value = '', allValues) => { + const valueToValidate = value.startsWith(selectValue.id) + ? value.substring(selectValue.id.length) + : (value ?? '') + let validationError = null + + if (!isEmpty(validationRules)) { + const [newRules, isValidField] = checkPatternsValidity(rules, valueToValidate) + const invalidRules = newRules.filter(rule => !rule.isValid) + + if (!isValidField) { + validationError = invalidRules.map(rule => ({ name: rule.name, label: rule.label })) + } + } + + if (isEmpty(validationError)) { + if (valueToValidate.startsWith(' ')) { + validationError = { name: 'empty', label: invalidText } + } else if (required && valueToValidate.trim().length === 0) { + validationError = { name: 'required', label: 'This field is required' } + } + } + + if (!validationError && validator) { + validationError = validator(value, allValues) + } + + return validationError + } + + const warningIconClick = () => { + setShowValidationRules(state => !state) + setShowSelectDropdown(false) + } + + const comboboxClassNames = classnames( + comboboxClassName, + 'form-field-combobox', + 'form-field', + isInvalid && 'form-field-combobox_invalid' + ) + const iconClassNames = classnames( + showSelectDropdown && 'form-field-combobox__icon_open', + 'form-field-combobox__icon' + ) + const selectValueClassNames = classnames(selectValue.className) + + const wrapperClassNames = classnames( + 'form-field__wrapper', + `form-field__wrapper-${density}`, + disabled && 'form-field__wrapper-disabled', + isInvalid && 'form-field__wrapper-invalid', + withoutBorder && 'without-border' + ) + + return ( + + {({ input, meta }) => ( +
+ {label && ( +
+ +
+ )} +
+
+ +
+
+
+ {selectValue.id} + {selectValue.id.length === 0 && selectPlaceholder && ( +
+ +
+ )} +
+ {showSelectDropdown && ( + +
    + {selectOptions.map(option => { + if (!option.hidden) { + const selectOptionClassNames = classnames( + 'form-field-combobox__dropdown-list-option', + option.className + ) + + return ( +
  • handleSelectOptionClick(option)} + > + {option.label} +
  • + ) + } + })} +
+
+ )} +
+ + {showSuggestionList && (dropdownList.length > 0 || searchIsFocused) && ( + +
+ {!hideSearchInput && ( +
+ setSearchIsFocused(true)} + placeholder="Type to search" + type="text" + /> + +
+ )} +
    + {searchIsFocused && dropdownList.length === 0 ? ( +
  • + No data +
  • + ) : ( + dropdownList.map(value => ( +
  • handleSuggestionListOptionClick(value)} + > + {value.label} +
  • + )) + )} +
+
+
+ )} +
+ {isInvalid && !Array.isArray(meta.error) && ( + } + > + + + )} + {isInvalid && Array.isArray(meta.error) && ( + + )} +
+ {!isEmpty(validationRules) && ( + + {getValidationRules()} + + )} +
+
+ )} +
+ ) +} + +FormCombobox.propTypes = { + comboboxClassName: PropTypes.string, + density: DENSITY, + disabled: PropTypes.bool, + hideSearchInput: PropTypes.bool, + inputDefaultValue: PropTypes.string, + inputPlaceholder: PropTypes.string, + invalidText: PropTypes.string, + label: PropTypes.string, + maxSuggestedMatches: PropTypes.number, + name: PropTypes.string.isRequired, + onBlur: PropTypes.func, + onChange: PropTypes.func, + onFocus: PropTypes.func, + required: PropTypes.bool, + rules: PropTypes.array, + selectDefaultValue: PropTypes.shape({}), + selectOptions: COMBOBOX_SELECT_OPTIONS.isRequired, + selectPlaceholder: PropTypes.string, + suggestionList: COMBOBOX_SUGGESTION_LIST, + validator: PropTypes.func, + withoutBorder: PropTypes.bool +} + +export default FormCombobox diff --git a/src/igz-controls/components/FormCombobox/formCombobox.scss b/src/igz-controls/components/FormCombobox/formCombobox.scss new file mode 100644 index 0000000000..cbc1a45e24 --- /dev/null +++ b/src/igz-controls/components/FormCombobox/formCombobox.scss @@ -0,0 +1,134 @@ +@use '../../scss/colors'; +@use '../../scss/borders'; +@use '../../scss/shadows'; +@use '../../scss/mixins'; + +.form-field.form-field-combobox { + width: 100%; + + .form-field { + @include mixins.inputSelectField; + + &__icons { + .form-field-combobox__icon { + cursor: pointer; + padding: 0; + transition: transform 200ms linear; + + &_open { + transform: rotate(90deg); + transform-origin: center center; + } + } + } + + &-combobox { + &__placeholder { + color: colors.$topaz; + font-size: 15px; + text-align: left; + text-transform: capitalize; + background-color: transparent; + + label { + cursor: inherit; + } + } + + &__select { + padding: 0; + overflow: visible; + + &-header { + display: flex; + flex: 1; + align-items: center; + cursor: pointer; + height: 100%; + } + } + + &__input { + width: 100%; + padding: 0 8px 0 0; + + &_hidden { + flex: 0; + } + } + } + } +} + +.form-field-combobox { + &__search { + width: 100%; + padding: 12px 0; + + &-wrapper { + position: sticky; + top: 0; + display: flex; + align-items: center; + margin: 0 9px; + border-bottom: borders.$dividerBorder; + background-color: colors.$white; + } + } + + &__dropdown { + &-select { + max-width: 220px; + } + + &-suggestions { + max-width: 350px; + } + + &-list { + margin: 0; + padding: 0; + min-width: 140px; + list-style-type: none; + + &-option { + padding: 8px 15px; + word-break: break-all; + cursor: pointer; + + &:hover { + background-color: colors.$alabaster; + } + } + } + + .pop-up-dialog { + width: 100%; + max-height: 250px; + padding: 0; + } + } + + .path-type, + &__dropdown .path-type { + &-store { + color: colors.$amethyst; + } + + &-v3io { + color: colors.$cornflowerBlueTwo; + } + + &-az, + &-gs, + &-http, + &-https, + &-s3 { + color: colors.$sorbus; + } + + &-dbfs { + color: colors.$chateauGreen; + } + } +} diff --git a/src/igz-controls/components/FormInput/FormInput.jsx b/src/igz-controls/components/FormInput/FormInput.jsx new file mode 100644 index 0000000000..31518cd968 --- /dev/null +++ b/src/igz-controls/components/FormInput/FormInput.jsx @@ -0,0 +1,453 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useState, useEffect, useRef, forwardRef } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import { isEmpty, isNil } from 'lodash' +import { Field, useField } from 'react-final-form' + +import InputNumberButtons from './InputNumberButtons/InputNumberButtons' +import OptionsMenu from '../../elements/OptionsMenu/OptionsMenu' +import ValidationTemplate from '../../elements/ValidationTemplate/ValidationTemplate' +import { TextTooltipTemplate, Tip, Tooltip } from '../../components' + +import { DENSITY, INPUT_LINK, INPUT_VALIDATION_RULES } from '../../types' +import { checkPatternsValidity, checkPatternsValidityAsync } from '../../utils/validation.util' +import { useDetectOutsideClick, useDebounce } from '../../hooks' +import { validation as ValidationConstants } from '../../constants' + +import ExclamationMarkIcon from '../../images/exclamation-mark.svg?react' +import Popout from '../../images/popout.svg?react' +import WarningIcon from '../../images/warning.svg?react' + +import './formInput.scss' + +const defaultProps = { + iconClick: () => {}, + link: { show: '', value: '' }, + onBlur: () => {}, + onKeyDown: () => {}, + onValidationError: () => {}, + validator: () => {}, + rules: [] +} + +let FormInput = ( + { + async = false, + className = '', + customRequiredLabel = '', + density = 'normal', + disabled = false, + focused = false, + iconClass = '', + iconClick = defaultProps.iconClick, + inputIcon = null, + invalidText = 'This field is invalid', + label = '', + link = defaultProps.link, + name, + onBlur = defaultProps.onBlur, + onFocus, + onKeyDown = defaultProps.onKeyDown, + pattern = null, + required = false, + onValidationError = defaultProps.onValidationError, + suggestionList = [], + step = '1', + tip = '', + type = 'text', + validationRules: rules = defaultProps.rules, + validator = defaultProps.validator, + withoutBorder = false, + ...inputProps + }, + ref +) => { + const { input, meta } = useField(name) + const [isInvalid, setIsInvalid] = useState(false) + const [isFocused, setIsFocused] = useState(false) + const [typedValue, setTypedValue] = useState('') + const [validationPattern] = useState(RegExp(pattern)) + const [validationRules, setValidationRules] = useState(rules) + const [showValidationRules, setShowValidationRules] = useState(false) + const wrapperRef = useRef() + ref ??= wrapperRef + const inputRef = useRef() + const errorsRef = useRef() + const isRequiredRulePresentRef = useRef(false) + useDetectOutsideClick(ref, () => setShowValidationRules(false)) + const debounceAsync = useDebounce() + + const formFieldClassNames = classNames('form-field-input', className) + + const inputWrapperClassNames = classNames( + 'form-field__wrapper', + `form-field__wrapper-${density}`, + disabled && 'form-field__wrapper-disabled', + isInvalid && 'form-field__wrapper-invalid', + withoutBorder && 'without-border' + ) + const labelClassNames = classNames('form-field__label', disabled && 'form-field__label-disabled') + + useEffect(() => { + setTypedValue(String(input.value)) // convert from number to string + }, [input.value]) + + useEffect(() => { + const isInputInvalid = + errorsRef.current && + meta.invalid && + (meta.validating || meta.modified || (meta.submitFailed && meta.touched)) + setIsInvalid(isInputInvalid) + onValidationError(isInputInvalid) + }, [ + meta.invalid, + meta.modified, + meta.submitFailed, + meta.touched, + meta.validating, + onValidationError + ]) + + useEffect(() => { + if (!errorsRef.current) { + if (meta.valid && showValidationRules) { + setShowValidationRules(false) + } + } + }, [meta.valid, showValidationRules]) + + useEffect(() => { + if (showValidationRules) { + window.addEventListener('scroll', handleScroll, true) + } + return () => { + window.removeEventListener('scroll', handleScroll, true) + } + }, [showValidationRules]) + + useEffect(() => { + if (focused) { + inputRef.current.focus() + } + }, [focused]) + + useEffect(() => { + setValidationRules(() => { + isRequiredRulePresentRef.current = false + + return rules.map(rule => { + if (rule.name === ValidationConstants.REQUIRED.NAME) { + isRequiredRulePresentRef.current = true + } + + return { + ...rule, + isValid: + !errorsRef.current || !Array.isArray(errorsRef.current) + ? true + : !errorsRef.current.some(err => err.name === rule.name) + } + }) + }) + }, [rules]) + + const getValidationRules = () => { + return validationRules.map(({ isValid = false, label, name }) => { + return + }) + } + + const isValueEmptyAndValid = value => { + return (!value && !required) || disabled + } + + const handleInputBlur = event => { + input.onBlur && input.onBlur(event) + + if (!event.relatedTarget || !event.relatedTarget?.closest('.form-field__suggestion-list')) { + setIsFocused(false) + onBlur && onBlur(event) + } + } + const handleInputFocus = event => { + input.onFocus && input.onFocus(event) + onFocus && onFocus(event) + setIsFocused(true) + } + + const handleInputKeyDown = event => { + input.onKeyDown && input.onKeyDown(event) + onKeyDown && onKeyDown(event) + } + + const handleScroll = event => { + if (inputRef.current && inputRef.current.contains(event.target)) return + + if ( + !event.target.closest('.options-menu') && + !event.target.classList.contains('form-field-input') + ) { + setShowValidationRules(false) + } + } + + const handleSuggestionClick = item => { + input.onChange && input.onChange(item) + setIsFocused(false) + onBlur() + } + + const toggleValidationRulesMenu = () => { + inputRef.current.focus() + setShowValidationRules(state => !state) + } + + const validateField = (value, allValues) => { + let valueToValidate = isNil(value) ? '' : String(value) + + if (isValueEmptyAndValid(valueToValidate)) return + + let validationError = null + + if (required && valueToValidate.trim().length === 0 && !isRequiredRulePresentRef.current) { + validationError = { + name: 'required', + label: customRequiredLabel || 'This field is required' + } + } else if (!isEmpty(rules) && !async) { + const [newRules, isValidField] = checkPatternsValidity(rules, valueToValidate) + const invalidRules = newRules.filter(rule => !rule.isValid) + + if (!isValidField) { + validationError = invalidRules.map(rule => ({ name: rule.name, label: rule.label })) + } + } + + if (isEmpty(validationError)) { + if (type === 'number') { + if (inputProps.max && +valueToValidate > +inputProps.max) { + validationError = { + name: 'maxValue', + label: `The maximum value must be ${inputProps.max}` + } + } + + if (inputProps.min && +valueToValidate < +inputProps.min) { + validationError = { + name: 'minValue', + label: `The minimum value must be ${inputProps.min}` + } + } + } + if (pattern && !validationPattern.test(valueToValidate)) { + validationError = { name: 'pattern', label: invalidText } + } else if (valueToValidate.startsWith(' ')) { + validationError = { name: 'empty', label: invalidText } + } + } + + if (!validationError && validator) { + validationError = validator(value, allValues) + } + + errorsRef.current = validationError + + return validationError + } + + const validateFieldAsync = debounceAsync(async (value, allValues) => { + let valueToValidate = isNil(value) ? '' : String(value) + + if (isValueEmptyAndValid(valueToValidate)) return + + let validationError = validateField(valueToValidate, allValues) + + if (!isEmpty(rules)) { + const [newRules, isValidField] = await checkPatternsValidityAsync(rules, valueToValidate) + + const invalidRules = newRules.filter(rule => !rule.isValid) + + if (!isValidField) { + validationError = invalidRules.map(rule => ({ name: rule.name, label: rule.label })) + } + } + + errorsRef.current = validationError + + return validationError + }, 400) + + const parseField = val => { + return type === 'number' && val ? parseFloat(val) || val : val + } + + return ( + + {({ input }) => { + return ( +
+ {label && ( +
+ + {link && link.show && typedValue.trim() && ( + + )} +
+ )} +
+
+ +
+
+ {isInvalid && !Array.isArray(errorsRef.current) && ( + + } + > + + + )} + {isInvalid && Array.isArray(errorsRef.current) && ( + + )} + {tip && } + {inputIcon && ( + + {inputIcon} + + )} +
+ {type === 'number' && ( + + )} +
+ {suggestionList?.length > 0 && isFocused && ( +
    + {suggestionList.map((item, index) => { + return ( +
  • { + handleSuggestionClick(item) + }} + tabIndex={index} + dangerouslySetInnerHTML={{ + __html: item.replace(new RegExp(typedValue, 'gi'), match => + match ? `${match}` : match + ) + }} + /> + ) + })} +
+ )} + {!isEmpty(validationRules) && isInvalid && Array.isArray(errorsRef.current) && ( + + {getValidationRules()} + + )} +
+ ) + }} +
+ ) +} + +FormInput = React.memo(forwardRef(FormInput)) + +FormInput.displayName = 'FormInput' + +FormInput.propTypes = { + async: PropTypes.bool, + className: PropTypes.string, + customRequiredLabel: PropTypes.string, + density: DENSITY, + disabled: PropTypes.bool, + focused: PropTypes.bool, + iconClass: PropTypes.string, + iconClick: PropTypes.func, + inputIcon: PropTypes.element, + invalidText: PropTypes.string, + label: PropTypes.string, + link: INPUT_LINK, + max: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + name: PropTypes.string.isRequired, + onBlur: PropTypes.func, + onFocus: PropTypes.func, + onKeyDown: PropTypes.func, + onValidationError: PropTypes.func, + pattern: PropTypes.string, + placeholder: PropTypes.string, + required: PropTypes.bool, + step: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + suggestionList: PropTypes.arrayOf(PropTypes.string), + tip: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), + type: PropTypes.string, + validationRules: INPUT_VALIDATION_RULES, + validator: PropTypes.func, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + withoutBorder: PropTypes.bool +} + +export default React.memo(FormInput) diff --git a/src/igz-controls/components/FormInput/InputNumberButtons/InputNumberButtons.jsx b/src/igz-controls/components/FormInput/InputNumberButtons/InputNumberButtons.jsx new file mode 100644 index 0000000000..dd37b5d896 --- /dev/null +++ b/src/igz-controls/components/FormInput/InputNumberButtons/InputNumberButtons.jsx @@ -0,0 +1,95 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React from 'react' +import PropTypes from 'prop-types' +import { isNil } from 'lodash' + +import { performFloatOperation } from '../../../utils/math.util' + +import Arrow from '../../../images/range-arrow-small.svg?react' + +import './InputNumberButtons.scss' + +let InputNumberButtons = ({ + disabled = false, + min = null, + max = null, + onChange, + step = 1, + value +}) => { + const handleIncrease = event => { + event.preventDefault() + if (max && value >= max) return + + let newValue = isCurrentValueEmpty() ? step : performFloatOperation(value, step, '+') + newValue = max && newValue > max ? max : newValue + + onChange(newValue) + } + + const handleDecrease = event => { + event.preventDefault() + + if (min && value <= min) return + + let newValue = isCurrentValueEmpty() ? -step : performFloatOperation(value, step, '-') + newValue = min && newValue < min ? min : newValue + + onChange(newValue) + } + + const isCurrentValueEmpty = () => { + return isNil(value) || value === '' + } + + return ( +
+
+ + +
+
+ ) +} + +InputNumberButtons.propTypes = { + disabled: PropTypes.bool, + min: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + max: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + onChange: PropTypes.func.isRequired, + step: PropTypes.number, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired +} + +InputNumberButtons = React.memo(InputNumberButtons) + +export default InputNumberButtons diff --git a/src/igz-controls/components/FormInput/InputNumberButtons/InputNumberButtons.scss b/src/igz-controls/components/FormInput/InputNumberButtons/InputNumberButtons.scss new file mode 100644 index 0000000000..02b24ee7f5 --- /dev/null +++ b/src/igz-controls/components/FormInput/InputNumberButtons/InputNumberButtons.scss @@ -0,0 +1,123 @@ +@use '../../../scss/colors'; +@use '../../../scss/borders'; + +.form-field-range { + align-self: stretch; + + .range { + &__buttons { + display: flex; + flex-direction: column; + justify-content: center; + width: 28px; + height: 100%; + } + + &__button { + display: flex; + width: 100%; + height: calc(50% + 1px); + align-items: center; + justify-content: center; + padding: 0; + background-color: colors.$wildSand; + cursor: pointer; + + svg { + path { + fill: colors.$topaz; + } + } + + &:hover { + background-color: colors.$mercury; + + svg { + path { + fill: colors.$primary; + } + } + } + + &:focus { + border: borders.$focusBorder; + } + + &:active { + background-color: rgba(colors.$black, 0.2); + border: borders.$focusBorder; + + svg { + path { + fill: colors.$primary; + } + } + } + + &:disabled { + cursor: not-allowed; + + svg { + path { + fill: colors.$spunPearl; + } + } + + &:focus { + border: none; + } + + &:hover { + background-color: colors.$wildSand; + } + } + + &-increase { + border-bottom: borders.$transparentBorder; + border-left: borders.$transparentBorder; + border-top-right-radius: 4px; + } + + &-decrease { + border-top: borders.$transparentBorder; + border-left: borders.$transparentBorder; + border-bottom-right-radius: 4px; + } + + .decrease { + transform: rotate(180deg); + } + } + + &-warning { + border: borders.$errorBorder; + + &_asterisk { + position: absolute; + top: 50%; + right: 35px; + color: colors.$amaranth; + transform: translateY(-50%); + } + + .range__button { + &-increase { + border-top: borders.$errorBorder; + border-right: borders.$errorBorder; + } + + &-decrease { + border-right: borders.$errorBorder; + border-bottom: borders.$errorBorder; + } + } + } + + &__warning-icon { + position: absolute; + top: 50%; + right: 30px; + transform: translateY(-50%); + } + } +} diff --git a/src/igz-controls/components/FormInput/formInput.scss b/src/igz-controls/components/FormInput/formInput.scss new file mode 100644 index 0000000000..f761158fed --- /dev/null +++ b/src/igz-controls/components/FormInput/formInput.scss @@ -0,0 +1,75 @@ +@use '../../scss/colors'; +@use '../../scss/shadows'; +@use '../../scss/mixins'; + +.form-field-input { + width: 100%; + + input { + height: inherit; + width: 100%; + padding: 12px 16px; + } + + .form-field { + @include mixins.inputSelectField; + + &__label { + &-icon { + display: inline-flex; + margin-left: 3px; + + & > *, + a { + display: inline-flex; + } + + a { + transform: translateY(-1px); + } + + svg { + width: 12px; + height: 12px; + + path { + fill: colors.$cornflowerBlue; + } + } + } + } + + &__suggestion-list { + position: absolute; + top: 100%; + left: 0; + z-index: 5; + margin: 0; + padding: 7px 0; + background-color: colors.$white; + border-radius: 4px; + box-shadow: shadows.$previewBoxShadow; + + .suggestion-item { + padding: 7px 15px; + color: colors.$mulledWine; + list-style-type: none; + + &:hover { + background-color: colors.$alabaster; + cursor: pointer; + } + } + } + } + + input[type='number'] { + border: none; + -moz-appearance: textfield; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + } + } +} diff --git a/src/igz-controls/components/FormKeyValueTable/FormKeyValueTable.jsx b/src/igz-controls/components/FormKeyValueTable/FormKeyValueTable.jsx new file mode 100644 index 0000000000..78a2972eb1 --- /dev/null +++ b/src/igz-controls/components/FormKeyValueTable/FormKeyValueTable.jsx @@ -0,0 +1,229 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import { FieldArray } from 'react-final-form-arrays' + +import FormSelect from '../../components/FormSelect/FormSelect' +import FormInput from '../../components/FormInput/FormInput' +import Tooltip from '../../components/Tooltip/Tooltip' +import TextTooltipTemplate from '../../components/TooltipTemplate/TextTooltipTemplate' +import FormActionButton from '../../elements/FormActionButton/FormActionButton' +import FormRowActions from '../../elements/FormRowActions/FormRowActions' + +import { useFormTable } from '../../hooks' +import { INPUT_VALIDATION_RULES } from '../../types' + +const FormKeyValueTable = ({ + actionButtonId = '', + addNewItemLabel = 'Add new item', + className = '', + defaultKey = '', + disabled = false, + exitEditModeTriggerItem = null, + fieldsPath, + formState, + isKeyEditable = true, + isKeyRequired = true, + isValueRequired = true, + keyHeader = 'Key', + keyLabel = 'Key', + keyOptions = null, + keyValidationRules = [], + onExitEditModeCallback = () => {}, + valueHeader = 'Value', + valueLabel = 'Value', + valueType = 'text', + valueValidationRules = [] +}) => { + const tableClassNames = classnames( + 'form-table form-key-value-table', + disabled && 'form-table_disabled', + className + ) + const { + addNewRow, + applyChanges, + bottomScrollRef, + deleteRow, + discardOrDelete, + editingItem, + enterEditMode, + isCurrentRowEditing + } = useFormTable(formState, exitEditModeTriggerItem, onExitEditModeCallback) + + const uniquenessValidator = (fields, newValue) => { + return !fields.value.some(({ data: { key } }, index) => { + return newValue.trim() === key.trim() && index !== editingItem.ui.index + }) + } + + const getKeyTextTemplate = keyValue => { + return }>{keyValue} + } + + return ( +
+
+
{keyHeader}
+
{valueHeader}
+
+
+ + {({ fields }) => ( + <> + {fields.map((rowPath, index) => { + const tableRowClassNames = classnames( + 'form-table__row', + isCurrentRowEditing(rowPath) && 'form-table__row_active' + ) + + return editingItem && index === editingItem.ui.index && !disabled ? ( +
+
+ {keyOptions ? ( + + ) : isKeyEditable || editingItem.ui.isNew ? ( + uniquenessValidator(fields, newValue) + } + ]} + /> + ) : ( + getKeyTextTemplate(fields.value[index].data.key) + )} +
+
+ +
+ +
+ ) : ( +
!disabled && enterEditMode(event, fields, fieldsPath, index)} + > +
+ {getKeyTextTemplate(fields.value[index].data.key)} +
+
+ + } + > + {valueType === 'password' ? '*****' : fields.value[index].data.value} + +
+ +
+ ) + })} + +
+
+ ) +} + +FormKeyValueTable.propTypes = { + actionButtonId: PropTypes.string, + addNewItemLabel: PropTypes.string, + className: PropTypes.string, + defaultKey: PropTypes.string, + disabled: PropTypes.bool, + exitEditModeTriggerItem: PropTypes.any, + fieldsPath: PropTypes.string.isRequired, + formState: PropTypes.shape({}).isRequired, + isKeyEditable: PropTypes.bool, + isKeyRequired: PropTypes.bool, + isValueRequired: PropTypes.bool, + keyHeader: PropTypes.string, + keyLabel: PropTypes.string, + keyOptions: PropTypes.arrayOf( + PropTypes.shape({ + label: PropTypes.string.isRequired, + id: PropTypes.string.isRequired + }) + ), + keyValidationRules: INPUT_VALIDATION_RULES, + onExitEditModeCallback: PropTypes.func, + valueHeader: PropTypes.string, + valueLabel: PropTypes.string, + valueType: PropTypes.string, + valueValidationRules: INPUT_VALIDATION_RULES +} + +export default FormKeyValueTable diff --git a/src/igz-controls/components/FormOnChange/FormOnChange.jsx b/src/igz-controls/components/FormOnChange/FormOnChange.jsx new file mode 100644 index 0000000000..1abd7afa29 --- /dev/null +++ b/src/igz-controls/components/FormOnChange/FormOnChange.jsx @@ -0,0 +1,60 @@ +/* +Copyright 2019 Iguazio Systems Ltd. + +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. + +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useEffect, useState } from 'react' +import { Field } from 'react-final-form' +import PropTypes from 'prop-types' + +const OnChangeState = ({ inputValue, handler }) => { + const [previousValue, setPreviousValue] = useState(inputValue) + + useEffect(() => { + if (inputValue !== previousValue) { + setPreviousValue(inputValue) + handler(inputValue, previousValue) + } + }, [handler, inputValue, previousValue]) + + return null +} + +OnChangeState.propTypes = { + inputValue: PropTypes.any.isRequired, + handler: PropTypes.func.isRequired +} + +const FormOnChange = ({ handler, name }) => { + return ( + } + /> + ) +} + +FormOnChange.propTypes = { + handler: PropTypes.func.isRequired, + name: PropTypes.string.isRequired +} + +export default FormOnChange diff --git a/src/igz-controls/components/FormRadio/FormRadio.jsx b/src/igz-controls/components/FormRadio/FormRadio.jsx new file mode 100644 index 0000000000..4b824e59e8 --- /dev/null +++ b/src/igz-controls/components/FormRadio/FormRadio.jsx @@ -0,0 +1,82 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React from 'react' +import PropTypes from 'prop-types' +import { Field } from 'react-final-form' +import classNames from 'classnames' + +import Tooltip from '../Tooltip/Tooltip' +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' + +import './FormRadio.scss' + +let FormRadio = ({ + className = '', + name, + label, + readOnly = false, + tooltip = '', + ...inputProps +}) => { + const formFieldClassNames = classNames( + 'form-field-radio', + readOnly && 'form-field-radio_readonly', + className + ) + + return ( + + {({ input }) => ( +
+ + {tooltip ? ( + }> + + + ) : ( + + )} +
+ )} +
+ ) +} + +FormRadio.propTypes = { + className: PropTypes.string, + label: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + readOnly: PropTypes.bool, + tooltip: PropTypes.string +} + +FormRadio = React.memo(FormRadio) + +export default FormRadio diff --git a/src/igz-controls/components/FormRadio/FormRadio.scss b/src/igz-controls/components/FormRadio/FormRadio.scss new file mode 100644 index 0000000000..49c4f09f85 --- /dev/null +++ b/src/igz-controls/components/FormRadio/FormRadio.scss @@ -0,0 +1,41 @@ +@use '../../scss/colors'; +@use '../../scss/mixins'; + +.form-field-radio { + display: inline-flex; + align-items: center; + justify-content: flex-start; + color: colors.$primary; + margin-right: 15px; + + &_readonly { + @include mixins.radioCheckReadonly; + } + + input[type='radio'] { + width: 16px; + height: 16px; + border-radius: 50%; + + @include mixins.radioCheckField; + + &::before { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 10px; + height: 10px; + border-radius: 50%; + transform: scale(0); + transition: transform 0.2s ease-in-out; + box-shadow: inset 1em 1em currentColor; + } + + &:checked { + &::before { + transform: scale(1); + } + } + } +} diff --git a/src/igz-controls/components/FormRadio/FormRadio.stories.js b/src/igz-controls/components/FormRadio/FormRadio.stories.js new file mode 100644 index 0000000000..10ebfae88f --- /dev/null +++ b/src/igz-controls/components/FormRadio/FormRadio.stories.js @@ -0,0 +1,38 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React from 'react' +import { Form } from 'react-final-form' + +import FormRadio from '/src/lib/components/FormRadio/FormRadio.jsx' + +export default { + title: 'Example/FormRadio', + component: FormRadio +} + +const commonArgs = { + disabled: false, + label: 'label', + name: 'radio' +} + +const Template = args =>
null}>{() => } + +export const Normal = Template.bind({}) +Normal.args = { + ...commonArgs +} diff --git a/src/igz-controls/components/FormSelect/FormSelect.jsx b/src/igz-controls/components/FormSelect/FormSelect.jsx new file mode 100644 index 0000000000..cc086c7b4a --- /dev/null +++ b/src/igz-controls/components/FormSelect/FormSelect.jsx @@ -0,0 +1,426 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react' +import PropTypes from 'prop-types' +import classNames from 'classnames' +import { Field, useField } from 'react-final-form' + +import ConfirmDialog from '../ConfirmDialog/ConfirmDialog' +import PopUpDialog from '../PopUpDialog/PopUpDialog' +import SelectOption from '../../elements/SelectOption/SelectOption' +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' +import Tooltip from '../Tooltip/Tooltip' + +import { DENSITY, SELECT_OPTIONS } from '../../types' +import { TERTIARY_BUTTON } from '../../constants' + +import Caret from '../../images/dropdown.svg?react' + +import './formSelect.scss' + +let FormSelect = ({ + className = '', + density = 'normal', + disabled = false, + hideSelectedOption = false, + label = '', + multiple = false, + name, + onChange = null, + options, + placeholder = '', + preventWidthOverflow = false, + required = false, + scrollToView = true, + search = false, + selectedItemAction = null, + tooltip = '', + withSelectedIcon = true, + withoutBorder = false +}) => { + const { input, meta } = useField(name) + const [isInvalid, setIsInvalid] = useState(false) + const [isConfirmDialogOpen, setConfirmDialogOpen] = useState(false) + const [isOpen, setIsOpen] = useState(false) + const [searchValue, setSearchValue] = useState('') + const optionsListRef = useRef() + const popUpRef = useRef() + const selectRef = useRef() + const searchRef = useRef() + const { width: selectWidth } = selectRef?.current?.getBoundingClientRect() || {} + + const selectWrapperClassNames = classNames( + 'form-field__wrapper', + `form-field__wrapper-${density}`, + disabled && 'form-field__wrapper-disabled', + isOpen && 'form-field__wrapper-active', + isInvalid && 'form-field__wrapper-invalid', + withoutBorder && 'without-border' + ) + + const selectLabelClassName = classNames( + 'form-field__label', + disabled && 'form-field__label-disabled' + ) + + const selectValueClassName = classNames( + 'form-field__select-value', + !input.value && 'form-field__select-placeholder' + ) + + const selectedOption = options.find(option => option.id === input.value) + + const getFilteredOptions = useCallback( + options => { + return options.filter(option => { + return !search || option.label.toLowerCase().includes(searchValue.toLowerCase()) + }) + }, + [search, searchValue] + ) + + const sortedOptionsList = useMemo(() => { + if (scrollToView) { + return getFilteredOptions(options) + } + + const optionsList = [...options] + + const selectedOption = optionsList.filter((option, idx, arr) => { + if (option.id === input.value) { + arr.splice(idx, 1) + return true + } + return false + }) + + return getFilteredOptions([...selectedOption, ...optionsList]) + }, [input.value, getFilteredOptions, options, scrollToView]) + + const getSelectValue = () => { + if (!input.value || !input.value.length) { + return `Select Option${multiple ? 's' : ''}` + } + + const multipleValue = + multiple && input.value.includes('all') && input.value.length > 1 + ? options + .filter(option => option.id !== 'all') + .filter(option => input.value.includes(option.id)) + .map(option => option.label) + .join(', ') + : options + .filter(option => input.value.includes(option.id)) + .map(option => option.label) + .join(', ') + + return !multiple + ? selectedOption?.label + : input.value.length <= 2 + ? multipleValue + : `${input.value.length} items selected` + } + + useEffect(() => { + setIsInvalid( + meta.invalid && (meta.validating || meta.modified || (meta.submitFailed && meta.touched)) + ) + }, [meta.invalid, meta.modified, meta.submitFailed, meta.touched, meta.validating]) + + const openMenu = useCallback(() => { + if (!isOpen) { + setIsOpen(true) + input.onFocus(new Event('focus')) + } + }, [input, isOpen]) + + const closeMenu = useCallback(() => { + if (isOpen) { + setIsOpen(false) + input.onBlur(new Event('blur')) + } + }, [input, isOpen]) + + const clickHandler = useCallback( + event => { + if (selectRef.current !== event.target.closest('.form-field-select')) { + closeMenu() + } + }, + [closeMenu] + ) + + const handleScroll = useCallback( + event => { + if (!event.target.closest('.options-list__body')) { + closeMenu() + } + }, + [closeMenu] + ) + + useEffect(() => { + if (isOpen) { + window.addEventListener('scroll', handleScroll, true) + } + + window.addEventListener('click', clickHandler) + + return () => { + window.removeEventListener('click', clickHandler) + window.removeEventListener('scroll', handleScroll, true) + } + }, [clickHandler, handleScroll, isOpen]) + + const scrollOptionToView = useCallback(() => { + const selectedOptionEl = optionsListRef.current.querySelector( + `[data-custom-id="${input.value}"]` + ) + + if (!selectedOptionEl) return + + searchValue + ? optionsListRef.current.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) + : setTimeout(() => { + selectedOptionEl.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }) + }, 0) + }, [input.value, searchValue]) + + useEffect(() => { + if (isOpen && optionsListRef.current && scrollToView) { + scrollOptionToView() + } + }, [isOpen, scrollOptionToView, scrollToView]) + + useEffect(() => { + if (isOpen && search && searchRef.current) { + searchRef.current.focus() + } + }, [isOpen, search]) + + const toggleOpen = () => { + if (isOpen) { + closeMenu() + } else { + !disabled && openMenu() + } + } + + const handleCloseSelectBody = useCallback( + event => { + event.stopPropagation() + if (multiple) return + + if ( + !event.target.classList.contains('disabled') && + !event.target.closest('.options-list__search') + ) { + closeMenu() + setSearchValue('') + } + }, + [closeMenu, multiple] + ) + + const handleSelectOptionClick = (selectedOption, option) => { + if (selectedOption !== input.value) { + option.handler && option.handler() + onChange && onChange(selectedOption) + + setTimeout(() => { + input.onChange(selectedOption) + }) + } + } + + const validateField = value => { + if (required) { + return value ? undefined : 'Required' + } + } + + return ( + + {({ input, meta }) => ( + } + hidden={!tooltip} + > +
+ {label && ( +
+ +
+ )} +
+
+ {!hideSelectedOption && ( +
+ {getSelectValue() || placeholder} +
+ )} +
+
+ {input.value && selectedItemAction && ( + <> + {selectedItemAction.handler ? ( + }> + + + ) : ( + {selectedItemAction.icon} + )} + + )} + + + +
+
+ {isConfirmDialogOpen && ( + { + setConfirmDialogOpen(false) + }, + label: 'Cancel', + variant: TERTIARY_BUTTON + }} + closePopUp={() => { + setConfirmDialogOpen(false) + }} + confirmButton={{ + handler: () => { + selectedItemAction.handler(input.value) + setConfirmDialogOpen(false) + }, + label: selectedItemAction.confirm.btnConfirmLabel, + variant: selectedItemAction.confirm.btnConfirmType + }} + header={selectedItemAction.confirm.title} + isOpen={isConfirmDialogOpen} + message={selectedItemAction.confirm.message} + /> + )} + {isOpen && ( + +
+ {search && ( +
+ setSearchValue(event.target.value)} + ref={searchRef} + autoFocus + /> +
+ )} +
    + {sortedOptionsList.map(option => { + return ( + { + handleSelectOptionClick(selectedOption, option) + }} + multiple={multiple} + selectedId={!multiple ? input.value : ''} + withSelectedIcon={withSelectedIcon} + /> + ) + })} +
+
+
+ )} + +
+
+ )} +
+ ) +} + +FormSelect.propTypes = { + className: PropTypes.string, + density: DENSITY, + disabled: PropTypes.bool, + hideSelectedOption: PropTypes.bool, + label: PropTypes.string, + multiple: PropTypes.bool, + name: PropTypes.string.isRequired, + onChange: PropTypes.func, + options: SELECT_OPTIONS.isRequired, + placeholder: PropTypes.string, + preventWidthOverflow: PropTypes.bool, + required: PropTypes.bool, + scrollToView: PropTypes.bool, + search: PropTypes.bool, + selectedItemAction: PropTypes.object, + tooltip: PropTypes.string, + withSelectedIcon: PropTypes.bool, + withoutBorder: PropTypes.bool +} + +FormSelect = React.memo(FormSelect) + +export default FormSelect diff --git a/src/igz-controls/components/FormSelect/formSelect.scss b/src/igz-controls/components/FormSelect/formSelect.scss new file mode 100644 index 0000000000..767e896be1 --- /dev/null +++ b/src/igz-controls/components/FormSelect/formSelect.scss @@ -0,0 +1,98 @@ +@use '../../scss/mixins'; +@use '../../scss/colors'; +@use '../../scss/shadows'; +@use '../../scss/borders'; + +.select-tooltip { + width: 100%; +} + +.form-field-select { + width: 100%; + + .form-field { + @include mixins.inputSelectField; + + &__wrapper { + cursor: pointer; + + &-active { + background: colors.$alabaster; + } + + &-disabled { + cursor: not-allowed; + + .form-field__caret { + path { + fill: colors.$spunPearl; + } + } + } + } + + &__select { + display: flex; + align-items: center; + width: 100%; + padding: 0 20px 0 16px; + + &-value { + display: block; + font-size: 15px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + &-placeholder { + color: colors.$spunPearl; + } + + &-sub_label { + display: block; + margin-left: 10px; + overflow: hidden; + color: colors.$topaz; + white-space: nowrap; + text-overflow: ellipsis; + } + } + } + + &__options-list { + .pop-up-dialog { + width: 100%; + padding: 0; + border-radius: 0; + } + + .options-list { + margin: 0; + padding: 0; + list-style-type: none; + max-height: 250px; + overflow-y: auto; + + &__body { + width: 100%; + color: colors.$mulledWineTwo; + background-color: colors.$white; + border: borders.$primaryBorder; + border-radius: 4px; + box-shadow: shadows.$filterShadow; + } + + &__search { + width: 100%; + + input { + width: 100%; + padding: 10px; + border: none; + border-bottom: borders.$primaryBorder; + } + } + } + } +} diff --git a/src/igz-controls/components/FormTextarea/FormTextarea.jsx b/src/igz-controls/components/FormTextarea/FormTextarea.jsx new file mode 100644 index 0000000000..91e7701c2d --- /dev/null +++ b/src/igz-controls/components/FormTextarea/FormTextarea.jsx @@ -0,0 +1,189 @@ +/* +Copyright 2022 Iguazio Systems Ltd. +Licensed under the Apache License, Version 2.0 (the "License") with +an addition restriction as set forth herein. You may not use this +file except in compliance with the License. You may obtain a copy of +the License at http://www.apache.org/licenses/LICENSE-2.0. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied. See the License for the specific language governing +permissions and limitations under the License. +In addition, you may not use the software for any purposes that are +illegal under applicable law, and the grant of the foregoing license +under the Apache 2.0 license is conditioned upon your compliance with +such restriction. +*/ +import React, { forwardRef, useEffect, useLayoutEffect, useRef, useState } from 'react' +import classnames from 'classnames' +import PropTypes from 'prop-types' +import { Field, useField } from 'react-final-form' + +import TextTooltipTemplate from '../TooltipTemplate/TextTooltipTemplate' +import Tip from '../Tip/Tip' +import Tooltip from '../Tooltip/Tooltip' + +import ExclamationMarkIcon from '../../images/exclamation-mark.svg?react' + +import './formTextarea.scss' + +let FormTextarea = ( + { + className = '', + disabled = false, + focused = false, + iconClass = '', + invalidText = 'This field is invalid', + label = '', + maxLength = null, + name, + onBlur = () => {}, + onChange = () => {}, + required = false, + rows = 3, + textAreaIcon = null, + tip = '', + withoutBorder = false, + ...textareaProps + }, + ref +) => { + const { input, meta } = useField(name) + const [isInvalid, setIsInvalid] = useState(false) + const [textAreaCount, setTextAreaCount] = useState(input.value.length) + const textAreaRef = useRef() + + const formFieldClassNames = classnames('form-field-textarea', className) + const labelClassNames = classnames('form-field__label', disabled && 'form-field__label-disabled') + const textAreaClassNames = classnames( + 'form-field__wrapper', + disabled && 'form-field__wrapper-disabled', + isInvalid && 'form-field__wrapper-invalid', + withoutBorder && 'without-border' + ) + + useLayoutEffect(() => { + setTextAreaCount(input.value.length) + }, [input.value.length]) + + useEffect(() => { + if (focused) { + textAreaRef.current.focus() + } + }, [focused, textAreaRef]) + + useEffect(() => { + setIsInvalid( + meta.invalid && (meta.validating || meta.modified || (meta.submitFailed && meta.touched)) + ) + }, [meta.invalid, meta.modified, meta.submitFailed, meta.touched, meta.validating]) + + const handleInputBlur = event => { + input.onBlur(event) + onBlur && onBlur(event) + } + + const handleInputChange = event => { + input.onChange(event) + onChange && onChange(event.target.value) + } + + const handleInputFocus = event => { + input.onFocus(event) + } + + const validateField = value => { + const valueToValidate = value ?? '' + let validationError = null + + if (valueToValidate.startsWith(' ')) { + validationError = { name: 'empty', label: invalidText } + } else if (required && valueToValidate.trim().length === 0) { + validationError = { name: 'required', label: 'This field is required' } + } + + return validationError + } + + return ( + + {({ input, meta }) => ( +
+
+ {label && ( + + )} +
+
+
+