Merge pull request #239 from akhilmhdh/feat/component-update-1

Infisical component library foundations
This commit is contained in:
mv-turtle
2023-01-23 21:47:28 -08:00
committed by GitHub
48 changed files with 3217 additions and 921 deletions

View File

@@ -1,3 +1,4 @@
node_modules
built
healthcheck.js
tailwind.config.js

View File

@@ -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' }
}
};

View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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" />
}
};

View File

@@ -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>
);
}

View File

@@ -1 +1,2 @@
export type { ButtonProps } from './Button';
export { Button } from './Button';

View 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
}
};

View 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';

View File

@@ -0,0 +1,2 @@
export type { CardBodyProps, CardFooterProps, CardProps, CardTitleProps } from './Card';
export { Card, CardBody, CardFooter, CardTitle } from './Card';

View 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
}
};

View 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>
);
};

View File

@@ -0,0 +1,2 @@
export type { CheckboxProps } from './Checkbox';
export { Checkbox } from './Checkbox';

View 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>
)
};

View 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';

View 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';

View File

@@ -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
}
};

View 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>
);
};

View File

@@ -0,0 +1,2 @@
export type { FormControlProps, FormHelperTextProps, FormLabelProps } from './FormControl';
export { FormControl, FormHelperText, FormLabel } from './FormControl';

View 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
}
};

View 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';

View File

@@ -0,0 +1,2 @@
export type { IconButtonProps } from './IconButton';
export { IconButton } from './IconButton';

View 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} />
}
};

View 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';

View File

@@ -0,0 +1,2 @@
export type { InputProps } from './Input';
export { Input } from './Input';

View 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: {}
};

View 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}
</>
);

View File

@@ -0,0 +1,2 @@
export type { MenuGroupProps, MenuItemProps, MenuProps } from './Menu';
export { Menu, MenuGroup, MenuItem } from './Menu';

View 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'
}
};

View 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;

View File

@@ -0,0 +1,2 @@
export type { ModalContentProps, ModalProps, ModalTriggerProps } from './Modal';
export { Modal, ModalContent, ModalTrigger } from './Modal';

View 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} />
};

View 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';

View File

@@ -0,0 +1,2 @@
export type { SelectItemProps, SelectProps } from './Select';
export { Select, SelectItem } from './Select';

View 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>
);
};

View File

@@ -0,0 +1 @@
export { Spinner } from './Spinner';

View 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
}
};

View 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>
);

View File

@@ -0,0 +1,2 @@
export type { SwitchProps } from './Switch';
export { Switch } from './Switch';

View 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>
)
};

View 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>
);

View 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';

View 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'
}
};

View 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';

View File

@@ -0,0 +1,2 @@
export type { TextAreaProps } from './TextArea';
export { TextArea } from './TextArea';

View 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';

View File

@@ -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