forked from cerc-io/snowballtools-base
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd4a51c830 | ||
|
|
4a87c100d6 | ||
|
|
3493d735b9 | ||
|
|
a324d32ebf | ||
|
|
4519494be8 | ||
|
|
319822c8e6 | ||
|
|
c5273ff530 | ||
|
|
2585418ed6 | ||
|
|
89306ddcc7 | ||
|
|
3b67396c43 | ||
|
|
3535bd3a58 | ||
|
|
282001c317 | ||
|
|
2f62d086de | ||
|
|
4c544e8d15 | ||
|
|
a02ee13f9d | ||
|
|
e32a51a771 | ||
|
|
513ca69d01 | ||
|
|
e34b8d6a2e | ||
|
|
f4d5b77784 | ||
|
|
399b80ce3b | ||
|
|
fdaec2da94 | ||
|
|
13fc92bf0e | ||
|
|
8a8ee1d6ae | ||
|
|
5680961964 | ||
|
|
aa0ca8dd21 | ||
|
|
8280f4c7f7 | ||
|
|
ab72bbb03f | ||
|
|
cc4e5fd627 | ||
|
|
195471b888 | ||
|
|
c731dd308c | ||
|
|
8ce531274d | ||
|
|
6c38094093 | ||
|
|
95285d30dd | ||
|
|
61c8846400 | ||
|
|
d73c3c7daf |
+1
-1
@@ -4,4 +4,4 @@ yarn-error.log
|
||||
.yarn/
|
||||
.yarnrc
|
||||
|
||||
packages/backend/environments/local.toml
|
||||
packages/backend/environments/local.toml
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# deployer
|
||||
|
||||
- Install dependencies
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
```bash
|
||||
brew install jq # if you do not have jq installed already
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
Example of how to make the necessary deploy edits [here](https://github.com/snowball-tools/snowballtools-base/pull/131/files).
|
||||
|
||||
- Replace variables in the following files
|
||||
- [records/application-deployment-request.yml](records/application-deployment-request.yml)
|
||||
- update the name & application version numbers
|
||||
- `<CURRENT_DATE_TIME>`: Replace with current time which can be generated by command `date -u`
|
||||
```yml
|
||||
# Example
|
||||
record:
|
||||
...
|
||||
meta:
|
||||
note: Added by Snowball @ Friday 23 February 2024 06:35:50 AM UTC
|
||||
...
|
||||
```
|
||||
|
||||
- Update record version in [records/application-record.yml](records/application-record.yml)
|
||||
```yml
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: <NEW_VERSION>
|
||||
...
|
||||
```
|
||||
|
||||
- Update commit hash in the following places:
|
||||
- [records/application-record.yml](records/application-record.yml)
|
||||
```yml
|
||||
record:
|
||||
...
|
||||
repository_ref: <COMMIT_HASH>
|
||||
...
|
||||
```
|
||||
- [records/application-deployment-request.yml](records/application-deployment-request.yml)
|
||||
```yml
|
||||
record:
|
||||
...
|
||||
meta:
|
||||
...
|
||||
repository_ref: <COMMIT_HASH>
|
||||
```
|
||||
- [deploy-frontend.sh](deploy-frontend.sh)
|
||||
Also be sure to update the app version
|
||||
```bash
|
||||
...
|
||||
RCD_APP_VERSION="<NEW_VERSION>"
|
||||
REPO_REF="<COMMIT_HASH>"
|
||||
...
|
||||
```
|
||||
|
||||
- Run script to deploy app
|
||||
```
|
||||
./deploy-frontend.sh
|
||||
```
|
||||
|
||||
- Commit the updated [ApplicationRecord](records/application-record.yml) and [ApplicationDeploymentRequest](records/application-deployment-request.yml) files to the repository
|
||||
|
||||
## Notes
|
||||
|
||||
- Any config env can be updated in [records/application-deployment-request.yml](records/application-deployment-request.yml)
|
||||
```yml
|
||||
record:
|
||||
...
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
|
||||
...
|
||||
```
|
||||
- On changing `LACONIC_HOSTED_CONFIG_app_github_clientid`, the GitHub client ID and secret need to be changed in backend config too
|
||||
|
||||
## Troubleshoot
|
||||
|
||||
- Check deployment status [here](https://console.laconic.com/deployer).
|
||||
- Check records [here](https://console.laconic.com/#/registry).
|
||||
|
||||
- If deployment fails due to low bond balance
|
||||
- Check balances
|
||||
```bash
|
||||
# Account balance
|
||||
yarn laconic cns account get
|
||||
|
||||
# Bond balance
|
||||
yarn laconic cns bond get --id 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac
|
||||
```
|
||||
- Command to refill bond
|
||||
```bash
|
||||
yarn laconic cns bond refill --id 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac --type aphoton --quantity 10000000
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
cns:
|
||||
restEndpoint: http://console.laconic.com:1317
|
||||
gqlEndpoint: http://console.laconic.com:9473/api
|
||||
chainId: laconic_9000-1
|
||||
gas: 1000000
|
||||
fees: 200000aphoton
|
||||
userKey: 0524fc22ea0a12e6c5cc4cfe08e73c95dffd0ab5ed72a59f459ed33134fa3b16
|
||||
bondId: 8fcf44b2f326b4b63ac57547777f1c78b7d494e5966e508f09001af53cb440ac
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Reference: https://git.vdb.to/cerc-io/test-progressive-web-app/src/branch/main/scripts
|
||||
|
||||
RECORD_FILE=records/application-record.yml
|
||||
CONFIG_FILE=config.yml
|
||||
RCD_APP_VERSION="0.1.3"
|
||||
REPO_REF="513ca69d01bee857cf207a0605483205b384e218"
|
||||
|
||||
# Publish ApplicationRecord
|
||||
RECORD_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationRecord published"
|
||||
echo $RECORD_ID
|
||||
|
||||
# Set name to record
|
||||
REGISTRY_APP_CRN="crn://snowballtools/applications/snowballtools-base-frontend"
|
||||
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${RCD_APP_VERSION}" "$RECORD_ID"
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN@${REPO_REF}" "$RECORD_ID"
|
||||
# Set name if latest release
|
||||
yarn --silent laconic -c $CONFIG_FILE cns name set "$REGISTRY_APP_CRN" "$RECORD_ID"
|
||||
echo "$REGISTRY_APP_CRN set for ApplicationRecord"
|
||||
|
||||
# Check if record found for REGISTRY_APP_CRN
|
||||
APP_RECORD=$(yarn --silent laconic -c $CONFIG_FILE cns name resolve "$REGISTRY_APP_CRN" | jq '.[0]')
|
||||
if [ -z "$APP_RECORD" ] || [ "null" == "$APP_RECORD" ]; then
|
||||
echo "No record found for $REGISTRY_APP_CRN."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RECORD_FILE=records/application-deployment-request.yml
|
||||
|
||||
DEPLOYMENT_REQUEST_ID=$(yarn --silent laconic -c $CONFIG_FILE cns record publish --filename $RECORD_FILE | jq -r '.id')
|
||||
echo "ApplicationDeploymentRequest published"
|
||||
echo $DEPLOYMENT_REQUEST_ID
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "deployer",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@cerc-io/laconic-registry-cli": "^0.1.10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
record:
|
||||
type: ApplicationDeploymentRequest
|
||||
version: '1.0.0'
|
||||
name: snowballtools-base-frontend@0.1.3
|
||||
application: crn://snowballtools/applications/snowballtools-base-frontend@0.1.3
|
||||
dns: dashboard
|
||||
config:
|
||||
env:
|
||||
LACONIC_HOSTED_CONFIG_app_server_url: https://snowballtools-base-api-001.apps.snowballtools.com
|
||||
# If GitHub client ID is changed, same ID and corresponding secret has to be set in backend config
|
||||
LACONIC_HOSTED_CONFIG_app_github_clientid: b7c63b235ca1dd5639ab
|
||||
LACONIC_HOSTED_CONFIG_app_github_templaterepo: snowball-tools-platform/test-progressive-web-app
|
||||
# New config env after changes for image upload PWA
|
||||
LACONIC_HOSTED_CONFIG_app_github_pwa_templaterepo: snowball-tools-platform/test-progressive-web-app
|
||||
LACONIC_HOSTED_CONFIG_app_github_image_upload_templaterepo: snowball-tools-platform/image-upload-pwa-example
|
||||
LACONIC_HOSTED_CONFIG_app_wallet_connect_id: eda9ba18042a5ea500f358194611ece2
|
||||
meta:
|
||||
# Set CURRENT_DATE_TIME; Use command date -u
|
||||
note: Added by Snowball @ Tue Feb 27 17:24:06 UTC 2024
|
||||
repository: "https://git.vdb.to/cerc-io/snowballtools-base"
|
||||
repository_ref: 513ca69d01bee857cf207a0605483205b384e218
|
||||
@@ -0,0 +1,10 @@
|
||||
record:
|
||||
type: ApplicationRecord
|
||||
version: 0.0.11
|
||||
repository_ref: 513ca69d01bee857cf207a0605483205b384e218
|
||||
repository: ["https://git.vdb.to/cerc-io/snowballtools-base"]
|
||||
app_type: webapp
|
||||
# name is set to repo name
|
||||
name: snowballtools-base-frontend
|
||||
# app_version is set from package.json
|
||||
app_version: 0.1.3
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
{
|
||||
// eslint extension options
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact"
|
||||
],
|
||||
"css.customData": [".vscode/tailwind.json"],
|
||||
// prettier extension setting
|
||||
"editor.formatOnSave": true,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.rulers": [80],
|
||||
"editor.codeActionsOnSave": [
|
||||
"source.addMissingImports",
|
||||
"source.fixAll",
|
||||
"source.organizeImports"
|
||||
],
|
||||
// Show in vscode "Problems" tab when there are errors
|
||||
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
|
||||
// Use absolute import for typescript files
|
||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||
// IntelliSense for taiwind variants
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
|
||||
]
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@fontsource-variable/jetbrains-mono": "^5.0.19",
|
||||
"@fontsource/inter": "^5.0.16",
|
||||
"@material-tailwind/react": "^2.1.7",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@radix-ui/react-avatar": "^1.0.4",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-radio-group": "^1.1.3",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@tanstack/react-query": "^5.22.2",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
@@ -27,7 +28,7 @@
|
||||
"axios": "^1.6.7",
|
||||
"clsx": "^2.1.0",
|
||||
"date-fns": "^3.3.1",
|
||||
"downshift": "^8.2.3",
|
||||
"downshift": "^8.3.2",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"gql-client": "^1.0.0",
|
||||
"luxon": "^3.4.4",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
<svg width="333" height="5" viewBox="0 0 333 5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 4L6.39555 1.30714C8.38078 0.47125 10.6192 0.47125 12.6045 1.30714L15.8955 2.69286C17.8808 3.52875 20.1192 3.52875 22.1045 2.69286L25.3955 1.30714C27.3808 0.47125 29.6192 0.47125 31.6045 1.30714L34.8955 2.69286C36.8808 3.52875 39.1192 3.52875 41.1045 2.69286L44.3955 1.30714C46.3808 0.47125 48.6192 0.47125 50.6045 1.30714L53.8955 2.69286C55.8808 3.52875 58.1192 3.52875 60.1045 2.69286L63.3955 1.30714C65.3808 0.47125 67.6192 0.47125 69.6045 1.30714L72.8955 2.69286C74.8808 3.52875 77.1192 3.52875 79.1045 2.69286L82.3955 1.30714C84.3808 0.47125 86.6192 0.47125 88.6045 1.30714L91.8955 2.69286C93.8808 3.52875 96.1192 3.52875 98.1045 2.69286L101.396 1.30714C103.381 0.47125 105.619 0.47125 107.604 1.30714L110.896 2.69286C112.881 3.52875 115.119 3.52875 117.104 2.69286L120.396 1.30714C122.381 0.47125 124.619 0.47125 126.604 1.30714L129.896 2.69286C131.881 3.52875 134.119 3.52875 136.104 2.69286L139.396 1.30714C141.381 0.47125 143.619 0.47125 145.604 1.30714L148.896 2.69286C150.881 3.52875 153.119 3.52875 155.104 2.69286L158.396 1.30714C160.381 0.47125 162.619 0.47125 164.604 1.30714L167.896 2.69286C169.881 3.52875 172.119 3.52875 174.104 2.69286L177.396 1.30714C179.381 0.47125 181.619 0.47125 183.604 1.30714L186.896 2.69286C188.881 3.52875 191.119 3.52875 193.104 2.69286L196.396 1.30714C198.381 0.47125 200.619 0.47125 202.604 1.30714L205.896 2.69286C207.881 3.52875 210.119 3.52875 212.104 2.69286L215.396 1.30714C217.381 0.47125 219.619 0.47125 221.604 1.30714L224.896 2.69286C226.881 3.52875 229.119 3.52875 231.104 2.69286L234.396 1.30714C236.381 0.47125 238.619 0.47125 240.604 1.30714L243.896 2.69286C245.881 3.52875 248.119 3.52875 250.104 2.69286L253.396 1.30714C255.381 0.47125 257.619 0.47125 259.604 1.30714L262.896 2.69286C264.881 3.52875 267.119 3.52875 269.104 2.69286L272.396 1.30714C274.381 0.47125 276.619 0.47125 278.604 1.30714L281.896 2.69286C283.881 3.52875 286.119 3.52875 288.104 2.69286L291.396 1.30714C293.381 0.47125 295.619 0.47125 297.604 1.30714L300.896 2.69286C302.881 3.52875 305.119 3.52875 307.104 2.69286L310.396 1.30714C312.381 0.47125 314.619 0.47125 316.604 1.30714L319.973 2.72566C321.913 3.54216 324.095 3.56183 326.049 2.78039L330.029 1.18845C331.936 0.425535 334.064 0.425535 335.971 1.18845L343 4" stroke="#DBEBF9"/>
|
||||
<path d="M6.39555 1.30714L0 4H342.5L336.027 1.27434C334.087 0.457837 331.905 0.438174 329.951 1.21961L326.049 2.78039C324.095 3.56183 321.913 3.54216 319.973 2.72566L316.604 1.30714C314.619 0.47125 312.381 0.47125 310.396 1.30714L307.104 2.69286C305.119 3.52875 302.881 3.52875 300.896 2.69286L297.604 1.30714C295.619 0.47125 293.381 0.47125 291.396 1.30714L288.104 2.69286C286.119 3.52875 283.881 3.52875 281.896 2.69286L278.604 1.30714C276.619 0.47125 274.381 0.47125 272.396 1.30714L269.104 2.69286C267.119 3.52875 264.881 3.52875 262.896 2.69286L259.604 1.30714C257.619 0.47125 255.381 0.47125 253.396 1.30714L250.104 2.69286C248.119 3.52875 245.881 3.52875 243.896 2.69286L240.604 1.30714C238.619 0.47125 236.381 0.47125 234.396 1.30714L231.104 2.69286C229.119 3.52875 226.881 3.52875 224.896 2.69286L221.604 1.30714C219.619 0.47125 217.381 0.47125 215.396 1.30714L212.104 2.69286C210.119 3.52875 207.881 3.52875 205.896 2.69286L202.604 1.30714C200.619 0.47125 198.381 0.47125 196.396 1.30714L193.104 2.69286C191.119 3.52875 188.881 3.52875 186.896 2.69286L183.604 1.30714C181.619 0.47125 179.381 0.47125 177.396 1.30714L174.104 2.69286C172.119 3.52875 169.881 3.52875 167.896 2.69286L164.604 1.30714C162.619 0.47125 160.381 0.47125 158.396 1.30714L155.104 2.69286C153.119 3.52875 150.881 3.52875 148.896 2.69286L145.604 1.30714C143.619 0.47125 141.381 0.47125 139.396 1.30714L136.104 2.69286C134.119 3.52875 131.881 3.52875 129.896 2.69286L126.604 1.30714C124.619 0.47125 122.381 0.47125 120.396 1.30714L117.104 2.69286C115.119 3.52875 112.881 3.52875 110.896 2.69286L107.604 1.30714C105.619 0.47125 103.381 0.47125 101.396 1.30714L98.1045 2.69286C96.1192 3.52875 93.8808 3.52875 91.8955 2.69286L88.6045 1.30714C86.6192 0.47125 84.3808 0.47125 82.3955 1.30714L79.1045 2.69286C77.1192 3.52875 74.8808 3.52875 72.8955 2.69286L69.6045 1.30714C67.6192 0.47125 65.3808 0.47125 63.3955 1.30714L60.1045 2.69286C58.1192 3.52875 55.8808 3.52875 53.8955 2.69286L50.6045 1.30714C48.6192 0.47125 46.3808 0.47125 44.3955 1.30714L41.1045 2.69286C39.1192 3.52875 36.8808 3.52875 34.8955 2.69286L31.6045 1.30714C29.6192 0.47125 27.3808 0.47125 25.3955 1.30714L22.1045 2.69286C20.1192 3.52875 17.8808 3.52875 15.8955 2.69286L12.6045 1.30714C10.6192 0.47125 8.38078 0.47125 6.39555 1.30714Z" fill="url(#paint0_linear_1729_11298)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1729_11298" x1="171.25" y1="0" x2="171.25" y2="4" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E6F4FF"/>
|
||||
<stop offset="1" stop-color="#F9FCFF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
@@ -102,10 +102,14 @@ const Sidebar = () => {
|
||||
<Tabs defaultValue="Projects" orientation="vertical" className="mt-10">
|
||||
<Tabs.List>
|
||||
{[
|
||||
{ title: 'Projects', icon: <FolderIcon /> },
|
||||
{ title: 'Settings', icon: <SettingsSlidersIcon /> },
|
||||
].map(({ title, icon }, index) => (
|
||||
<NavLink to={`/${orgSlug}/${title}`} key={index}>
|
||||
{ title: 'Projects', url: `/${orgSlug}/`, icon: <FolderIcon /> },
|
||||
{
|
||||
title: 'Settings',
|
||||
url: `/${orgSlug}/settings`,
|
||||
icon: <SettingsSlidersIcon />,
|
||||
},
|
||||
].map(({ title, icon, url }, index) => (
|
||||
<NavLink to={url} key={index}>
|
||||
<Tabs.Trigger icon={icon} value={title}>
|
||||
{title}
|
||||
</Tabs.Trigger>
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Project } from 'gql-client';
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuHandler,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Typography,
|
||||
Avatar,
|
||||
} from '@material-tailwind/react';
|
||||
|
||||
import { relativeTimeMs } from '../../utils/time';
|
||||
|
||||
interface ProjectCardProps {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => {
|
||||
return (
|
||||
<div className="bg-white border border-gray-200 rounded-lg shadow">
|
||||
<div className="flex gap-2 p-2 items-center">
|
||||
<Avatar
|
||||
variant="rounded"
|
||||
src={project.icon || '/gray.png'}
|
||||
placeholder={''}
|
||||
/>
|
||||
<div className="grow">
|
||||
<Link to={`projects/${project.id}`}>
|
||||
<Typography placeholder={''}>{project.name}</Typography>
|
||||
<Typography color="gray" variant="small" placeholder={''}>
|
||||
{project.deployments[0]?.domain?.name ??
|
||||
'No Production Deployment'}
|
||||
</Typography>
|
||||
</Link>
|
||||
</div>
|
||||
<Menu placement="bottom-end">
|
||||
<MenuHandler>
|
||||
<button>...</button>
|
||||
</MenuHandler>
|
||||
<MenuList placeholder={''}>
|
||||
<MenuItem placeholder={''}>^ Project settings</MenuItem>
|
||||
<MenuItem className="text-red-500" placeholder={''}>
|
||||
^ Delete project
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
<div className="border-t-2 border-solid p-4 bg-gray-50">
|
||||
{project.deployments.length > 0 ? (
|
||||
<>
|
||||
<Typography variant="small" color="gray" placeholder={''}>
|
||||
^ {project.deployments[0].commitMessage}
|
||||
</Typography>
|
||||
<Typography variant="small" color="gray" placeholder={''}>
|
||||
{relativeTimeMs(project.deployments[0].createdAt)} on ^
|
||||
{project.deployments[0].branch}
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Typography variant="small" color="gray" placeholder={''}>
|
||||
No Production deployment
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectCard;
|
||||
@@ -0,0 +1,71 @@
|
||||
import { VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const projectCardTheme = tv({
|
||||
slots: {
|
||||
wrapper: [
|
||||
'bg-surface-card',
|
||||
'shadow-card',
|
||||
'rounded-2xl',
|
||||
'flex',
|
||||
'flex-col',
|
||||
],
|
||||
upperContent: ['px-4', 'py-4', 'flex', 'items-start', 'gap-3', 'relative'],
|
||||
content: ['flex', 'flex-col', 'gap-1', 'flex-1'],
|
||||
title: [
|
||||
'text-sm',
|
||||
'font-medium',
|
||||
'text-elements-high-em',
|
||||
'tracking-[-0.006em]',
|
||||
],
|
||||
description: ['text-xs', 'text-elements-low-em'],
|
||||
icons: ['flex', 'items-center', 'gap-1'],
|
||||
lowerContent: [
|
||||
'bg-surface-card-hovered',
|
||||
'px-4',
|
||||
'py-4',
|
||||
'flex',
|
||||
'flex-col',
|
||||
'gap-2',
|
||||
'rounded-b-2xl',
|
||||
],
|
||||
latestDeployment: ['flex', 'items-center', 'gap-2'],
|
||||
deploymentStatusContainer: [
|
||||
'h-3',
|
||||
'w-3',
|
||||
'flex',
|
||||
'items-center',
|
||||
'justify-center',
|
||||
],
|
||||
deploymentStatus: ['w-1', 'h-1', 'rounded-full'],
|
||||
deploymentName: ['text-xs', 'text-elements-low-em'],
|
||||
deploymentText: [
|
||||
'text-xs',
|
||||
'text-elements-low-em',
|
||||
'font-mono',
|
||||
'flex',
|
||||
'items-center',
|
||||
'gap-2',
|
||||
],
|
||||
},
|
||||
variants: {
|
||||
status: {
|
||||
success: {
|
||||
deploymentStatus: ['bg-emerald-500'],
|
||||
},
|
||||
'in-progress': {
|
||||
deploymentStatus: ['bg-orange-400'],
|
||||
},
|
||||
failure: {
|
||||
deploymentStatus: ['bg-rose-500'],
|
||||
},
|
||||
pending: {
|
||||
deploymentStatus: ['bg-gray-500'],
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
status: 'pending',
|
||||
},
|
||||
});
|
||||
|
||||
export type ProjectCardTheme = VariantProps<typeof projectCardTheme>;
|
||||
@@ -0,0 +1,124 @@
|
||||
import React, { ComponentPropsWithoutRef, MouseEvent } from 'react';
|
||||
import { ProjectCardTheme, projectCardTheme } from './ProjectCard.theme';
|
||||
import { Project } from 'gql-client';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { WavyBorder } from 'components/shared/WavyBorder';
|
||||
import {
|
||||
BranchIcon,
|
||||
ClockIcon,
|
||||
GitHubLogo,
|
||||
HorizontalDotIcon,
|
||||
WarningDiamondIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { relativeTimeMs } from 'utils/time';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Avatar } from 'components/shared/Avatar';
|
||||
import { getInitials } from 'utils/geInitials';
|
||||
import {
|
||||
Menu,
|
||||
MenuHandler,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
} from '@material-tailwind/react';
|
||||
|
||||
export interface ProjectCardProps
|
||||
extends ComponentPropsWithoutRef<'div'>,
|
||||
ProjectCardTheme {
|
||||
project: Project;
|
||||
}
|
||||
|
||||
export const ProjectCard = ({
|
||||
className,
|
||||
project,
|
||||
status = 'failure',
|
||||
...props
|
||||
}: ProjectCardProps) => {
|
||||
const theme = projectCardTheme();
|
||||
const hasDeployment = project.deployments.length > 0;
|
||||
// TODO: Update this to use the actual status from the API
|
||||
const hasError = status === 'failure';
|
||||
|
||||
const handleOptionsClick = (
|
||||
e: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>,
|
||||
) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<div {...props} className={theme.wrapper({ className })}>
|
||||
{/* Upper content */}
|
||||
<div className={theme.upperContent()}>
|
||||
{/* Icon container */}
|
||||
<Avatar
|
||||
size={48}
|
||||
imageSrc={project.icon}
|
||||
initials={getInitials(project.name)}
|
||||
/>
|
||||
{/* </div> */}
|
||||
{/* Title and website */}
|
||||
<Link to={`projects/${project.id}`} className={theme.content()}>
|
||||
<p className={theme.title()}>{project.name}</p>
|
||||
<p className={theme.description()}>
|
||||
{project.deployments[0]?.domain?.name ?? 'No domain'}
|
||||
</p>
|
||||
</Link>
|
||||
{/* Icons */}
|
||||
<div className={theme.icons()}>
|
||||
{hasError && <WarningDiamondIcon className="text-elements-danger" />}
|
||||
<Menu placement="bottom-end">
|
||||
<MenuHandler>
|
||||
<Button
|
||||
shape="default"
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
iconOnly
|
||||
onClick={handleOptionsClick}
|
||||
>
|
||||
<HorizontalDotIcon />
|
||||
</Button>
|
||||
</MenuHandler>
|
||||
<MenuList placeholder={''}>
|
||||
<MenuItem placeholder={''}>Project settings</MenuItem>
|
||||
<MenuItem className="text-red-500" placeholder={''}>
|
||||
Delete project
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
{/* Wave */}
|
||||
<WavyBorder />
|
||||
{/* Lower content */}
|
||||
<div className={theme.lowerContent()}>
|
||||
{/* Latest deployment */}
|
||||
<div className={theme.latestDeployment()}>
|
||||
{/* Dot icon */}
|
||||
<div className={theme.deploymentStatusContainer()}>
|
||||
<div className={theme.deploymentStatus({ status })} />
|
||||
</div>
|
||||
<p className={theme.deploymentText()}>
|
||||
{hasDeployment
|
||||
? project.deployments[0]?.commitMessage
|
||||
: 'No production deployment'}
|
||||
</p>
|
||||
</div>
|
||||
{/* Deployment and branch name */}
|
||||
<div className={theme.deploymentText()}>
|
||||
{hasDeployment ? (
|
||||
<>
|
||||
<GitHubLogo />
|
||||
<span>{relativeTimeMs(project.deployments[0].createdAt)} on</span>
|
||||
<BranchIcon />
|
||||
<span>{project.deployments[0].branch}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ClockIcon />
|
||||
<span>Created {relativeTimeMs(project.createdAt)}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ProjectCard';
|
||||
@@ -7,7 +7,12 @@ import {
|
||||
GitIcon,
|
||||
EllipsesIcon,
|
||||
SnowballIcon,
|
||||
GithubIcon,
|
||||
GitTeaIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { useToast } from 'components/shared/Toast';
|
||||
import { IconWithFrame } from 'components/shared/IconWithFrame';
|
||||
import { Heading } from 'components/shared/Heading';
|
||||
|
||||
const SCOPES = 'repo user';
|
||||
const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${
|
||||
@@ -23,47 +28,68 @@ const ConnectAccount: React.FC<ConnectAccountInterface> = ({
|
||||
}: ConnectAccountInterface) => {
|
||||
const client = useGQLClient();
|
||||
|
||||
const { toast, dismiss } = useToast();
|
||||
|
||||
const handleCode = async (code: string) => {
|
||||
// Pass code to backend and get access token
|
||||
const {
|
||||
authenticateGitHub: { token },
|
||||
} = await client.authenticateGitHub(code);
|
||||
onToken(token);
|
||||
toast({
|
||||
onDismiss: dismiss,
|
||||
id: 'connected-to-github',
|
||||
title: 'The Git account is connected.',
|
||||
variant: 'success',
|
||||
});
|
||||
};
|
||||
|
||||
// TODO: Use correct height
|
||||
return (
|
||||
<div className="bg-gray-100 flex flex-col p-4 gap-7 justify-center items-center text-center text-sm h-full rounded-2xl">
|
||||
<div className="w-52 h-16 justify-center items-center gap-4 inline-flex">
|
||||
<div className="w-16 h-16 bg-blue-100 rounded-2xl shadow-inner border border-sky-950 border-opacity-10 justify-center items-center gap-2.5 inline-flex">
|
||||
<GitIcon />
|
||||
<div className="flex flex-col items-center max-w-[420px]">
|
||||
{/** Icons */}
|
||||
<div className="w-52 h-16 justify-center items-center gap-4 inline-flex mb-7">
|
||||
<IconWithFrame icon={<GitIcon />} />
|
||||
<EllipsesIcon className="items-center gap-1.5 flex" />
|
||||
<IconWithFrame className="bg-blue-400" icon={<SnowballIcon />} />
|
||||
</div>
|
||||
<EllipsesIcon className="items-center gap-1.5 flex" />
|
||||
<div className="w-16 h-16 bg-blue-400 rounded-2xl shadow-inner border border-sky-950 border-opacity-10 justify-center items-center gap-2.5 flex">
|
||||
<SnowballIcon />
|
||||
{/** Text */}
|
||||
<div className="flex flex-col gap-1.5 mb-6">
|
||||
<Heading className="text-xl font-medium">
|
||||
Connect to your Git account
|
||||
</Heading>
|
||||
<p className="text-center text-elements-mid-em">
|
||||
Once connected, you can import a repository from your account or
|
||||
start with one of our templates.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-center text-slate-900 text-xl font-medium leading-7">
|
||||
Connect to your Git account
|
||||
{/** CTA Buttons */}
|
||||
<div className="flex flex-col w-full sm:w-auto sm:flex-row gap-2 sm:gap-3">
|
||||
<OauthPopup
|
||||
url={GITHUB_OAUTH_URL}
|
||||
onCode={handleCode}
|
||||
onClose={() => {}}
|
||||
title="Snowball"
|
||||
width={1000}
|
||||
height={1000}
|
||||
>
|
||||
<Button
|
||||
className="w-full sm:w-auto"
|
||||
leftIcon={<GithubIcon />}
|
||||
variant="tertiary"
|
||||
>
|
||||
Connect to GitHub
|
||||
</Button>
|
||||
</OauthPopup>
|
||||
<Button
|
||||
className="w-full sm:w-auto"
|
||||
leftIcon={<GitTeaIcon />}
|
||||
variant="tertiary"
|
||||
>
|
||||
Connect to GitTea
|
||||
</Button>
|
||||
</div>
|
||||
<div className="text-center text-slate-600 text-base font-normal leading-normal">
|
||||
Once connected, you can import a repository from your account or start
|
||||
with one of our templates.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex gap-3">
|
||||
<OauthPopup
|
||||
url={GITHUB_OAUTH_URL}
|
||||
onCode={handleCode}
|
||||
onClose={() => {}}
|
||||
title="Snowball"
|
||||
width={1000}
|
||||
height={1000}
|
||||
>
|
||||
<Button>Connect to GitHub</Button>
|
||||
</OauthPopup>
|
||||
<Button>Connect to Gitea</Button>
|
||||
</div>
|
||||
|
||||
{/* TODO: Add ConnectAccountTabPanel */}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Chip, IconButton, Spinner } from '@material-tailwind/react';
|
||||
import { relativeTimeISO } from '../../../utils/time';
|
||||
import { GitRepositoryDetails } from '../../../types';
|
||||
import { useGQLClient } from '../../../context/GQLClientContext';
|
||||
import { GithubIcon, LockIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
interface ProjectRepoCardProps {
|
||||
repository: GitRepositoryDetails;
|
||||
@@ -47,16 +48,18 @@ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => {
|
||||
className="group flex items-center gap-4 text-gray-500 text-xs hover:bg-gray-100 p-2 cursor-pointer"
|
||||
onClick={createProject}
|
||||
>
|
||||
<div>^</div>
|
||||
<div className="w-10 h-10 bg-white rounded-md justify-center items-center gap-1.5 inline-flex">
|
||||
<GithubIcon />
|
||||
</div>
|
||||
<div className="grow">
|
||||
<div>
|
||||
<span className="text-black">{repository.full_name}</span>
|
||||
{repository.visibility === 'private' && (
|
||||
<Chip
|
||||
className="normal-case inline ml-6 font-normal"
|
||||
className="normal-case inline ml-6 font-normal text-xs text-xs bg-orange-50 border border-orange-200 text-orange-600 items-center gap-1 inline-flex"
|
||||
size="sm"
|
||||
value="Private"
|
||||
icon={'^'}
|
||||
icon={<LockIcon />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import SearchBar from '../../SearchBar';
|
||||
import ProjectRepoCard from './ProjectRepoCard';
|
||||
import { GitOrgDetails, GitRepositoryDetails } from '../../../types';
|
||||
import AsyncSelect from '../../shared/AsyncSelect';
|
||||
import { GithubIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
const DEFAULT_SEARCHED_REPO = '';
|
||||
const REPOS_PER_PAGE = 5;
|
||||
@@ -108,7 +109,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex gap-2 mb-2">
|
||||
<div className="flex gap-2 mb-2 items-center">
|
||||
<div className="basis-1/3">
|
||||
<AsyncSelect
|
||||
value={selectedAccount}
|
||||
@@ -116,12 +117,14 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
|
||||
>
|
||||
{accounts.map((account) => (
|
||||
<Option key={account.id} value={account.login}>
|
||||
^ {account.login}
|
||||
<div className="flex items-center gap-2 justify-start">
|
||||
<GithubIcon /> {account.login}
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
</AsyncSelect>
|
||||
</div>
|
||||
<div className="basis-2/3">
|
||||
<div className="basis-2/3 flex-grow flex items-center">
|
||||
<SearchBar
|
||||
value={searchedRepo}
|
||||
onChange={(event) => setSearchedRepo(event.target.value)}
|
||||
|
||||
@@ -58,6 +58,9 @@ export const avatarTheme = tv(
|
||||
44: {
|
||||
base: ['rounded-xl', 'h-[44px]', 'w-[44px]', 'text-sm'],
|
||||
},
|
||||
48: {
|
||||
base: ['rounded-xl', 'h-[48px]', 'w-[48px]', 'text-sm'],
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -14,6 +14,8 @@ export const buttonTheme = tv(
|
||||
'whitespace-nowrap',
|
||||
'focus-ring',
|
||||
'disabled:cursor-not-allowed',
|
||||
'transition-colors',
|
||||
'duration-150',
|
||||
],
|
||||
variants: {
|
||||
size: {
|
||||
@@ -26,7 +28,7 @@ export const buttonTheme = tv(
|
||||
true: 'w-full',
|
||||
},
|
||||
shape: {
|
||||
default: '',
|
||||
default: 'rounded-lg',
|
||||
rounded: 'rounded-full',
|
||||
},
|
||||
iconOnly: {
|
||||
@@ -61,14 +63,13 @@ export const buttonTheme = tv(
|
||||
tertiary: [
|
||||
'text-elements-on-tertiary',
|
||||
'border',
|
||||
'border-border-interactive/10',
|
||||
'border-border-interactive',
|
||||
'bg-controls-tertiary',
|
||||
'shadow-button',
|
||||
'hover:bg-controls-tertiary-hovered',
|
||||
'hover:border-border-interactive-hovered',
|
||||
'hover:border-border-interactive-hovered/[0.14]',
|
||||
'focus-visible:bg-controls-tertiary-hovered',
|
||||
'focus-visible:border-border-interactive-hovered',
|
||||
'focus-visible:border-border-interactive-hovered/[0.14]',
|
||||
'disabled:text-elements-on-disabled',
|
||||
'disabled:bg-controls-disabled',
|
||||
'disabled:border-transparent',
|
||||
@@ -81,10 +82,8 @@ export const buttonTheme = tv(
|
||||
'bg-transparent',
|
||||
'hover:bg-controls-tertiary-hovered',
|
||||
'hover:border-border-interactive-hovered',
|
||||
'hover:border-border-interactive-hovered/[0.14]',
|
||||
'focus-visible:bg-controls-tertiary-hovered',
|
||||
'focus-visible:border-border-interactive-hovered',
|
||||
'focus-visible:border-border-interactive-hovered/[0.14]',
|
||||
'disabled:text-elements-on-disabled',
|
||||
'disabled:bg-controls-disabled',
|
||||
'disabled:border-transparent',
|
||||
@@ -109,10 +108,8 @@ export const buttonTheme = tv(
|
||||
'bg-transparent',
|
||||
'hover:bg-controls-tertiary-hovered',
|
||||
'hover:border-border-interactive-hovered',
|
||||
'hover:border-border-interactive-hovered/[0.14]',
|
||||
'focus-visible:bg-controls-tertiary-hovered',
|
||||
'focus-visible:border-border-interactive-hovered',
|
||||
'focus-visible:border-border-interactive-hovered/[0.14]',
|
||||
'disabled:text-elements-on-disabled',
|
||||
'disabled:bg-controls-disabled',
|
||||
'disabled:border-transparent',
|
||||
|
||||
@@ -5,7 +5,7 @@ export const calendarTheme = tv({
|
||||
wrapper: [
|
||||
'max-w-[352px]',
|
||||
'bg-surface-floating',
|
||||
'shadow-calendar',
|
||||
'shadow-dropdown',
|
||||
'rounded-xl',
|
||||
],
|
||||
calendar: ['flex', 'flex-col', 'py-2', 'px-2', 'gap-2'],
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const BranchIcon = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M3.5 1C2.67157 1 2 1.67157 2 2.5C2 3.24325 2.54057 3.86024 3.25 3.97926V8.02074C2.54057 8.13976 2 8.75675 2 9.5C2 10.3284 2.67157 11 3.5 11C4.32843 11 5 10.3284 5 9.5C5 8.75675 4.45943 8.13976 3.75 8.02074V7C3.75 6.58579 4.08579 6.25 4.5 6.25H7.5C8.19036 6.25 8.75 5.69036 8.75 5V3.97926C9.45943 3.86024 10 3.24325 10 2.5C10 1.67157 9.32843 1 8.5 1C7.67157 1 7 1.67157 7 2.5C7 3.24325 7.54057 3.86024 8.25 3.97926V5C8.25 5.41421 7.91421 5.75 7.5 5.75H4.5C4.2186 5.75 3.95892 5.84299 3.75 5.99991V3.97926C4.45943 3.86024 5 3.24325 5 2.5C5 1.67157 4.32843 1 3.5 1Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const CheckRadioIcon = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 16.5C13.1421 16.5 16.5 13.1421 16.5 9C16.5 4.85786 13.1421 1.5 9 1.5C4.85786 1.5 1.5 4.85786 1.5 9C1.5 13.1421 4.85786 16.5 9 16.5ZM7.93079 12.3663L12.3055 7.01944L11.1446 6.06958L7.81944 10.1336L6.37511 8.68932L5.31445 9.74998L7.93079 12.3663Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const ChevronDownIcon = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M20 9L13.4142 15.5858C12.6332 16.3668 11.3669 16.3668 10.5858 15.5858L4 9"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const ClockIcon = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M6 1.125C5.03582 1.125 4.09329 1.41091 3.2916 1.94659C2.48991 2.48226 1.86507 3.24363 1.49609 4.13442C1.12711 5.02521 1.03057 6.00541 1.21867 6.95107C1.40678 7.89672 1.87108 8.76536 2.55286 9.44715C3.23464 10.1289 4.10328 10.5932 5.04894 10.7813C5.99459 10.9694 6.97479 10.8729 7.86558 10.5039C8.75637 10.1349 9.51775 9.51009 10.0534 8.7084C10.5891 7.90671 10.875 6.96418 10.875 6C10.8736 4.70749 10.3596 3.46831 9.44564 2.55436C8.5317 1.64042 7.29251 1.12636 6 1.125ZM8.625 6.375H6C5.90055 6.375 5.80516 6.33549 5.73484 6.26517C5.66451 6.19484 5.625 6.09946 5.625 6V3.375C5.625 3.27554 5.66451 3.18016 5.73484 3.10984C5.80516 3.03951 5.90055 3 6 3C6.09946 3 6.19484 3.03951 6.26517 3.10984C6.33549 3.18016 6.375 3.27554 6.375 3.375V5.625H8.625C8.72446 5.625 8.81984 5.66451 8.89017 5.73484C8.96049 5.80516 9 5.90054 9 6C9 6.09946 8.96049 6.19484 8.89017 6.26517C8.81984 6.33549 8.72446 6.375 8.625 6.375Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const GitHubLogo = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10.125 4.87501V5.25001C10.1242 5.88451 9.89387 6.4973 9.47652 6.97522C9.05917 7.45314 8.48299 7.76392 7.85437 7.85016C8.11096 8.1785 8.25024 8.5833 8.25 9.00001V10.875C8.25 10.9745 8.21049 11.0698 8.14017 11.1402C8.06984 11.2105 7.97446 11.25 7.875 11.25H4.875C4.77554 11.25 4.68016 11.2105 4.60984 11.1402C4.53951 11.0698 4.5 10.9745 4.5 10.875V10.125H3.375C2.87772 10.125 2.40081 9.92746 2.04917 9.57583C1.69754 9.2242 1.5 8.74729 1.5 8.25001C1.5 7.95164 1.38147 7.66549 1.1705 7.45451C0.959517 7.24353 0.673369 7.12501 0.375 7.12501C0.275544 7.12501 0.180161 7.0855 0.109835 7.01517C0.0395088 6.94484 0 6.84946 0 6.75001C0 6.65055 0.0395088 6.55517 0.109835 6.48484C0.180161 6.41451 0.275544 6.37501 0.375 6.37501C0.621229 6.37501 0.865046 6.4235 1.09253 6.51773C1.32002 6.61196 1.52672 6.75007 1.70083 6.92418C1.87494 7.09829 2.01305 7.30499 2.10727 7.53247C2.2015 7.75996 2.25 8.00378 2.25 8.25001C2.25 8.54837 2.36853 8.83452 2.5795 9.0455C2.79048 9.25648 3.07663 9.37501 3.375 9.37501H4.5V9.00001C4.49976 8.5833 4.63904 8.1785 4.89563 7.85016C4.26701 7.76392 3.69083 7.45314 3.27348 6.97522C2.85613 6.4973 2.62579 5.88451 2.625 5.25001V4.87501C2.62967 4.40896 2.75378 3.9519 2.98547 3.54751C2.87085 3.17775 2.83408 2.78826 2.87748 2.40359C2.92089 2.01892 3.04352 1.64742 3.23766 1.31251C3.27058 1.25549 3.31793 1.20814 3.37495 1.17523C3.43198 1.14231 3.49666 1.12499 3.5625 1.12501C3.99929 1.12409 4.43023 1.22535 4.82091 1.42069C5.21159 1.61602 5.55116 1.90003 5.8125 2.25001H6.9375C7.19884 1.90003 7.53841 1.61602 7.92909 1.42069C8.31977 1.22535 8.75071 1.12409 9.1875 1.12501C9.25334 1.12499 9.31802 1.14231 9.37505 1.17523C9.43207 1.20814 9.47942 1.25549 9.51234 1.31251C9.70651 1.64741 9.82912 2.01894 9.87245 2.40362C9.91577 2.78831 9.87887 3.1778 9.76406 3.54751C9.99619 3.95175 10.1205 4.40888 10.125 4.87501Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const GitTeaIcon: React.FC<CustomIconProps> = (props) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12.372 15.1312L8.40635 13.225C8.01573 13.0375 7.84698 12.5625 8.0376 12.1687L9.94385 8.20308C10.1314 7.81245 10.6064 7.6437 11.0001 7.83433C11.5376 8.0937 11.847 8.24058 11.847 8.24058L11.8439 4.82808L12.3657 4.82495L12.3689 8.48433C12.3689 8.48433 14.1626 9.24058 14.9657 9.73745C15.0814 9.80933 15.2845 9.94995 15.3689 10.1875C15.4345 10.3781 15.4314 10.5968 15.3376 10.7906L13.4314 14.7562C13.2376 15.1531 12.7626 15.3218 12.372 15.1312Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M19.4595 4.68123C19.3314 4.5531 19.1595 4.55623 19.1595 4.55623C19.1595 4.55623 15.497 4.76248 13.6001 4.80623C13.1845 4.8156 12.772 4.82498 12.3626 4.8281C12.3626 6.04998 12.3626 7.27185 12.3626 8.4906C12.1907 8.40935 12.0157 8.32498 11.8439 8.24373C11.8439 7.10623 11.8407 4.83123 11.8407 4.83123C10.9345 4.84373 9.05324 4.76248 9.05324 4.76248C9.05324 4.76248 4.63449 4.5406 4.15324 4.49685C3.84699 4.4781 3.45011 4.43123 2.93449 4.54373C2.66261 4.59998 1.88761 4.77498 1.25324 5.38435C-0.153014 6.63748 0.206361 8.63123 0.250111 8.93123C0.303236 9.29685 0.465736 10.3125 1.24074 11.1969C2.67199 12.95 5.75324 12.9094 5.75324 12.9094C5.75324 12.9094 6.13136 13.8125 6.70949 14.6437C7.49074 15.6781 8.29386 16.4844 9.07511 16.5812C11.0439 16.5812 14.9782 16.5781 14.9782 16.5781C14.9782 16.5781 15.3532 16.5812 15.8626 16.2562C16.3001 15.9906 16.6907 15.525 16.6907 15.525C16.6907 15.525 17.0939 15.0937 17.6564 14.1094C17.8282 13.8062 17.972 13.5125 18.097 13.2344C18.097 13.2344 19.822 9.57498 19.822 6.01248C19.7876 4.93435 19.522 4.74373 19.4595 4.68123ZM3.92511 11.0594C3.11574 10.7937 2.77199 10.475 2.77199 10.475C2.77199 10.475 2.17511 10.0562 1.87511 9.23123C1.35949 7.84998 1.83136 7.00623 1.83136 7.00623C1.83136 7.00623 2.09386 6.3031 3.03449 6.06873C3.46574 5.9531 4.00324 5.97185 4.00324 5.97185C4.00324 5.97185 4.22511 7.8281 4.49386 8.9156C4.71886 9.8281 5.26886 11.3437 5.26886 11.3437C5.26886 11.3437 4.45324 11.2469 3.92511 11.0594ZM13.3095 14.4219C13.3095 14.4219 13.1189 14.875 12.697 14.9031C12.5157 14.9156 12.3751 14.8656 12.3751 14.8656C12.3751 14.8656 12.3657 14.8625 12.2095 14.8L8.68136 13.0812C8.68136 13.0812 8.34074 12.9031 8.28136 12.5937C8.21261 12.3406 8.36574 12.0281 8.36574 12.0281L10.0626 8.53123C10.0626 8.53123 10.2126 8.2281 10.4439 8.12498C10.4626 8.1156 10.5157 8.09373 10.5845 8.0781C10.8376 8.01248 11.147 8.1656 11.147 8.1656L14.6064 9.84373C14.6064 9.84373 15.0001 10.0219 15.0845 10.35C15.1439 10.5812 15.0689 10.7875 15.0282 10.8875C14.8314 11.3687 13.3095 14.4219 13.3095 14.4219Z"
|
||||
fill="#609926"
|
||||
/>
|
||||
<path
|
||||
d="M10.2126 11.8781C9.95632 11.8813 9.73132 12.0594 9.67195 12.3094C9.61257 12.5594 9.73445 12.8187 9.95632 12.9344C10.1969 13.0594 10.5032 12.9906 10.6657 12.7656C10.8251 12.5437 10.8001 12.2375 10.6094 12.0437L11.3594 10.5094C11.4063 10.5125 11.4751 10.5156 11.5532 10.4937C11.6813 10.4656 11.7751 10.3813 11.7751 10.3813C11.9063 10.4375 12.0438 10.5 12.1876 10.5719C12.3376 10.6469 12.4782 10.725 12.6063 10.8C12.6344 10.8156 12.6626 10.8344 12.6938 10.8594C12.7438 10.9 12.8001 10.9562 12.8407 11.0312C12.9001 11.2031 12.7813 11.4969 12.7813 11.4969C12.7094 11.7344 12.2063 12.7656 12.2063 12.7656C11.9532 12.7594 11.7282 12.9219 11.6532 13.1562C11.5719 13.4094 11.6876 13.6969 11.9313 13.8219C12.1751 13.9469 12.4751 13.875 12.6344 13.6562C12.7907 13.4437 12.7782 13.1469 12.6001 12.95C12.6594 12.8344 12.7157 12.7188 12.7751 12.5969C12.9313 12.2719 13.1969 11.6469 13.1969 11.6469C13.2251 11.5938 13.3751 11.325 13.2813 10.9812C13.2032 10.625 12.8876 10.4594 12.8876 10.4594C12.5063 10.2125 11.9751 9.98438 11.9751 9.98438C11.9751 9.98438 11.9751 9.85625 11.9407 9.7625C11.9063 9.66562 11.8532 9.60313 11.8188 9.56563C11.9657 9.2625 12.1126 8.9625 12.2594 8.65938C12.1313 8.59688 12.0063 8.53437 11.8782 8.46875C11.7282 8.775 11.5751 9.08438 11.4251 9.39062C11.2157 9.3875 11.0219 9.5 10.9219 9.68437C10.8157 9.88125 10.8376 10.125 10.9813 10.3031C10.7251 10.8281 10.4688 11.3531 10.2126 11.8781Z"
|
||||
fill="#609926"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const GithubIcon: React.FC<CustomIconProps> = (props) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.9702 0.206024C4.45694 0.206024 0 4.69582 0 10.2503C0 14.6903 2.85571 18.4487 6.81735 19.7789C7.31265 19.8789 7.49408 19.5628 7.49408 19.2968C7.49408 19.064 7.47776 18.2658 7.47776 17.4342C4.70429 18.033 4.12674 16.2368 4.12674 16.2368C3.68102 15.0728 3.02061 14.7736 3.02061 14.7736C2.11286 14.1583 3.08673 14.1583 3.08673 14.1583C4.09367 14.2248 4.62204 15.1893 4.62204 15.1893C5.51327 16.7191 6.94939 16.2868 7.52714 16.0207C7.60959 15.3721 7.87388 14.9232 8.15449 14.6738C5.94245 14.4409 3.6151 13.5762 3.6151 9.71807C3.6151 8.62051 4.01102 7.72256 4.63837 7.02419C4.53939 6.7748 4.19265 5.74358 4.73755 4.36337C4.73755 4.36337 5.57939 4.09725 7.47755 5.39439C8.29022 5.17453 9.12832 5.06268 9.9702 5.06174C10.812 5.06174 11.6702 5.17827 12.4627 5.39439C14.361 4.09725 15.2029 4.36337 15.2029 4.36337C15.7478 5.74358 15.4008 6.7748 15.3018 7.02419C15.9457 7.72256 16.3253 8.62051 16.3253 9.71807C16.3253 13.5762 13.998 14.4242 11.7694 14.6738C12.1327 14.9897 12.4461 15.5883 12.4461 16.5362C12.4461 17.8832 12.4298 18.9642 12.4298 19.2966C12.4298 19.5628 12.6114 19.8789 13.1065 19.7791C17.0682 18.4485 19.9239 14.6903 19.9239 10.2503C19.9402 4.69582 15.4669 0.206024 9.9702 0.206024Z"
|
||||
fill="#0B1D2E"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const HorizontalDotIcon = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M10.9375 10C10.9375 10.1854 10.8825 10.3667 10.7795 10.5208C10.6765 10.675 10.5301 10.7952 10.3588 10.8661C10.1875 10.9371 9.99896 10.9557 9.8171 10.9195C9.63525 10.8833 9.4682 10.794 9.33709 10.6629C9.20598 10.5318 9.11669 10.3648 9.08051 10.1829C9.04434 10.001 9.06291 9.81254 9.13386 9.64123C9.20482 9.46993 9.32498 9.32351 9.47915 9.2205C9.63332 9.11748 9.81458 9.0625 10 9.0625C10.2486 9.0625 10.4871 9.16127 10.6629 9.33709C10.8387 9.5129 10.9375 9.75136 10.9375 10ZM15.3125 9.0625C15.1271 9.0625 14.9458 9.11748 14.7917 9.2205C14.6375 9.32351 14.5173 9.46993 14.4464 9.64123C14.3754 9.81254 14.3568 10.001 14.393 10.1829C14.4292 10.3648 14.5185 10.5318 14.6496 10.6629C14.7807 10.794 14.9477 10.8833 15.1296 10.9195C15.3115 10.9557 15.5 10.9371 15.6713 10.8661C15.8426 10.7952 15.989 10.675 16.092 10.5208C16.195 10.3667 16.25 10.1854 16.25 10C16.25 9.75136 16.1512 9.5129 15.9754 9.33709C15.7996 9.16127 15.5611 9.0625 15.3125 9.0625ZM4.6875 9.0625C4.50208 9.0625 4.32082 9.11748 4.16665 9.2205C4.01248 9.32351 3.89232 9.46993 3.82136 9.64123C3.75041 9.81254 3.73184 10.001 3.76801 10.1829C3.80419 10.3648 3.89348 10.5318 4.02459 10.6629C4.1557 10.794 4.32275 10.8833 4.5046 10.9195C4.68646 10.9557 4.87496 10.9371 5.04627 10.8661C5.21757 10.7952 5.36399 10.675 5.467 10.5208C5.57002 10.3667 5.625 10.1854 5.625 10C5.625 9.75136 5.52623 9.5129 5.35041 9.33709C5.1746 9.16127 4.93614 9.0625 4.6875 9.0625Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const LockIcon: React.FC<CustomIconProps> = (props) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M7.99984 1.5C6.15889 1.5 4.6665 2.99238 4.6665 4.83333V6H4.49984C3.48732 6 2.6665 6.82081 2.6665 7.83333V12.8333C2.6665 13.8459 3.48732 14.6667 4.49984 14.6667H11.4998C12.5124 14.6667 13.3332 13.8459 13.3332 12.8333V7.83333C13.3332 6.82081 12.5124 6 11.4998 6H11.3332V4.83333C11.3332 2.99238 9.84079 1.5 7.99984 1.5ZM10.3332 6V4.83333C10.3332 3.54467 9.2885 2.5 7.99984 2.5C6.71117 2.5 5.6665 3.54467 5.6665 4.83333V6H10.3332ZM7.99984 8.83333C8.27598 8.83333 8.49984 9.05719 8.49984 9.33333V11.3333C8.49984 11.6095 8.27598 11.8333 7.99984 11.8333C7.72369 11.8333 7.49984 11.6095 7.49984 11.3333V9.33333C7.49984 9.05719 7.72369 8.83333 7.99984 8.83333Z"
|
||||
fill="#EA580C"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const PencilIcon = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M8.625 3.125C8.90114 3.125 9.125 2.90114 9.125 2.625C9.125 2.34886 8.90114 2.125 8.625 2.125V3.125ZM15.875 9.375C15.875 9.09886 15.6511 8.875 15.375 8.875C15.0989 8.875 14.875 9.09886 14.875 9.375H15.875ZM3.44401 15.2115L3.67101 14.766L3.67101 14.766L3.44401 15.2115ZM2.78849 14.556L3.23399 14.329H3.23399L2.78849 14.556ZM14.556 15.2115L14.329 14.766L14.329 14.766L14.556 15.2115ZM15.2115 14.556L14.766 14.329L14.766 14.329L15.2115 14.556ZM2.78849 3.44401L2.34299 3.21702L2.78849 3.44401ZM3.44401 2.78849L3.21702 2.34299V2.34299L3.44401 2.78849ZM9.375 6.375L9.02145 6.02145C8.92768 6.11521 8.875 6.24239 8.875 6.375H9.375ZM9.375 8.625H8.875C8.875 8.90114 9.09886 9.125 9.375 9.125V8.625ZM11.625 8.625V9.125C11.7576 9.125 11.8848 9.07232 11.9786 8.97855L11.625 8.625ZM13.3768 2.37316L13.7304 2.72671V2.72671L13.3768 2.37316ZM15.4982 2.37316L15.8517 2.01961L15.8517 2.01961L15.4982 2.37316ZM15.6268 2.50184L15.2733 2.85539V2.85539L15.6268 2.50184ZM15.6268 4.62316L15.9804 4.97671V4.97671L15.6268 4.62316ZM12.975 14.875H5.025V15.875H12.975V14.875ZM3.125 12.975V5.025H2.125V12.975H3.125ZM5.025 3.125H8.625V2.125H5.025V3.125ZM14.875 9.375V12.975H15.875V9.375H14.875ZM5.025 14.875C4.59671 14.875 4.30556 14.8746 4.08052 14.8562C3.86131 14.8383 3.74921 14.8059 3.67101 14.766L3.21702 15.657C3.45969 15.7807 3.71804 15.8299 3.99909 15.8529C4.2743 15.8754 4.61321 15.875 5.025 15.875V14.875ZM2.125 12.975C2.125 13.3868 2.12461 13.7257 2.1471 14.0009C2.17006 14.282 2.21934 14.5403 2.34299 14.783L3.23399 14.329C3.19415 14.2508 3.16169 14.1387 3.14378 13.9195C3.12539 13.6944 3.125 13.4033 3.125 12.975H2.125ZM3.67101 14.766C3.48285 14.6701 3.32987 14.5172 3.23399 14.329L2.34299 14.783C2.53473 15.1593 2.8407 15.4653 3.21702 15.657L3.67101 14.766ZM12.975 15.875C13.3868 15.875 13.7257 15.8754 14.0009 15.8529C14.282 15.8299 14.5403 15.7807 14.783 15.657L14.329 14.766C14.2508 14.8059 14.1387 14.8383 13.9195 14.8562C13.6944 14.8746 13.4033 14.875 12.975 14.875V15.875ZM14.875 12.975C14.875 13.4033 14.8746 13.6944 14.8562 13.9195C14.8383 14.1387 14.8059 14.2508 14.766 14.329L15.657 14.783C15.7807 14.5403 15.8299 14.282 15.8529 14.0009C15.8754 13.7257 15.875 13.3868 15.875 12.975H14.875ZM14.783 15.657C15.1593 15.4653 15.4653 15.1593 15.657 14.783L14.766 14.329C14.6701 14.5172 14.5172 14.6701 14.329 14.766L14.783 15.657ZM3.125 5.025C3.125 4.59671 3.12539 4.30556 3.14378 4.08052C3.16169 3.86131 3.19415 3.74921 3.23399 3.67101L2.34299 3.21702C2.21934 3.45969 2.17006 3.71804 2.1471 3.99909C2.12461 4.2743 2.125 4.61321 2.125 5.025H3.125ZM5.025 2.125C4.61321 2.125 4.2743 2.12461 3.99909 2.1471C3.71804 2.17006 3.45969 2.21934 3.21702 2.34299L3.67101 3.23399C3.74921 3.19415 3.86131 3.16169 4.08052 3.14378C4.30556 3.12539 4.59671 3.125 5.025 3.125V2.125ZM3.23399 3.67101C3.32987 3.48285 3.48285 3.32987 3.67101 3.23399L3.21702 2.34299C2.84069 2.53473 2.53473 2.84069 2.34299 3.21702L3.23399 3.67101ZM8.875 6.375V8.625H9.875V6.375H8.875ZM9.375 9.125H11.625V8.125H9.375V9.125ZM9.72855 6.72855L13.7304 2.72671L13.0233 2.01961L9.02145 6.02145L9.72855 6.72855ZM15.1446 2.72671L15.2733 2.85539L15.9804 2.14829L15.8517 2.01961L15.1446 2.72671ZM15.2733 4.26961L11.2714 8.27145L11.9786 8.97855L15.9804 4.97671L15.2733 4.26961ZM15.2733 2.85539C15.6638 3.24592 15.6638 3.87908 15.2733 4.26961L15.9804 4.97671C16.7614 4.19566 16.7614 2.92933 15.9804 2.14829L15.2733 2.85539ZM13.7304 2.72671C14.1209 2.33619 14.7541 2.33619 15.1446 2.72671L15.8517 2.01961C15.0707 1.23856 13.8043 1.23856 13.0233 2.01961L13.7304 2.72671Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { CustomIcon, CustomIconProps } from './CustomIcon';
|
||||
|
||||
export const WarningDiamondIcon = (props: CustomIconProps) => {
|
||||
return (
|
||||
<CustomIcon
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M18.3851 9.11879L10.8812 1.6141C10.647 1.38129 10.3302 1.25061 9.99999 1.25061C9.66976 1.25061 9.35294 1.38129 9.11874 1.6141L1.61874 9.11879C1.38593 9.35299 1.25525 9.66981 1.25525 10C1.25525 10.3303 1.38593 10.6471 1.61874 10.8813L9.12265 18.386C9.35685 18.6188 9.67367 18.7495 10.0039 18.7495C10.3341 18.7495 10.6509 18.6188 10.8851 18.386L18.3891 10.8813C18.6219 10.6471 18.7525 10.3303 18.7525 10C18.7525 9.66981 18.6219 9.35299 18.3891 9.11879H18.3851ZM9.37499 6.25004C9.37499 6.08428 9.44084 5.92531 9.55805 5.8081C9.67526 5.69089 9.83423 5.62504 9.99999 5.62504C10.1658 5.62504 10.3247 5.69089 10.4419 5.8081C10.5591 5.92531 10.625 6.08428 10.625 6.25004V10.625C10.625 10.7908 10.5591 10.9498 10.4419 11.067C10.3247 11.1842 10.1658 11.25 9.99999 11.25C9.83423 11.25 9.67526 11.1842 9.55805 11.067C9.44084 10.9498 9.37499 10.7908 9.37499 10.625V6.25004ZM9.99999 14.375C9.81457 14.375 9.63332 14.3201 9.47914 14.217C9.32497 14.114 9.20481 13.9676 9.13385 13.7963C9.0629 13.625 9.04433 13.4365 9.0805 13.2546C9.11668 13.0728 9.20597 12.9057 9.33708 12.7746C9.46819 12.6435 9.63524 12.5542 9.81709 12.5181C9.99895 12.4819 10.1875 12.5004 10.3588 12.5714C10.5301 12.6424 10.6765 12.7625 10.7795 12.9167C10.8825 13.0709 10.9375 13.2521 10.9375 13.4375C10.9375 13.6862 10.8387 13.9246 10.6629 14.1005C10.4871 14.2763 10.2486 14.375 9.99999 14.375Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</CustomIcon>
|
||||
);
|
||||
};
|
||||
@@ -22,3 +22,14 @@ export * from './EllipseIcon';
|
||||
export * from './EllipsesIcon';
|
||||
export * from './SnowballIcon';
|
||||
export * from './NotificationBellIcon';
|
||||
export * from './GithubIcon';
|
||||
export * from './GitTeaIcon';
|
||||
export * from './LockIcon';
|
||||
export * from './PencilIcon';
|
||||
export * from './CheckRadioIcon';
|
||||
export * from './ChevronDownIcon';
|
||||
export * from './BranchIcon';
|
||||
export * from './GitHubLogo';
|
||||
export * from './ClockIcon';
|
||||
export * from './HorizontalDotIcon';
|
||||
export * from './WarningDiamondIcon';
|
||||
|
||||
@@ -85,7 +85,10 @@ export const DatePicker = ({
|
||||
/>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content onInteractOutside={() => setOpen(false)}>
|
||||
<Popover.Content
|
||||
className="z-10"
|
||||
onInteractOutside={() => setOpen(false)}
|
||||
>
|
||||
<Calendar
|
||||
{...calendarProps}
|
||||
selectRange={selectRange}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const headingTheme = tv({
|
||||
base: ['text-elements-high-em', 'font-display', 'font-normal'],
|
||||
});
|
||||
|
||||
export type HeadingVariants = VariantProps<typeof headingTheme>;
|
||||
@@ -0,0 +1,30 @@
|
||||
import React, { type PropsWithChildren } from 'react';
|
||||
|
||||
import { headingTheme, type HeadingVariants } from './Heading.theme';
|
||||
import { PolymorphicProps } from 'types/common';
|
||||
|
||||
export type HeadingElementType = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
|
||||
export type HeadingProps<Element extends HeadingElementType> =
|
||||
PropsWithChildren<PolymorphicProps<Element, HeadingVariants>>;
|
||||
|
||||
export const Heading = <Element extends HeadingElementType>({
|
||||
children,
|
||||
as,
|
||||
className,
|
||||
...props
|
||||
}: HeadingProps<Element>) => {
|
||||
// Component is the element that will be rendered
|
||||
const Component = as ?? 'h1';
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
className={headingTheme({
|
||||
className,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Heading';
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { ComponentPropsWithoutRef, ReactNode } from 'react';
|
||||
import { cn } from 'utils/classnames';
|
||||
|
||||
interface IconWithFrameProps extends ComponentPropsWithoutRef<'div'> {
|
||||
icon: ReactNode;
|
||||
hasHighlight?: boolean;
|
||||
}
|
||||
|
||||
export const IconWithFrame = ({
|
||||
icon,
|
||||
className,
|
||||
hasHighlight = true,
|
||||
...props
|
||||
}: IconWithFrameProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'relative justify-center items-center gap-2.5 inline-flex',
|
||||
'w-16 h-16 rounded-2xl shadow-inner',
|
||||
'border border-b-[3px] border-border-interactive border-opacity-10',
|
||||
'bg-controls-secondary',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{hasHighlight && (
|
||||
<div className="bottom-0 absolute w-[37px] h-px bg-gradient-to-r from-gray-0/0 via-gray-0/50 to-gray-0/0" />
|
||||
)}
|
||||
{icon}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './IconWithFrame';
|
||||
@@ -0,0 +1,153 @@
|
||||
import { VariantProps, tv } from 'tailwind-variants';
|
||||
|
||||
export const selectTheme = tv({
|
||||
slots: {
|
||||
container: ['flex', 'flex-col', 'relative', 'gap-2'],
|
||||
label: ['text-sm', 'text-elements-high-em'],
|
||||
description: ['text-xs', 'text-elements-low-em'],
|
||||
inputWrapper: [
|
||||
'relative',
|
||||
'flex',
|
||||
'flex-wrap',
|
||||
'gap-1',
|
||||
'min-w-[200px]',
|
||||
'w-full',
|
||||
'rounded-lg',
|
||||
'bg-transparent',
|
||||
'text-elements-mid-em',
|
||||
'shadow-sm',
|
||||
'border',
|
||||
'border-border-interactive',
|
||||
'focus-ring',
|
||||
'disabled:shadow-none',
|
||||
'disabled:border-none',
|
||||
],
|
||||
input: ['outline-none'],
|
||||
iconContainer: [
|
||||
'absolute',
|
||||
'inset-y-0',
|
||||
'flex',
|
||||
'items-center',
|
||||
'gap-2',
|
||||
'z-10',
|
||||
'cursor-pointer',
|
||||
],
|
||||
icon: ['text-elements-mid-em'],
|
||||
helperIcon: [],
|
||||
helperText: ['flex', 'gap-2', 'items-center', 'text-elements-low-em'],
|
||||
popover: [
|
||||
'z-20',
|
||||
'absolute',
|
||||
'px-1',
|
||||
'py-1',
|
||||
'flex-col',
|
||||
'gap-0.5',
|
||||
'min-w-full',
|
||||
'bg-surface-floating',
|
||||
'shadow-dropdown',
|
||||
'w-auto',
|
||||
'max-h-60',
|
||||
'overflow-auto',
|
||||
'border',
|
||||
'border-gray-200',
|
||||
'rounded-xl',
|
||||
],
|
||||
},
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal: {
|
||||
container: [],
|
||||
},
|
||||
vertical: {
|
||||
container: [],
|
||||
},
|
||||
},
|
||||
variant: {
|
||||
default: {
|
||||
container: [],
|
||||
},
|
||||
danger: {
|
||||
container: [],
|
||||
},
|
||||
},
|
||||
error: {
|
||||
true: {
|
||||
inputWrapper: [
|
||||
'outline',
|
||||
'outline-offset-0',
|
||||
'outline-border-danger',
|
||||
'shadow-none',
|
||||
'focus:outline-border-danger',
|
||||
],
|
||||
helperText: ['text-elements-danger'],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
md: {
|
||||
container: ['min-h-11'],
|
||||
inputWrapper: ['min-h-11', 'text-sm', 'pl-4', 'pr-4', 'py-1'],
|
||||
icon: ['h-[18px]', 'w-[18px]'],
|
||||
helperText: 'text-sm',
|
||||
helperIcon: ['h-5', 'w-5'],
|
||||
popover: ['mt-12'],
|
||||
},
|
||||
sm: {
|
||||
container: ['min-h-8'],
|
||||
inputWrapper: ['min-h-8', 'text-xs', 'pl-3', 'pr-3', 'py-0.5'],
|
||||
icon: ['h-4', 'w-4'],
|
||||
helperText: 'text-xs',
|
||||
helperIcon: ['h-4', 'w-4'],
|
||||
popover: ['mt-9'],
|
||||
},
|
||||
},
|
||||
isOpen: {
|
||||
true: {
|
||||
popover: ['flex'],
|
||||
},
|
||||
false: {
|
||||
popover: ['hidden'],
|
||||
},
|
||||
},
|
||||
hasValue: {
|
||||
true: '',
|
||||
},
|
||||
searchable: {
|
||||
true: '',
|
||||
false: {
|
||||
input: ['cursor-pointer'],
|
||||
},
|
||||
},
|
||||
hideValues: {
|
||||
true: {
|
||||
input: ['placeholder:text-elements-mid-em'],
|
||||
},
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
size: 'md',
|
||||
hasValue: true,
|
||||
class: {
|
||||
inputWrapper: ['pl-1'],
|
||||
},
|
||||
},
|
||||
{
|
||||
size: 'sm',
|
||||
hasValue: true,
|
||||
class: {
|
||||
inputWrapper: ['pl-0.5'],
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultVariants: {
|
||||
orientation: 'horizontal',
|
||||
variant: 'default',
|
||||
size: 'md',
|
||||
state: 'default',
|
||||
error: false,
|
||||
isOpen: false,
|
||||
hasValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
export type SelectTheme = VariantProps<typeof selectTheme>;
|
||||
@@ -0,0 +1,438 @@
|
||||
import React, {
|
||||
ReactNode,
|
||||
useState,
|
||||
ComponentPropsWithoutRef,
|
||||
useMemo,
|
||||
useCallback,
|
||||
MouseEvent,
|
||||
useRef,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { useMultipleSelection, useCombobox } from 'downshift';
|
||||
import { SelectTheme, selectTheme } from './Select.theme';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
CrossIcon,
|
||||
WarningIcon,
|
||||
} from 'components/shared/CustomIcon';
|
||||
import { cloneIcon } from 'utils/cloneIcon';
|
||||
import { cn } from 'utils/classnames';
|
||||
import { SelectItem, EmptySelectItem } from './SelectItem';
|
||||
import { SelectValue } from './SelectValue';
|
||||
|
||||
export type SelectOption = {
|
||||
/**
|
||||
* The value of the option
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* The label of the option
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The description of the option
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Custom left icon for the option
|
||||
*/
|
||||
leftIcon?: ReactNode;
|
||||
/**
|
||||
* Custom right icon for the option
|
||||
*/
|
||||
rightIcon?: ReactNode;
|
||||
/**
|
||||
* Whether the option is disabled
|
||||
*/
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The orientation of the select
|
||||
*/
|
||||
export type SelectOrientation = 'horizontal' | 'vertical';
|
||||
|
||||
interface SelectProps
|
||||
extends Omit<
|
||||
ComponentPropsWithoutRef<'input'>,
|
||||
'size' | 'value' | 'onChange'
|
||||
>,
|
||||
SelectTheme {
|
||||
/**
|
||||
* The options of the select
|
||||
*/
|
||||
options: SelectOption[];
|
||||
/**
|
||||
* The label of the select
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The description of the select
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* Wheter the select is multiple or not
|
||||
*/
|
||||
multiple?: boolean;
|
||||
/**
|
||||
* Wheter the select is searchable or not
|
||||
*/
|
||||
searchable?: boolean;
|
||||
/**
|
||||
* Wheter the select is clearable or not
|
||||
*/
|
||||
clearable?: boolean;
|
||||
/**
|
||||
* Custom left icon for the select
|
||||
*/
|
||||
leftIcon?: ReactNode;
|
||||
/**
|
||||
* Custom right icon for the select
|
||||
*/
|
||||
rightIcon?: ReactNode;
|
||||
/**
|
||||
* The helper text of the select
|
||||
*/
|
||||
helperText?: string;
|
||||
/**
|
||||
* Show the values of the select if it's multiple
|
||||
*/
|
||||
hideValues?: boolean;
|
||||
/**
|
||||
* The value of the select
|
||||
*/
|
||||
value?: SelectOption | SelectOption[];
|
||||
/**
|
||||
* Callback function when reset the select
|
||||
*/
|
||||
onClear?: () => void;
|
||||
/**
|
||||
* Callback function when the value of the select changes
|
||||
*/
|
||||
onChange?: (value: SelectOption | SelectOption[]) => void;
|
||||
}
|
||||
|
||||
export const Select = ({
|
||||
options,
|
||||
multiple = false,
|
||||
searchable = false,
|
||||
clearable,
|
||||
size,
|
||||
error,
|
||||
orientation = 'horizontal',
|
||||
variant,
|
||||
label,
|
||||
description,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
helperText,
|
||||
hideValues = false,
|
||||
placeholder: placeholderProp = 'Select an option',
|
||||
value,
|
||||
onChange,
|
||||
onClear,
|
||||
}: SelectProps) => {
|
||||
const theme = selectTheme({ size, error, variant, orientation });
|
||||
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [selectedItem, setSelectedItem] = useState<SelectOption | null>(null);
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [dropdownPosition, setDropdownPosition] = useState<'top' | 'bottom'>(
|
||||
'bottom',
|
||||
);
|
||||
const popoverRef = useRef(null); // Ref for the popover
|
||||
const inputWrapperRef = useRef(null); // Ref for the input wrapper
|
||||
|
||||
// Calculate and update popover position
|
||||
useEffect(() => {
|
||||
if (dropdownOpen && popoverRef.current && inputWrapperRef.current) {
|
||||
const popover = popoverRef.current;
|
||||
// @ts-expect-error – we know it's not null lol
|
||||
const input = inputWrapperRef.current.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - input.bottom;
|
||||
const spaceAbove = input.top;
|
||||
// @ts-expect-error – we know it's not null lol
|
||||
const popoverHeight = popover.offsetHeight;
|
||||
|
||||
// Determine if there's enough space below
|
||||
if (spaceBelow >= popoverHeight) {
|
||||
setDropdownPosition('bottom');
|
||||
} else if (spaceAbove >= popoverHeight) {
|
||||
setDropdownPosition('top');
|
||||
} else {
|
||||
// Default to bottom if neither has enough space, but you could also set logic to choose the side with more space
|
||||
setDropdownPosition('bottom');
|
||||
}
|
||||
}
|
||||
}, [dropdownOpen]); // Re-calculate whenever the dropdown is opened
|
||||
|
||||
useEffect(() => {
|
||||
// If multiple selection is enabled, ensure the internal state is an array
|
||||
if (multiple) {
|
||||
if (Array.isArray(value)) {
|
||||
// Directly use the provided array
|
||||
setSelectedItems(value);
|
||||
} else {
|
||||
// Reset or set to empty array if the value is not an array
|
||||
setSelectedItems([]);
|
||||
}
|
||||
} else {
|
||||
// For single selection, directly set the selected item
|
||||
setSelectedItem(value as SelectOption);
|
||||
}
|
||||
}, [value, multiple]);
|
||||
|
||||
const handleSelectedItemChange = (selectedItem: SelectOption | null) => {
|
||||
setSelectedItem(selectedItem);
|
||||
setInputValue(selectedItem ? selectedItem.label : '');
|
||||
onChange?.(selectedItem as SelectOption);
|
||||
};
|
||||
|
||||
const {
|
||||
getSelectedItemProps,
|
||||
getDropdownProps,
|
||||
addSelectedItem,
|
||||
removeSelectedItem,
|
||||
selectedItems,
|
||||
setSelectedItems,
|
||||
reset,
|
||||
} = useMultipleSelection<SelectOption>({
|
||||
onSelectedItemsChange: multiple
|
||||
? undefined
|
||||
: ({ selectedItems }) => {
|
||||
handleSelectedItemChange(selectedItems?.[0] || null);
|
||||
},
|
||||
});
|
||||
|
||||
const filteredItems = useMemo(() => {
|
||||
// Show all items if the dropdown is not multiple and not searchable
|
||||
if (!multiple && !searchable) return options;
|
||||
// Show only the items that match the input value
|
||||
if (!multiple && searchable) {
|
||||
return options.filter((item) =>
|
||||
item.label.toLowerCase().includes(inputValue.toLowerCase()),
|
||||
);
|
||||
}
|
||||
// Show only the items that match the input value and are not already selected
|
||||
return options.filter(
|
||||
(item) =>
|
||||
item.label.toLowerCase().includes(inputValue.toLowerCase()) &&
|
||||
!(
|
||||
multiple && searchable // If the dropdown is multiple and searchable, show filtered items
|
||||
? selectedItems
|
||||
: []
|
||||
).includes(item),
|
||||
);
|
||||
}, [options, inputValue, selectedItem, selectedItems, multiple, searchable]);
|
||||
|
||||
const {
|
||||
isOpen,
|
||||
getMenuProps,
|
||||
getInputProps,
|
||||
highlightedIndex,
|
||||
getItemProps,
|
||||
openMenu,
|
||||
} = useCombobox({
|
||||
items: filteredItems,
|
||||
// @ts-expect-error – there are two params but we don't need the second one
|
||||
isItemDisabled: (item) => item.disabled,
|
||||
onInputValueChange: ({ inputValue = '' }) => setInputValue(inputValue),
|
||||
onSelectedItemChange: ({ selectedItem }) => {
|
||||
if (!multiple && selectedItem) {
|
||||
handleSelectedItemChange(selectedItem);
|
||||
} else if (multiple && selectedItem) {
|
||||
// If the item is not already selected, add it to the selected items
|
||||
if (!selectedItems.includes(selectedItem)) {
|
||||
addSelectedItem(selectedItem);
|
||||
// Callback for `onChange`
|
||||
const newSelectedItems = [...selectedItems, selectedItem];
|
||||
onChange?.(newSelectedItems);
|
||||
} else {
|
||||
// If the item is already selected, remove it from the selected items
|
||||
removeSelectedItem(selectedItem);
|
||||
// Callback for `onChange`
|
||||
const newSelectedItems = selectedItems.filter(
|
||||
(item) => selectedItem !== item,
|
||||
);
|
||||
onChange?.(newSelectedItems);
|
||||
}
|
||||
setInputValue('');
|
||||
}
|
||||
},
|
||||
onIsOpenChange: ({ isOpen }) => {
|
||||
setDropdownOpen(isOpen ?? false);
|
||||
if (isOpen && !multiple && selectedItem && searchable) {
|
||||
setInputValue('');
|
||||
}
|
||||
},
|
||||
selectedItem: multiple ? null : selectedItem,
|
||||
// TODO: Make the input value empty when the dropdown is open, has a value, it is not multiple, and searchable
|
||||
itemToString: (item) => (item && !multiple ? item.label : ''),
|
||||
});
|
||||
|
||||
const isSelected = useCallback(
|
||||
(item: SelectOption) =>
|
||||
multiple ? selectedItems.includes(item) : selectedItem === item,
|
||||
[selectedItems, selectedItem, multiple],
|
||||
);
|
||||
|
||||
const handleClear = (e: MouseEvent<SVGSVGElement, globalThis.MouseEvent>) => {
|
||||
e.stopPropagation();
|
||||
reset();
|
||||
setSelectedItem(null);
|
||||
setInputValue('');
|
||||
onClear?.();
|
||||
};
|
||||
|
||||
const renderLabels = useMemo(
|
||||
() => (
|
||||
<div className="space-y-1">
|
||||
<p className={theme.label()}>{label}</p>
|
||||
<p className={theme.description()}>{description}</p>
|
||||
</div>
|
||||
),
|
||||
[theme, label, description],
|
||||
);
|
||||
|
||||
const renderLeftIcon = useMemo(() => {
|
||||
return (
|
||||
<div className={theme.iconContainer({ class: 'left-0 pl-4' })}>
|
||||
{cloneIcon(leftIcon, { className: theme.icon(), 'aria-hidden': true })}
|
||||
</div>
|
||||
);
|
||||
}, [cloneIcon, theme, leftIcon]);
|
||||
|
||||
const renderRightIcon = useMemo(() => {
|
||||
return (
|
||||
<div className={theme.iconContainer({ class: 'pr-4 right-0' })}>
|
||||
{clearable && (selectedItems.length > 0 || selectedItem) && (
|
||||
<CrossIcon
|
||||
className={theme.icon({ class: 'h-4 w-4' })}
|
||||
onClick={handleClear}
|
||||
/>
|
||||
)}
|
||||
{rightIcon ? (
|
||||
cloneIcon(rightIcon, { className: theme.icon(), 'aria-hidden': true })
|
||||
) : (
|
||||
<ChevronDownIcon className={theme.icon()} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}, [cloneIcon, theme, rightIcon]);
|
||||
|
||||
const renderHelperText = useMemo(
|
||||
() => (
|
||||
<div className={theme.helperText()}>
|
||||
{error &&
|
||||
cloneIcon(<WarningIcon className={theme.helperIcon()} />, {
|
||||
'aria-hidden': true,
|
||||
})}
|
||||
<p>{helperText}</p>
|
||||
</div>
|
||||
),
|
||||
[cloneIcon, error, theme, helperText],
|
||||
);
|
||||
|
||||
const isMultipleHasValue = multiple && selectedItems.length > 0;
|
||||
const isMultipleHasValueButNotSearchable =
|
||||
multiple && !searchable && selectedItems.length > 0;
|
||||
const displayPlaceholder = useMemo(() => {
|
||||
if (hideValues && isMultipleHasValue) {
|
||||
return `${selectedItems.length} selected`;
|
||||
}
|
||||
if (isMultipleHasValueButNotSearchable) {
|
||||
return '';
|
||||
}
|
||||
return placeholderProp;
|
||||
}, [hideValues, multiple, selectedItems.length, placeholderProp]);
|
||||
|
||||
return (
|
||||
<div className={theme.container()}>
|
||||
{/* Label & description */}
|
||||
{renderLabels}
|
||||
|
||||
{/* Input */}
|
||||
<div
|
||||
ref={inputWrapperRef}
|
||||
className={theme.inputWrapper({
|
||||
hasValue: isMultipleHasValue && !hideValues,
|
||||
})}
|
||||
onClick={() => !dropdownOpen && openMenu()}
|
||||
>
|
||||
{/* Left icon */}
|
||||
{leftIcon && renderLeftIcon}
|
||||
|
||||
{/* Multiple input values */}
|
||||
{isMultipleHasValue &&
|
||||
!hideValues &&
|
||||
selectedItems.map((item, index) => (
|
||||
<SelectValue
|
||||
key={`selected-item-${index}`}
|
||||
{...getSelectedItemProps({ selectedItem: item, index })}
|
||||
option={item}
|
||||
size={size}
|
||||
onDelete={removeSelectedItem}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Single input value or searchable area */}
|
||||
<input
|
||||
{...getInputProps(getDropdownProps())}
|
||||
placeholder={displayPlaceholder}
|
||||
// Control readOnly based on searchable
|
||||
readOnly={!searchable || hideValues}
|
||||
className={cn(
|
||||
theme.input({
|
||||
searchable,
|
||||
hideValues: hideValues && selectedItems.length > 0,
|
||||
}),
|
||||
{
|
||||
// Make the input width smaller because we don't need it (not searchable)
|
||||
'w-6': isMultipleHasValueButNotSearchable && !hideValues,
|
||||
// Add margin to the X icon
|
||||
'ml-6': isMultipleHasValueButNotSearchable && clearable,
|
||||
},
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Right icon */}
|
||||
{renderRightIcon}
|
||||
</div>
|
||||
|
||||
{/* Helper text */}
|
||||
{renderHelperText}
|
||||
|
||||
{/* Popover */}
|
||||
<ul
|
||||
{...getMenuProps({ ref: popoverRef }, { suppressRefError: true })}
|
||||
id="popover"
|
||||
ref={popoverRef}
|
||||
className={cn(theme.popover({ isOpen }), {
|
||||
// Position the popover based on the dropdown position
|
||||
'top-[27.5%]': dropdownPosition === 'bottom' && !label,
|
||||
'top-[35%]': dropdownPosition === 'bottom' && label,
|
||||
'top-[42.5%]': dropdownPosition === 'bottom' && label && description,
|
||||
'bottom-[92.5%]': dropdownPosition === 'top' && !label,
|
||||
'bottom-[75%]': dropdownPosition === 'top' && label,
|
||||
'bottom-[65%]': dropdownPosition === 'top' && label && description,
|
||||
})}
|
||||
>
|
||||
{isOpen && filteredItems.length !== 0 ? (
|
||||
filteredItems.map((item, index) => (
|
||||
<SelectItem
|
||||
{...getItemProps({ item, index })}
|
||||
key={item.value}
|
||||
selected={isSelected(item)}
|
||||
option={item}
|
||||
hovered={highlightedIndex === index}
|
||||
orientation={orientation}
|
||||
variant={variant}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<EmptySelectItem />
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { tv, VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const selectItemTheme = tv({
|
||||
slots: {
|
||||
wrapper: [
|
||||
'p-2',
|
||||
'gap-3',
|
||||
'flex',
|
||||
'items-start',
|
||||
'justify-between',
|
||||
'rounded-lg',
|
||||
'group',
|
||||
'data-[disabled]:cursor-not-allowed',
|
||||
],
|
||||
icon: ['h-4.5', 'w-4.5', 'text-elements-high-em'],
|
||||
content: ['flex', 'flex-1', 'whitespace-nowrap'],
|
||||
label: [
|
||||
'text-sm',
|
||||
'text-elements-high-em',
|
||||
'tracking-[-0.006em]',
|
||||
'data-[disabled]:text-elements-disabled',
|
||||
],
|
||||
description: [
|
||||
'text-xs',
|
||||
'text-elements-low-em',
|
||||
'data-[disabled]:text-elements-disabled',
|
||||
],
|
||||
dot: ['h-1', 'w-1', 'rounded-full', 'bg-border-interactive-hovered/[0.14]'],
|
||||
},
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal: {
|
||||
wrapper: ['items-center'],
|
||||
content: ['flex-row', 'items-center', 'gap-2'],
|
||||
},
|
||||
vertical: {
|
||||
content: ['flex-col', 'gap-0.5'],
|
||||
},
|
||||
},
|
||||
variant: {
|
||||
default: {
|
||||
wrapper: [],
|
||||
},
|
||||
danger: {
|
||||
wrapper: [],
|
||||
},
|
||||
},
|
||||
active: {
|
||||
true: {
|
||||
wrapper: ['bg-base-bg-emphasized', 'data-[disabled]:bg-transparent'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export type SelectItemTheme = VariantProps<typeof selectItemTheme>;
|
||||
@@ -0,0 +1,95 @@
|
||||
import React, { forwardRef, ComponentPropsWithoutRef, useMemo } from 'react';
|
||||
import { Overwrite, UseComboboxGetItemPropsReturnValue } from 'downshift';
|
||||
import { SelectOption, SelectOrientation } from 'components/shared/Select';
|
||||
import { selectItemTheme, SelectItemTheme } from './SelectItem.theme';
|
||||
import { cloneIcon } from 'utils/cloneIcon';
|
||||
import { cn } from 'utils/classnames';
|
||||
import { CheckRadioIcon } from 'components/shared/CustomIcon';
|
||||
import { OmitCommon } from 'types/common';
|
||||
|
||||
/**
|
||||
* Represents a type that merges ComponentPropsWithoutRef<'li'> with certain exclusions.
|
||||
* @type {MergedComponentPropsWithoutRef}
|
||||
*/
|
||||
type MergedComponentPropsWithoutRef = OmitCommon<
|
||||
ComponentPropsWithoutRef<'li'>,
|
||||
Omit<
|
||||
Overwrite<UseComboboxGetItemPropsReturnValue, SelectOption[]>,
|
||||
'index' | 'item'
|
||||
>
|
||||
>;
|
||||
|
||||
export interface SelectItemProps
|
||||
extends MergedComponentPropsWithoutRef,
|
||||
SelectItemTheme {
|
||||
selected: boolean;
|
||||
option: SelectOption;
|
||||
orientation?: SelectOrientation;
|
||||
hovered?: boolean;
|
||||
}
|
||||
|
||||
const SelectItem = forwardRef<HTMLLIElement, SelectItemProps>(
|
||||
(
|
||||
{ className, selected, orientation, hovered, variant, option, ...props },
|
||||
ref,
|
||||
) => {
|
||||
const theme = selectItemTheme({ active: hovered, orientation, variant });
|
||||
|
||||
const { label, description, leftIcon, rightIcon, disabled } = option;
|
||||
|
||||
const renderRightIcon = useMemo(() => {
|
||||
if (rightIcon) {
|
||||
return cloneIcon(rightIcon, { className: theme.icon() });
|
||||
} else if (selected) {
|
||||
return (
|
||||
<CheckRadioIcon
|
||||
className={cn(theme.icon(), 'text-controls-primary')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}, [rightIcon, theme, cloneIcon, cn, selected]);
|
||||
|
||||
return (
|
||||
<li
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={theme.wrapper({ className })}
|
||||
data-disabled={disabled}
|
||||
>
|
||||
{leftIcon && cloneIcon(leftIcon, { className: theme.icon() })}
|
||||
<div className={theme.content()}>
|
||||
<p className={theme.label()} data-disabled={disabled}>
|
||||
{label}
|
||||
</p>
|
||||
{orientation === 'horizontal' && <span className={theme.dot()} />}
|
||||
{description && (
|
||||
<p className={theme.description()} data-disabled={disabled}>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{renderRightIcon}
|
||||
</li>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SelectItem.displayName = 'SelectItem';
|
||||
|
||||
/**
|
||||
* Represents an empty select item.
|
||||
* @returns {JSX.Element} - A JSX element representing the empty select item.
|
||||
*/
|
||||
export const EmptySelectItem = () => {
|
||||
const theme = selectItemTheme();
|
||||
return (
|
||||
<li className={theme.wrapper()}>
|
||||
<p className={theme.label({ class: 'text-elements-disabled' })}>
|
||||
No results found
|
||||
</p>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export { SelectItem };
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SelectItem';
|
||||
@@ -0,0 +1,30 @@
|
||||
import { tv, VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const selectValueTheme = tv({
|
||||
slots: {
|
||||
wrapper: [
|
||||
'flex',
|
||||
'items-center',
|
||||
'gap-1',
|
||||
'pl-2',
|
||||
'pr-2',
|
||||
'rounded-md',
|
||||
'text-elements-mid-em',
|
||||
'bg-base-bg-emphasized',
|
||||
'hover:bg-base-bg-emphasized/80',
|
||||
],
|
||||
icon: ['h-3.5', 'w-3.5'],
|
||||
},
|
||||
variants: {
|
||||
size: {
|
||||
sm: {
|
||||
wrapper: ['pl-1', 'pr-0.5', 'gap-0.5'],
|
||||
},
|
||||
md: {
|
||||
wrapper: ['pl-2', 'pr-1', 'gap-1'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export type SelectValueTheme = VariantProps<typeof selectValueTheme>;
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { forwardRef, ComponentPropsWithoutRef } from 'react';
|
||||
import {
|
||||
Overwrite,
|
||||
UseMultipleSelectionGetSelectedItemReturnValue,
|
||||
} from 'downshift';
|
||||
import { SelectValueTheme, selectValueTheme } from './SelectValue.theme';
|
||||
import { OmitCommon } from 'types/common';
|
||||
import { SelectOption } from 'components/shared/Select';
|
||||
import { Button } from 'components/shared/Button';
|
||||
import { CrossIcon } from 'components/shared/CustomIcon';
|
||||
|
||||
type MergedComponentPropsWithoutRef = OmitCommon<
|
||||
ComponentPropsWithoutRef<'span'>,
|
||||
Omit<
|
||||
Overwrite<UseMultipleSelectionGetSelectedItemReturnValue, SelectOption[]>,
|
||||
'index' | 'selectedItem'
|
||||
>
|
||||
>;
|
||||
|
||||
export interface SelectValueProps
|
||||
extends MergedComponentPropsWithoutRef,
|
||||
SelectValueTheme {
|
||||
/**
|
||||
* The option of the select value
|
||||
*/
|
||||
option: SelectOption;
|
||||
/**
|
||||
* The function to call when the delete button is clicked
|
||||
* @param item
|
||||
* @returns none;
|
||||
*/
|
||||
onDelete?: (item: SelectOption) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The SelectValue component is used to display the selected value of a select component
|
||||
*/
|
||||
const SelectValue = forwardRef<HTMLSpanElement, SelectValueProps>(
|
||||
({ className, size, option, onDelete, ...props }, ref) => {
|
||||
const theme = selectValueTheme();
|
||||
|
||||
return (
|
||||
<span {...props} ref={ref} className={theme.wrapper({ className, size })}>
|
||||
{option.label}
|
||||
<Button
|
||||
onClick={() => onDelete?.(option)}
|
||||
iconOnly
|
||||
variant="unstyled"
|
||||
size="xs"
|
||||
>
|
||||
<CrossIcon className={theme.icon()} />
|
||||
</Button>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
SelectValue.displayName = 'SelectValue';
|
||||
|
||||
export { SelectValue };
|
||||
@@ -0,0 +1 @@
|
||||
export * from './SelectValue';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './Select';
|
||||
@@ -23,7 +23,6 @@ export const tabsTheme = tv({
|
||||
// Vertical
|
||||
'data-[orientation=vertical]:px-3',
|
||||
'data-[orientation=vertical]:py-3',
|
||||
'data-[orientation=vertical]:min-w-[240px]',
|
||||
'data-[orientation=vertical]:w-full',
|
||||
'data-[orientation=vertical]:focus-ring',
|
||||
'data-[orientation=vertical]:rounded-xl',
|
||||
@@ -61,6 +60,7 @@ export const tabsTheme = tv({
|
||||
'data-[orientation=horizontal]:focus-ring',
|
||||
// Vertical
|
||||
'data-[orientation=vertical]:gap-2',
|
||||
'data-[orientation=vertical]:justify-start',
|
||||
],
|
||||
triggerList: [
|
||||
'flex',
|
||||
|
||||
@@ -35,20 +35,23 @@ const TabsTrigger = forwardRef<
|
||||
// Disabled focus state for horizontal tabs
|
||||
tabIndex={orientation === 'horizontal' ? -1 : undefined}
|
||||
className={triggerWrapper({ className })}
|
||||
asChild
|
||||
{...props}
|
||||
>
|
||||
{/* Need to add button in the trigger children because there's focus state inside the children */}
|
||||
<button
|
||||
data-orientation={orientation}
|
||||
// Disabled focus state for vertical tabs
|
||||
tabIndex={orientation === 'vertical' ? -1 : undefined}
|
||||
className={trigger()}
|
||||
>
|
||||
{/* Put the icon on the left of the text for veritcal tab */}
|
||||
{orientation === 'vertical' && icon}
|
||||
{children}
|
||||
{/* Put the icon on the right of the text for horizontal tab */}
|
||||
{orientation === 'horizontal' && icon}
|
||||
<button className={triggerWrapper({ className })}>
|
||||
<span
|
||||
// Disabled focus state for vertical tabs
|
||||
tabIndex={orientation === 'horizontal' ? 0 : -1}
|
||||
data-orientation={orientation}
|
||||
className={trigger()}
|
||||
>
|
||||
{/* Put the icon on the left of the text for veritcal tab */}
|
||||
{orientation === 'vertical' && icon}
|
||||
{children}
|
||||
{/* Put the icon on the right of the text for horizontal tab */}
|
||||
{orientation === 'horizontal' && icon}
|
||||
</span>
|
||||
</button>
|
||||
</Trigger>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import React, { ComponentPropsWithoutRef } from 'react';
|
||||
import { cn } from 'utils/classnames';
|
||||
|
||||
export interface WavyBorderProps extends ComponentPropsWithoutRef<'div'> {}
|
||||
|
||||
export const WavyBorder = ({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: WavyBorderProps) => {
|
||||
return (
|
||||
<div {...props} className={cn('wave', className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from './WavyBorder';
|
||||
@@ -2,8 +2,163 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-Thin.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-ThinItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-ExtraLight.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-ExtraLightItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-Light.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-LightItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-Regular.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-Italic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-Medium.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-MediumItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-SemiBold.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-SemiBoldItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-Bold.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-BoldItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-ExtraBold.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-ExtraBoldItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-Black.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter Display';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url('../public/fonts/InterDisplay/InterDisplay-BlackItalic.woff2')
|
||||
format('woff2');
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.focus-ring {
|
||||
@apply focus-visible:ring-[3px] focus-visible:ring-snowball-200 focus-visible:ring-offset-1 focus-visible:ring-offset-snowball-500 focus-visible:outline-none;
|
||||
}
|
||||
|
||||
.wave {
|
||||
background-image: url('../public/wave-border.svg');
|
||||
background-repeat: repeat-x;
|
||||
background-position: top;
|
||||
background-size: cover;
|
||||
@apply w-full h-1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ThemeProvider } from '@material-tailwind/react';
|
||||
|
||||
import './index.css';
|
||||
import '@fontsource/inter';
|
||||
import '@fontsource-variable/jetbrains-mono';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import { GQLClientProvider } from './context/GQLClientContext';
|
||||
|
||||
@@ -25,6 +25,12 @@ const ProjectSearch = () => {
|
||||
fetchUser();
|
||||
}, []);
|
||||
|
||||
const fetchOrgSlug = useCallback(async () => {
|
||||
const { organizations } = await client.getOrganizations();
|
||||
// TODO: Get the selected organization. This is temp
|
||||
return organizations[0].slug;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="sticky top-0 bg-white z-30">
|
||||
@@ -38,7 +44,15 @@ const ProjectSearch = () => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button variant={'secondary'} iconOnly>
|
||||
<Button
|
||||
variant={'secondary'}
|
||||
iconOnly
|
||||
onClick={() => {
|
||||
fetchOrgSlug().then((organizationSlug) => {
|
||||
navigate(`/${organizationSlug}/projects/create`);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<Button variant={'ghost'} iconOnly>
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import React from 'react';
|
||||
import './Snow.css';
|
||||
|
||||
const Login = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-5 h-screen bg-light-blue-50 py-10">
|
||||
<div className="col-span-2"></div>
|
||||
<div className="col-span-1">
|
||||
<div className="flex items-center justify-center h-screen bg-snowball-900 snow">
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<img
|
||||
src="/logo.svg"
|
||||
alt="snowball logo"
|
||||
className="w-32 h-32 rounded-full mb-4"
|
||||
/>
|
||||
<w3m-button />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.snow {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 50 50' style='enable-background:new 0 0 50 50%3B' xml:space='preserve'%3E%3Cstyle type='text/css'%3E.st1%7Bopacity:0.3%3Bfill:%23FFFFFF%3B%7D.st3%7Bopacity:0.1%3Bfill:%23FFFFFF%3B%7D%3C/style%3E%3Ccircle class='st1' cx='5' cy='8' r='1'/%3E%3Ccircle class='st1' cx='38' cy='3' r='1'/%3E%3Ccircle class='st1' cx='12' cy='4' r='1'/%3E%3Ccircle class='st1' cx='16' cy='16' r='1'/%3E%3Ccircle class='st1' cx='47' cy='46' r='1'/%3E%3Ccircle class='st1' cx='32' cy='10' r='1'/%3E%3Ccircle class='st1' cx='3' cy='46' r='1'/%3E%3Ccircle class='st1' cx='45' cy='13' r='1'/%3E%3Ccircle class='st1' cx='10' cy='28' r='1'/%3E%3Ccircle class='st1' cx='22' cy='35' r='1'/%3E%3Ccircle class='st1' cx='3' cy='21' r='1'/%3E%3Ccircle class='st1' cx='26' cy='20' r='1'/%3E%3Ccircle class='st1' cx='30' cy='45' r='1'/%3E%3Ccircle class='st1' cx='15' cy='45' r='1'/%3E%3Ccircle class='st1' cx='34' cy='36' r='1'/%3E%3Ccircle class='st1' cx='41' cy='32' r='1'/%3E%3C/svg%3E");
|
||||
background-position: 0px 0px;
|
||||
animation: animatedBackground 230s linear infinite;
|
||||
}
|
||||
|
||||
.snow div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 50 50' style='enable-background:new 0 0 50 50%3B' xml:space='preserve'%3E%3Cstyle type='text/css'%3E.st1%7Bopacity:0.7%3Bfill:%23FFFFFF%3B%7D.st3%7Bopacity:0.1%3Bfill:%23FFFFFF%3B%7D%3C/style%3E%3Ccircle class='st3' cx='4' cy='14' r='1'/%3E%3Ccircle class='st3' cx='43' cy='3' r='1'/%3E%3Ccircle class='st3' cx='31' cy='30' r='2'/%3E%3Ccircle class='st3' cx='19' cy='23' r='1'/%3E%3Ccircle class='st3' cx='37' cy='22' r='1'/%3E%3Ccircle class='st3' cx='43' cy='16' r='1'/%3E%3Ccircle class='st3' cx='8' cy='45' r='1'/%3E%3Ccircle class='st3' cx='29' cy='39' r='1'/%3E%3Ccircle class='st3' cx='13' cy='37' r='1'/%3E%3Ccircle class='st3' cx='47' cy='32' r='1'/%3E%3Ccircle class='st3' cx='15' cy='4' r='2'/%3E%3Ccircle class='st3' cx='9' cy='27' r='1'/%3E%3Ccircle class='st3' cx='30' cy='9' r='1'/%3E%3Ccircle class='st3' cx='25' cy='15' r='1'/%3E%3Ccircle class='st3' cx='21' cy='45' r='2'/%3E%3Ccircle class='st3' cx='42' cy='45' r='1'/%3E%3C/svg%3E");
|
||||
background-position: 0px 0px;
|
||||
animation: animatedBackground 260s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes animatedBackground {
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 0px 11600px;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Calendar } from 'components/shared/Calendar';
|
||||
import { DatePicker } from 'components/shared/DatePicker';
|
||||
import { Radio } from 'components/shared/Radio';
|
||||
import { SegmentedControls } from 'components/shared/SegmentedControls';
|
||||
import { Switch } from 'components/shared/Switch';
|
||||
import { useState } from 'react';
|
||||
import { Value } from 'react-calendar/dist/cjs/shared/types';
|
||||
import { avatars, avatarsFallback } from './renders/avatar';
|
||||
import { renderBadges } from './renders/badge';
|
||||
import {
|
||||
@@ -16,6 +18,7 @@ import {
|
||||
renderCheckbox,
|
||||
renderCheckboxWithDescription,
|
||||
} from './renders/checkbox';
|
||||
import { DropdownExample } from './renders/dropdown';
|
||||
import {
|
||||
renderInlineNotificationWithDescriptions,
|
||||
renderInlineNotifications,
|
||||
@@ -32,9 +35,6 @@ import { renderDefaultTag, renderMinimalTag } from './renders/tag';
|
||||
import { renderToast, renderToastsWithCta } from './renders/toast';
|
||||
import { renderTooltips } from './renders/tooltip';
|
||||
|
||||
type ValuePiece = Date | null;
|
||||
type Value = ValuePiece | [ValuePiece, ValuePiece];
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const [singleDate, setSingleDate] = useState<Value>();
|
||||
const [dateRange, setDateRange] = useState<Value>();
|
||||
@@ -270,6 +270,14 @@ const Page: React.FC = () => {
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
{/* Dropdown */}
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Dropdown / Select</h1>
|
||||
<DropdownExample />
|
||||
</div>
|
||||
|
||||
<div className="w-full h border border-gray-200 px-20 my-10" />
|
||||
|
||||
{/* Inline notification */}
|
||||
<div className="flex flex-col gap-10 items-center justify-between">
|
||||
<h1 className="text-2xl font-bold">Inline Notification</h1>
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
import React, { useState } from 'react';
|
||||
import { PencilIcon } from 'components/shared/CustomIcon';
|
||||
import { SelectOption, Select } from 'components/shared/Select';
|
||||
|
||||
export const DROPDOWN_ITEMS: SelectOption[] = [
|
||||
{
|
||||
value: 'apple',
|
||||
label: 'Apple',
|
||||
description: 'Apple is fruit',
|
||||
leftIcon: <PencilIcon />,
|
||||
},
|
||||
{
|
||||
value: 'banana',
|
||||
label: 'Banana',
|
||||
description: 'Banana is fruit',
|
||||
leftIcon: <PencilIcon />,
|
||||
},
|
||||
{
|
||||
value: 'orange',
|
||||
label: 'Orange',
|
||||
description: 'Orange is fruit',
|
||||
leftIcon: <PencilIcon />,
|
||||
},
|
||||
{
|
||||
value: 'watermelon',
|
||||
label: 'Watermelon',
|
||||
description: 'Watermelon is fruit',
|
||||
disabled: true,
|
||||
leftIcon: <PencilIcon />,
|
||||
},
|
||||
];
|
||||
|
||||
export const DropdownExample = () => {
|
||||
const [singleValue, setSingleValue] = useState<SelectOption>();
|
||||
const [multipleValue, setMultipleValue] = useState<SelectOption[]>([]);
|
||||
|
||||
const handleSelect = (
|
||||
type: 'single' | 'multiple',
|
||||
value: SelectOption | SelectOption[],
|
||||
) => {
|
||||
if (type === 'single') {
|
||||
setSingleValue(value as SelectOption);
|
||||
} else {
|
||||
setMultipleValue(value as SelectOption[]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-sm text-center text-gray-500 -mb-8">Single – Small</p>
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
<Select
|
||||
size="sm"
|
||||
placeholder="Default"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
placeholder="Clearable"
|
||||
clearable
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
searchable
|
||||
placeholder="Searchable"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
placeholder="Vertical"
|
||||
orientation="vertical"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-center text-gray-500 -mb-8">Single – Medium</p>
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
<Select
|
||||
placeholder="Default"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Clearable"
|
||||
clearable
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
searchable
|
||||
placeholder="Searchable"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
placeholder="Vertical"
|
||||
orientation="vertical"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-center text-gray-500 -mb-8">
|
||||
Multiple – Small
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
<Select
|
||||
multiple
|
||||
size="sm"
|
||||
placeholder="Default"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
multiple
|
||||
size="sm"
|
||||
placeholder="Clearable"
|
||||
clearable
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
searchable
|
||||
multiple
|
||||
size="sm"
|
||||
placeholder="Searchable"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
multiple
|
||||
size="sm"
|
||||
placeholder="Vertical"
|
||||
orientation="vertical"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
multiple
|
||||
hideValues
|
||||
size="sm"
|
||||
orientation="vertical"
|
||||
placeholder="Hide values"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-center text-gray-500 -mb-8">
|
||||
Multiple – Medium
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
<Select
|
||||
multiple
|
||||
placeholder="Default"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
multiple
|
||||
placeholder="Clearable"
|
||||
clearable
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
searchable
|
||||
multiple
|
||||
placeholder="Searchable"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
multiple
|
||||
placeholder="Vertical"
|
||||
orientation="vertical"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
multiple
|
||||
hideValues
|
||||
orientation="vertical"
|
||||
placeholder="Hide values"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-center text-gray-500 -mb-4">
|
||||
Single – With label, description, and helper text
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
<Select
|
||||
label="Default"
|
||||
description="Single select component"
|
||||
helperText="This is a helper text"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
label="Clearable"
|
||||
description="Single select component"
|
||||
helperText="This is a helper text"
|
||||
clearable
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
searchable
|
||||
label="Searchable"
|
||||
description="Single select component"
|
||||
helperText="This is a helper text"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
<Select
|
||||
label="Vertical"
|
||||
description="Single select component"
|
||||
helperText="This is a helper text"
|
||||
orientation="vertical"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={singleValue}
|
||||
onChange={(value) => handleSelect('single', value)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-center text-gray-500 -mb-4">
|
||||
Multiple – With label, description, and helper text
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
<Select
|
||||
label="Default"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
multiple
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
label="Clearable"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
multiple
|
||||
clearable
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
searchable
|
||||
label="Searchable"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
multiple
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
<Select
|
||||
label="Vertical"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
multiple
|
||||
orientation="vertical"
|
||||
options={DROPDOWN_ITEMS}
|
||||
value={multipleValue}
|
||||
onChange={(value) => handleSelect('multiple', value)}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-center text-gray-500 -mb-4">
|
||||
Error – With label, description, and helper text
|
||||
</p>
|
||||
<div className="flex gap-4 flex-wrap justify-center">
|
||||
<Select
|
||||
error
|
||||
label="Default"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
options={DROPDOWN_ITEMS}
|
||||
/>
|
||||
<Select
|
||||
error
|
||||
label="Clearable"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
clearable
|
||||
options={DROPDOWN_ITEMS}
|
||||
/>
|
||||
<Select
|
||||
error
|
||||
searchable
|
||||
label="Searchable"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
options={DROPDOWN_ITEMS}
|
||||
/>
|
||||
<Select
|
||||
error
|
||||
label="Vertical"
|
||||
description="Multiple select component"
|
||||
helperText="This is a helper text"
|
||||
orientation="vertical"
|
||||
options={DROPDOWN_ITEMS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -5,9 +5,9 @@ import { Button } from 'components/shared/Button';
|
||||
|
||||
import { Typography, Chip } from '@material-tailwind/react';
|
||||
|
||||
import ProjectCard from '../../components/projects/ProjectCard';
|
||||
import { useGQLClient } from '../../context/GQLClientContext';
|
||||
import { PlusIcon } from 'components/shared/CustomIcon';
|
||||
import { ProjectCard } from 'components/projects/ProjectCard';
|
||||
|
||||
const Projects = () => {
|
||||
const client = useGQLClient();
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
type ElementType,
|
||||
type ComponentPropsWithoutRef,
|
||||
forwardRef,
|
||||
} from 'react';
|
||||
|
||||
/**
|
||||
* Construct a type by excluding common keys from one type to another.
|
||||
* @template T - The type from which to omit properties.
|
||||
* @template U - The type whose properties to omit from T.
|
||||
* @param {T} - The source type.
|
||||
* @param {U} - The target type.
|
||||
* @returns A new type that includes all properties from T except those that are common with U.
|
||||
*/
|
||||
export type OmitCommon<T, U> = Pick<T, Exclude<keyof T, keyof U>>;
|
||||
|
||||
export type PolymorphicProps<Element extends ElementType, Props> = Props &
|
||||
Omit<ComponentPropsWithoutRef<Element>, 'as'> & {
|
||||
as?: Element;
|
||||
};
|
||||
|
||||
// taken from : https://github.com/total-typescript/react-typescript-tutorial/blob/main/src/08-advanced-patterns/72-as-prop-with-forward-ref.solution.tsx
|
||||
type FixedForwardRef = <T, P = object>(
|
||||
render: (props: P, ref: React.Ref<T>) => React.ReactNode,
|
||||
) => (props: P & React.RefAttributes<T>) => JSX.Element;
|
||||
|
||||
export const fixedForwardRef = forwardRef as FixedForwardRef;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Get initials from a full name.
|
||||
*
|
||||
* @param {string} name - The full name string from which to get the initials.
|
||||
* @returns {string} A string of initials.
|
||||
*/
|
||||
export function getInitials(name: string): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.filter((n) => n)
|
||||
.map((n) => n[0].toUpperCase())
|
||||
.join('');
|
||||
}
|
||||
@@ -14,6 +14,8 @@ export default withMT({
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
display: ['Inter Display', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'monospace'],
|
||||
},
|
||||
fontSize: {
|
||||
'2xs': '0.625rem',
|
||||
@@ -150,16 +152,18 @@ export default withMT({
|
||||
},
|
||||
boxShadow: {
|
||||
button:
|
||||
'inset 0px 0px 4px rgba(255, 255, 255, 0.25), inset 0px -2px 0px rgba(0, 0, 0, 0.04)',
|
||||
calendar:
|
||||
'0px -2px 0px 0px rgba(0, 0, 0, 0.04) inset, 0px 0px 4px 0px rgba(255, 255, 255, 0.25) inset',
|
||||
dropdown:
|
||||
'0px 3px 20px rgba(8, 47, 86, 0.1), 0px 0px 4px rgba(8, 47, 86, 0.14)',
|
||||
field: '0px 1px 2px rgba(0, 0, 0, 0.04)',
|
||||
inset: 'inset 0px 1px 0px rgba(8, 47, 86, 0.06)',
|
||||
card: '0px 0px 0px 1px #E8F0F7, 0px 2px 4px rgba(8, 47, 86, 0.04)',
|
||||
},
|
||||
spacing: {
|
||||
2.5: '0.625rem',
|
||||
3.25: '0.8125rem',
|
||||
3.5: '0.875rem',
|
||||
4.5: '1.125rem',
|
||||
},
|
||||
zIndex: {
|
||||
toast: '9999',
|
||||
|
||||
@@ -1301,7 +1301,19 @@
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@cerc-io/laconic-sdk@^0.1.14":
|
||||
"@cerc-io/laconic-registry-cli@^0.1.10":
|
||||
version "0.1.10"
|
||||
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Flaconic-registry-cli/-/0.1.10/laconic-registry-cli-0.1.10.tgz#9adf5fe9bc532f1b1d9cdfaa409fc42ec879454f"
|
||||
integrity sha512-wiww5Zbio5RnP9SwCftk9rfRq1xGJUi5Iujbr3mbkyG5Aff4Z9QIT6cwxdP6r83osf5CF4eRu203t4CipdKgiA==
|
||||
dependencies:
|
||||
"@cerc-io/laconic-sdk" "^0.1.15"
|
||||
fs-extra "^10.1.0"
|
||||
js-yaml "^3.14.1"
|
||||
lodash "^4.17.21"
|
||||
lodash-clean "^2.2.3"
|
||||
yargs "^17.4.1"
|
||||
|
||||
"@cerc-io/laconic-sdk@^0.1.14", "@cerc-io/laconic-sdk@^0.1.15":
|
||||
version "0.1.15"
|
||||
resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Flaconic-sdk/-/0.1.15/laconic-sdk-0.1.15.tgz#1011a07933c6f1525e05e1ba7dd6a4e4df9b6edb"
|
||||
integrity sha512-Ifl4JUGpckZsu2RkaGyGlObpu9B9GhwFVoDCt8WM9ApdtFnGEVSbDzRuh4f2vLj9WaZkbfdSI/Xci1Fugj3lZg==
|
||||
@@ -2148,6 +2160,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2"
|
||||
integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==
|
||||
|
||||
"@fontsource-variable/jetbrains-mono@^5.0.19":
|
||||
version "5.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource-variable/jetbrains-mono/-/jetbrains-mono-5.0.19.tgz#e718de646e83b7caaeee484d2e18e1e3680ba8e1"
|
||||
integrity sha512-7PTG1Kp3B/FgoNVPT8da4humj5JxQYxTtO08ZPS2IfanS37Yb/WShbgScnG/iupuFwiRnHVkIuC/2czxItxmyg==
|
||||
|
||||
"@fontsource/inter@^5.0.16":
|
||||
version "5.0.16"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/inter/-/inter-5.0.16.tgz#b858508cdb56dcbbf3166903122851e2fbd16b50"
|
||||
@@ -8744,7 +8761,7 @@ dotenv@~16.3.1:
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.2.tgz#3cb611ce5a63002dbabf7c281bc331f69d28f03f"
|
||||
integrity sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==
|
||||
|
||||
downshift@^8.2.3:
|
||||
downshift@^8.3.2:
|
||||
version "8.3.2"
|
||||
resolved "https://registry.yarnpkg.com/downshift/-/downshift-8.3.2.tgz#35cccfdadfe183a9e6bceb1dca68a6a592bb5602"
|
||||
integrity sha512-kO5mnwMbWB1OIPgIO4wxK0HtSJbaPzx3XTOcnN36I6i08iVc4C2Fftye3UZYVN+W03b13o1kEmN2118G5vcgeQ==
|
||||
@@ -10151,7 +10168,7 @@ fs-constants@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
||||
|
||||
fs-extra@^10.0.0:
|
||||
fs-extra@^10.0.0, fs-extra@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
|
||||
@@ -12846,6 +12863,13 @@ locate-path@^6.0.0:
|
||||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash-clean@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash-clean/-/lodash-clean-2.2.3.tgz#eb72801ba9ebfb2771bed3cc971d9e7835a78611"
|
||||
integrity sha512-ioRhn/L0NNKq220nba58FPvjZ+bTdlUCb37+mhlDe4kzIzuPC/prUHLwDM9izeicr/rcnWrn0EanzNxhAbo8oA==
|
||||
dependencies:
|
||||
lodash "^4.17.21"
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
@@ -19063,7 +19087,7 @@ yargs-parser@^20.2.2, yargs-parser@^20.2.3:
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
|
||||
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
|
||||
|
||||
yargs@17.7.2, yargs@^17.5.1, yargs@^17.6.2:
|
||||
yargs@17.7.2, yargs@^17.4.1, yargs@^17.5.1, yargs@^17.6.2:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
||||
|
||||
Reference in New Issue
Block a user