mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
console: add components to manage query collections
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5658 GitOrigin-RevId: a962f85e53396cd1d2cd64f46b91d3c042641ab3
This commit is contained in:
parent
6ab128a7e3
commit
2e83b22591
@ -9,6 +9,7 @@ import '../src/theme/tailwind.css';
|
||||
import { store } from '../src/store';
|
||||
import '../src/components/Common/Common.module.scss';
|
||||
import { DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
|
||||
import 'react-loading-skeleton/dist/skeleton.css';
|
||||
|
||||
const channel = addons.getChannel();
|
||||
initialize();
|
||||
|
805
console/package-lock.json
generated
805
console/package-lock.json
generated
@ -16,10 +16,12 @@
|
||||
"@hookform/resolvers": "2.8.10",
|
||||
"@radix-ui/react-collapsible": "^0.1.1",
|
||||
"@radix-ui/react-dialog": "^0.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "^1.0.0",
|
||||
"@radix-ui/react-switch": "^0.1.5",
|
||||
"@radix-ui/react-tabs": "^1.0.0",
|
||||
"@radix-ui/react-tooltip": "^0.1.0",
|
||||
"@reduxjs/toolkit": "^1.5.1",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.get": "^4.4.6",
|
||||
"@xstate/react": "^2.0.0",
|
||||
"ace-builds": "^1.4.11",
|
||||
@ -43,6 +45,7 @@
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-decode": "^3.0.0",
|
||||
"less": "3.11.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.merge": "4.6.2",
|
||||
@ -62,6 +65,7 @@
|
||||
"react-hook-form": "7.15.4",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-modal": "3.11.2",
|
||||
"react-notification-system-redux": "2.0.1",
|
||||
"react-progress-bar-plus": "1.3.1",
|
||||
@ -3003,6 +3007,32 @@
|
||||
"@f/map-obj": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
|
||||
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/react-dom": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
|
||||
"integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^0.5.3",
|
||||
"use-isomorphic-layout-effect": "^1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
@ -4966,6 +4996,126 @@
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-1.0.0.tgz",
|
||||
"integrity": "sha512-Ptben3TxPWrZLbInO7zjAK73kmjYuStsxfg6ujgt+EywJyREoibhZYnsSNqC+UiOtl4PdW/MOHhxVDtew5fouQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-menu": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
||||
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
||||
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
|
||||
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
||||
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
|
||||
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
||||
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-0.1.0.tgz",
|
||||
@ -5018,6 +5168,297 @@
|
||||
"react": "^16.8 || ^17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-1.0.0.tgz",
|
||||
"integrity": "sha512-icW4C64T6nHh3Z4Q1fxO1RlSShouFF4UpUmPV8FLaJZfphDljannKErDuALDx4ClRLihAPZ9i+PrLNPoWS2DMA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-collection": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-direction": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.0",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.0",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-popper": "1.0.0",
|
||||
"@radix-ui/react-portal": "1.0.0",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-roving-focus": "1.0.0",
|
||||
"@radix-ui/react-slot": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
|
||||
"integrity": "sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
||||
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
||||
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
|
||||
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
|
||||
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
|
||||
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.0.tgz",
|
||||
"integrity": "sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@floating-ui/react-dom": "0.7.2",
|
||||
"@radix-ui/react-arrow": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0",
|
||||
"@radix-ui/react-use-rect": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0",
|
||||
"@radix-ui/rect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
|
||||
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
|
||||
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
|
||||
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
||||
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
|
||||
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
||||
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
|
||||
"integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/rect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
|
||||
"integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
|
||||
"integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
|
||||
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.3",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.0",
|
||||
"use-sidecar": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-0.1.4.tgz",
|
||||
@ -10786,6 +11227,14 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
|
||||
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
|
||||
},
|
||||
"node_modules/@types/lodash.debounce": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz",
|
||||
"integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==",
|
||||
"dependencies": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash.get": {
|
||||
"version": "4.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz",
|
||||
@ -33876,6 +34325,14 @@
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"node_modules/react-loading-skeleton": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
|
||||
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-merge-refs": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
|
||||
@ -43622,6 +44079,28 @@
|
||||
"@f/map-obj": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg=="
|
||||
},
|
||||
"@floating-ui/dom": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
|
||||
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
|
||||
"requires": {
|
||||
"@floating-ui/core": "^0.7.3"
|
||||
}
|
||||
},
|
||||
"@floating-ui/react-dom": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz",
|
||||
"integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==",
|
||||
"requires": {
|
||||
"@floating-ui/dom": "^0.5.3",
|
||||
"use-isomorphic-layout-effect": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@gar/promisify": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
|
||||
@ -45177,6 +45656,99 @@
|
||||
"@radix-ui/react-use-escape-keydown": "0.1.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-dropdown-menu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-1.0.0.tgz",
|
||||
"integrity": "sha512-Ptben3TxPWrZLbInO7zjAK73kmjYuStsxfg6ujgt+EywJyREoibhZYnsSNqC+UiOtl4PdW/MOHhxVDtew5fouQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-menu": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
||||
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-context": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
||||
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-slot": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
|
||||
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
||||
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz",
|
||||
"integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
||||
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-focus-guards": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-0.1.0.tgz",
|
||||
@ -45217,6 +45789,225 @@
|
||||
"@radix-ui/react-primitive": "0.1.4"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-menu": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-1.0.0.tgz",
|
||||
"integrity": "sha512-icW4C64T6nHh3Z4Q1fxO1RlSShouFF4UpUmPV8FLaJZfphDljannKErDuALDx4ClRLihAPZ9i+PrLNPoWS2DMA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-collection": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-direction": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.0",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.0",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-popper": "1.0.0",
|
||||
"@radix-ui/react-portal": "1.0.0",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-roving-focus": "1.0.0",
|
||||
"@radix-ui/react-slot": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-arrow": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz",
|
||||
"integrity": "sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz",
|
||||
"integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-context": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz",
|
||||
"integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz",
|
||||
"integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
|
||||
"integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz",
|
||||
"integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-id": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz",
|
||||
"integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-popper": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.0.tgz",
|
||||
"integrity": "sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@floating-ui/react-dom": "0.7.2",
|
||||
"@radix-ui/react-arrow": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0",
|
||||
"@radix-ui/react-use-rect": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0",
|
||||
"@radix-ui/rect": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-portal": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz",
|
||||
"integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-presence": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz",
|
||||
"integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-primitive": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz",
|
||||
"integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-slot": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz",
|
||||
"integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
|
||||
"integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz",
|
||||
"integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz",
|
||||
"integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-rect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz",
|
||||
"integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/rect": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-use-size": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz",
|
||||
"integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/rect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz",
|
||||
"integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"react-remove-scroll": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz",
|
||||
"integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==",
|
||||
"requires": {
|
||||
"react-remove-scroll-bar": "^2.3.3",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.0",
|
||||
"use-sidecar": "^1.1.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-popper": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-0.1.4.tgz",
|
||||
@ -49578,6 +50369,14 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
|
||||
"integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q=="
|
||||
},
|
||||
"@types/lodash.debounce": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.7.tgz",
|
||||
"integrity": "sha512-X1T4wMZ+gT000M2/91SYj0d/7JfeNZ9PeeOldSNoE/lunLeQXKvkmIumI29IaKMotU/ln/McOIvgzZcQ/3TrSA==",
|
||||
"requires": {
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash.get": {
|
||||
"version": "4.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz",
|
||||
@ -67644,6 +68443,12 @@
|
||||
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
|
||||
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
|
||||
},
|
||||
"react-loading-skeleton": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-loading-skeleton/-/react-loading-skeleton-3.1.0.tgz",
|
||||
"integrity": "sha512-j1U1CWWs68nBPOg7tkQqnlFcAMFF6oEK6MgqAo15f8A5p7mjH6xyKn2gHbkcimpwfO0VQXqxAswnSYVr8lWzjw==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-merge-refs": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz",
|
||||
|
@ -70,6 +70,7 @@
|
||||
"@hookform/resolvers": "2.8.10",
|
||||
"@radix-ui/react-collapsible": "^0.1.1",
|
||||
"@radix-ui/react-dialog": "^0.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "1.0.0",
|
||||
"@radix-ui/react-switch": "^0.1.5",
|
||||
"@radix-ui/react-tabs": "^1.0.0",
|
||||
"@radix-ui/react-tooltip": "^0.1.0",
|
||||
@ -97,6 +98,7 @@
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-decode": "^3.0.0",
|
||||
"less": "3.11.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.merge": "4.6.2",
|
||||
@ -116,6 +118,7 @@
|
||||
"react-hook-form": "7.15.4",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-modal": "3.11.2",
|
||||
"react-notification-system-redux": "2.0.1",
|
||||
"react-progress-bar-plus": "1.3.1",
|
||||
@ -182,6 +185,7 @@
|
||||
"@types/jquery": "3.3.33",
|
||||
"@types/jwt-decode": "2.2.1",
|
||||
"@types/lodash": "^4.14.159",
|
||||
"@types/lodash.debounce": "^4.0.7",
|
||||
"@types/lodash.isequal": "^4.5.6",
|
||||
"@types/lodash.merge": "^4.6.6",
|
||||
"@types/lodash.pickby": "^4.6.7",
|
||||
|
@ -10,6 +10,7 @@ import { telemetryNotificationShown } from '../../telemetry/Actions';
|
||||
import { showTelemetryNotification } from '../../telemetry/Notifications';
|
||||
import globals from '../../Globals';
|
||||
import styles from './App.module.scss';
|
||||
import 'react-loading-skeleton/dist/skeleton.css';
|
||||
|
||||
import { theme } from '../UIKit/theme';
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { AllowListSidebar } from './AllowListSidebar';
|
||||
import { handlers } from '../../../QueryCollections/hooks/useQueryCollections/mocks/handlers.mock';
|
||||
|
||||
export default {
|
||||
title: 'Features/Allow List/Allow List Sidebar',
|
||||
component: AllowListSidebar,
|
||||
decorators: [
|
||||
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
|
||||
ReactQueryDecorator(),
|
||||
],
|
||||
parameters: {
|
||||
msw: handlers(1500),
|
||||
},
|
||||
} as ComponentMeta<typeof AllowListSidebar>;
|
||||
|
||||
export const Primary = () => (
|
||||
<AllowListSidebar selectedCollectionQuery="allowed_queries" />
|
||||
);
|
@ -0,0 +1,50 @@
|
||||
import debounce from 'lodash.debounce';
|
||||
import React from 'react';
|
||||
import { IndicatorCard } from '@/new-components/IndicatorCard';
|
||||
import { useServerConfig } from '@/hooks';
|
||||
import { AllowListSidebarHeader } from './AllowListSidebarHeader';
|
||||
import { QueryCollectionList } from './QueryCollectionList';
|
||||
import { AllowListSidebarSearchForm } from './AllowListSidebarSearchForm';
|
||||
|
||||
interface AllowListSidebarProps {
|
||||
selectedCollectionQuery: string;
|
||||
}
|
||||
|
||||
export const AllowListSidebar: React.FC<AllowListSidebarProps> = props => {
|
||||
const { selectedCollectionQuery } = props;
|
||||
const [search, setSearch] = React.useState('');
|
||||
const debouncedSearch = React.useMemo(() => debounce(setSearch, 300), []);
|
||||
|
||||
const { data: configData, isLoading: isConfigLoading } = useServerConfig();
|
||||
|
||||
const renderInstructions =
|
||||
!isConfigLoading && !configData?.is_allow_list_enabled;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AllowListSidebarHeader />
|
||||
<AllowListSidebarSearchForm
|
||||
setSearch={(searchString: string) => debouncedSearch(searchString)}
|
||||
/>
|
||||
<QueryCollectionList
|
||||
selectedCollectionQuery={selectedCollectionQuery}
|
||||
search={search}
|
||||
/>
|
||||
{renderInstructions && (
|
||||
<IndicatorCard status="info">
|
||||
<p>
|
||||
Want to enable your allow list? You can set{' '}
|
||||
<span className="text-red-600 font-mono bg-red-50 rounded px-1.5 py-0.5">
|
||||
HASURA_GRAPHQL_ENABLE_ALLOWLIST
|
||||
</span>{' '}
|
||||
to{' '}
|
||||
<span className="text-red-600 font-mono bg-red-50 rounded px-1.5 py-0.5">
|
||||
true
|
||||
</span>{' '}
|
||||
so that your API will only allow accepted pre-selected operations.
|
||||
</p>
|
||||
</IndicatorCard>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,36 @@
|
||||
import { Button } from '@/new-components/Button';
|
||||
|
||||
import React from 'react';
|
||||
import { FaFolderPlus } from 'react-icons/fa';
|
||||
import { QueryCollectionCreateDialog } from './QueryCollectionCreateDialog';
|
||||
import { AllowListStatus } from './AllowListStatus';
|
||||
|
||||
export const AllowListSidebarHeader = () => {
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = React.useState(false);
|
||||
return (
|
||||
<>
|
||||
{isCreateModalOpen && (
|
||||
<QueryCollectionCreateDialog
|
||||
onClose={() => setIsCreateModalOpen(false)}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<span className="text-sm font-semibold text-muted uppercase tracking-wider">
|
||||
Allow List
|
||||
</span>
|
||||
<div className="ml-1.5">
|
||||
<AllowListStatus />
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<Button
|
||||
icon={<FaFolderPlus />}
|
||||
size="sm"
|
||||
onClick={() => setIsCreateModalOpen(true)}
|
||||
>
|
||||
Add Collection
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import z from 'zod';
|
||||
import { Form, InputField } from '@/new-components/Form';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { FaSearch } from 'react-icons/fa';
|
||||
|
||||
const schema = z.object({
|
||||
search: z.string(),
|
||||
});
|
||||
|
||||
interface AllowListSidebarSearchFormProps {
|
||||
setSearch: (search: string) => void;
|
||||
}
|
||||
const SearchInput: React.FC<AllowListSidebarSearchFormProps> = ({
|
||||
setSearch,
|
||||
}) => {
|
||||
const { watch } = useFormContext();
|
||||
const search = watch('search');
|
||||
useEffect(() => {
|
||||
setSearch(search);
|
||||
}, [search]);
|
||||
|
||||
return (
|
||||
<InputField
|
||||
id="search"
|
||||
placeholder="Search Collections..."
|
||||
icon={<FaSearch />}
|
||||
name="search"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const AllowListSidebarSearchForm: React.FC<AllowListSidebarSearchFormProps> =
|
||||
({ setSearch }) => {
|
||||
return (
|
||||
<Form schema={schema} onSubmit={() => {}} className="p-0">
|
||||
{() => <SearchInput setSearch={setSearch} />}
|
||||
</Form>
|
||||
);
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { useServerConfig } from '@/hooks';
|
||||
import { Badge } from '@/new-components/Badge';
|
||||
import { Tooltip } from '@/new-components/Tooltip';
|
||||
import React from 'react';
|
||||
import { FaExclamationTriangle } from 'react-icons/fa';
|
||||
|
||||
export const AllowListStatus = () => {
|
||||
const { data: configData, isLoading, isError } = useServerConfig();
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton width={80} height={20} />;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return (
|
||||
<Tooltip tooltipContentChildren="Status unknown. Config API is currently unavailable.">
|
||||
<Badge color="yellow" className="-ml-1 p-0">
|
||||
<FaExclamationTriangle />
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
if (configData?.is_allow_list_enabled) {
|
||||
return <Badge color="green">Enabled</Badge>;
|
||||
}
|
||||
|
||||
return <Badge color="red">disabled</Badge>;
|
||||
};
|
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import z from 'zod';
|
||||
import { Dialog } from '@/new-components/Dialog';
|
||||
import { Form, InputField } from '@/new-components/Form';
|
||||
import { useCreateQueryCollection } from '../../../QueryCollections/hooks/useCreateQueryCollection';
|
||||
|
||||
interface QueryCollectionCreateDialogProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
});
|
||||
export const QueryCollectionCreateDialog: React.FC<QueryCollectionCreateDialogProps> =
|
||||
props => {
|
||||
const { onClose } = props;
|
||||
const { createQueryCollection, isLoading } = useCreateQueryCollection();
|
||||
|
||||
return (
|
||||
<Form schema={schema} onSubmit={() => {}}>
|
||||
{({ watch, setError, trigger }) => {
|
||||
const name = watch('name');
|
||||
return (
|
||||
<Dialog hasBackdrop title="Create Collection" onClose={onClose}>
|
||||
<>
|
||||
<div className="p-4">
|
||||
<InputField
|
||||
id="name"
|
||||
name="name"
|
||||
label="New Collection Name"
|
||||
placeholder="New Collection Name..."
|
||||
/>
|
||||
</div>
|
||||
<Dialog.Footer
|
||||
callToDeny="Cancel"
|
||||
callToAction="Create Collection"
|
||||
onClose={onClose}
|
||||
onSubmit={async () => {
|
||||
if (await trigger()) {
|
||||
// TODO: remove as when proper form types will be available
|
||||
createQueryCollection(name as string, {
|
||||
addToAllowList: true,
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
onError: error => {
|
||||
setError('name', {
|
||||
type: 'manual',
|
||||
message: (error as Error).message,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</>
|
||||
</Dialog>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { FaFolder, FaFolderOpen } from 'react-icons/fa';
|
||||
|
||||
interface QueryCollectionItemProps extends React.ComponentProps<'a'> {
|
||||
name: string;
|
||||
selected: boolean;
|
||||
}
|
||||
|
||||
export const QueryCollectionItem: React.FC<QueryCollectionItemProps> =
|
||||
props => {
|
||||
const { name, selected, className, ...rest } = props;
|
||||
const Icon = selected ? FaFolderOpen : FaFolder;
|
||||
const textClassName = selected
|
||||
? 'text-amber-500 hover:text-amber-600'
|
||||
: 'hover:bg-gray-100 hover:text-gray-900';
|
||||
return (
|
||||
<a
|
||||
className={clsx(
|
||||
`cursor-pointer flex items-center text-muted rounded py-1.5 px-sm`,
|
||||
textClassName,
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<Icon className="mr-1.5" />
|
||||
{name}
|
||||
</a>
|
||||
);
|
||||
};
|
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { useQueryCollections } from '../../../QueryCollections/hooks/useQueryCollections';
|
||||
import { QueryCollectionItem } from './QueryCollectionItem';
|
||||
|
||||
interface QueryCollectionItemProps {
|
||||
selectedCollectionQuery: string;
|
||||
search: string;
|
||||
}
|
||||
|
||||
export const QueryCollectionList = ({
|
||||
selectedCollectionQuery,
|
||||
search,
|
||||
}: QueryCollectionItemProps) => {
|
||||
const { data: queryCollections, isLoading, isError } = useQueryCollections();
|
||||
|
||||
if (isError) {
|
||||
return null; // TOOD: we're waiting for error state design
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Skeleton width={200} height={20} />;
|
||||
}
|
||||
|
||||
const matchingQueryCollections = (queryCollections || []).filter(
|
||||
({ name }) => !search || name.includes(search)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="-mt-2 mb-xs">
|
||||
{queryCollections &&
|
||||
matchingQueryCollections.map(({ name }) => (
|
||||
<QueryCollectionItem
|
||||
href="#"
|
||||
key={name}
|
||||
name={name}
|
||||
selected={name === selectedCollectionQuery}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './AllowListSidebar';
|
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||
import { ComponentMeta } from '@storybook/react';
|
||||
|
||||
import { QueryCollectionHeader } from './QueryCollectionHeader';
|
||||
import { handlers } from '../../../QueryCollections/hooks/useQueryCollections/mocks/handlers.mock';
|
||||
import { createMetadata } from '../../../QueryCollections/hooks/useQueryCollections/mocks/mockData';
|
||||
|
||||
export default {
|
||||
title: 'Features/Allow List/Query Collection Header',
|
||||
component: QueryCollectionHeader,
|
||||
decorators: [
|
||||
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
|
||||
ReactQueryDecorator(),
|
||||
],
|
||||
parameters: {
|
||||
msw: handlers(1500, 'http://localhost:8080'),
|
||||
},
|
||||
} as ComponentMeta<typeof QueryCollectionHeader>;
|
||||
|
||||
export const Primary = () => {
|
||||
const metadata = createMetadata();
|
||||
return (
|
||||
metadata.metadata.query_collections?.[0] && (
|
||||
<QueryCollectionHeader
|
||||
queryCollection={metadata.metadata.query_collections?.[0]}
|
||||
/>
|
||||
)
|
||||
);
|
||||
};
|
@ -0,0 +1,87 @@
|
||||
import { getConfirmation } from '@/components/Common/utils/jsUtils';
|
||||
import { QueryCollectionEntry } from '@/metadata/types';
|
||||
import { Button } from '@/new-components/Button';
|
||||
import { DropdownButton } from '@/new-components/DropdownButton';
|
||||
import React, { useState } from 'react';
|
||||
import { FaEllipsisH, FaPlusCircle } from 'react-icons/fa';
|
||||
import { useDeleteQueryCollections } from '../../../QueryCollections/hooks/useDeleteQueryCollections';
|
||||
import { QueryCollectionRenameDialog } from './QueryCollectionRenameDialog';
|
||||
|
||||
interface QueryCollectionHeaderProps {
|
||||
queryCollection: QueryCollectionEntry;
|
||||
}
|
||||
export const QueryCollectionHeader: React.FC<QueryCollectionHeaderProps> =
|
||||
props => {
|
||||
const { queryCollection } = props;
|
||||
|
||||
const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);
|
||||
const { deleteQueryCollection } = useDeleteQueryCollections();
|
||||
return (
|
||||
<>
|
||||
{isRenameModalOpen && (
|
||||
<QueryCollectionRenameDialog
|
||||
currentName={queryCollection.name}
|
||||
onClose={() => {
|
||||
setIsRenameModalOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center mb-xs">
|
||||
<div>
|
||||
<h1 className="text-xl font-semibold">{queryCollection.name}</h1>
|
||||
<p className="text-muted">
|
||||
Add queries to the collection to create a safe list of operations
|
||||
which can be run against your GraphQL API.
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative ml-auto mr-sm">
|
||||
<DropdownButton
|
||||
items={[
|
||||
[
|
||||
<div
|
||||
className="font-semibold"
|
||||
onClick={() => {
|
||||
// this is a workaround for a weird but caused by interaction of radix ui dialog and dropdown menu
|
||||
setTimeout(() => {
|
||||
setIsRenameModalOpen(true);
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
Edit Collection Name
|
||||
</div>,
|
||||
<div
|
||||
className="font-semibold text-red-600"
|
||||
onClick={() => {
|
||||
const confirmMessage = `This will permanently delete the query collection "${queryCollection.name}"`;
|
||||
const isOk = getConfirmation(
|
||||
confirmMessage,
|
||||
true,
|
||||
queryCollection.name
|
||||
);
|
||||
if (isOk) {
|
||||
deleteQueryCollection(queryCollection.name, {
|
||||
onSuccess: () => {
|
||||
// fire notification
|
||||
},
|
||||
onError: () => {
|
||||
// fire notification
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete Collection
|
||||
</div>,
|
||||
],
|
||||
]}
|
||||
>
|
||||
<FaEllipsisH />
|
||||
</DropdownButton>
|
||||
</div>
|
||||
<Button mode="primary" icon={<FaPlusCircle />}>
|
||||
Add Operation
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import z from 'zod';
|
||||
import { Dialog } from '@/new-components/Dialog';
|
||||
import { Form, InputField } from '@/new-components/Form';
|
||||
import { useRenameQueryCollection } from '../../../QueryCollections/hooks/useRenameQueryCollection';
|
||||
|
||||
interface QueryCollectionCreateDialogProps {
|
||||
onClose: () => void;
|
||||
currentName: string;
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
});
|
||||
export const QueryCollectionRenameDialog: React.FC<QueryCollectionCreateDialogProps> =
|
||||
props => {
|
||||
const { onClose, currentName } = props;
|
||||
const { renameQueryCollection, isLoading } = useRenameQueryCollection();
|
||||
|
||||
return (
|
||||
<Form schema={schema} onSubmit={() => {}}>
|
||||
{({ watch, setError, trigger }) => {
|
||||
const name = watch('name');
|
||||
return (
|
||||
<Dialog hasBackdrop title="Rename Collection" onClose={onClose}>
|
||||
<>
|
||||
<div className="p-4">
|
||||
<InputField
|
||||
id="name"
|
||||
name="name"
|
||||
label="New Collection Name"
|
||||
placeholder="New Collection Name..."
|
||||
/>
|
||||
</div>
|
||||
<Dialog.Footer
|
||||
callToDeny="Cancel"
|
||||
callToAction="Rename Collection"
|
||||
onClose={onClose}
|
||||
onSubmit={async () => {
|
||||
if (await trigger()) {
|
||||
// TODO: remove as when proper form types will be available
|
||||
renameQueryCollection(currentName, name as string, {
|
||||
onSuccess: () => {
|
||||
onClose();
|
||||
},
|
||||
onError: error => {
|
||||
setError('name', {
|
||||
type: 'manual',
|
||||
message: (error as Error).message,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</>
|
||||
</Dialog>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './QueryCollectionHeader';
|
@ -4,7 +4,7 @@ type ResponseBodyOnSuccess = {
|
||||
message: 'success';
|
||||
};
|
||||
|
||||
type ResponseBodyMetadataTypeError = {
|
||||
export type ResponseBodyMetadataTypeError = {
|
||||
code: string;
|
||||
error: string;
|
||||
path: string;
|
||||
|
@ -13,6 +13,7 @@ import type {
|
||||
rsToDbRelDef,
|
||||
ObjectRelationship,
|
||||
ArrayRelationship,
|
||||
QueryCollectionEntry,
|
||||
} from '@/metadata/types';
|
||||
import { MetadataResponse } from '..';
|
||||
|
||||
@ -264,6 +265,10 @@ export namespace MetadataSelector {
|
||||
export const getAllDriversList = (m: MetadataResponse) =>
|
||||
m.metadata?.sources.map(s => ({ source: s.name, kind: s.kind }));
|
||||
|
||||
export const getQueryCollections = (
|
||||
m: MetadataResponse
|
||||
): QueryCollectionEntry[] => m.metadata?.query_collections ?? [];
|
||||
|
||||
export const getOperationsFromQueryCollection =
|
||||
(queryCollectionName: string) => (m: MetadataResponse) => {
|
||||
const queryCollectionDefinition = m.metadata?.query_collections?.find(
|
||||
|
@ -0,0 +1 @@
|
||||
export * from './useCreateQueryCollection';
|
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import { Button } from '@/new-components/Button';
|
||||
|
||||
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
|
||||
import { useCreateQueryCollection } from './useCreateQueryCollection';
|
||||
import { useQueryCollections } from '../useQueryCollections';
|
||||
|
||||
const UseQueryCollections: React.FC<{ name: string }> = ({ name }) => {
|
||||
const { data: queryCollections } = useQueryCollections();
|
||||
const { createQueryCollection, isSuccess, isLoading, error } =
|
||||
useCreateQueryCollection();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ReactJson
|
||||
name="Data"
|
||||
src={{
|
||||
query_collections: queryCollections?.map(
|
||||
({ name: collectionName }) => collectionName
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ReactJson
|
||||
name="Hook State"
|
||||
src={{
|
||||
isSuccess,
|
||||
isLoading,
|
||||
error: error?.message,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={() => createQueryCollection(name)}>
|
||||
Create Collection
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Primary: Story = ({ collectionName }) => {
|
||||
return <UseQueryCollections name={collectionName} />;
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'hooks/Query Collections/useCreateQueryCollection',
|
||||
decorators: [
|
||||
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
|
||||
ReactQueryDecorator(),
|
||||
],
|
||||
parameters: {
|
||||
msw: handlers(),
|
||||
},
|
||||
argTypes: {
|
||||
collectionName: {
|
||||
defaultValue: 'test',
|
||||
description: 'The name of the query collection to create',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta;
|
@ -0,0 +1,45 @@
|
||||
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
|
||||
|
||||
export const useCreateQueryCollection = () => {
|
||||
const { mutate, isSuccess, isLoading, error } = useMetadataMigration();
|
||||
const { data: metadata } = useMetadata();
|
||||
|
||||
const createQueryCollection = async (
|
||||
name: string,
|
||||
options?: Parameters<typeof mutate>[1] & { addToAllowList?: boolean }
|
||||
) => {
|
||||
const { addToAllowList = false, ...mutateOptions } = options || {};
|
||||
mutate(
|
||||
{
|
||||
query: {
|
||||
...(metadata?.resource_version && {
|
||||
resource_version: metadata.resource_version,
|
||||
}),
|
||||
type: 'bulk',
|
||||
args: [
|
||||
{
|
||||
type: 'create_query_collection',
|
||||
args: {
|
||||
name,
|
||||
definition: {
|
||||
queries: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
addToAllowList
|
||||
? {
|
||||
type: 'add_collection_to_allowlist',
|
||||
args: {
|
||||
collection: name,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
].filter(op => op),
|
||||
},
|
||||
},
|
||||
mutateOptions
|
||||
);
|
||||
};
|
||||
|
||||
return { createQueryCollection, isSuccess, isLoading, error };
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
|
||||
import { useCreateQueryCollection } from './useCreateQueryCollection';
|
||||
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe('useCreateQueryCollections', () => {
|
||||
beforeEach(() => {
|
||||
server.use(...handlers(0, ''));
|
||||
});
|
||||
|
||||
test('should work correctly when creating a non-existing collection', async () => {
|
||||
const { waitForValueToChange, result }: any = renderHook(
|
||||
() => useCreateQueryCollection(),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await result.current.createQueryCollection('new-collection');
|
||||
|
||||
await waitForValueToChange(() => result.current.isSuccess);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
test('should go in error state when creating an existing collection', async () => {
|
||||
const { waitForValueToChange, result }: any = renderHook(
|
||||
() => useCreateQueryCollection(),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await result.current.createQueryCollection('allowed_queries');
|
||||
|
||||
await waitForValueToChange(() => result.current.error);
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export * from './useDeleteQueryCollections';
|
@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import { Button } from '@/new-components/Button';
|
||||
|
||||
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
|
||||
import { useDeleteQueryCollections } from './useDeleteQueryCollections';
|
||||
import { useQueryCollections } from '../useQueryCollections';
|
||||
|
||||
const UseQueryCollections: React.FC<{ name: string }> = ({ name }) => {
|
||||
const { data: queryCollections } = useQueryCollections();
|
||||
const { deleteQueryCollection, isSuccess, isLoading, error } =
|
||||
useDeleteQueryCollections();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ReactJson
|
||||
name="Data"
|
||||
src={{
|
||||
query_collections: queryCollections?.map(
|
||||
({ name: collectionName }) => collectionName
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ReactJson
|
||||
name="Hook State"
|
||||
src={{
|
||||
isSuccess,
|
||||
isLoading,
|
||||
error: error?.message,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={() => deleteQueryCollection(name)}>
|
||||
Delete Collection
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Primary: Story = ({ collectionName }) => {
|
||||
return <UseQueryCollections name={collectionName} />;
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'hooks/Query Collections/useDeleteQueryCollections',
|
||||
decorators: [
|
||||
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
|
||||
ReactQueryDecorator(),
|
||||
],
|
||||
parameters: {
|
||||
msw: handlers(),
|
||||
},
|
||||
argTypes: {
|
||||
collectionName: {
|
||||
defaultValue: 'allowed_queries',
|
||||
description: 'The name of the query collection to delete',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta;
|
@ -0,0 +1,40 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
|
||||
import { useDeleteQueryCollections } from './useDeleteQueryCollections';
|
||||
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe('useDeleteQueryCollections', () => {
|
||||
beforeEach(() => {
|
||||
server.use(...handlers(0, ''));
|
||||
});
|
||||
|
||||
test('should work correctly when deleting an existing collection', async () => {
|
||||
const { waitForValueToChange, result }: any = renderHook(
|
||||
() => useDeleteQueryCollections(),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await result.current.deleteQueryCollection('allowed_queries');
|
||||
|
||||
await waitForValueToChange(() => result.current.isSuccess);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
test('should go in error state when deleting a non existing collection', async () => {
|
||||
const { waitForValueToChange, result }: any = renderHook(
|
||||
() => useDeleteQueryCollections(),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await result.current.deleteQueryCollection('not-existing');
|
||||
|
||||
await waitForValueToChange(() => result.current.error);
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
@ -0,0 +1,29 @@
|
||||
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
|
||||
|
||||
export const useDeleteQueryCollections = () => {
|
||||
const { data: metadata } = useMetadata();
|
||||
const { mutate, isSuccess, isLoading, error } = useMetadataMigration({});
|
||||
|
||||
const deleteQueryCollection = async (
|
||||
name: string,
|
||||
options?: Parameters<typeof mutate>[1]
|
||||
) => {
|
||||
mutate(
|
||||
{
|
||||
query: {
|
||||
...(metadata?.resource_version && {
|
||||
resource_version: metadata.resource_version,
|
||||
}),
|
||||
type: 'drop_query_collection',
|
||||
args: {
|
||||
collection: name,
|
||||
cascade: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
return { deleteQueryCollection, isSuccess, isLoading, error };
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './useQueryCollections';
|
@ -0,0 +1,91 @@
|
||||
import { rest } from 'msw';
|
||||
import { TMigration } from '../../../../MetadataAPI/hooks/useMetadataMigration';
|
||||
|
||||
import {
|
||||
alreadyExistsResponse,
|
||||
createMetadata,
|
||||
notExistsResponse,
|
||||
configApiEnabled,
|
||||
} from './mockData';
|
||||
|
||||
export const handlers = (delay = 0, url = 'http://localhost:8080') => {
|
||||
const metadata = createMetadata();
|
||||
|
||||
return [
|
||||
rest.get(`${url}/v1alpha1/config`, (req, res, ctx) => {
|
||||
return res(ctx.delay(delay), ctx.status(200), ctx.json(configApiEnabled));
|
||||
}),
|
||||
|
||||
rest.post(`${url}/v1/metadata`, (req, res, ctx) => {
|
||||
const body = req.body as TMigration['query'];
|
||||
|
||||
switch (body?.type) {
|
||||
case 'export_metadata':
|
||||
return res(ctx.delay(delay), ctx.json(metadata));
|
||||
|
||||
case 'bulk':
|
||||
const name = body?.args?.[0]?.args?.name;
|
||||
if (
|
||||
(metadata.metadata.query_collections || []).find(
|
||||
c => c.name === name
|
||||
)
|
||||
) {
|
||||
return res(
|
||||
ctx.delay(delay),
|
||||
ctx.status(400),
|
||||
ctx.json(alreadyExistsResponse(name))
|
||||
);
|
||||
}
|
||||
(metadata.metadata.query_collections || []).push({
|
||||
name,
|
||||
definition: {
|
||||
queries: [],
|
||||
},
|
||||
});
|
||||
return res(ctx.delay(delay), ctx.json(metadata));
|
||||
|
||||
case 'drop_query_collection':
|
||||
const collection = body.args.collection;
|
||||
if (
|
||||
!(metadata.metadata.query_collections || []).find(
|
||||
c => c.name === collection
|
||||
)
|
||||
) {
|
||||
return res(
|
||||
ctx.delay(delay),
|
||||
ctx.status(400),
|
||||
ctx.json(notExistsResponse(collection))
|
||||
);
|
||||
}
|
||||
metadata.metadata.query_collections = (
|
||||
metadata.metadata.query_collections || []
|
||||
).filter(c => c.name !== collection);
|
||||
return res(ctx.delay(delay), ctx.json(metadata));
|
||||
|
||||
case 'rename_query_collection':
|
||||
const fromName = body?.args?.name;
|
||||
if (
|
||||
!(metadata.metadata.query_collections || []).find(
|
||||
c => c.name === fromName
|
||||
)
|
||||
) {
|
||||
return res(
|
||||
ctx.delay(delay),
|
||||
ctx.status(400),
|
||||
ctx.json(notExistsResponse(fromName))
|
||||
);
|
||||
}
|
||||
metadata.metadata.query_collections = (
|
||||
metadata.metadata.query_collections || []
|
||||
).map(c =>
|
||||
c.name === fromName ? { ...c, name: body.args.new_name } : c
|
||||
);
|
||||
|
||||
return res(ctx.delay(delay), ctx.json(metadata));
|
||||
|
||||
default:
|
||||
return res(ctx.delay(delay), ctx.status(500));
|
||||
}
|
||||
}),
|
||||
];
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
import { MetadataResponse } from '@/features/MetadataAPI';
|
||||
import { ServerConfig } from '@/hooks';
|
||||
import { ResponseBodyMetadataTypeError } from '../../../../CronTriggers/components/Form/mocks/types';
|
||||
|
||||
export const createMetadata = (): MetadataResponse => ({
|
||||
resource_version: 1,
|
||||
metadata: {
|
||||
version: 3,
|
||||
sources: [],
|
||||
remote_schemas: [],
|
||||
inherited_roles: [],
|
||||
allowlist: [
|
||||
{
|
||||
collection: 'allowed_queries',
|
||||
scope: {
|
||||
global: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
collection: 'allowed_queries',
|
||||
scope: {
|
||||
global: false,
|
||||
roles: ['user'],
|
||||
},
|
||||
},
|
||||
],
|
||||
query_collections: [
|
||||
{ name: 'allowed_queries', definition: { queries: [] } },
|
||||
{ name: 'other_queries', definition: { queries: [] } },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
export const createResponse: {
|
||||
type: string;
|
||||
version: number;
|
||||
args: Record<string, unknown>;
|
||||
} = {
|
||||
type: 'export_metadata',
|
||||
version: 2,
|
||||
args: {},
|
||||
};
|
||||
|
||||
export const alreadyExistsResponse = (
|
||||
name: string
|
||||
): ResponseBodyMetadataTypeError => ({
|
||||
path: '$.args.name',
|
||||
error: `query collection with name "${name}" already exists`,
|
||||
code: 'already-exists',
|
||||
});
|
||||
|
||||
export const notExistsResponse = (
|
||||
name: string
|
||||
): ResponseBodyMetadataTypeError => ({
|
||||
path: '$.args.collection',
|
||||
error: `query collection with name "${name}" does not exist`,
|
||||
code: 'not-exists',
|
||||
});
|
||||
|
||||
export const configApiEnabled: Partial<ServerConfig> = {
|
||||
is_allow_list_enabled: true,
|
||||
};
|
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
|
||||
import { handlers } from './mocks/handlers.mock';
|
||||
import { useQueryCollections } from './useQueryCollections';
|
||||
|
||||
const UseQueryCollections: React.FC = () => {
|
||||
const { data, isLoading, isError } = useQueryCollections();
|
||||
|
||||
if (isLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return <div>Error</div>;
|
||||
}
|
||||
|
||||
return data ? <ReactJson name="query_collections" src={data} /> : null;
|
||||
};
|
||||
|
||||
export const Primary: Story = () => {
|
||||
return <UseQueryCollections />;
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'hooks/Query Collections/useQueryCollections',
|
||||
decorators: [
|
||||
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
|
||||
ReactQueryDecorator(),
|
||||
],
|
||||
parameters: {
|
||||
msw: handlers(),
|
||||
},
|
||||
} as Meta;
|
@ -0,0 +1,30 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { handlers } from './mocks/handlers.mock';
|
||||
import { useQueryCollections } from './useQueryCollections';
|
||||
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe('useQueryCollections', () => {
|
||||
beforeEach(() => {
|
||||
server.use(...handlers(0, ''));
|
||||
});
|
||||
test('should return the query collection when called without errors', async () => {
|
||||
const { result, waitForValueToChange } = renderHook(
|
||||
() => useQueryCollections(),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitForValueToChange(() => result.current.isSuccess);
|
||||
|
||||
const queryCollections = result.current.data;
|
||||
|
||||
expect(queryCollections).toHaveLength(2);
|
||||
expect(queryCollections?.[0]?.name).toEqual('allowed_queries');
|
||||
expect(queryCollections?.[1]?.name).toEqual('other_queries');
|
||||
});
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
import { MetadataSelector, useMetadata } from '@/features/MetadataAPI';
|
||||
|
||||
export const useQueryCollections = () => {
|
||||
const { data, isLoading, isError, isRefetching, isSuccess } = useMetadata(
|
||||
MetadataSelector.getQueryCollections
|
||||
);
|
||||
|
||||
return { data, isLoading: isLoading || isRefetching, isError, isSuccess };
|
||||
};
|
@ -0,0 +1 @@
|
||||
export * from './useRenameQueryCollection';
|
@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
|
||||
import { ReduxDecorator } from '@/storybook/decorators/redux-decorator';
|
||||
import ReactJson from 'react-json-view';
|
||||
import { Meta, Story } from '@storybook/react';
|
||||
import { Button } from '@/new-components/Button';
|
||||
|
||||
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
|
||||
import { useRenameQueryCollection } from './useRenameQueryCollection';
|
||||
import { useQueryCollections } from '../useQueryCollections';
|
||||
|
||||
const UseQueryCollections: React.FC<{ name: string; newName: string }> = ({
|
||||
name,
|
||||
newName,
|
||||
}) => {
|
||||
const { data: queryCollections } = useQueryCollections();
|
||||
const { renameQueryCollection, isSuccess, isLoading, error } =
|
||||
useRenameQueryCollection();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ReactJson
|
||||
name="Data"
|
||||
src={{
|
||||
query_collections: queryCollections?.map(
|
||||
({ name: collectionName }) => collectionName
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<ReactJson
|
||||
name="Hook State"
|
||||
src={{
|
||||
isSuccess,
|
||||
isLoading,
|
||||
error: error?.message,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={() => renameQueryCollection(name, newName)}>
|
||||
Rename Collection
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Primary: Story = ({ name, newName }) => {
|
||||
return <UseQueryCollections name={name} newName={newName} />;
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'hooks/Query Collections/useRenameQueryCollection',
|
||||
decorators: [
|
||||
ReduxDecorator({ tables: { currentDataSource: 'default' } }),
|
||||
ReactQueryDecorator(),
|
||||
],
|
||||
parameters: {
|
||||
msw: handlers(),
|
||||
},
|
||||
argTypes: {
|
||||
name: {
|
||||
defaultValue: 'allowed_queries',
|
||||
description: 'The name of the query collection to rename',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
newName: {
|
||||
defaultValue: 'queries_allowed',
|
||||
description: 'The new name of the query collection',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as Meta;
|
@ -0,0 +1,30 @@
|
||||
import { useMetadata, useMetadataMigration } from '@/features/MetadataAPI';
|
||||
|
||||
export const useRenameQueryCollection = () => {
|
||||
const { data: metadata } = useMetadata();
|
||||
const { mutate, isSuccess, isLoading, error } = useMetadataMigration({});
|
||||
|
||||
const renameQueryCollection = async (
|
||||
name: string,
|
||||
newName: string,
|
||||
options?: Parameters<typeof mutate>[1]
|
||||
) => {
|
||||
mutate(
|
||||
{
|
||||
query: {
|
||||
...(metadata?.resource_version && {
|
||||
resource_version: metadata.resource_version,
|
||||
}),
|
||||
type: 'rename_query_collection',
|
||||
args: {
|
||||
name,
|
||||
new_name: newName,
|
||||
},
|
||||
},
|
||||
},
|
||||
options
|
||||
);
|
||||
};
|
||||
|
||||
return { renameQueryCollection, isSuccess, isLoading, error };
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import { setupServer } from 'msw/node';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { handlers } from '../useQueryCollections/mocks/handlers.mock';
|
||||
import { useRenameQueryCollection } from './useRenameQueryCollection';
|
||||
import { wrapper } from '../../../../hooks/__tests__/common/decorator';
|
||||
|
||||
const server = setupServer();
|
||||
|
||||
beforeAll(() => server.listen());
|
||||
afterAll(() => server.close());
|
||||
|
||||
describe('useRenameQueryCollection', () => {
|
||||
beforeEach(() => {
|
||||
server.use(...handlers(0, ''));
|
||||
});
|
||||
|
||||
test('should work correctly when renaming an existing collection', async () => {
|
||||
const { waitForValueToChange, result }: any = renderHook(
|
||||
() => useRenameQueryCollection(),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await result.current.renameQueryCollection('allowed_queries', 'new-name');
|
||||
|
||||
await waitForValueToChange(() => result.current.isSuccess);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
test('should go in error state when renaming a non-existing collection', async () => {
|
||||
const { waitForValueToChange, result }: any = renderHook(
|
||||
() => useRenameQueryCollection(),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await result.current.renameQueryCollection('not-existing', 'new-name');
|
||||
|
||||
await waitForValueToChange(() => result.current.error);
|
||||
expect(result.current.error).toBeDefined();
|
||||
});
|
||||
});
|
@ -15,6 +15,7 @@ export interface ServerConfig {
|
||||
is_function_permissions_inferred: boolean;
|
||||
is_admin_secret_set: boolean;
|
||||
is_auth_hook_set: boolean;
|
||||
is_allow_list_enabled: boolean;
|
||||
is_remote_schema_permissions_enabled: boolean;
|
||||
is_jwt_set: boolean;
|
||||
experimental_features: ExperimentalFeature[];
|
||||
|
@ -69,6 +69,7 @@ export const metadataQueryTypes = [
|
||||
'add_existing_table_or_view',
|
||||
'create_query_collection',
|
||||
'drop_query_collection',
|
||||
'rename_query_collection',
|
||||
'add_query_to_collection',
|
||||
'drop_query_from_collection',
|
||||
'add_collection_to_allowlist',
|
||||
|
130
console/src/new-components/Badge/Badge.stories.mdx
Normal file
130
console/src/new-components/Badge/Badge.stories.mdx
Normal file
@ -0,0 +1,130 @@
|
||||
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { TemplateStoriesFactory } from '@/utils/StoryUtils';
|
||||
import { Badge } from '@/new-components/Badge/';
|
||||
|
||||
<Meta
|
||||
title="components/Badge ⚛️"
|
||||
component={Badge}
|
||||
parameters={{
|
||||
docs: { source: { type: 'code' } },
|
||||
chromatic: { disableSnapshot: true },
|
||||
}}
|
||||
decorators={[Story => <div className={'p-4'}>{Story()}</div>]}
|
||||
argTypes={{
|
||||
content: {
|
||||
table: {
|
||||
disable: true,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
# Badge ⚛️
|
||||
|
||||
- [🧰 Overview](#-overview)
|
||||
- [🔁 States](#-states)
|
||||
- [🎭 Variants](#-variants)
|
||||
- [⚙️ API](#%EF%B8%8F-api)
|
||||
- [🧪 Testing](#-testing)
|
||||
- [🐙 Code on Github](https://github.com/hasura/graphql-engine-mono/tree/main/console/src/new-components/Badge/Badge.tsx)
|
||||
|
||||
## 🧰 Overview
|
||||
|
||||
A component that displays a badge of a particular color.
|
||||
|
||||
### Basic usage
|
||||
|
||||
```ts
|
||||
import { Badge } from '@/new-components/Badge';
|
||||
```
|
||||
|
||||
```tsx
|
||||
<Badge color='green'>
|
||||
The badge label</span>
|
||||
</Badge>
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story name="Overview">
|
||||
<div>
|
||||
<Badge color={'green'} onClick={action('onClick')}>
|
||||
Hello from badge!
|
||||
</Badge>
|
||||
</div>
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
export const Template = ({ content, ...rest }) => (
|
||||
<Badge {...rest}>{content}</Badge>
|
||||
);
|
||||
|
||||
export const stories = {
|
||||
Green: {
|
||||
content: 'Green Badge',
|
||||
color: 'green',
|
||||
},
|
||||
Red: {
|
||||
content: 'Red Badge',
|
||||
color: 'red',
|
||||
},
|
||||
'API playground': {
|
||||
content: 'The badge content',
|
||||
color: 'indigo',
|
||||
},
|
||||
};
|
||||
|
||||
#### 🚦 Usage
|
||||
|
||||
- The badge could be used to represent tags or positive/negative status, like a feature that can be enabled or disabled
|
||||
- You specify the color of the badge, then background and foreground colors are derived from it
|
||||
- You can also specify an onClick handler to be called when the badge is clicked
|
||||
|
||||
### 🎭 Color
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Variant - Color"
|
||||
args={{
|
||||
Green: stories['Green'],
|
||||
Red: stories['Red'],
|
||||
}}
|
||||
>
|
||||
{TemplateStoriesFactory(Template).bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## ⚙️ API
|
||||
|
||||
<Canvas>
|
||||
<Story name="API playground" args={stories['API playground']}>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
<details open>
|
||||
<summary className="mdx-collapsible-section">
|
||||
<p className="mdx-collapsible-section__chevron">
|
||||
<strong>></strong>
|
||||
</p>
|
||||
<p className="mdx-collapsible-section__label">
|
||||
<strong>Show/hide</strong> props
|
||||
</p>
|
||||
</summary>
|
||||
<ArgsTable story="API playground" />
|
||||
</details>
|
||||
|
||||
### 🧪 Snapshot 📸
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="Testing - Snapshot"
|
||||
args={stories}
|
||||
parameters={{
|
||||
chromatic: { disableSnapshot: false },
|
||||
}}
|
||||
>
|
||||
{TemplateStoriesFactory(Template).bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
36
console/src/new-components/Badge/Badge.tsx
Normal file
36
console/src/new-components/Badge/Badge.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
type BadgeColor = 'green' | 'red' | 'yellow' | 'indigo' | 'gray';
|
||||
interface BadgeProps extends React.ComponentProps<'span'> {
|
||||
/**
|
||||
* The color of the basge
|
||||
*/
|
||||
color: BadgeColor;
|
||||
}
|
||||
|
||||
const badgeClassnames: Record<BadgeColor, string> = {
|
||||
green: 'bg-green-100 text-green-800',
|
||||
red: 'bg-red-100 text-red-800',
|
||||
yellow: 'bg-yellow-100 text-yellow-800',
|
||||
gray: 'bg-gray-100 text-gray-800',
|
||||
indigo: 'bg-indigo-100 text-indigo-800',
|
||||
};
|
||||
|
||||
export const Badge: React.FC<React.PropsWithChildren<BadgeProps>> = props => {
|
||||
const { color, children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<span
|
||||
{...rest}
|
||||
className={clsx(
|
||||
'inline-flex items-center mr-sm px-sm py-0.5 rounded-full text-sm tracking-wide font-semibold',
|
||||
badgeClassnames[color],
|
||||
rest.className,
|
||||
rest.onClick && 'cursor-pointer'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
1
console/src/new-components/Badge/index.tsx
Normal file
1
console/src/new-components/Badge/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './Badge';
|
@ -0,0 +1,94 @@
|
||||
import { Canvas, Meta, Story, ArgsTable } from '@storybook/addon-docs';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
|
||||
import { TemplateStoriesFactory } from '@/utils/StoryUtils';
|
||||
import { DropdownButton } from '@/new-components/DropdownButton';
|
||||
|
||||
<Meta
|
||||
title="components/Dropdown Button 🧬"
|
||||
component={DropdownButton}
|
||||
parameters={{
|
||||
docs: { source: { type: 'code' } },
|
||||
chromatic: { disableSnapshot: true },
|
||||
}}
|
||||
decorators={[Story => <div className={'p-4'}>{Story()}</div>]}
|
||||
/>
|
||||
|
||||
# DropdownButton ⚛️
|
||||
|
||||
- [🧰 Overview](#-overview)
|
||||
- [🐙 Code on Github](https://github.com/hasura/graphql-engine-mono/tree/main/console/src/new-components/DropdownButton/DropdownButton.tsx)
|
||||
|
||||
## 🧰 Overview
|
||||
|
||||
A component that display a button that opens a dropdown menu.
|
||||
|
||||
### Basic usage
|
||||
|
||||
```ts
|
||||
import { DropdownButton } from '@/new-components/DropdownButton';
|
||||
```
|
||||
|
||||
```tsx
|
||||
<DropdownButton
|
||||
items={[
|
||||
['Action', <span className="text-red-600">Destructive Action</span>],
|
||||
['Another action'],
|
||||
]}
|
||||
>
|
||||
The DropdownButton label
|
||||
</DropdownButton>
|
||||
```
|
||||
|
||||
<Canvas>
|
||||
<Story name="Overview">
|
||||
<div>
|
||||
<DropdownButton
|
||||
items={[
|
||||
['Action', <span className="text-red-600">Destructive Action</span>],
|
||||
['Another action'],
|
||||
]}
|
||||
>
|
||||
The DropdownButton label
|
||||
</DropdownButton>
|
||||
</div>
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
#### 🚦 Usage
|
||||
|
||||
- The dropdown button is used for opening a contextual menu with list of actions
|
||||
- The `items` prop is an array of array of react nodes (each array is a group of items) that will be rendered in the dropdown
|
||||
- The Button prop is an object of props forwarded to the button component
|
||||
- The children of this component are forwarded to the button component
|
||||
|
||||
## ⚙️ API
|
||||
|
||||
export const Template = args => <DropdownButton {...args} />;
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
name="API playground"
|
||||
args={{
|
||||
items: [
|
||||
['Action', <span className="text-red-600">Destructive</span>],
|
||||
['Another action', 'Another action'],
|
||||
],
|
||||
children: 'Button label',
|
||||
}}
|
||||
>
|
||||
{Template.bind({})}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
<details open>
|
||||
<summary className="mdx-collapsible-section">
|
||||
<p className="mdx-collapsible-section__chevron">
|
||||
<strong>></strong>
|
||||
</p>
|
||||
<p className="mdx-collapsible-section__label">
|
||||
<strong>Show/hide</strong> props
|
||||
</p>
|
||||
</summary>
|
||||
<ArgsTable story="API playground" />
|
||||
</details>
|
37
console/src/new-components/DropdownButton/DropdownButton.tsx
Normal file
37
console/src/new-components/DropdownButton/DropdownButton.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import { Button } from '@/new-components/Button';
|
||||
|
||||
interface DropdownButtonProps {
|
||||
buttonProps?: React.ComponentProps<typeof Button>;
|
||||
items: React.ReactNode[][];
|
||||
}
|
||||
|
||||
export const DropdownButton: React.FC<DropdownButtonProps> = ({
|
||||
children,
|
||||
buttonProps,
|
||||
items,
|
||||
}) => (
|
||||
<DropdownMenuPrimitive.Root>
|
||||
<DropdownMenuPrimitive.Trigger>
|
||||
<Button {...buttonProps}>{children}</Button>
|
||||
</DropdownMenuPrimitive.Trigger>
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content align="start">
|
||||
<div className="origin-top-left absolute left-0 z-10 mt-xs w-max max-w-xs rounded shadow-md bg-white ring-1 ring-gray-300 divide-y divide-gray-300 focus:outline-none">
|
||||
{items.map(group => (
|
||||
<div className="py-1">
|
||||
{group.map(item => (
|
||||
<DropdownMenuPrimitive.Item>
|
||||
<div className="cursor-pointer flex items-center mx-1 px-xs py-xs rounded whitespace-nowrap hover:bg-gray-100">
|
||||
{item}
|
||||
</div>
|
||||
</DropdownMenuPrimitive.Item>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</DropdownMenuPrimitive.Content>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
</DropdownMenuPrimitive.Root>
|
||||
);
|
1
console/src/new-components/DropdownButton/index.tsx
Normal file
1
console/src/new-components/DropdownButton/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './DropdownButton';
|
Loading…
Reference in New Issue
Block a user