mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #239 from akhilmhdh/feat/component-update-1
Infisical component library foundations
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
built
|
||||
healthcheck.js
|
||||
tailwind.config.js
|
||||
@@ -1,3 +1,4 @@
|
||||
import { themes } from '@storybook/theming';
|
||||
import '../src/styles/globals.css';
|
||||
|
||||
export const parameters = {
|
||||
@@ -7,5 +8,8 @@ export const parameters = {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/
|
||||
}
|
||||
},
|
||||
darkMode: {
|
||||
dark: { ...themes.dark, appContentBg: '#0e1014', appBg: '#0e1014' }
|
||||
}
|
||||
};
|
||||
|
||||
298
frontend/package-lock.json
generated
298
frontend/package-lock.json
generated
@@ -7,6 +7,7 @@
|
||||
"dependencies": {
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/server": "^11.10.0",
|
||||
"@fontsource/inter": "^4.5.15",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
@@ -15,7 +16,9 @@
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@radix-ui/react-accordion": "^1.1.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.1",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
||||
"@radix-ui/react-label": "^2.0.0",
|
||||
"@radix-ui/react-popover": "^1.0.3",
|
||||
"@radix-ui/react-progress": "^1.0.1",
|
||||
@@ -30,6 +33,7 @@
|
||||
"axios-auth-refresh": "^3.3.3",
|
||||
"classnames": "^2.3.1",
|
||||
"cookies": "^0.8.0",
|
||||
"cva": "npm:class-variance-authority@^0.4.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"gray-matter": "^4.0.3",
|
||||
"http-proxy": "^1.18.1",
|
||||
@@ -94,7 +98,7 @@
|
||||
"prettier": "^2.8.3",
|
||||
"storybook": "^7.0.0-beta.30",
|
||||
"storybook-dark-mode": "^2.0.5",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"tailwindcss": "3.2",
|
||||
"typescript": "^4.9.3"
|
||||
}
|
||||
},
|
||||
@@ -2790,6 +2794,11 @@
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/inter": {
|
||||
"version": "4.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.15.tgz",
|
||||
"integrity": "sha512-FzleM9AxZQK2nqsTDtBiY0PMEVWvnKnuu2i09+p6DHvrHsuucoV2j0tmw+kAT3L4hvsLdAIDv6MdGehsPIdT+Q=="
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.2.tgz",
|
||||
@@ -3659,6 +3668,26 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-checkbox": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.1.tgz",
|
||||
"integrity": "sha512-TisH0B8hWmYP3ONRduYCyN04rR9yLPIw/Rwyn1RoC1suSoGCa8Wn+YPdSSSarSszeIbcg3p2lBkDp2XXit4sZw==",
|
||||
"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-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"@radix-ui/react-use-previous": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.1.tgz",
|
||||
@@ -3771,6 +3800,25 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-dropdown-menu": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.2.tgz",
|
||||
"integrity": "sha512-r0kN0fstrSi+uAdK2GkLxnnbhqVBy/9Q4o4PvGOYipW0BldQlYBMSmZprvCNj2i2mAATx16kvzIn12GnaGjbMw==",
|
||||
"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": "2.0.2",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@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-focus-guards": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz",
|
||||
@@ -3822,6 +3870,36 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menu": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.2.tgz",
|
||||
"integrity": "sha512-H5dtBi/k3tc45IMd2Pu+Q2PyONFlsYJ5sWUlflSs8BQRghh5GhJHLRuB1yb88VOywuzzvGkaR/HUJJ65Jf2POA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-collection": "1.0.1",
|
||||
"@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.2",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-popper": "1.1.0",
|
||||
"@radix-ui/react-portal": "1.0.1",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-roving-focus": "1.0.2",
|
||||
"@radix-ui/react-slot": "1.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.3.tgz",
|
||||
@@ -3925,6 +4003,27 @@
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.2.tgz",
|
||||
"integrity": "sha512-HLK+CqD/8pN6GfJm3U+cqpqhSKYAWiOJDe+A+8MfxBnOue39QEeMa43csUn2CXCHQT0/mewh1LrrG4tfkM9DMA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-collection": "1.0.1",
|
||||
"@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-id": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "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-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.0.tgz",
|
||||
@@ -9938,6 +10037,23 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
|
||||
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
|
||||
},
|
||||
"node_modules/cva": {
|
||||
"name": "class-variance-authority",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.4.0.tgz",
|
||||
"integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==",
|
||||
"funding": {
|
||||
"url": "https://joebell.co.uk"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">= 4.5.5 < 5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@@ -11993,9 +12109,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
|
||||
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@@ -17168,12 +17284,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-nested": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
|
||||
"integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
|
||||
"integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"postcss-selector-parser": "^6.0.6"
|
||||
"postcss-selector-parser": "^6.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0"
|
||||
@@ -20385,9 +20501,9 @@
|
||||
"integrity": "sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww=="
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.7.tgz",
|
||||
"integrity": "sha512-r7mgumZ3k0InfVPpGWcX8X/Ut4xBfv+1O/+C73ar/m01LxGVzWvPxF/w6xIUPEztrCoz7axfx0SMdh8FH8ZvRQ==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz",
|
||||
"integrity": "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"arg": "^5.0.2",
|
||||
@@ -20396,18 +20512,19 @@
|
||||
"detective": "^5.2.1",
|
||||
"didyoumean": "^1.2.2",
|
||||
"dlv": "^1.1.3",
|
||||
"fast-glob": "^3.2.11",
|
||||
"fast-glob": "^3.2.12",
|
||||
"glob-parent": "^6.0.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"lilconfig": "^2.0.6",
|
||||
"micromatch": "^4.0.5",
|
||||
"normalize-path": "^3.0.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss": "^8.4.18",
|
||||
"postcss-import": "^14.1.0",
|
||||
"postcss-js": "^4.0.0",
|
||||
"postcss-load-config": "^3.1.4",
|
||||
"postcss-nested": "5.0.6",
|
||||
"postcss-nested": "6.0.0",
|
||||
"postcss-selector-parser": "^6.0.10",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"quick-lru": "^5.1.1",
|
||||
@@ -20424,6 +20541,30 @@
|
||||
"postcss": "^8.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss/node_modules/postcss": {
|
||||
"version": "8.4.21",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
|
||||
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
|
||||
@@ -21135,7 +21276,7 @@
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
|
||||
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -24029,6 +24170,11 @@
|
||||
"use-isomorphic-layout-effect": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"@fontsource/inter": {
|
||||
"version": "4.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-4.5.15.tgz",
|
||||
"integrity": "sha512-FzleM9AxZQK2nqsTDtBiY0PMEVWvnKnuu2i09+p6DHvrHsuucoV2j0tmw+kAT3L4hvsLdAIDv6MdGehsPIdT+Q=="
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.2.tgz",
|
||||
@@ -24592,6 +24738,22 @@
|
||||
"@radix-ui/react-primitive": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-checkbox": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.1.tgz",
|
||||
"integrity": "sha512-TisH0B8hWmYP3ONRduYCyN04rR9yLPIw/Rwyn1RoC1suSoGCa8Wn+YPdSSSarSszeIbcg3p2lBkDp2XXit4sZw==",
|
||||
"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-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"@radix-ui/react-use-previous": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-collapsible": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.1.tgz",
|
||||
@@ -24679,6 +24841,21 @@
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.2"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-dropdown-menu": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.2.tgz",
|
||||
"integrity": "sha512-r0kN0fstrSi+uAdK2GkLxnnbhqVBy/9Q4o4PvGOYipW0BldQlYBMSmZprvCNj2i2mAATx16kvzIn12GnaGjbMw==",
|
||||
"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": "2.0.2",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "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",
|
||||
@@ -24716,6 +24893,32 @@
|
||||
"@radix-ui/react-primitive": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-menu": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.2.tgz",
|
||||
"integrity": "sha512-H5dtBi/k3tc45IMd2Pu+Q2PyONFlsYJ5sWUlflSs8BQRghh5GhJHLRuB1yb88VOywuzzvGkaR/HUJJ65Jf2POA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-collection": "1.0.1",
|
||||
"@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.2",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-popper": "1.1.0",
|
||||
"@radix-ui/react-portal": "1.0.1",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-roving-focus": "1.0.2",
|
||||
"@radix-ui/react-slot": "1.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-popover": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.3.tgz",
|
||||
@@ -24795,6 +24998,23 @@
|
||||
"@radix-ui/react-primitive": "1.0.1"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-roving-focus": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.2.tgz",
|
||||
"integrity": "sha512-HLK+CqD/8pN6GfJm3U+cqpqhSKYAWiOJDe+A+8MfxBnOue39QEeMa43csUn2CXCHQT0/mewh1LrrG4tfkM9DMA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-collection": "1.0.1",
|
||||
"@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-id": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-1.2.0.tgz",
|
||||
@@ -29292,6 +29512,12 @@
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz",
|
||||
"integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA=="
|
||||
},
|
||||
"cva": {
|
||||
"version": "npm:class-variance-authority@0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.4.0.tgz",
|
||||
"integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"damerau-levenshtein": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
|
||||
@@ -30880,9 +31106,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.11",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz",
|
||||
"integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
|
||||
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@@ -34590,12 +34816,12 @@
|
||||
}
|
||||
},
|
||||
"postcss-nested": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
|
||||
"integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
|
||||
"integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"postcss-selector-parser": "^6.0.6"
|
||||
"postcss-selector-parser": "^6.0.10"
|
||||
}
|
||||
},
|
||||
"postcss-selector-parser": {
|
||||
@@ -36960,9 +37186,9 @@
|
||||
"integrity": "sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww=="
|
||||
},
|
||||
"tailwindcss": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.7.tgz",
|
||||
"integrity": "sha512-r7mgumZ3k0InfVPpGWcX8X/Ut4xBfv+1O/+C73ar/m01LxGVzWvPxF/w6xIUPEztrCoz7axfx0SMdh8FH8ZvRQ==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz",
|
||||
"integrity": "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arg": "^5.0.2",
|
||||
@@ -36971,22 +37197,36 @@
|
||||
"detective": "^5.2.1",
|
||||
"didyoumean": "^1.2.2",
|
||||
"dlv": "^1.1.3",
|
||||
"fast-glob": "^3.2.11",
|
||||
"fast-glob": "^3.2.12",
|
||||
"glob-parent": "^6.0.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"lilconfig": "^2.0.6",
|
||||
"micromatch": "^4.0.5",
|
||||
"normalize-path": "^3.0.0",
|
||||
"object-hash": "^3.0.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss": "^8.4.18",
|
||||
"postcss-import": "^14.1.0",
|
||||
"postcss-js": "^4.0.0",
|
||||
"postcss-load-config": "^3.1.4",
|
||||
"postcss-nested": "5.0.6",
|
||||
"postcss-nested": "6.0.0",
|
||||
"postcss-selector-parser": "^6.0.10",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"quick-lru": "^5.1.1",
|
||||
"resolve": "^1.22.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"postcss": {
|
||||
"version": "8.4.21",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
|
||||
"integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tapable": {
|
||||
@@ -37538,7 +37778,7 @@
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz",
|
||||
"integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"uc.micro": {
|
||||
"version": "1.0.6",
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
"start:docker": "next build && next start",
|
||||
"lint": "eslint --fix --ext js,ts,tsx ./src",
|
||||
"type-check": "tsc --project tsconfig.json",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook": "storybook dev -p 6006 -s ./public",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/css": "^11.10.0",
|
||||
"@emotion/server": "^11.10.0",
|
||||
"@fontsource/inter": "^4.5.15",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
@@ -22,7 +23,9 @@
|
||||
"@headlessui/react": "^1.6.6",
|
||||
"@radix-ui/react-accordion": "^1.1.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.1",
|
||||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.2",
|
||||
"@radix-ui/react-label": "^2.0.0",
|
||||
"@radix-ui/react-popover": "^1.0.3",
|
||||
"@radix-ui/react-progress": "^1.0.1",
|
||||
@@ -37,6 +40,7 @@
|
||||
"axios-auth-refresh": "^3.3.3",
|
||||
"classnames": "^2.3.1",
|
||||
"cookies": "^0.8.0",
|
||||
"cva": "npm:class-variance-authority@^0.4.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"gray-matter": "^4.0.3",
|
||||
"http-proxy": "^1.18.1",
|
||||
@@ -101,7 +105,7 @@
|
||||
"prettier": "^2.8.3",
|
||||
"storybook": "^7.0.0-beta.30",
|
||||
"storybook-dark-mode": "^2.0.5",
|
||||
"tailwindcss": "^3.1.4",
|
||||
"tailwindcss": "3.2",
|
||||
"typescript": "^4.9.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Button } from './Button';
|
||||
@@ -6,7 +8,16 @@ const meta: Meta<typeof Button> = {
|
||||
title: 'Components/Button',
|
||||
component: Button,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
argTypes: {
|
||||
isRounded: {
|
||||
defaultValue: true,
|
||||
type: 'boolean'
|
||||
},
|
||||
isFullWidth: {
|
||||
defaultValue: false,
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -18,3 +29,61 @@ export const Primary: Story = {
|
||||
children: 'Hello Infisical'
|
||||
}
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
colorSchema: 'secondary',
|
||||
variant: 'outline'
|
||||
}
|
||||
};
|
||||
|
||||
export const Danger: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
colorSchema: 'danger',
|
||||
variant: 'solid'
|
||||
}
|
||||
};
|
||||
|
||||
export const Plain: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
variant: 'plain'
|
||||
}
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
disabled: true
|
||||
}
|
||||
};
|
||||
|
||||
export const FullWidth: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
isFullWidth: true
|
||||
}
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
isLoading: true
|
||||
}
|
||||
};
|
||||
|
||||
export const LeftIcon: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
leftIcon: <FontAwesomeIcon icon={faPlus} className="pr-0.5" />
|
||||
}
|
||||
};
|
||||
|
||||
export const RightIcon: Story = {
|
||||
args: {
|
||||
children: 'Hello Infisical',
|
||||
rightIcon: <FontAwesomeIcon icon={faPlus} className="pr-0.5" />
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,32 +1,166 @@
|
||||
import { ButtonHTMLAttributes, forwardRef, ReactNode } from 'react';
|
||||
import { cva, VariantProps } from 'cva';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
isDisabled?: boolean;
|
||||
leftIcon?: ReactNode;
|
||||
rightIcon?: ReactNode;
|
||||
// loading state
|
||||
isLoading?: boolean;
|
||||
// various button sizes
|
||||
size: 'sm' | 'md' | 'lg';
|
||||
};
|
||||
|
||||
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & Props;
|
||||
const buttonVariants = cva(
|
||||
[
|
||||
'button',
|
||||
'transition-all',
|
||||
'font-inter font-medium',
|
||||
'cursor-pointer',
|
||||
'inline-flex items-center justify-center',
|
||||
'relative'
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
colorSchema: {
|
||||
primary: ['bg-primary', 'text-black', 'border-primary hover:bg-opacity-80'],
|
||||
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'],
|
||||
danger: ['bg-red', 'text-white', 'border-red']
|
||||
},
|
||||
variant: {
|
||||
solid: '',
|
||||
outline: ['bg-transparent', 'border-2', 'border-solid'],
|
||||
plain: ''
|
||||
},
|
||||
isDisabled: {
|
||||
true: 'bg-opacity-70 cursor-not-allowed',
|
||||
false: ''
|
||||
},
|
||||
isFullWidth: {
|
||||
true: 'w-full',
|
||||
false: ''
|
||||
},
|
||||
isRounded: {
|
||||
true: 'rounded-md',
|
||||
false: ''
|
||||
},
|
||||
size: {
|
||||
xs: ['text-xs', 'py-1', 'px-2'],
|
||||
sm: ['text-sm', 'py-2', 'px-4'],
|
||||
md: ['text-md', 'py-2', 'px-6'],
|
||||
lg: ['text-lg', 'py-2', 'px-8']
|
||||
}
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'outline',
|
||||
className: 'text-primary hover:bg-primary hover:text-black'
|
||||
},
|
||||
{
|
||||
colorSchema: 'secondary',
|
||||
variant: 'outline',
|
||||
className: 'hover:bg-mineshaft'
|
||||
},
|
||||
{
|
||||
colorSchema: 'danger',
|
||||
variant: 'outline',
|
||||
className: 'text-red hover:bg-red hover:text-black'
|
||||
},
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'plain',
|
||||
className: 'text-primary'
|
||||
},
|
||||
{
|
||||
colorSchema: 'secondary',
|
||||
variant: 'plain',
|
||||
className: 'text-mineshaft'
|
||||
},
|
||||
{
|
||||
colorSchema: 'danger',
|
||||
variant: 'plain',
|
||||
className: 'text-red'
|
||||
},
|
||||
{
|
||||
colorSchema: ['danger', 'primary', 'secondary'],
|
||||
variant: ['plain'],
|
||||
className: 'bg-transparent py-1 px-1'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &
|
||||
VariantProps<typeof buttonVariants> &
|
||||
Props;
|
||||
|
||||
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ children, isDisabled = false, className, ...props }, ref): JSX.Element => {
|
||||
(
|
||||
{
|
||||
children,
|
||||
isDisabled = false,
|
||||
className = '',
|
||||
size = 'md',
|
||||
variant = 'solid',
|
||||
isFullWidth,
|
||||
isRounded = true,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
isLoading,
|
||||
colorSchema = 'primary',
|
||||
...props
|
||||
},
|
||||
ref
|
||||
): JSX.Element => {
|
||||
const loadingToggleClass = isLoading ? 'opacity-0' : 'opacity-100';
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
aria-disabled={isDisabled}
|
||||
type="button"
|
||||
className={twMerge(
|
||||
'bg-primary hover:opacity-80 transition-all text-sm px-4 py-2 font-bold rounded',
|
||||
className
|
||||
buttonVariants({
|
||||
className,
|
||||
colorSchema,
|
||||
size,
|
||||
variant,
|
||||
isRounded,
|
||||
isDisabled,
|
||||
isFullWidth
|
||||
})
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{isLoading && (
|
||||
<img
|
||||
src="/images/loading/loadingblack.gif"
|
||||
width={36}
|
||||
alt="loading animation"
|
||||
className="absolute rounded-xl"
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className={twMerge(
|
||||
'transition-all shrink-0 cursor-pointer',
|
||||
loadingToggleClass,
|
||||
size === 'xs' ? 'mr-1' : 'mr-2'
|
||||
)}
|
||||
>
|
||||
{leftIcon}
|
||||
</span>
|
||||
<span className={twMerge('transition-all', loadingToggleClass)}>{children}</span>
|
||||
<span
|
||||
className={twMerge(
|
||||
'transition-all shrink-0 cursor-pointer',
|
||||
loadingToggleClass,
|
||||
size === 'xs' ? 'ml-1' : 'ml-2'
|
||||
)}
|
||||
>
|
||||
{rightIcon}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export type { ButtonProps } from './Button';
|
||||
export { Button } from './Button';
|
||||
|
||||
40
frontend/src/components/v2/Card/Card.stories.tsx
Normal file
40
frontend/src/components/v2/Card/Card.stories.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Card, CardBody, CardFooter, CardProps, CardTitle } from './Card';
|
||||
|
||||
const meta: Meta<typeof Card> = {
|
||||
title: 'Components/Card',
|
||||
component: Card,
|
||||
tags: ['v2'],
|
||||
argTypes: {
|
||||
isRounded: {
|
||||
type: 'boolean',
|
||||
defaultValue: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Card>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
const Template = (args: CardProps) => (
|
||||
<div className="w-96">
|
||||
<Card {...args}>
|
||||
<CardTitle subTitle="Please add your subtitle here">Title</CardTitle>
|
||||
<CardBody>Content</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => <Template {...args} />
|
||||
};
|
||||
|
||||
export const Hoverable: Story = {
|
||||
render: (args) => <Template {...args} />,
|
||||
args: {
|
||||
isHoverable: true
|
||||
}
|
||||
};
|
||||
64
frontend/src/components/v2/Card/Card.tsx
Normal file
64
frontend/src/components/v2/Card/Card.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { forwardRef, ReactNode } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type CardTitleProps = {
|
||||
children: ReactNode;
|
||||
subTitle?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => (
|
||||
<div className={twMerge('p-6 pb-4 font-sans text-xl font-medium', className)}>
|
||||
{children}
|
||||
{subTitle && <p className="py-1 text-sm font-normal text-gray-400">{subTitle}</p>}
|
||||
</div>
|
||||
);
|
||||
|
||||
export type CardFooterProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const CardFooter = ({ children, className }: CardFooterProps) => (
|
||||
<div className={twMerge('p-6 pt-0', className)}>{children}</div>
|
||||
);
|
||||
|
||||
export type CardBodyProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const CardBody = ({ children, className }: CardBodyProps) => (
|
||||
<div className={twMerge('p-6 pt-0', className)}>{children}</div>
|
||||
);
|
||||
|
||||
export type CardProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
isFullHeight?: boolean;
|
||||
isRounded?: boolean;
|
||||
isPlain?: boolean;
|
||||
isHoverable?: boolean;
|
||||
};
|
||||
|
||||
export const Card = forwardRef<HTMLDivElement, CardProps>(
|
||||
({ children, isFullHeight, isRounded, isHoverable, isPlain, className }, ref): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={twMerge(
|
||||
'flex flex-col w-full font-inter text-gray-200 bg-mineshaft-700 shadow-md',
|
||||
isFullHeight && 'h-full',
|
||||
isRounded && 'rounded-md',
|
||||
isPlain && 'shadow-none',
|
||||
isHoverable && 'hover:shadow-xl',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Card.displayName = 'Card';
|
||||
2
frontend/src/components/v2/Card/index.tsx
Normal file
2
frontend/src/components/v2/Card/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { CardBodyProps, CardFooterProps, CardProps, CardTitleProps } from './Card';
|
||||
export { Card, CardBody, CardFooter, CardTitle } from './Card';
|
||||
34
frontend/src/components/v2/Checkbox/Checkbox.stories.tsx
Normal file
34
frontend/src/components/v2/Checkbox/Checkbox.stories.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Checkbox } from './Checkbox';
|
||||
|
||||
const meta: Meta<typeof Checkbox> = {
|
||||
title: 'Components/Checkbox',
|
||||
component: Checkbox,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Checkbox>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
children: 'Accept the condition'
|
||||
}
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
children: 'Accept the condition',
|
||||
isDisabled: true
|
||||
}
|
||||
};
|
||||
|
||||
export const Required: Story = {
|
||||
args: {
|
||||
children: 'Accept the condition',
|
||||
isRequired: true
|
||||
}
|
||||
};
|
||||
51
frontend/src/components/v2/Checkbox/Checkbox.tsx
Normal file
51
frontend/src/components/v2/Checkbox/Checkbox.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type CheckboxProps = Omit<
|
||||
CheckboxPrimitive.CheckboxProps,
|
||||
'checked' | 'disabled' | 'required'
|
||||
> & {
|
||||
children: ReactNode;
|
||||
id: string;
|
||||
isDisabled?: boolean;
|
||||
isChecked?: boolean;
|
||||
isRequired?: boolean;
|
||||
};
|
||||
|
||||
export const Checkbox = ({
|
||||
children,
|
||||
className,
|
||||
id,
|
||||
isChecked,
|
||||
isDisabled,
|
||||
isRequired,
|
||||
...props
|
||||
}: CheckboxProps): JSX.Element => {
|
||||
return (
|
||||
<div className="flex items-center font-inter text-bunker-300">
|
||||
<CheckboxPrimitive.Root
|
||||
className={twMerge(
|
||||
'flex items-center justify-center w-5 h-5 mr-3 transition-all rounded shadow hover:bg-bunker-200 bg-bunker-300',
|
||||
isDisabled && 'bg-bunker-400 hover:bg-bunker-400',
|
||||
className
|
||||
)}
|
||||
required={isRequired}
|
||||
checked={isChecked}
|
||||
disabled={isDisabled}
|
||||
{...props}
|
||||
id={id}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator className="text-bunker-800">
|
||||
<FontAwesomeIcon icon={faCheck} size="sm" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
<label className="text-sm" htmlFor={id}>
|
||||
{children}
|
||||
{isRequired && <span className="pl-1 text-red">*</span>}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
2
frontend/src/components/v2/Checkbox/index.tsx
Normal file
2
frontend/src/components/v2/Checkbox/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { CheckboxProps } from './Checkbox';
|
||||
export { Checkbox } from './Checkbox';
|
||||
111
frontend/src/components/v2/Dropdown/Dropdown.stories.tsx
Normal file
111
frontend/src/components/v2/Dropdown/Dropdown.stories.tsx
Normal file
@@ -0,0 +1,111 @@
|
||||
import { faPlus, faTrash, faUndo } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconButton } from '../IconButton';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from './Dropdown';
|
||||
|
||||
const meta: Meta<typeof DropdownMenu> = {
|
||||
title: 'Components/DropdownMenu',
|
||||
component: DropdownMenu,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof DropdownMenuContent>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex justify-center w-full">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton ariaLabel="add">
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" {...args}>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
<DropdownMenuItem>Undo</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const Icons: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex justify-center w-full">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton ariaLabel="add">
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" {...args}>
|
||||
<DropdownMenuItem icon={<FontAwesomeIcon icon={faTrash} size="sm" />}>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem icon={<FontAwesomeIcon icon={faUndo} size="sm" />}>
|
||||
Undo
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const WithDivider: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex justify-center w-full">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton ariaLabel="add">
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" {...args}>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Undo</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Redo</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const Group: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex justify-center w-full">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton ariaLabel="add">
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" {...args}>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>Group</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Undo</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuLabel>Group#2</DropdownMenuLabel>
|
||||
<DropdownMenuItem>Undo</DropdownMenuItem>
|
||||
<DropdownMenuItem>Delete</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
97
frontend/src/components/v2/Dropdown/Dropdown.tsx
Normal file
97
frontend/src/components/v2/Dropdown/Dropdown.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { ComponentPropsWithRef, ElementType, forwardRef, ReactNode, Ref } from 'react';
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
// Main menu or parent container
|
||||
export type DropdownMenuProps = DropdownMenuPrimitive.DropdownMenuProps;
|
||||
export const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||
|
||||
// trigger
|
||||
export type DropdownMenuTriggerProps = DropdownMenuPrimitive.DropdownMenuTriggerProps;
|
||||
export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||
|
||||
// item container
|
||||
export type DropdownMenuContentProps = DropdownMenuPrimitive.MenuContentProps;
|
||||
export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuContentProps>(
|
||||
({ children, className, ...props }, forwardedRef) => {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
sideOffset={10}
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
className={twMerge(
|
||||
'min-w-[220px] bg-bunker will-change-auto text-bunker-300 rounded-md shadow data-[side=top]:animate-slideDownAndFade data-[side=left]:animate-slideRightAndFade data-[side=right]:animate-slideLeftAndFade data-[side=bottom]:animate-slideUpAndFade',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.Content>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
DropdownMenuContent.displayName = 'DropdownMenuContent';
|
||||
|
||||
// item label component
|
||||
export type DropdownLabelProps = DropdownMenuPrimitive.MenuLabelProps;
|
||||
export const DropdownMenuLabel = ({ className, ...props }: DropdownLabelProps) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
{...props}
|
||||
className={twMerge('text-xs text-bunker-400 px-4 pt-2 pb-1', className)}
|
||||
/>
|
||||
);
|
||||
|
||||
// dropdown items
|
||||
export type DropdownMenuItemProps<T extends ElementType> =
|
||||
DropdownMenuPrimitive.MenuContentProps & {
|
||||
icon?: ReactNode;
|
||||
as?: T;
|
||||
inputRef?: Ref<T>;
|
||||
};
|
||||
|
||||
export const DropdownMenuItem = <T extends ElementType = 'button'>({
|
||||
children,
|
||||
inputRef,
|
||||
className,
|
||||
icon,
|
||||
as: Item = 'button',
|
||||
...props
|
||||
}: DropdownMenuItemProps<T> & ComponentPropsWithRef<T>) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
{...props}
|
||||
className={twMerge(
|
||||
'text-sm block font-inter px-4 py-2 data-[highlighted]:bg-gray-700 outline-none cursor-pointer',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Item type="button" role="menuitem" class="flex w-full items-center" ref={inputRef}>
|
||||
{icon && <span className="flex items-center mr-2">{icon}</span>}
|
||||
<span className="flex-grow text-left">{children}</span>
|
||||
</Item>
|
||||
</DropdownMenuPrimitive.Item>
|
||||
);
|
||||
|
||||
// grouping items into 1
|
||||
export type DropdownMenuGroupProps = DropdownMenuPrimitive.DropdownMenuGroupProps;
|
||||
|
||||
export const DropdownMenuGroup = forwardRef<HTMLDivElement, DropdownMenuGroupProps>(
|
||||
({ ...props }, ref) => <DropdownMenuPrimitive.Group {...props} ref={ref} />
|
||||
);
|
||||
|
||||
DropdownMenuGroup.displayName = 'DropdownMenuGroup';
|
||||
|
||||
// Divider
|
||||
export const DropdownMenuSeparator = forwardRef<
|
||||
HTMLDivElement,
|
||||
DropdownMenuPrimitive.DropdownMenuSeparatorProps
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={twMerge('h-[1px] bg-gray-700 m-1', className)}
|
||||
/>
|
||||
));
|
||||
|
||||
DropdownMenuSeparator.displayName = 'DropdownMenuSeperator';
|
||||
17
frontend/src/components/v2/Dropdown/index.tsx
Normal file
17
frontend/src/components/v2/Dropdown/index.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
export type {
|
||||
DropdownLabelProps,
|
||||
DropdownMenuContentProps,
|
||||
DropdownMenuGroupProps,
|
||||
DropdownMenuItemProps,
|
||||
DropdownMenuProps,
|
||||
DropdownMenuTriggerProps
|
||||
} from './Dropdown';
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from './Dropdown';
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
// Be careful on dep cycle
|
||||
import { Input } from '../Input/Input';
|
||||
import { FormControl } from './FormControl';
|
||||
|
||||
const meta: Meta<typeof FormControl> = {
|
||||
title: 'Components/FormControl',
|
||||
component: FormControl,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof FormControl>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Basic: Story = {
|
||||
args: {
|
||||
children: <Input />,
|
||||
label: 'Email',
|
||||
id: 'email',
|
||||
helperText: 'Type something..'
|
||||
}
|
||||
};
|
||||
|
||||
export const RequiredInput: Story = {
|
||||
args: {
|
||||
...Basic.args,
|
||||
isRequired: true
|
||||
}
|
||||
};
|
||||
|
||||
export const ErrorInput: Story = {
|
||||
args: {
|
||||
...Basic.args,
|
||||
errorText: 'Some random error',
|
||||
isError: true
|
||||
}
|
||||
};
|
||||
72
frontend/src/components/v2/FormControl/FormControl.tsx
Normal file
72
frontend/src/components/v2/FormControl/FormControl.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { cloneElement, ReactNode } from 'react';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as Label from '@radix-ui/react-label';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type FormLabelProps = {
|
||||
id?: string;
|
||||
isRequired?: boolean;
|
||||
label?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormLabel = ({ id, label, isRequired }: FormLabelProps) => (
|
||||
<Label.Root className="text-mineshaft-300 text-sm font-medium block mb-1 ml-0.5" htmlFor={id}>
|
||||
{label}
|
||||
{isRequired && <span className="ml-1 text-red">*</span>}
|
||||
</Label.Root>
|
||||
);
|
||||
|
||||
export type FormHelperTextProps = {
|
||||
isError?: boolean;
|
||||
text?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormHelperText = ({ isError, text }: FormHelperTextProps) => (
|
||||
<div
|
||||
className={twMerge(
|
||||
'text-xs font-inter flex items-center opacity-90 text-mineshaft-300 mt-2',
|
||||
isError && 'text-red-600'
|
||||
)}
|
||||
>
|
||||
{isError && (
|
||||
<span>
|
||||
<FontAwesomeIcon icon={faExclamationTriangle} size="sm" className="mr-1" />
|
||||
</span>
|
||||
)}
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export type FormControlProps = {
|
||||
id?: string;
|
||||
isRequired?: boolean;
|
||||
isError?: boolean;
|
||||
label?: ReactNode;
|
||||
helperText?: ReactNode;
|
||||
errorText?: ReactNode;
|
||||
children: JSX.Element;
|
||||
};
|
||||
|
||||
export const FormControl = ({
|
||||
children,
|
||||
isRequired,
|
||||
label,
|
||||
helperText,
|
||||
errorText,
|
||||
id,
|
||||
isError
|
||||
}: FormControlProps): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
{typeof label === 'string' ? (
|
||||
<FormLabel label={label} isRequired={isRequired} id={id} />
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
{cloneElement(children, { isRequired, 'data-required': isRequired, isError })}
|
||||
{!isError && helperText && <FormHelperText isError={isError} text={helperText} />}
|
||||
{isError && errorText && <FormHelperText isError={isError} text={errorText} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
2
frontend/src/components/v2/FormControl/index.tsx
Normal file
2
frontend/src/components/v2/FormControl/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { FormControlProps, FormHelperTextProps, FormLabelProps } from './FormControl';
|
||||
export { FormControl, FormHelperText, FormLabel } from './FormControl';
|
||||
60
frontend/src/components/v2/IconButton/IconButton.stories.tsx
Normal file
60
frontend/src/components/v2/IconButton/IconButton.stories.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconButton } from './IconButton';
|
||||
|
||||
const meta: Meta<typeof IconButton> = {
|
||||
title: 'Components/IconButton',
|
||||
component: IconButton,
|
||||
tags: ['v2'],
|
||||
argTypes: {
|
||||
isRounded: {
|
||||
defaultValue: true,
|
||||
type: 'boolean'
|
||||
},
|
||||
ariaLabel: {
|
||||
defaultValue: 'Some buttons...'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof IconButton>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
children: <FontAwesomeIcon icon={faPlus} />
|
||||
}
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
children: <FontAwesomeIcon icon={faPlus} />,
|
||||
colorSchema: 'secondary',
|
||||
variant: 'outline'
|
||||
}
|
||||
};
|
||||
|
||||
export const Danger: Story = {
|
||||
args: {
|
||||
children: <FontAwesomeIcon icon={faPlus} />,
|
||||
colorSchema: 'danger',
|
||||
variant: 'solid'
|
||||
}
|
||||
};
|
||||
|
||||
export const Plain: Story = {
|
||||
args: {
|
||||
children: <FontAwesomeIcon icon={faPlus} />,
|
||||
variant: 'plain'
|
||||
}
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
children: <FontAwesomeIcon icon={faPlus} />,
|
||||
disabled: true
|
||||
}
|
||||
};
|
||||
131
frontend/src/components/v2/IconButton/IconButton.tsx
Normal file
131
frontend/src/components/v2/IconButton/IconButton.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { ButtonHTMLAttributes, forwardRef, ReactNode } from 'react';
|
||||
import { cva, VariantProps } from 'cva';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
// This is kept as required because by accessibility convention and eslint
|
||||
// when button doesn't have text an aria-label needs to be passed
|
||||
ariaLabel: string;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const iconButtonVariants = cva(
|
||||
[
|
||||
'button',
|
||||
'transition-all',
|
||||
'font-inter font-medium',
|
||||
'cursor-pointer',
|
||||
'inline-flex items-center justify-center',
|
||||
'relative'
|
||||
],
|
||||
{
|
||||
variants: {
|
||||
colorSchema: {
|
||||
primary: ['bg-primary', 'text-black', 'border-primary hover:bg-opacity-80'],
|
||||
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'],
|
||||
danger: ['bg-red', 'text-white', 'border-red']
|
||||
},
|
||||
variant: {
|
||||
solid: '',
|
||||
outline: ['bg-transparent', 'border-2', 'border-solid'],
|
||||
plain: ''
|
||||
},
|
||||
isDisabled: {
|
||||
true: 'bg-opacity-70 cursor-not-allowed',
|
||||
false: ''
|
||||
},
|
||||
isRounded: {
|
||||
true: 'rounded-md',
|
||||
false: ''
|
||||
},
|
||||
size: {
|
||||
xs: ['text-xs', 'py-1', 'px-2'],
|
||||
sm: ['text-sm', 'py-2', 'px-3'],
|
||||
md: ['text-md', 'py-3', 'px-4'],
|
||||
lg: ['text-lg', 'py-3', 'px-6']
|
||||
}
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'outline',
|
||||
className: 'text-primary hover:bg-primary hover:text-black'
|
||||
},
|
||||
{
|
||||
colorSchema: 'secondary',
|
||||
variant: 'outline',
|
||||
className: 'hover:bg-mineshaft'
|
||||
},
|
||||
{
|
||||
colorSchema: 'danger',
|
||||
variant: 'outline',
|
||||
className: 'text-red hover:bg-red hover:text-black'
|
||||
},
|
||||
{
|
||||
colorSchema: 'primary',
|
||||
variant: 'plain',
|
||||
className: 'text-primary'
|
||||
},
|
||||
{
|
||||
colorSchema: 'secondary',
|
||||
variant: 'plain',
|
||||
className: 'text-mineshaft'
|
||||
},
|
||||
{
|
||||
colorSchema: 'danger',
|
||||
variant: 'plain',
|
||||
className: 'text-red'
|
||||
},
|
||||
{
|
||||
colorSchema: ['danger', 'primary', 'secondary'],
|
||||
variant: ['plain'],
|
||||
className: 'bg-transparent py-1 px-1'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
export type IconButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'aria-label'> &
|
||||
VariantProps<typeof iconButtonVariants> &
|
||||
Props;
|
||||
|
||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
ariaLabel,
|
||||
isDisabled = false,
|
||||
className,
|
||||
size = 'md',
|
||||
variant = 'solid',
|
||||
isRounded = true,
|
||||
colorSchema = 'primary',
|
||||
...props
|
||||
},
|
||||
ref
|
||||
): JSX.Element => (
|
||||
<button
|
||||
ref={ref}
|
||||
aria-disabled={isDisabled}
|
||||
type="button"
|
||||
aria-label={ariaLabel}
|
||||
className={twMerge(
|
||||
iconButtonVariants({
|
||||
className,
|
||||
colorSchema,
|
||||
size,
|
||||
variant,
|
||||
isRounded,
|
||||
isDisabled
|
||||
})
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
|
||||
IconButton.displayName = 'IconButton';
|
||||
2
frontend/src/components/v2/IconButton/index.tsx
Normal file
2
frontend/src/components/v2/IconButton/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { IconButtonProps } from './IconButton';
|
||||
export { IconButton } from './IconButton';
|
||||
66
frontend/src/components/v2/Input/Input.stories.tsx
Normal file
66
frontend/src/components/v2/Input/Input.stories.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { faEye, faMailBulk } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Input } from './Input';
|
||||
|
||||
const meta: Meta<typeof Input> = {
|
||||
title: 'Components/Input',
|
||||
component: Input,
|
||||
tags: ['v2'],
|
||||
argTypes: {
|
||||
isRounded: {
|
||||
defaultValue: true,
|
||||
type: 'boolean'
|
||||
},
|
||||
placeholder: {
|
||||
defaultValue: 'Type anything',
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Input>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Filled: Story = {
|
||||
args: {}
|
||||
};
|
||||
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
variant: 'outline'
|
||||
}
|
||||
};
|
||||
|
||||
export const Plain: Story = {
|
||||
args: {
|
||||
variant: 'plain'
|
||||
}
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
args: {
|
||||
isError: true
|
||||
}
|
||||
};
|
||||
|
||||
export const AutoWidth: Story = {
|
||||
args: {
|
||||
isFullWidth: false,
|
||||
className: 'w-auto'
|
||||
}
|
||||
};
|
||||
|
||||
export const RightIcon: Story = {
|
||||
args: {
|
||||
rightIcon: <FontAwesomeIcon icon={faEye} />
|
||||
}
|
||||
};
|
||||
|
||||
export const LeftIcon: Story = {
|
||||
args: {
|
||||
leftIcon: <FontAwesomeIcon icon={faMailBulk} />
|
||||
}
|
||||
};
|
||||
102
frontend/src/components/v2/Input/Input.tsx
Normal file
102
frontend/src/components/v2/Input/Input.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { forwardRef, InputHTMLAttributes, ReactNode } from 'react';
|
||||
import { cva, VariantProps } from 'cva';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
type Props = {
|
||||
placeholder?: string;
|
||||
isFullWidth?: boolean;
|
||||
isRequired?: boolean;
|
||||
leftIcon?: ReactNode;
|
||||
rightIcon?: ReactNode;
|
||||
};
|
||||
|
||||
const inputVariants = cva(
|
||||
'input w-full py-2 text-gray-400 placeholder-gray-500 placeholder-opacity-50',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: ['text-xs'],
|
||||
sm: ['text-sm'],
|
||||
md: ['text-md'],
|
||||
lg: ['text-lg']
|
||||
},
|
||||
isRounded: {
|
||||
true: ['rounded-md'],
|
||||
false: ''
|
||||
},
|
||||
variant: {
|
||||
filled: ['bg-bunker-800', 'text-gray-400'],
|
||||
outline: ['bg-transparent'],
|
||||
plain: 'bg-transparent outline-none'
|
||||
},
|
||||
isError: {
|
||||
true: 'focus:ring-red/50 placeholder-red-300',
|
||||
false: 'focus:ring-primary/50'
|
||||
}
|
||||
},
|
||||
compoundVariants: []
|
||||
}
|
||||
);
|
||||
|
||||
const inputParentContainerVariants = cva('inline-flex font-inter items-center border relative', {
|
||||
variants: {
|
||||
isRounded: {
|
||||
true: ['rounded-md'],
|
||||
false: ''
|
||||
},
|
||||
isError: {
|
||||
true: 'border-red',
|
||||
false: 'border-mineshaft-400'
|
||||
},
|
||||
isFullWidth: {
|
||||
true: 'w-full',
|
||||
false: ''
|
||||
},
|
||||
variant: {
|
||||
filled: ['bg-bunker-800', 'text-gray-400'],
|
||||
outline: ['bg-transparent'],
|
||||
plain: 'border-none'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> &
|
||||
VariantProps<typeof inputVariants> &
|
||||
Props;
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
isRounded = true,
|
||||
isFullWidth = true,
|
||||
isError = false,
|
||||
isRequired,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
variant = 'filled',
|
||||
size = 'md',
|
||||
...props
|
||||
},
|
||||
ref
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<div className={inputParentContainerVariants({ isRounded, isError, isFullWidth, variant })}>
|
||||
{leftIcon && <span className="absolute left-0 ml-2">{leftIcon}</span>}
|
||||
<input
|
||||
{...props}
|
||||
required={isRequired}
|
||||
ref={ref}
|
||||
className={twMerge(
|
||||
leftIcon ? 'pl-9' : 'pl-4',
|
||||
rightIcon ? 'pr-9' : 'pr-4',
|
||||
inputVariants({ className, isError, size, isRounded, variant })
|
||||
)}
|
||||
/>
|
||||
{rightIcon && <span className="absolute right-0 mr-2">{rightIcon}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Input.displayName = 'Input';
|
||||
2
frontend/src/components/v2/Input/index.tsx
Normal file
2
frontend/src/components/v2/Input/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { InputProps } from './Input';
|
||||
export { Input } from './Input';
|
||||
107
frontend/src/components/v2/Menu/Menu.stories.tsx
Normal file
107
frontend/src/components/v2/Menu/Menu.stories.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { faKey, faUser } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Menu, MenuGroup, MenuItem } from './Menu';
|
||||
|
||||
const meta: Meta<typeof Menu> = {
|
||||
title: 'Components/Menu',
|
||||
component: Menu,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Menu>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => (
|
||||
<Menu {...args}>
|
||||
<MenuItem>Secrets</MenuItem>
|
||||
<MenuItem>Members</MenuItem>
|
||||
<MenuItem>Integrations</MenuItem>
|
||||
</Menu>
|
||||
),
|
||||
args: {}
|
||||
};
|
||||
|
||||
export const SelectedItem: Story = {
|
||||
render: (args) => (
|
||||
<Menu {...args}>
|
||||
<MenuItem isSelected>Secrets</MenuItem>
|
||||
<MenuItem>Members</MenuItem>
|
||||
<MenuItem>Integrations</MenuItem>
|
||||
</Menu>
|
||||
),
|
||||
args: {}
|
||||
};
|
||||
|
||||
export const GroupedItem: Story = {
|
||||
render: (args) => (
|
||||
<Menu {...args}>
|
||||
<MenuGroup title="Group 1">
|
||||
<MenuItem>Secrets</MenuItem>
|
||||
<MenuItem>Members</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Group 2">
|
||||
<MenuItem>Secrets</MenuItem>
|
||||
<MenuItem>Members</MenuItem>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
),
|
||||
args: {}
|
||||
};
|
||||
|
||||
export const DisabledItem: Story = {
|
||||
render: (args) => (
|
||||
<Menu {...args}>
|
||||
<MenuGroup title="Group 1">
|
||||
<MenuItem isDisabled>Secrets</MenuItem>
|
||||
<MenuItem>Members</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Group 2">
|
||||
<MenuItem>Secrets</MenuItem>
|
||||
<MenuItem>Members</MenuItem>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
),
|
||||
args: {}
|
||||
};
|
||||
|
||||
export const WithIcons: Story = {
|
||||
render: (args) => (
|
||||
<Menu {...args}>
|
||||
<MenuGroup title="Group 1">
|
||||
<MenuItem isDisabled icon={<FontAwesomeIcon icon={faKey} />}>
|
||||
Secrets
|
||||
</MenuItem>
|
||||
<MenuItem icon={<FontAwesomeIcon icon={faUser} />}>Members</MenuItem>
|
||||
</MenuGroup>
|
||||
<MenuGroup title="Group 2">
|
||||
<MenuItem icon={<FontAwesomeIcon icon={faKey} />}>Secrets</MenuItem>
|
||||
<MenuItem icon={<FontAwesomeIcon icon={faKey} />}>Members</MenuItem>
|
||||
</MenuGroup>
|
||||
</Menu>
|
||||
),
|
||||
args: {}
|
||||
};
|
||||
|
||||
export const WithDescription: Story = {
|
||||
render: (args) => (
|
||||
<Menu {...args}>
|
||||
<MenuItem
|
||||
isDisabled
|
||||
icon={<FontAwesomeIcon icon={faKey} />}
|
||||
description="Some random description"
|
||||
>
|
||||
Secrets
|
||||
</MenuItem>
|
||||
<MenuItem icon={<FontAwesomeIcon icon={faUser} />} description="Some random description">
|
||||
Members
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
),
|
||||
args: {}
|
||||
};
|
||||
64
frontend/src/components/v2/Menu/Menu.tsx
Normal file
64
frontend/src/components/v2/Menu/Menu.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { ComponentPropsWithRef, ElementType, ReactNode, Ref } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type MenuProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Menu = ({ children, className }: MenuProps): JSX.Element => {
|
||||
return <ul className={twMerge('p-2', className)}>{children}</ul>;
|
||||
};
|
||||
|
||||
export type MenuItemProps<T extends ElementType> = {
|
||||
// Kudos to https://itnext.io/react-polymorphic-components-with-typescript-f7ce72ea7af2
|
||||
as?: T;
|
||||
children: ReactNode;
|
||||
icon?: ReactNode;
|
||||
description?: ReactNode;
|
||||
isDisabled?: boolean;
|
||||
isSelected?: boolean;
|
||||
className?: string;
|
||||
inputRef?: Ref<T>;
|
||||
};
|
||||
|
||||
export const MenuItem = <T extends ElementType = 'button'>({
|
||||
children,
|
||||
icon,
|
||||
className,
|
||||
isDisabled,
|
||||
isSelected,
|
||||
as: Item = 'button',
|
||||
description,
|
||||
// wrapping in forward ref with generic component causes the loss of ts definitions on props
|
||||
inputRef
|
||||
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => (
|
||||
<li
|
||||
className={twMerge(
|
||||
'px-2 py-3 font-inter flex flex-col text-sm text-white transition-all rounded cursor-pointer hover:bg-gray-700',
|
||||
isSelected && 'text-primary',
|
||||
isDisabled && 'text-gray-500 hover:bg-transparent cursor-not-allowed',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Item type="button" role="menuitem" class="flex items-center" ref={inputRef}>
|
||||
{icon && <span className="mr-2">{icon}</span>}
|
||||
<span className="flex-grow text-left">{children}</span>
|
||||
</Item>
|
||||
{description && <span className="mt-2 text-xs">{description}</span>}
|
||||
</li>
|
||||
);
|
||||
|
||||
MenuItem.displayName = 'MenuItem';
|
||||
|
||||
export type MenuGroupProps = {
|
||||
children: ReactNode;
|
||||
title: ReactNode;
|
||||
};
|
||||
|
||||
export const MenuGroup = ({ children, title }: MenuGroupProps): JSX.Element => (
|
||||
<>
|
||||
<li className="p-2 text-xs text-gray-400">{title}</li>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
2
frontend/src/components/v2/Menu/index.tsx
Normal file
2
frontend/src/components/v2/Menu/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { MenuGroupProps, MenuItemProps, MenuProps } from './Menu';
|
||||
export { Menu, MenuGroup, MenuItem } from './Menu';
|
||||
33
frontend/src/components/v2/Modal/Modal.stories.tsx
Normal file
33
frontend/src/components/v2/Modal/Modal.stories.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Button } from '../Button';
|
||||
import { Modal, ModalContent, ModalContentProps, ModalTrigger } from './Modal';
|
||||
|
||||
const meta: Meta<typeof Modal> = {
|
||||
title: 'Components/Modal',
|
||||
component: Modal,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ModalContent>;
|
||||
|
||||
const Template = (args: ModalContentProps) => (
|
||||
<Modal>
|
||||
<ModalTrigger asChild>
|
||||
<Button>Open</Button>
|
||||
</ModalTrigger>
|
||||
<ModalContent {...args}>Hello world</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => <Template {...args} />,
|
||||
args: {
|
||||
title: 'Title',
|
||||
subTitle: 'Something as subtitle',
|
||||
footerContent: 'footer content'
|
||||
}
|
||||
};
|
||||
57
frontend/src/components/v2/Modal/Modal.tsx
Normal file
57
frontend/src/components/v2/Modal/Modal.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { forwardRef, ReactNode } from 'react';
|
||||
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { Card, CardBody, CardFooter, CardTitle } from '../Card';
|
||||
import { IconButton } from '../IconButton';
|
||||
|
||||
export type ModalContentProps = DialogPrimitive.DialogContentProps & {
|
||||
title?: ReactNode;
|
||||
subTitle?: string;
|
||||
footerContent?: ReactNode;
|
||||
};
|
||||
|
||||
export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
|
||||
({ children, title, subTitle, className, footerContent, ...props }, forwardedRef) => (
|
||||
<DialogPrimitive.Portal>
|
||||
<DialogPrimitive.Overlay
|
||||
className="fixed inset-0 w-full h-full animate-fadeIn"
|
||||
style={{ backgroundColor: 'rgba(0, 0, 0, 0.5)' }}
|
||||
/>
|
||||
<DialogPrimitive.Content {...props} ref={forwardedRef}>
|
||||
<Card
|
||||
isRounded
|
||||
className={twMerge(
|
||||
'fixed max-w-md animate-popIn top-1/2 left-1/2 -translate-y-2/4 -translate-x-2/4',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{title && <CardTitle subTitle={subTitle}>{title}</CardTitle>}
|
||||
<CardBody>{children}</CardBody>
|
||||
{footerContent && <CardFooter>{footerContent}</CardFooter>}
|
||||
<DialogPrimitive.Close aria-label="Close" asChild>
|
||||
<IconButton
|
||||
variant="plain"
|
||||
ariaLabel="close"
|
||||
className="absolute top-2.5 right-2.5 text-white hover:bg-gray-600 rounded"
|
||||
>
|
||||
<FontAwesomeIcon icon={faTimes} size="sm" className="cursor-pointer" />
|
||||
</IconButton>
|
||||
</DialogPrimitive.Close>
|
||||
</Card>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
|
||||
ModalContent.displayName = 'ModalContent';
|
||||
|
||||
export type ModalProps = Omit<DialogPrimitive.DialogProps, 'open'> & { isOpen?: boolean };
|
||||
export const Modal = ({ isOpen, ...props }: ModalProps) => (
|
||||
<DialogPrimitive.Root open={isOpen} {...props} />
|
||||
);
|
||||
|
||||
export const ModalTrigger = DialogPrimitive.Trigger;
|
||||
export type ModalTriggerProps = DialogPrimitive.DialogTriggerProps;
|
||||
2
frontend/src/components/v2/Modal/index.tsx
Normal file
2
frontend/src/components/v2/Modal/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { ModalContentProps, ModalProps, ModalTriggerProps } from './Modal';
|
||||
export { Modal, ModalContent, ModalTrigger } from './Modal';
|
||||
113
frontend/src/components/v2/Select/Select.stories.tsx
Normal file
113
frontend/src/components/v2/Select/Select.stories.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SelectProps } from '@radix-ui/react-select';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Select, SelectItem } from './Select';
|
||||
|
||||
const meta: Meta<typeof Select> = {
|
||||
title: 'Components/Select',
|
||||
component: Select,
|
||||
tags: ['v2'],
|
||||
argTypes: {
|
||||
placeholder: {
|
||||
defaultValue: 'Type something...'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Select>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => (
|
||||
<div className="">
|
||||
<Select placeholder="Type anything..." className="w-72" {...args}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
const Controlled = (args: SelectProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selected, setSelected] = useState('');
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<Select
|
||||
defaultValue="1"
|
||||
className="w-72"
|
||||
open={isOpen}
|
||||
onValueChange={(val) => setSelected(val)}
|
||||
value={selected}
|
||||
onOpenChange={(open) => setIsOpen(open)}
|
||||
{...args}
|
||||
>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Control: Story = {
|
||||
render: (args) => <Controlled {...args} />
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
render: (args) => (
|
||||
<div className="">
|
||||
<Select defaultValue="1" className="w-72" {...args}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2" isDisabled>
|
||||
Peter
|
||||
</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
render: (args) => (
|
||||
<div className="">
|
||||
<Select defaultValue="1" className="w-72" isLoading {...args}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
const AsyncSelectOptions = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new Promise<void>((resolve): void => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<Select placeholder="Hello" className="w-72" isLoading={isLoading}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Async: Story = {
|
||||
render: (args) => <AsyncSelectOptions {...args} />
|
||||
};
|
||||
96
frontend/src/components/v2/Select/Select.tsx
Normal file
96
frontend/src/components/v2/Select/Select.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { forwardRef, ReactNode } from 'react';
|
||||
import { faCheck, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { Spinner } from '../Spinner';
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
export type SelectProps = SelectPrimitive.SelectProps & Props;
|
||||
|
||||
export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
({ children, placeholder, className, isLoading, ...props }, ref): JSX.Element => {
|
||||
return (
|
||||
<SelectPrimitive.Root {...props}>
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={twMerge(
|
||||
`inline-flex items-center justify-between data-[placeholder]:text-gray-500
|
||||
px-4 py-2.5 font-inter text-sm text-white rounded-md bg-mineshaft-800`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<SelectPrimitive.Value placeholder={placeholder} />
|
||||
<SelectPrimitive.Icon className="ml-3">
|
||||
<FontAwesomeIcon icon={faChevronDown} size="sm" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
position="popper"
|
||||
sideOffset={5}
|
||||
className="overflow-hidden text-white rounded-md shadow-md font-inter bg-mineshaft-800"
|
||||
style={{ width: 'var(--radix-select-trigger-width)' }}
|
||||
>
|
||||
<SelectPrimitive.ScrollUpButton>
|
||||
<FontAwesomeIcon icon={faChevronUp} size="sm" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
<SelectPrimitive.Viewport className="p-2">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center">
|
||||
<Spinner size="xs" />
|
||||
<span className="ml-2 text-xs text-gray-500">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectPrimitive.ScrollDownButton>
|
||||
<FontAwesomeIcon icon={faChevronDown} size="sm" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
</SelectPrimitive.Root>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Select.displayName = 'Select';
|
||||
|
||||
export type SelectItemProps = Omit<SelectPrimitive.SelectItemProps, 'disabled'> & {
|
||||
isDisabled?: boolean;
|
||||
isSelected?: boolean;
|
||||
};
|
||||
|
||||
export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
||||
({ children, className, isSelected, isDisabled, ...props }, forwardedRef) => {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
{...props}
|
||||
className={twMerge(
|
||||
`text-sm rounded-sm transition-all hover:text-primary
|
||||
hover:bg-mineshaft-700 flex items-center pl-10 pr-4 py-2 cursor-pointer
|
||||
select-none outline-none relative`,
|
||||
isSelected && 'text-primary',
|
||||
isDisabled && 'text-gray-600 hover:bg-transparent cursor-not-allowed hover:text-gray-600',
|
||||
className
|
||||
)}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<SelectPrimitive.ItemIndicator className="absolute left-2">
|
||||
<FontAwesomeIcon icon={faCheck} size="sm" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
<SelectPrimitive.ItemText className="">{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SelectItem.displayName = 'SelectItem';
|
||||
2
frontend/src/components/v2/Select/index.tsx
Normal file
2
frontend/src/components/v2/Select/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { SelectItemProps, SelectProps } from './Select';
|
||||
export { Select, SelectItem } from './Select';
|
||||
42
frontend/src/components/v2/Spinner/Spinner.tsx
Normal file
42
frontend/src/components/v2/Spinner/Spinner.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
type SizeOptions = 'xs' | 'sm' | 'md' | 'lg';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
size?: SizeOptions;
|
||||
};
|
||||
|
||||
const sizeChart: Record<SizeOptions, string> = {
|
||||
xs: 'w-4 h-4',
|
||||
sm: 'w-6 h-6',
|
||||
md: 'w-8 h-8',
|
||||
lg: 'w-12 h-12'
|
||||
};
|
||||
|
||||
export const Spinner = ({ className, size = 'md' }: Props): JSX.Element => {
|
||||
return (
|
||||
<div role="status">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className={twMerge(
|
||||
' text-gray-200 animate-spin dark:text-gray-600 fill-primary m-1',
|
||||
sizeChart[size],
|
||||
className
|
||||
)}
|
||||
viewBox="0 0 100 101"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
||||
fill="currentFill"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
1
frontend/src/components/v2/Spinner/index.tsx
Normal file
1
frontend/src/components/v2/Spinner/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { Spinner } from './Spinner';
|
||||
34
frontend/src/components/v2/Switch/Switch.stories.tsx
Normal file
34
frontend/src/components/v2/Switch/Switch.stories.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Switch } from './Switch';
|
||||
|
||||
const meta: Meta<typeof Switch> = {
|
||||
title: 'Components/Switch',
|
||||
component: Switch,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Switch>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
children: 'Dark mode'
|
||||
}
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
children: 'Dark mode',
|
||||
isDisabled: true
|
||||
}
|
||||
};
|
||||
|
||||
export const Required: Story = {
|
||||
args: {
|
||||
children: 'Dark mode',
|
||||
isRequired: true
|
||||
}
|
||||
};
|
||||
42
frontend/src/components/v2/Switch/Switch.tsx
Normal file
42
frontend/src/components/v2/Switch/Switch.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ReactNode } from 'react';
|
||||
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type SwitchProps = Omit<SwitchPrimitive.SwitchProps, 'checked' | 'disabled' | 'required'> & {
|
||||
children: ReactNode;
|
||||
id: string;
|
||||
isChecked?: boolean;
|
||||
isRequired?: boolean;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
export const Switch = ({
|
||||
children,
|
||||
id,
|
||||
className,
|
||||
isChecked,
|
||||
isDisabled,
|
||||
isRequired,
|
||||
...props
|
||||
}: SwitchProps): JSX.Element => (
|
||||
<div className="flex items-center text-bunker-300 font-inter">
|
||||
<label className="text-sm" htmlFor={id}>
|
||||
{children}
|
||||
{isRequired && <span className="pl-1 text-red">*</span>}
|
||||
</label>
|
||||
<SwitchPrimitive.Root
|
||||
{...props}
|
||||
required={isRequired}
|
||||
checked={isChecked}
|
||||
disabled={isDisabled}
|
||||
className={twMerge(
|
||||
'h-5 ml-3 transition-all rounded-full w-9 bg-bunker-300 data-[state=checked]:bg-bunker-200',
|
||||
isDisabled && 'bg-bunker-400 hover:bg-bunker-400',
|
||||
className
|
||||
)}
|
||||
id={id}
|
||||
>
|
||||
<SwitchPrimitive.Thumb className="w-4 h-4 border-none will-change-transform rounded-full shadow bg-black block transition-all translate-x-0.5 data-[state=checked]:translate-x-[18px]" />
|
||||
</SwitchPrimitive.Root>
|
||||
</div>
|
||||
);
|
||||
2
frontend/src/components/v2/Switch/index.tsx
Normal file
2
frontend/src/components/v2/Switch/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { SwitchProps } from './Switch';
|
||||
export { Switch } from './Switch';
|
||||
41
frontend/src/components/v2/Table/Table.stories.tsx
Normal file
41
frontend/src/components/v2/Table/Table.stories.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Table, TableContainer, TBody, Td, Th, THead, Tr } from './Table';
|
||||
|
||||
const meta: Meta<typeof Table> = {
|
||||
title: 'Components/Table',
|
||||
component: Table,
|
||||
tags: ['v2'],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Table>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => (
|
||||
<TableContainer>
|
||||
<Table {...args}>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Head#1</Th>
|
||||
<Th>Head#2</Th>
|
||||
<Th>Head#3</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
<Tr>
|
||||
<Td>Row#1</Td>
|
||||
<Td>Row#2</Td>
|
||||
<Td>Row#3</Td>
|
||||
</Tr>
|
||||
<Tr>
|
||||
<Td>Row#1</Td>
|
||||
<Td>Row#2</Td>
|
||||
<Td>Row#3</Td>
|
||||
</Tr>
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
};
|
||||
93
frontend/src/components/v2/Table/Table.tsx
Normal file
93
frontend/src/components/v2/Table/Table.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export type TableContainerProps = {
|
||||
children: ReactNode;
|
||||
isRounded?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const TableContainer = ({
|
||||
children,
|
||||
className,
|
||||
isRounded = true
|
||||
}: TableContainerProps): JSX.Element => (
|
||||
<div
|
||||
className={twMerge(
|
||||
'overflow-x-auto font-inter shadow-md relative border border-solid border-mineshaft-700',
|
||||
isRounded && 'rounded-md',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
// main parent table
|
||||
export type TableProps = {
|
||||
className?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const Table = ({ children, className }: TableProps): JSX.Element => (
|
||||
<table
|
||||
className={twMerge(
|
||||
'bg-bunker-800 p-2 roun text-gray-300 w-full text-sm text-left rounded-md',
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</table>
|
||||
);
|
||||
|
||||
// table head
|
||||
export type THeadProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const THead = ({ children, className }: THeadProps): JSX.Element => (
|
||||
<thead className={twMerge('text-xs bg-bunker text-bunker-300 uppercase', className)}>
|
||||
{children}
|
||||
</thead>
|
||||
);
|
||||
|
||||
// table rows
|
||||
export type TrProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Tr = ({ children, className }: TrProps): JSX.Element => (
|
||||
<tr className={twMerge('border border-solid border-mineshaft-700', className)}>{children}</tr>
|
||||
);
|
||||
|
||||
// table head columns
|
||||
export type ThProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Th = ({ children, className }: ThProps): JSX.Element => (
|
||||
<th className={twMerge('px-6 py-3 font-medium', className)}>{children}</th>
|
||||
);
|
||||
|
||||
// table body
|
||||
export type TBodyProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const TBody = ({ children, className }: TBodyProps): JSX.Element => (
|
||||
<tbody className={twMerge(className)}>{children}</tbody>
|
||||
);
|
||||
|
||||
// table body columns
|
||||
export type TdProps = {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const Td = ({ children, className }: TdProps): JSX.Element => (
|
||||
<td className={twMerge('text-left px-6 py-3', className)}>{children}</td>
|
||||
);
|
||||
10
frontend/src/components/v2/Table/index.tsx
Normal file
10
frontend/src/components/v2/Table/index.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
export type {
|
||||
TableContainerProps,
|
||||
TableProps,
|
||||
TBodyProps,
|
||||
TdProps,
|
||||
THeadProps,
|
||||
ThProps,
|
||||
TrProps
|
||||
} from './Table';
|
||||
export { Table, TableContainer, TBody, Td, Th, THead, Tr } from './Table';
|
||||
48
frontend/src/components/v2/TextArea/TextArea.stories.tsx
Normal file
48
frontend/src/components/v2/TextArea/TextArea.stories.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { TextArea } from './TextArea';
|
||||
|
||||
const meta: Meta<typeof TextArea> = {
|
||||
title: 'Components/TextArea',
|
||||
component: TextArea,
|
||||
tags: ['v2'],
|
||||
argTypes: {
|
||||
placeholder: {
|
||||
defaultValue: 'Type anything',
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof TextArea>;
|
||||
|
||||
// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args
|
||||
export const Filled: Story = {
|
||||
args: {}
|
||||
};
|
||||
|
||||
export const Outline: Story = {
|
||||
args: {
|
||||
variant: 'outline'
|
||||
}
|
||||
};
|
||||
|
||||
export const Plain: Story = {
|
||||
args: {
|
||||
variant: 'plain'
|
||||
}
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
args: {
|
||||
isError: true
|
||||
}
|
||||
};
|
||||
|
||||
export const AutoWidth: Story = {
|
||||
args: {
|
||||
isFullWidth: false,
|
||||
className: 'w-auto'
|
||||
}
|
||||
};
|
||||
74
frontend/src/components/v2/TextArea/TextArea.tsx
Normal file
74
frontend/src/components/v2/TextArea/TextArea.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { forwardRef, TextareaHTMLAttributes } from 'react';
|
||||
import { cva, VariantProps } from 'cva';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
type Props = {
|
||||
placeholder?: string;
|
||||
isFullWidth?: boolean;
|
||||
isRequired?: boolean;
|
||||
reSize?: 'none' | 'both' | 'vertical' | 'horizontal';
|
||||
};
|
||||
|
||||
const textAreaVariants = cva(
|
||||
'textarea w-full p-2 border border-solid text-gray-400 font-inter placeholder-gray-500 placeholder-opacity-50',
|
||||
{
|
||||
variants: {
|
||||
size: {
|
||||
xs: ['text-xs'],
|
||||
sm: ['text-sm'],
|
||||
md: ['text-md'],
|
||||
lg: ['text-lg']
|
||||
},
|
||||
isRounded: {
|
||||
true: ['rounded-md'],
|
||||
false: ''
|
||||
},
|
||||
variant: {
|
||||
filled: ['bg-bunker-800', 'text-gray-400'],
|
||||
outline: ['bg-transparent'],
|
||||
plain: 'bg-transparent outline-none'
|
||||
},
|
||||
isError: {
|
||||
true: 'focus:ring-red/50 placeholder-red-300 border-red',
|
||||
false: 'focus:ring-primary/50 border-mineshaft-400'
|
||||
}
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
variant: 'plain',
|
||||
isError: [true, false],
|
||||
className: 'border-none'
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
|
||||
export type TextAreaProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> &
|
||||
VariantProps<typeof textAreaVariants> &
|
||||
Props;
|
||||
|
||||
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
isRounded = true,
|
||||
isError = false,
|
||||
isRequired,
|
||||
variant = 'filled',
|
||||
size = 'md',
|
||||
reSize = 'both',
|
||||
...props
|
||||
},
|
||||
ref
|
||||
): JSX.Element => (
|
||||
<textarea
|
||||
{...props}
|
||||
style={{ resize: reSize }}
|
||||
required={isRequired}
|
||||
ref={ref}
|
||||
className={twMerge(textAreaVariants({ className, isError, size, isRounded, variant }))}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
TextArea.displayName = 'TextArea';
|
||||
2
frontend/src/components/v2/TextArea/index.tsx
Normal file
2
frontend/src/components/v2/TextArea/index.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export type { TextAreaProps } from './TextArea';
|
||||
export { TextArea } from './TextArea';
|
||||
14
frontend/src/components/v2/index.tsx
Normal file
14
frontend/src/components/v2/index.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
export * from './Button';
|
||||
export * from './Card';
|
||||
export * from './Checkbox';
|
||||
export * from './Dropdown';
|
||||
export * from './FormControl';
|
||||
export * from './IconButton';
|
||||
export * from './Input';
|
||||
export * from './Menu';
|
||||
export * from './Modal';
|
||||
export * from './Select';
|
||||
export * from './Spinner';
|
||||
export * from './Switch';
|
||||
export * from './Table';
|
||||
export * from './TextArea';
|
||||
@@ -1,3 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@import '@fontsource/inter/400.css';
|
||||
@import '@fontsource/inter/500.css';
|
||||
@import '@fontsource/inter/700.css';
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user