mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-21 11:37:53 +00:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
1e4f6a4b9d | |||
a73fc6de19 | |||
0bb750488b | |||
32f98f83c5 | |||
6943785ce5 | |||
86558a8221 | |||
f2c35a302d | |||
0794b6132a | |||
062c287e75 | |||
e67d68a7b9 | |||
054acc689a | |||
9b95d18b85 | |||
7f9bc77253 | |||
b92907aca6 | |||
c4ee03c73b | |||
89ba80740b | |||
606a5e5317 | |||
f859bf528e | |||
ad504fa84e | |||
e7ac74c5a0 | |||
b80504ae00 | |||
68f1887d66 | |||
201c8352e3 | |||
a0f0ffe566 | |||
4b4e8e2bfc | |||
4db4c172c1 | |||
00fee63ff3 | |||
6b80cd6590 | |||
840efbdc2f |
.eslintignoreMakefiletailwind.config.js
backend/src
cli/packages
docker-compose.dev.ymldocs
cli
contributing
integrations/platforms
self-hosting/deployments
frontend
.storybook
package-lock.jsonpackage.jsonsrc
components
basic/dialog
integrations
v2
Button
Card
Checkbox
Dropdown
FormControl
IconButton
Input
Menu
Modal
Select
Spinner
Switch
Table
TextArea
index.tsxpages
styles
@ -1,3 +1,4 @@
|
||||
node_modules
|
||||
built
|
||||
healthcheck.js
|
||||
tailwind.config.js
|
2
Makefile
2
Makefile
@ -8,7 +8,7 @@ up-dev:
|
||||
docker-compose -f docker-compose.dev.yml up --build
|
||||
|
||||
i-dev:
|
||||
infisical export && infisical export > .env && docker-compose -f docker-compose.dev.yml up --build
|
||||
infisical run -- docker-compose -f docker-compose.dev.yml up --build
|
||||
|
||||
up-prod:
|
||||
docker-compose -f docker-compose.yml up --build
|
||||
|
@ -8,6 +8,7 @@ import { DatabaseService } from './services';
|
||||
import { setUpHealthEndpoint } from './services/health';
|
||||
import { initSmtp } from './services/smtp';
|
||||
import { setTransporter } from './helpers/nodemailer';
|
||||
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
||||
|
||||
DatabaseService.initDatabase(MONGO_URL);
|
||||
|
||||
@ -23,3 +24,5 @@ if (NODE_ENV !== 'test') {
|
||||
environment: NODE_ENV
|
||||
});
|
||||
}
|
||||
|
||||
createTestUserForDevelopment()
|
||||
|
@ -25,7 +25,6 @@ const requireIntegrationAuthorizationAuth = ({
|
||||
}) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { integrationAuthId } = req[location];
|
||||
|
||||
const integrationAuth = await IntegrationAuth.findOne({
|
||||
_id: integrationAuthId
|
||||
})
|
||||
|
140
backend/src/utils/addDevelopmentUser.ts
Normal file
140
backend/src/utils/addDevelopmentUser.ts
Normal file
@ -0,0 +1,140 @@
|
||||
/************************************************************************************************
|
||||
*
|
||||
* Attention: The credentials below are only for development purposes, it should never be used for production
|
||||
*
|
||||
************************************************************************************************/
|
||||
|
||||
import { NODE_ENV } from "../config"
|
||||
import { Key, Membership, MembershipOrg, Organization, User, Workspace } from "../models";
|
||||
import { Types } from 'mongoose';
|
||||
|
||||
export const createTestUserForDevelopment = async () => {
|
||||
if (NODE_ENV === "development") {
|
||||
const testUserEmail = "test@localhost.local"
|
||||
const testUserPassword = "testInfisical1"
|
||||
const testUserId = "63cefa6ec8d3175601cfa980"
|
||||
const testWorkspaceId = "63cefb15c8d3175601cfa989"
|
||||
const testOrgId = "63cefb15c8d3175601cfa985"
|
||||
const testMembershipId = "63cefb159185d9aa3ef0cf35"
|
||||
const testMembershipOrgId = "63cefb159185d9aa3ef0cf31"
|
||||
const testWorkspaceKeyId = "63cf48f0225e6955acec5eff"
|
||||
|
||||
const testUser = {
|
||||
_id: testUserId,
|
||||
email: testUserEmail,
|
||||
refreshVersion: 0,
|
||||
encryptedPrivateKey: 'ITMdDXtLoxib4+53U/qzvIV/T/UalRwimogFCXv/UsulzEoiKM+aK2aqOb0=',
|
||||
firstName: 'Jake',
|
||||
iv: '9fp0dZHI+UuHeKkWMDvD6w==',
|
||||
lastName: 'Moni',
|
||||
publicKey: 'cf44BhkybbBfsE0fZHe2jvqtCj6KLXvSq4hVjV0svzk=',
|
||||
salt: 'd8099dc70958090346910fb9639262b83cf526fc9b4555a171b36a9e1bcd0240',
|
||||
tag: 'bQ/UTghqcQHRoSMpLQD33g==',
|
||||
verifier: '12271fcd50937ca4512e1e3166adaf9d9fc7a5cd0e4c4cb3eda89f35572ede4d9eef23f64aef9220367abff9437b0b6fa55792c442f177201d87051cf77dadade254ff667170440327355fb7d6ac4745d4db302f4843632c2ed5919ebdcff343287a4cd552255d9e3ce81177edefe089617b7616683901475d393405f554634b9bf9230c041ac85624f37a60401be20b78044932580ae0868323be3749fbf856df1518153ba375fec628275f0c445f237446ea4aa7f12c1aa1d6b5fd74b7f2e88d062845a19819ec63f2d2ed9e9f37c055149649461d997d2ae1482f53b04f9de7493efbb9686fb19b2d559b9aa2b502c22dec83f9fc43290dfea89a1dc6f03580b3642b3824513853e81a441be9a0b2fde2231bac60f3287872617a36884697805eeea673cf1a351697834484ada0f282e4745015c9c2928d61e6d092f1b9c3a27eda8413175d23bb2edae62f82ccaf52bf5a6a90344a766c7e4ebf65dae9ae90b2ad4ae65dbf16e3a6948e429771cc50307ae86d454f71a746939ed061f080dd3ae369c1a0739819aca17af46a085bac1f2a5d936d198e7951a8ac3bb38b893665fe7312835abd3f61811f81efa2a8761af5070085f9b6adcca80bf9b0d81899c3d41487fba90728bb24eceb98bd69770360a232624133700ceb4d153f2ad702e0a5b7dfaf97d20bc8aa71dc8c20024a58c06a8fecdad18cb5a2f89c51eaf7'
|
||||
}
|
||||
|
||||
const testWorkspaceKey = {
|
||||
_id: new Types.ObjectId(testWorkspaceKeyId),
|
||||
workspace: testWorkspaceId,
|
||||
encryptedKey: '96ZIRSU21CjVzIQ4Yp994FGWQvDdyK3gq+z+NCaJLK0ByTlvUePmf+AYGFJjkAdz',
|
||||
nonce: '1jhCGqg9Wx3n0OtVxbDgiYYGq4S3EdgO',
|
||||
sender: '63cefa6ec8d3175601cfa980',
|
||||
receiver: '63cefa6ec8d3175601cfa980',
|
||||
}
|
||||
|
||||
const testWorkspace = {
|
||||
_id: new Types.ObjectId(testWorkspaceId),
|
||||
name: 'Example Project',
|
||||
organization: testOrgId,
|
||||
environments: [
|
||||
{
|
||||
_id: '63cefb15c8d3175601cfa98a',
|
||||
name: 'Development',
|
||||
slug: 'dev'
|
||||
},
|
||||
{
|
||||
_id: '63cefb15c8d3175601cfa98b',
|
||||
name: 'Test',
|
||||
slug: 'test'
|
||||
},
|
||||
{
|
||||
_id: '63cefb15c8d3175601cfa98c',
|
||||
name: 'Staging',
|
||||
slug: 'staging'
|
||||
},
|
||||
{
|
||||
_id: '63cefb15c8d3175601cfa98d',
|
||||
name: 'Production',
|
||||
slug: 'prod'
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
const testOrg = {
|
||||
_id: testOrgId,
|
||||
name: 'Jake\'s organization'
|
||||
}
|
||||
|
||||
const testMembershipOrg = {
|
||||
_id: testMembershipOrgId,
|
||||
organization: testOrgId,
|
||||
role: 'owner',
|
||||
status: 'accepted',
|
||||
user: testUserId,
|
||||
}
|
||||
|
||||
const testMembership = {
|
||||
_id: testMembershipId,
|
||||
role: 'admin',
|
||||
user: testUserId,
|
||||
workspace: testWorkspaceId
|
||||
}
|
||||
|
||||
try {
|
||||
// create user if not exist
|
||||
const userInDB = await User.findById(testUserId)
|
||||
if (!userInDB) {
|
||||
await User.create(testUser)
|
||||
}
|
||||
|
||||
// create org if not exist
|
||||
const orgInDB = await Organization.findById(testOrgId)
|
||||
if (!orgInDB) {
|
||||
await Organization.create(testOrg)
|
||||
}
|
||||
|
||||
// create membership org if not exist
|
||||
const membershipOrgInDB = await MembershipOrg.findById(testMembershipOrgId)
|
||||
if (!membershipOrgInDB) {
|
||||
await MembershipOrg.create(testMembershipOrg)
|
||||
}
|
||||
|
||||
// create membership
|
||||
const membershipInDB = await Membership.findById(testMembershipId)
|
||||
if (!membershipInDB) {
|
||||
await Membership.create(testMembership)
|
||||
}
|
||||
|
||||
// create workspace if not exist
|
||||
const workspaceInDB = await Workspace.findById(testWorkspaceId)
|
||||
if (!workspaceInDB) {
|
||||
await Workspace.create(testWorkspace)
|
||||
}
|
||||
|
||||
// create workspace key if not exist
|
||||
const workspaceKeyInDB = await Key.findById(testWorkspaceKeyId)
|
||||
if (!workspaceKeyInDB) {
|
||||
await Key.create(testWorkspaceKey)
|
||||
}
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.info(`DEVELOPMENT MODE DETECTED: You may login with test user with email: ${testUserEmail} and password: ${testUserPassword}`)
|
||||
/* eslint-enable no-console */
|
||||
|
||||
} catch (e) {
|
||||
/* eslint-disable no-console */
|
||||
console.error(`Unable to create test user while booting up [err=${e}]`)
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
}
|
||||
}
|
@ -90,7 +90,7 @@ const INTEGRATION_OPTIONS = [
|
||||
name: 'Fly.io',
|
||||
slug: 'flyio',
|
||||
image: 'Flyio.svg',
|
||||
isAvailable: true,
|
||||
isAvailable: false,
|
||||
type: 'pat',
|
||||
clientId: '',
|
||||
docsLink: ''
|
||||
|
@ -17,7 +17,7 @@ var rootCmd = &cobra.Command{
|
||||
Short: "Infisical CLI is used to inject environment variables into any process",
|
||||
Long: `Infisical is a simple, end-to-end encrypted service that enables teams to sync and manage their environment variables across their development life cycle.`,
|
||||
CompletionOptions: cobra.CompletionOptions{HiddenDefaultCmd: true},
|
||||
Version: "0.2.5",
|
||||
Version: util.CLI_VERSION,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
@ -32,14 +32,15 @@ func Execute() {
|
||||
func init() {
|
||||
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
rootCmd.PersistentFlags().BoolVarP(&debugLogging, "debug", "d", false, "Enable verbose logging")
|
||||
rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", util.INFISICAL_DEFAULT_API_URL, "Point the CLI to your own backend [can also set via environment variable name: API_URL]")
|
||||
// rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
// }
|
||||
rootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", util.INFISICAL_DEFAULT_API_URL, "Point the CLI to your own backend [can also set via environment variable name: INFISICAL_API_URL]")
|
||||
rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
||||
util.CheckForUpdate()
|
||||
}
|
||||
|
||||
// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
|
||||
// this is used to allow overrides of the default value
|
||||
if !rootCmd.Flag("domain").Changed {
|
||||
if envInfisicalBackendUrl, ok := os.LookupEnv("API_URL"); ok {
|
||||
if envInfisicalBackendUrl, ok := os.LookupEnv("INFISICAL_API_URL"); ok {
|
||||
config.INFISICAL_URL = envInfisicalBackendUrl
|
||||
}
|
||||
}
|
||||
|
42
cli/packages/util/check-for-update.go
Normal file
42
cli/packages/util/check-for-update.go
Normal file
@ -0,0 +1,42 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func CheckForUpdate() {
|
||||
latestVersion, err := getLatestTag("infisical", "infisical")
|
||||
if err != nil {
|
||||
// do nothing and continue
|
||||
return
|
||||
}
|
||||
if latestVersion != CLI_VERSION {
|
||||
PrintWarning(fmt.Sprintf("Please update your CLI. You are running version %s but the latest version is %s", CLI_VERSION, latestVersion))
|
||||
}
|
||||
}
|
||||
|
||||
func getLatestTag(repoOwner string, repoName string) (string, error) {
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/tags", repoOwner, repoName)
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var tags []struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
json.Unmarshal(body, &tags)
|
||||
|
||||
return tags[0].Name, nil
|
||||
}
|
@ -11,4 +11,5 @@ const (
|
||||
KEYRING_SERVICE_NAME = "infisical"
|
||||
PERSONAL_SECRET_TYPE_NAME = "personal"
|
||||
SHARED_SECRET_TYPE_NAME = "shared"
|
||||
CLI_VERSION = "v0.2.6"
|
||||
)
|
||||
|
@ -93,7 +93,7 @@ services:
|
||||
|
||||
smtp-server:
|
||||
container_name: infisical-dev-smtp-server
|
||||
image: mailhog/mailhog
|
||||
image: lytrax/mailhog:latest # https://github.com/mailhog/MailHog/issues/353#issuecomment-821137362
|
||||
restart: always
|
||||
logging:
|
||||
driver: 'none' # disable saving logs
|
||||
|
@ -109,11 +109,11 @@ infisical login
|
||||
The CLI is set to connect to Infisical Cloud by default, but if you're running your own instance of Infisical, you can direct the CLI to it using one of the methods provided below.
|
||||
|
||||
#### Export environment variable
|
||||
You can point the CLI to the self hosted Infisical instance by exporting the environment variable `API_URL` in your terminal.
|
||||
You can point the CLI to the self hosted Infisical instance by exporting the environment variable `INFISICAL_API_URL` in your terminal.
|
||||
|
||||
```bash
|
||||
# Example
|
||||
export API_URL="https://your-self-hosted-infisical.com/api"
|
||||
export INFISICAL_API_URL="https://your-self-hosted-infisical.com/api"
|
||||
```
|
||||
|
||||
#### Set manually on every command
|
||||
|
@ -1,13 +1,12 @@
|
||||
---
|
||||
title: 'Developing'
|
||||
title: 'Local development'
|
||||
description: 'This guide will help you set up and run Infisical in development mode.'
|
||||
---
|
||||
|
||||
## Clone the repo
|
||||
|
||||
```bash
|
||||
# change location to the path you want Infisical to be installed
|
||||
cd ~
|
||||
# navigate location to the path you want Infisical to be installed
|
||||
|
||||
# clone the repo and cd to Infisical dir
|
||||
git clone https://github.com/Infisical/infisical
|
||||
@ -16,57 +15,71 @@ cd infisical
|
||||
|
||||
## Set up environment variables
|
||||
|
||||
Start by creating a .env file at the root of the Infisical directory. It's best to start with the provided [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) template containing the necessary envars to fill out your .env file — you only have to modify the SMTP parameters.
|
||||
|
||||
Start by creating a .env file at the root of the Infisical directory then copy the contents of the file below into the .env file.
|
||||
|
||||
<Accordion title=".env file content">
|
||||
```env
|
||||
# Keys
|
||||
# Required key for platform encryption/decryption ops
|
||||
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
|
||||
|
||||
# JWT
|
||||
# Required secrets to sign JWT tokens
|
||||
JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
|
||||
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
|
||||
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
|
||||
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
|
||||
|
||||
# MongoDB
|
||||
# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref
|
||||
# to the MongoDB container instance or Mongo Cloud
|
||||
# Required
|
||||
MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
|
||||
|
||||
# Optional credentials for MongoDB container instance and Mongo-Express
|
||||
MONGO_USERNAME=root
|
||||
MONGO_PASSWORD=example
|
||||
|
||||
# Website URL
|
||||
# Required
|
||||
SITE_URL=http://localhost:8080
|
||||
|
||||
# Mail/SMTP
|
||||
SMTP_HOST='smtp-server'
|
||||
SMTP_PORT='1025'
|
||||
SMTP_NAME='local'
|
||||
SMTP_USERNAME='team@infisical.com'
|
||||
SMTP_PASSWORD=
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Warning>
|
||||
The pre-populated environment variable values in the `.env.example` file are meant to be used in development only.
|
||||
You'll want to fill in your own values in production, especially concerning encryption keys, secrets, and SMTP parameters.
|
||||
The pre-populated environment variable values above are meant to be used in development only. They should never be used in production.
|
||||
</Warning>
|
||||
|
||||
Refer to the [environment variable list](https://infisical.com/docs/self-hosting/configuration/envars) for guidance on each envar.
|
||||
View all available [environment variables](https://infisical.com/docs/self-hosting/configuration/envars) and guidance for each.
|
||||
|
||||
### Helpful tips for developing with Infisical:
|
||||
## Starting Infisical for development
|
||||
|
||||
<Tip>
|
||||
Use the `ENCRYPTION_KEY`, JWT-secret envars, `MONGO_URL`, `MONGO_USERNAME`, `MONGO_PASSWORD` provided in the `.env.example` file.
|
||||
We use use Docker to easily spin up all required services to have Infisical up and running for development. If you are unfamiliar with Docker, don't worry, all you have to do is install [Docker](https://docs.docker.com/get-docker/) for your
|
||||
machine and run the commands below to start up the development server.
|
||||
|
||||
If setting your own values:
|
||||
|
||||
- `ENCRYPTION_KEY` should be a [32-byte random hex](https://www.browserling.com/tools/random-hex)
|
||||
- `MONGO_URL` should take the form: `mongodb://[MONGO_USERNAME]:[MONGO_PASSWORD]@mongo:27017/?authSource=admin`.
|
||||
</Tip>
|
||||
|
||||
<Tip>
|
||||
Bring and configure your own SMTP server by following our [email configuration guide](https://infisical.com/docs/self-hosting/configuration/email) (we recommend using either SendGrid or Mailgun).
|
||||
|
||||
Alternatively, you can use the provided development (Mailhog) SMTP server to send and browse emails sent by the backend on http://localhost:8025; to use this option, set the following `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM_NAME`, `SMTP_USERNAME`, `SMTP_PASSWORD` below.
|
||||
</Tip>
|
||||
|
||||
```
|
||||
SMTP_HOST=smtp-server
|
||||
SMTP_PORT=1025
|
||||
SMTP_FROM_ADDRESS=team@infisical.com
|
||||
SMTP_FROM_NAME=Infisical
|
||||
SMTP_USERNAME=team@infisical.com
|
||||
SMTP_PASSWORD=
|
||||
```
|
||||
|
||||
<Warning>
|
||||
If using Mailhog, make sure to leave the `SMTP_PASSWORD` blank so the backend can connect to MailHog.
|
||||
</Warning>
|
||||
|
||||
## Docker for development
|
||||
#### Start local server
|
||||
|
||||
```bash
|
||||
# build and start the services
|
||||
docker-compose -f docker-compose.dev.yml up --build --force-recreate
|
||||
```
|
||||
#### Access local server
|
||||
|
||||
Then browse http://localhost:8080
|
||||
Once all the services have spun up, browse to http://localhost:8080. To sign in, you may use the default credentials listed below.
|
||||
|
||||
Email: `test@localhost.local`
|
||||
Password: `testInfisical1`
|
||||
|
||||
#### Shutdown local server
|
||||
|
||||
```bash
|
||||
# To stop environment use Control+C (on Mac) CTRL+C (on Win) or
|
||||
docker-compose -f docker-compose.dev.yml down
|
||||
# start services
|
||||
docker-compose -f docker-compose.dev.yml up
|
||||
```
|
@ -31,25 +31,22 @@ Infisical can be used in a Dockerfile to inject environment variables into a Doc
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Modify the start command in your Dockerfile
|
||||
## Modify your Dockerfile start command
|
||||
|
||||
```dockerfile
|
||||
CMD ["infisical", "--env=[env]", "projectId=[projectId]", "run", "---", "[your application start command]"]
|
||||
To make your Docker container consume Infisical secrets, you can start your application with Infisical.
|
||||
This will automatically pull the necessary secrets and make them available to your application as if they were natively exposed within the container.
|
||||
|
||||
# example
|
||||
CMD ["infisical", "--env=prod", "projectId=62faf98ae0b05e83239b5da41", "run", "---", "npm run start"]
|
||||
```
|
||||
```dockerfile
|
||||
CMD ["infisical", "run", "---", "[your application start command]"]
|
||||
|
||||
Required options:
|
||||
# example with single single command
|
||||
CMD ["infisical", "run", "---", "npm run start"]
|
||||
|
||||
| Option | Description | Default value |
|
||||
| ------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
|
||||
| `--projectId` | Used to link a local project to the platform | `None` |
|
||||
# example with multiple commands
|
||||
CMD ["infisical", "run", "--command" "npm run start && ..."]
|
||||
```
|
||||
|
||||
## Generate an Infisical Token
|
||||
|
||||
[Generate an Infisical Token](../../getting-started/dashboard/token) and keep it handy.
|
||||
View more options for the `run` command [here](../../cli/commands/run)
|
||||
|
||||
## Feed Docker your Infisical Token
|
||||
|
||||
@ -58,3 +55,8 @@ The CLI looks out for an environment variable called `INFISICAL_TOKEN`. If the t
|
||||
```bash
|
||||
docker run --env INFISICAL_TOKEN=[token]...
|
||||
```
|
||||
|
||||
## Generate an Infisical Token
|
||||
|
||||
[Generate an Infisical Token](../../getting-started/dashboard/token) and keep it handy.
|
||||
|
||||
|
@ -23,7 +23,7 @@ If you're less technical and looking for a hands-free experience with minimal ov
|
||||
Before you can deploy the Helm chart, you must fill out the required environment variables. To do so, please copy the below file to a `.yaml` file.
|
||||
Refer to the available [environment variables](../../self-hosting/configuration/envars) to learn more
|
||||
|
||||
<Accordion title="vaules.yaml">
|
||||
<Accordion title="values.yaml">
|
||||
```yaml
|
||||
#####
|
||||
# INFISICAL K8 DEFAULT VALUES FIL
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ type Props = {
|
||||
isOpen: boolean;
|
||||
closeModal: () => void;
|
||||
selectedIntegrationOption: IntegrationOption | null;
|
||||
handleBotActivate: () => Promise<void>;
|
||||
integrationOptionPress: (integrationOption: IntegrationOption) => void;
|
||||
};
|
||||
|
||||
@ -27,20 +26,16 @@ const ActivateBotDialog = ({
|
||||
isOpen,
|
||||
closeModal,
|
||||
selectedIntegrationOption,
|
||||
handleBotActivate,
|
||||
integrationOptionPress
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
// 1. activate bot
|
||||
await handleBotActivate();
|
||||
|
||||
// type check
|
||||
if (!selectedIntegrationOption) return;
|
||||
|
||||
// 2. start integration or probe for PAT
|
||||
// start integration or probe for PAT
|
||||
integrationOptionPress(selectedIntegrationOption);
|
||||
|
||||
} catch (err) {
|
||||
|
@ -52,7 +52,6 @@ const IntegrationAccessTokenDialog = ({
|
||||
<div>
|
||||
<Transition appear show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-10" onClose={() => {
|
||||
console.log('onClose');
|
||||
closeModal();
|
||||
}}>
|
||||
<Transition.Child
|
||||
|
@ -30,8 +30,8 @@ const ProjectIntegrationSection = ({
|
||||
setBot,
|
||||
environments = [],
|
||||
handleDeleteIntegration
|
||||
}: Props) =>
|
||||
integrations.length > 0 ? (
|
||||
}: Props) => {
|
||||
return integrations.length > 0 ? (
|
||||
<div className="mb-12">
|
||||
<div className="flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2">
|
||||
<h1 className="font-semibold text-3xl">Current Integrations</h1>
|
||||
@ -40,7 +40,6 @@ const ProjectIntegrationSection = ({
|
||||
</p>
|
||||
</div>
|
||||
{integrations.map((integration: Integration) => {
|
||||
console.log('IntegrationSection integration: ', integration);
|
||||
return (
|
||||
<IntegrationTile
|
||||
key={`integration-${integration._id.toString()}`}
|
||||
@ -58,5 +57,6 @@ const ProjectIntegrationSection = ({
|
||||
) : (
|
||||
<div />
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default ProjectIntegrationSection;
|
@ -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,5 +1,4 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
||||
import { faSlack } from '@fortawesome/free-brands-svg-icons';
|
||||
@ -40,47 +39,45 @@ const learningItem = ({
|
||||
}: ItemProps): JSX.Element => {
|
||||
if (link) {
|
||||
return (
|
||||
<Link href={link}>
|
||||
<a
|
||||
target={`${link.includes('https') ? '_blank' : '_self'}`}
|
||||
rel="noopener noreferrer"
|
||||
className="w-full"
|
||||
href="#"
|
||||
<a
|
||||
target={`${link.includes('https') ? '_blank' : '_self'}`}
|
||||
rel="noopener noreferrer"
|
||||
className="w-full"
|
||||
href={link}
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={async () => {
|
||||
if (userAction && userAction !== 'first_time_secrets_pushed') {
|
||||
await registerUserAction({
|
||||
action: userAction
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="relative bg-bunker-700 hover:bg-bunker-500 shadow-xl duration-200 rounded-md border border-dashed border-bunker-400 pl-2 pr-6 py-2 h-[5.5rem] w-full flex items-center justify-between overflow-hidden my-1.5 cursor-pointer"
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={async () => {
|
||||
if (userAction && userAction !== 'first_time_secrets_pushed') {
|
||||
await registerUserAction({
|
||||
action: userAction
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="relative bg-bunker-700 hover:bg-bunker-500 shadow-xl duration-200 rounded-md border border-dashed border-bunker-400 pl-2 pr-6 py-2 h-[5.5rem] w-full flex items-center justify-between overflow-hidden my-1.5 cursor-pointer"
|
||||
>
|
||||
<div className="flex flex-row items-center mr-4">
|
||||
<FontAwesomeIcon icon={icon} className="text-4xl mx-2 w-16" />
|
||||
{complete && (
|
||||
<div className="bg-bunker-700 w-7 h-7 rounded-full absolute left-12 top-10 p-2 flex items-center justify-center">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="text-4xl w-5 h-5 text-green" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="text-xl font-semibold mt-0.5">{text}</div>
|
||||
<div className="text-sm font-normal">{subText}</div>
|
||||
<div className="flex flex-row items-center mr-4">
|
||||
<FontAwesomeIcon icon={icon} className="text-4xl mx-2 w-16" />
|
||||
{complete && (
|
||||
<div className="bg-bunker-700 w-7 h-7 rounded-full absolute left-12 top-10 p-2 flex items-center justify-center">
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="text-4xl w-5 h-5 text-green" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="text-xl font-semibold mt-0.5">{text}</div>
|
||||
<div className="text-sm font-normal">{subText}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`pr-4 font-semibold text-sm w-28 text-right ${complete && 'text-green'}`}
|
||||
>
|
||||
{complete ? 'Complete!' : `About ${time}`}
|
||||
</div>
|
||||
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
<div
|
||||
className={`pr-4 font-semibold text-sm w-28 text-right ${complete && 'text-green'}`}
|
||||
>
|
||||
{complete ? 'Complete!' : `About ${time}`}
|
||||
</div>
|
||||
{complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
@ -157,7 +154,7 @@ export default function Home() {
|
||||
icon: faHandPeace,
|
||||
time: '3 min',
|
||||
userAction: 'intro_cta_clicked',
|
||||
link: 'https://www.youtube.com/watch?v=JS3OKYU2078'
|
||||
link: 'https://www.youtube.com/watch?v=3F7FNYX94zA'
|
||||
})}
|
||||
{learningItem({
|
||||
text: 'Add your secrets',
|
||||
|
@ -183,6 +183,10 @@ export default function Integrations() {
|
||||
accessToken?: string;
|
||||
}) => {
|
||||
try {
|
||||
if (!bot.isActive) {
|
||||
await handleBotActivate();
|
||||
}
|
||||
|
||||
if (integrationOption.type === 'oauth') {
|
||||
// integration is of type OAuth
|
||||
|
||||
@ -233,7 +237,7 @@ export default function Integrations() {
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
@ -247,6 +251,7 @@ export default function Integrations() {
|
||||
* @returns
|
||||
*/
|
||||
const integrationOptionPress = async (integrationOption: IntegrationOption) => {
|
||||
// consider: don't start integration until at [handleIntegrationOption] step
|
||||
try {
|
||||
const integrationAuthX = integrationAuths.find((integrationAuth) => integrationAuth.integration === integrationOption.slug);
|
||||
|
||||
@ -264,12 +269,15 @@ export default function Integrations() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bot.isActive) {
|
||||
await handleBotActivate();
|
||||
}
|
||||
|
||||
// case: integration has been authorized before
|
||||
// -> create new integration
|
||||
const integration = await createIntegration({
|
||||
integrationAuthId: integrationAuthX._id
|
||||
});
|
||||
|
||||
setIntegrations([...integrations, integration]);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@ -350,7 +358,6 @@ export default function Integrations() {
|
||||
isOpen={isActivateBotDialogOpen}
|
||||
closeModal={() => setIsActivateBotDialogOpen(false)}
|
||||
selectedIntegrationOption={selectedIntegrationOption}
|
||||
handleBotActivate={handleBotActivate}
|
||||
integrationOptionPress={integrationOptionPress}
|
||||
/>
|
||||
<IntegrationAccessTokenDialog
|
||||
@ -358,7 +365,6 @@ export default function Integrations() {
|
||||
closeModal={() => setIntegrationAccessTokenDialogOpen(false)}
|
||||
selectedIntegrationOption={selectedIntegrationOption}
|
||||
handleIntegrationOption={handleIntegrationOption}
|
||||
|
||||
/>
|
||||
<IntegrationSection
|
||||
integrations={integrations}
|
||||
|
@ -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