feat(admin,admin-ui,medusa): Add Medusa Admin plugin (#3334)

This commit is contained in:
Kasper Fabricius Kristensen
2023-03-03 10:09:16 +01:00
committed by GitHub
parent d6b1ad1ccd
commit 40de54b010
928 changed files with 85441 additions and 384 deletions

View File

@@ -0,0 +1,7 @@
---
"@medusajs/admin-ui": patch
"@medusajs/medusa": patch
"@medusajs/admin": patch
---
feat(medusa,admin,admin-ui): Add new plugin to serve the admin dashboard from the server. Adds a new plugin injection step `setup`, code placed in the `setup` folder of a plugin will be run before any code from a plugin is injected into the Medusa server.

View File

@@ -7,6 +7,8 @@ jest*
packages/* packages/*
# List of packages to Lint # List of packages to Lint
!packages/medusa !packages/medusa
!packages/admin-ui
!packages/admin
!packages/medusa-payment-stripe !packages/medusa-payment-stripe

View File

@@ -83,7 +83,8 @@ module.exports = {
project: [ project: [
"./packages/medusa/tsconfig.json", "./packages/medusa/tsconfig.json",
"./packages/medusa-payment-stripe/tsconfig.spec.json", "./packages/medusa-payment-stripe/tsconfig.spec.json",
] "./packages/admin-ui/tsconfig.json",
],
}, },
rules: { rules: {
"valid-jsdoc": "off", "valid-jsdoc": "off",
@@ -111,5 +112,58 @@ module.exports = {
"@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-var-requires": "off",
}, },
}, },
{
files: ["packages/admin-ui/ui/**/*.ts", "packages/admin-ui/ui/**/*.tsx"],
plugins: ["unused-imports"],
extends: [
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
project: "./packages/admin-ui/ui/tsconfig.json",
},
env: {
browser: true,
},
rules: {
"prettier/prettier": "error",
"react/prop-types": "off",
"new-cap": "off",
"require-jsdoc": "off",
"valid-jsdoc": "off",
"no-unused-expressions": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
},
},
{
files: ["packages/admin-ui/lib/**/*.ts"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./packages/admin-ui/tsconfig.json",
},
},
{
files: ["packages/admin/**/*.ts"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./packages/admin/tsconfig.json",
},
},
], ],
} }

View File

@@ -4,5 +4,13 @@
"singleQuote": false, "singleQuote": false,
"tabWidth": 2, "tabWidth": 2,
"trailingComma": "es5", "trailingComma": "es5",
"arrowParens": "always" "arrowParens": "always",
"overrides": [
{
"files": "./packages/admin-ui/**/*.{js,jsx,ts,tsx}",
"options": {
"plugins": ["prettier-plugin-tailwindcss"]
}
}
]
} }

View File

@@ -19,6 +19,7 @@
"@babel/core": "^7.12.10", "@babel/core": "^7.12.10",
"@babel/node": "^7.12.10", "@babel/node": "^7.12.10",
"babel-preset-medusa-package": "*", "babel-preset-medusa-package": "*",
"jest": "^26.6.3" "jest": "^26.6.3",
"jest-environment-node": "26.6.2"
} }
} }

View File

@@ -21,6 +21,7 @@
"@babel/core": "^7.12.10", "@babel/core": "^7.12.10",
"@babel/node": "^7.12.10", "@babel/node": "^7.12.10",
"babel-preset-medusa-package": "*", "babel-preset-medusa-package": "*",
"jest": "^26.6.3" "jest": "^26.6.3",
"jest-environment-node": "26.6.2"
} }
} }

View File

@@ -17,6 +17,7 @@
"@babel/core": "^7.12.10", "@babel/core": "^7.12.10",
"@babel/node": "^7.12.10", "@babel/node": "^7.12.10",
"babel-preset-medusa-package": "*", "babel-preset-medusa-package": "*",
"jest": "^26.6.3" "jest": "^26.6.3",
"jest-environment-node": "26.6.2"
} }
} }

View File

@@ -23,8 +23,8 @@
"@medusajs/medusa-oas-cli": "*", "@medusajs/medusa-oas-cli": "*",
"@readme/openapi-parser": "^2.4.0", "@readme/openapi-parser": "^2.4.0",
"@redocly/cli": "1.0.0-beta.123", "@redocly/cli": "1.0.0-beta.123",
"@typescript-eslint/eslint-plugin": "^5.36.2", "@typescript-eslint/eslint-plugin": "^5.53.0",
"@typescript-eslint/parser": "^5.36.2", "@typescript-eslint/parser": "^5.53.0",
"axios": "^0.21.4", "axios": "^0.21.4",
"axios-mock-adapter": "^1.19.0", "axios-mock-adapter": "^1.19.0",
"babel-jest": "^26.6.3", "babel-jest": "^26.6.3",
@@ -36,6 +36,8 @@
"eslint-plugin-markdown": "^3.0.0", "eslint-plugin-markdown": "^3.0.0",
"eslint-plugin-prettier": "^4.2.1", "eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.31.11", "eslint-plugin-react": "^7.31.11",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-unused-imports": "^2.0.0",
"execa": "^5.1.1", "execa": "^5.1.1",
"express": "^4.17.1", "express": "^4.17.1",
"get-port": "^5.1.1", "get-port": "^5.1.1",
@@ -47,6 +49,7 @@
"microbundle": "^0.13.3", "microbundle": "^0.13.3",
"pg-god": "^1.0.12", "pg-god": "^1.0.12",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.2.3",
"resolve-cwd": "^3.0.0", "resolve-cwd": "^3.0.0",
"ts-jest": "^26.5.6", "ts-jest": "^26.5.6",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",

4
packages/admin-ui/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/dist
/build
.vercel
/ui/preview

View File

@@ -0,0 +1,6 @@
.DS_store
.turbo
/src
/build
.vercel
/ui/preview

View File

@@ -0,0 +1,38 @@
<p align="center">
<a href="https://www.medusajs.com">
<img alt="Medusa" src="https://user-images.githubusercontent.com/7554214/153162406-bf8fd16f-aa98-4604-b87b-e13ab4baf604.png" width="100" />
</a>
</p>
<h1 align="center">
@medusajs/admin-ui
</h1>
<h4 align="center">
<a href="https://docs.medusajs.com">Documentation</a> |
<a href="https://demo.medusajs.com/">Medusa Admin Demo</a> |
<a href="https://www.medusajs.com">Website</a>
</h4>
<p align="center">
An open source composable commerce engine built for developers.
</p>
<p align="center">
<a href="https://github.com/medusajs/medusa/blob/master/LICENSE">
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Medusa is released under the MIT license." />
</a>
<a href="https://circleci.com/gh/medusajs/medusa">
<img src="https://circleci.com/gh/medusajs/medusa.svg?style=shield" alt="Current CircleCI build status." />
</a>
<a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
</a>
<a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
<a href="https://discord.gg/xpCwq3Kfn8">
<img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
</a>
<a href="https://twitter.com/intent/follow?screen_name=medusajs">
<img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
</a>
</p>
## The Medusa Admin App. Included with the [`@medusajs/admin`](https://www.npmjs.com/package/@medusajs/admin) plugin. You shouldn't install this package separately.

View File

@@ -0,0 +1,90 @@
{
"name": "@medusajs/admin-ui",
"author": "Kasper Kristensen <kasper@medusajs.com>",
"license": "MIT",
"version": "0.0.0",
"repository": {
"type": "git",
"url": "https://github.com/medusajs/medusa.git",
"directory": "packages/admin-ui"
},
"exports": {
".": "./dist/index.js",
"./ui": "./ui",
"./package.json": "./package.json"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"ui"
],
"scripts": {
"dev": "vite -c vite.config.dev.ts --port 7001",
"build": "tsc --build",
"test:ui": "vitest --config vite.config.dev.ts",
"test:ui:once": "vitest --config vite.config.dev.ts --run",
"test": "echo \"Tests disabled temporarily\""
},
"dependencies": {
"@hookform/error-message": "^2.0.1",
"@radix-ui/react-accordion": "^1.0.1",
"@radix-ui/react-avatar": "^1.0.1",
"@radix-ui/react-collapsible": "^1.0.1",
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.2",
"@radix-ui/react-popover": "^1.0.3",
"@radix-ui/react-radio-group": "^1.1.1",
"@radix-ui/react-select": "^1.2.0",
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-tooltip": "^1.0.3",
"@segment/analytics-next": "^1.51.1",
"@tanstack/react-query": "4.22.0",
"@tanstack/react-table": "^8.7.9",
"@vitejs/plugin-react": "^3.1.0",
"clsx": "^1.2.1",
"confetti-js": "^0.0.18",
"copy-to-clipboard": "^3.3.1",
"emoji-picker-react": "^4.4.3",
"framer-motion": "^9.1.6",
"medusa-react": "*",
"react": "^18.2.0",
"react-collapsible": "^2.8.3",
"react-country-flag": "^3.0.2",
"react-currency-input-field": "^3.6.8",
"react-datepicker": "^4.8.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.2.0",
"react-helmet": "^6.0.0",
"react-highlight-words": "^0.18.0",
"react-hook-form": "7.38.0",
"react-hot-toast": "^2.4.0",
"react-hotkeys-hook": "^3.4.7",
"react-json-tree": "^0.17.0",
"react-jwt": "^1.1.4",
"react-router-dom": "^6.8.0",
"react-select": "^5.5.4",
"react-table": "^7.7.0",
"type-fest": "^3.6.0",
"vite": "^4.1.4"
},
"devDependencies": {
"@medusajs/medusa": "*",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/line-clamp": "^0.4.2",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/react-table": "^7.7.9",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.21",
"tailwindcss": "3.2.2",
"tailwindcss-radix": "^2.7.0",
"typescript": "^4.9.3",
"vitest": "^0.28.5"
},
"packageManager": "yarn@3.2.1"
}

View File

@@ -0,0 +1,28 @@
import fse from "fs-extra"
import { resolve } from "path"
import vite from "vite"
import { AdminBuildConfig } from "./types"
import { getCustomViteConfig } from "./utils"
async function build(options?: AdminBuildConfig) {
const config = getCustomViteConfig(options)
await vite.build(config).catch((_err) => {
process.exit(1)
})
await fse.writeJSON(
resolve(config.build.outDir, "build-manifest.json"),
options
)
}
async function watch() {
throw new Error("Not implemented")
}
async function clean() {
throw new Error("Not implemented")
}
export { build, watch, clean }

View File

@@ -0,0 +1,15 @@
import { DeepPartial } from "./misc"
type GlobalsConfig = {
base?: string
backend?: string
}
type BuildConfig = {
outDir?: string
}
export type AdminBuildConfig = {
globals?: DeepPartial<GlobalsConfig>
build?: DeepPartial<BuildConfig>
}

View File

@@ -0,0 +1,5 @@
import { AdminBuildConfig } from "./build"
export type AdminUIConfig = {
build?: AdminBuildConfig
}

View File

@@ -0,0 +1,2 @@
export * from "./build"
export * from "./misc"

View File

@@ -0,0 +1,9 @@
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? DeepPartial<U>[]
: T[P] extends ReadonlyArray<infer V>
? ReadonlyArray<DeepPartial<V>>
: DeepPartial<T[P]>
}
export type Base<T extends string> = `/${T}/`

View File

@@ -0,0 +1,5 @@
import { Base } from "../types"
export const formatBase = <T extends string>(base: T): Base<T> => {
return `/${base}/`
}

View File

@@ -0,0 +1,76 @@
import react from "@vitejs/plugin-react"
import { resolve } from "path"
import { BuildOptions, InlineConfig } from "vite"
import { AdminBuildConfig } from "../types"
import { formatBase } from "./format-base"
export const getCustomViteConfig = (config: AdminBuildConfig): InlineConfig => {
const { globals = {}, build = {} } = config
const uiPath = resolve(__dirname, "..", "..", "ui")
const globalReplacements = () => {
const base = globals.base || "app"
let backend = "/"
if (globals.backend) {
try {
// Test if the backend is a valid URL
new URL(globals.backend)
backend = globals.backend
} catch (_e) {
throw new Error(
`The provided backend URL is not valid: ${globals.backend}. Please provide a valid URL (e.g. https://my-medusa-server.com).`
)
}
}
return {
__BASE__: JSON.stringify(`/${base}`),
__MEDUSA_BACKEND_URL__: JSON.stringify(backend),
}
}
const buildConfig = (): BuildOptions => {
const { outDir } = build
let destDir: string
if (!outDir) {
/**
* Default build directory is at the root of the `@medusajs/admin-ui` package.
*/
destDir = resolve(__dirname, "..", "..", "build")
} else {
/**
* If a custom build directory is specified, it is resolved relative to the
* current working directory.
*/
destDir = resolve(process.cwd(), outDir)
}
return {
outDir: destDir,
emptyOutDir: true,
}
}
return {
plugins: [react()],
root: uiPath,
mode: "production",
base: formatBase(globals.base),
define: globalReplacements(),
build: buildConfig(),
resolve: {
alias: {
"@tanstack/react-query": resolve(
require.resolve("@tanstack/react-query")
),
},
},
clearScreen: false,
logLevel: "error",
}
}

View File

@@ -0,0 +1,2 @@
export * from "./format-base"
export * from "./get-custom-vite-config"

View File

@@ -0,0 +1,18 @@
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"module": "commonjs",
"moduleResolution": "node",
"noEmit": false,
"resolveJsonModule": true,
"esModuleInterop": true,
"outDir": "dist",
"rootDir": "src",
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["**/node_modules", "ui"]
}

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Medusa</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,9 @@
const path = require("path")
module.exports = {
plugins: {
"tailwindcss/nesting": {},
tailwindcss: { config: path.join(__dirname, "tailwind.config.js") },
autoprefixer: {},
},
}

View File

@@ -0,0 +1,10 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M26.4471 5.86972L19.6283 1.96144C17.3973 0.679521 14.6635 0.679521 12.4325 1.96144L5.58223 5.86972C3.38261 7.15164 2 9.52788 2 12.0604V19.9083C2 22.4721 3.38261 24.8171 5.58223 26.099L12.401 30.0386C14.6321 31.3205 17.3659 31.3205 19.5969 30.0386L26.4157 26.099C28.6468 24.8171 29.9979 22.4721 29.9979 19.9083V12.0604C30.0608 9.52788 28.6782 7.15164 26.4471 5.86972ZM16.0147 22.9724C12.1496 22.9724 9.00734 19.8458 9.00734 16C9.00734 12.1542 12.1496 9.02762 16.0147 9.02762C19.8797 9.02762 23.0534 12.1542 23.0534 16C23.0534 19.8458 19.9111 22.9724 16.0147 22.9724Z" fill="url(#paint0_linear_362_938)"/>
<defs>
<linearGradient id="paint0_linear_362_938" x1="2" y1="31" x2="35.7561" y2="21.5406" gradientUnits="userSpaceOnUse">
<stop stop-color="#7C53FF"/>
<stop offset="1" stop-color="#F796FF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 990 B

View File

@@ -0,0 +1,45 @@
import { lazy, Suspense } from "react"
import {
createBrowserRouter,
createRoutesFromElements,
Route,
RouterProvider,
} from "react-router-dom"
import Spinner from "./components/atoms/spinner"
const NotFound = lazy(() => import("./pages/404"))
const Dashboard = lazy(() => import("./pages/a"))
const IndexPage = lazy(() => import("./pages/index"))
const InvitePage = lazy(() => import("./pages/invite"))
const LoginPage = lazy(() => import("./pages/login"))
const ResetPasswordPage = lazy(() => import("./pages/reset-password"))
const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route path="/" element={<IndexPage />} />
<Route path="a/*" element={<Dashboard />} />
<Route path="invite" element={<InvitePage />} />
<Route path="login" element={<LoginPage />} />
<Route path="reset-password" element={<ResetPasswordPage />} />
<Route path="*" element={<NotFound />} />
</>
),
{
basename: __BASE__,
}
)
const Loading = () => (
<div className="bg-grey-5 text-grey-90 flex h-screen w-full items-center justify-center">
<Spinner variant="secondary" />
</div>
)
const App = () => (
<Suspense fallback={<Loading />}>
<RouterProvider router={router} />
</Suspense>
)
export default App

View File

@@ -0,0 +1,107 @@
.emoji-picker-react {
padding: 16px !important;
border: none !important;
}
.emoji-picker-react .emoji-group {
padding: 0 !important;
font-size: 12px !important;
font-weight: 400 !important;
}
.emoji-picker-react .emoji-group:before {
font-family: "Inter" !important;
text-transform: none !important;
font-size: 12px !important;
font-weight: 600 !important;
}
.emoji-picker-react .native {
font-size: 24px !important;
}
.emoji-picker-react .emoji {
color: #F3F4F6 !important;
}
.emoji-picker-react input.emoji-search {
background-color: #F9FAFB !important;
border-radius: 4px !important;
border-color: #E5E7EB !important;
margin: 0 !important;
width: 100% !important;
font-size: 12px !important;
font-family: "Inter" !important;
color: #111827 !important;
caret-color: #7C3AED !important;
}
.emoji-picker-react input.emoji-search::placeholder {
font-size: 12px !important;
font-family: "Inter" !important;
color: #9CA3AF !important;
}
.emoji-picker-react .emoji-categories button.icn-smileys_people {
background-image: url("../svg/happy.svg") !important;
}
.emoji-picker-react .emoji-categories button.icn-animals_nature {
background-image: url("../svg/sprout.svg") !important;
}
.emoji-picker-react .emoji-categories button.icn-food_drink {
background-image: url("../svg/carrot.svg") !important;
}
.emoji-picker-react .emoji-categories button.icn-travel_places {
background-image: url("../svg/plane.svg") !important;
}
.emoji-picker-react .emoji-categories button.icn-activities {
background-image: url("../svg/controller.svg") !important;
}
.emoji-picker-react .emoji-categories button.icn-objects {
background-image: url("../svg/lightbulb.svg") !important;
}
.emoji-picker-react .emoji-categories button.icn-symbols {
background-image: url("../svg/heart.svg") !important;
}
.emoji-picker-react .emoji-categories button.icn-flags {
background-image: url("../svg/flag.svg") !important;
}
.emoji-picker-react .emoji-categories button {
width: 32px !important;
height: 32px !important;
border-radius: 4px !important;
}
.emoji-picker-react .emoji-categories button.active {
background-color: white !important;
}
.emoji-picker-react .emoji-categories {
background-color: #F3F4F6 !important;
padding: 4px !important;
border-radius: 4px !important;
margin-bottom: 8px !important;
}
.emoji-picker-react .active-category-indicator-wrapper .active-category-indicator {
display: none !important;
}
.emoji-scroll-wrapper {
overflow-x: hidden !important;
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.emoji-scroll-wrapper::-webkit-scrollbar {
/* chrome */
display: none;
}

View File

@@ -0,0 +1,456 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: "Inter";
src: url("../../fonts/Inter-Regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Inter";
src: url("../../fonts/Inter-SemiBold.ttf") format("truetype");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Roboto Mono";
src: url("../../fonts/RobotoMono-Bold.ttf") format("truetype");
font-weight: bold;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Roboto Mono";
src: url("../../fonts/RobotoMono-Regular.ttf") format("truetype");
font-weight: normal;
font-style: normal;
font-display: swap;
}
@layer base {
html {
@apply overflow-hidden;
}
}
@layer components {
.inter-5xlarge-regular {
@apply font-sans text-5xlarge leading-4xlarge font-normal;
}
.inter-5xlarge-semibold {
@apply font-sans text-5xlarge leading-4xlarge font-semibold;
}
.inter-4xlarge-regular {
@apply font-sans text-4xlarge leading-3xlarge font-normal;
}
.inter-4xlarge-semibold {
@apply font-sans text-4xlarge leading-3xlarge font-semibold;
}
.inter-3xlarge-regular {
@apply font-sans text-3xlarge leading-2xlarge font-normal;
}
.inter-3xlarge-semibold {
@apply font-sans text-3xlarge leading-2xlarge font-semibold;
}
.inter-2xlarge-regular {
@apply font-sans text-2xlarge leading-xlarge font-normal;
}
.inter-2xlarge-semibold {
@apply font-sans text-2xlarge leading-xlarge font-semibold;
}
.inter-xlarge-regular {
@apply font-sans text-xlarge leading-large font-normal;
}
.inter-xlarge-semibold {
@apply font-sans text-xlarge leading-large font-semibold;
}
.inter-large-regular {
@apply font-sans text-large leading-base font-normal;
}
.inter-large-semibold {
@apply font-sans text-large leading-base font-semibold;
}
.inter-base-regular {
@apply font-sans text-base leading-base font-normal;
}
.inter-base-semibold {
@apply font-sans text-base leading-base font-semibold;
}
.inter-small-regular {
@apply font-sans text-small leading-small font-normal;
}
.inter-small-semibold {
@apply font-sans text-small leading-small font-semibold;
}
.inter-xsmall-regular {
@apply font-sans text-xsmall leading-xsmall font-normal;
}
.inter-xsmall-semibold {
@apply font-sans text-xsmall leading-xsmall font-semibold;
}
.mono-5xlarge-regular {
@apply font-mono text-5xlarge leading-4xlarge font-normal;
}
.mono-5xlarge-semibold {
@apply font-mono text-5xlarge leading-4xlarge font-bold;
}
.mono-4xlarge-regular {
@apply font-mono text-4xlarge leading-3xlarge font-normal;
}
.mono-4xlarge-semibold {
@apply font-mono text-4xlarge leading-3xlarge font-bold;
}
.mono-3xlarge-regular {
@apply font-mono text-3xlarge leading-2xlarge font-normal;
}
.mono-3xlarge-semibold {
@apply font-mono text-3xlarge leading-2xlarge font-bold;
}
.mono-2xlarge-regular {
@apply font-mono text-2xlarge leading-xlarge font-normal;
}
.mono-2xlarge-semibold {
@apply font-mono text-2xlarge leading-xlarge font-bold;
}
.mono-xlarge-regular {
@apply font-mono text-xlarge leading-large font-normal;
}
.mono-xlarge-semibold {
@apply font-mono text-xlarge leading-large font-bold;
}
.mono-large-regular {
@apply font-mono text-large leading-base font-normal;
}
.mono-large-semibold {
@apply font-mono text-large leading-base font-bold;
}
.mono-base-regular {
@apply font-mono text-base leading-base font-normal;
}
.mono-base-semibold {
@apply font-mono text-base leading-base font-bold;
}
.mono-small-regular {
@apply font-mono text-small leading-small font-normal;
}
.mono-small-semibold {
@apply font-mono text-small leading-small font-bold;
}
.mono-xsmall-regular {
@apply font-mono text-xsmall leading-xsmall font-normal;
}
.mono-xsmall-semibold {
@apply font-mono text-xsmall leading-xsmall font-bold;
}
.radio-outer-ring > span.indicator[data-state="checked"] {
@apply rounded-circle shadow-violet-60 shadow-[0_0_0_2px];
}
.bold-active-item + span {
@apply inter-base-semibold;
}
}
@layer components {
.react-select-container {
@apply p-0 -mx-3 border-0 mb-1 cursor-text h-6;
.react-select__control {
@apply border-0 bg-inherit shadow-none;
}
.react-select__control,
.react-select__control--is-focused,
.react-select__control--menu-is-open {
@apply h-6 p-0 m-0 !important;
}
.react-select__value-container--is-multi,
.react-select__value-container--has-value {
@apply h-6 pl-3 p-0 m-0 !important;
}
.react-select__menu,
.react-select__menu-list {
@apply rounded-t-none mt-0 z-[110] !important;
}
.react-select__value-container {
@apply pl-3 pr-0;
}
.react-select__indicators {
@apply p-0 h-full items-center flex pr-3;
.react-select__indicator {
@apply p-0;
}
}
.react-select__input {
@apply w-full mt-0 min-w-[120px] pt-0 !important;
}
.react-select__option,
.react-select__option--is-focused,
.react-select__option--is-selected {
@apply bg-grey-0 hover:bg-grey-5 !important;
}
.react-select__multi-value,
.react-select__input-container {
@apply my-0 py-0;
}
}
}
@layer components {
.badge {
@apply w-min py-0.5 px-2 rounded-rounded inter-small-semibold;
}
.badge-disabled {
@apply bg-grey-50 bg-opacity-10 text-grey-50;
}
.badge-primary {
@apply bg-violet-60 bg-opacity-10 text-violet-60;
}
.badge-danger {
@apply bg-rose-50 bg-opacity-10 text-rose-50;
}
.badge-success {
@apply bg-teal-50 bg-opacity-10 text-teal-50;
}
.badge-warning {
@apply bg-yellow-40 bg-opacity-20 text-yellow-60;
}
.badge-ghost {
@apply text-grey-90 border border-grey-20 whitespace-nowrap;
}
.badge-default {
@apply inter-small-regular bg-grey-10 text-grey-90 whitespace-nowrap;
}
.btn {
@apply flex items-center justify-center rounded-rounded focus:outline-none focus:shadow-cta;
}
.btn-large {
@apply inter-base-semibold px-large py-small;
}
.btn-medium {
@apply inter-base-semibold px-base py-xsmall;
}
.btn-small {
@apply inter-small-semibold px-small py-[6px];
}
.btn-primary {
@apply bg-violet-60 text-grey-0 hover:bg-violet-50 active:bg-violet-70 disabled:bg-grey-20 disabled:text-grey-40;
}
.btn-secondary {
@apply bg-grey-0 text-grey-90 border border-grey-20 hover:bg-grey-5 active:bg-grey-5 active:text-violet-60 focus:border-violet-60 disabled:bg-grey-0 disabled:text-grey-30;
}
.btn-danger {
@apply bg-grey-0 text-rose-50 border border-grey-20 hover:bg-grey-10 active:bg-grey-20 disabled:bg-grey-0 disabled:text-grey-30;
}
.btn-nuclear {
@apply bg-rose-50 text-grey-0 hover:bg-rose-40 active:bg-rose-60 disabled:bg-grey-20 disabled:text-grey-40;
}
.btn-ghost {
@apply bg-transparent text-grey-90 hover:bg-grey-5 active:bg-grey-5 active:text-violet-60 focus:border-violet-60 disabled:bg-transparent disabled:text-grey-30;
}
.btn-primary-large {
@apply btn btn-large btn-primary;
}
.btn-primary-medium {
@apply btn btn-medium;
}
.btn-primary-small {
@apply btn btn-small;
}
.btn-secondary-large {
@apply btn btn-large btn-seconday;
}
.btn-secondary-medium {
@apply btn btn-medium btn-seconday;
}
.btn-secondary-small {
@apply btn btn-small btn-seconday;
}
.btn-ghost-large {
@apply btn btn-large btn-ghost;
}
.btn-ghost-medium {
@apply btn btn-medium btn-ghost;
}
.btn-ghost-small {
@apply btn btn-small btn-ghost;
}
}
@layer components {
.date-picker {
@apply border-0 outline-none pt-6 !important;
.react-datepicker__month-container {
.react-datepicker__header {
@apply bg-inherit border-0;
}
}
.react-datepicker__day-names {
@apply inter-base-semibold pt-4;
.react-datepicker__day-name {
@apply w-[40px] m-0;
}
}
.react-datepicker__month {
@apply m-0;
}
.react-datepicker__day {
@apply inter-base-regular;
}
.react-datepicker__day--today {
@apply text-grey-90 inter-base-semibold bg-grey-10 rounded !important;
}
.react-datepicker__day--outside-month,
.past {
@apply text-grey-40 !important;
}
.date {
@apply text-grey-90 m-[0px] w-[38px] h-[38px] align-middle relative leading-none pt-3;
:hover {
@apply cursor-pointer;
}
}
.chosen,
.react-datepicker__day--keyboard-selected {
@apply bg-violet-60 text-grey-0 inter-base-semibold leading-none !important;
}
}
.time-list::-webkit-scrollbar {
/* chrome */
display: none;
}
.time-list {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
}
@layer utilities {
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.vice-city {
@apply bg-gradient-to-tr from-vice-start to-vice-stop;
}
.hidden-actions[data-state="open"] {
opacity: 1;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
::-webkit-scrollbar-track {
@apply bg-transparent;
}
::-webkit-scrollbar-thumb {
@apply rounded-rounded bg-grey-30;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-grey-40;
}
.accordion-margin-transition {
@apply transition-[margin] duration-300 ease-[cubic-bezier(0.87,0,0.13,1)];
}
.col-tree:last-child .bottom-half-dash {
@apply border-none;
}
}
.scrolling-touch {
-webkit-overflow-scrolling: touch;
}
.scrolling-auto {
-webkit-overflow-scrolling: auto;
}
/* Classes to remove number spinners from inputs of type number */
/* Chrome, Safari, Edge, Opera */
.remove-number-spinner::-webkit-outer-spin-button,
.remove-number-spinner::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
.remove-number-spinner {
-moz-appearance: textfield;
}

View File

@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.4292 12.3984L10.556 10.5251M2.47656 17.5004C2.47656 17.5004 9.99245 14.8351 12.1703 12.6573C12.4888 12.3393 12.7416 11.9617 12.9141 11.546C13.0867 11.1303 13.1757 10.6847 13.176 10.2346C13.1764 9.78457 13.0881 9.33883 12.9162 8.92288C12.7443 8.50693 12.4921 8.12891 12.1741 7.81041C11.8561 7.49191 11.4785 7.23917 11.0628 7.0666C10.6471 6.89404 10.2015 6.80504 9.75147 6.80469C9.3014 6.80434 8.85566 6.89263 8.43971 7.06454C8.02376 7.23645 7.64575 7.48861 7.32724 7.80661C5.14177 9.99208 2.47656 17.5004 2.47656 17.5004ZM7.32724 11.6369L5.76619 10.0835L7.32724 11.6369Z" stroke="#9CA3AF" stroke-width="1.52298" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M17.5003 7.82962C17.5003 7.82962 16.4876 6.30664 14.8351 6.30664C13.5863 6.30664 12.1699 7.82962 12.1699 7.82962C12.1699 7.82962 13.1827 9.3526 14.8351 9.3526C16.4876 9.3526 17.5003 7.82962 17.5003 7.82962Z" stroke="#9CA3AF" stroke-width="1.52298" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.1695 2.49805C12.1695 2.49805 10.6465 3.51083 10.6465 5.16326C10.6465 6.81569 12.1695 7.82847 12.1695 7.82847C12.1695 7.82847 13.6924 6.42733 13.6924 5.16326C13.6924 3.51083 12.1695 2.49805 12.1695 2.49805Z" stroke="#9CA3AF" stroke-width="1.52298" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,7 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.47266 8.89453H8.47266" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.97266 7.39453V10.3945" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.4512 9.58398H12.4599" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14.1055 7.71289H14.1142" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.99 4.5H6.01C5.26771 4.50018 4.55184 4.78865 4.00078 5.30965C3.44971 5.83065 3.10258 6.54717 3.0265 7.32071C3.022 7.36157 3.019 7.40007 3.01375 7.44014C2.953 7.96971 2.5 11.9297 2.5 13.1429C2.5 13.768 2.73705 14.3676 3.15901 14.8096C3.58097 15.2517 4.15326 15.5 4.75 15.5C5.5 15.5 5.875 15.1071 6.25 14.7143L7.3105 13.6033C7.59174 13.3086 7.97321 13.1429 8.371 13.1429H11.629C12.0268 13.1429 12.4083 13.3086 12.6895 13.6033L13.75 14.7143C14.125 15.1071 14.5 15.5 15.25 15.5C15.8467 15.5 16.419 15.2517 16.841 14.8096C17.2629 14.3676 17.5 13.768 17.5 13.1429C17.5 11.9289 17.047 7.96971 16.9863 7.44014C16.981 7.40086 16.978 7.36157 16.9735 7.3215C16.8976 6.54782 16.5505 5.8311 15.9995 5.30994C15.4484 4.78878 14.7324 4.5002 13.99 4.5V4.5Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.16602 12.25C4.16602 12.25 4.91602 11.5 7.16602 11.5C9.41602 11.5 10.916 13 13.166 13C15.416 13 16.166 12.25 16.166 12.25V3.25C16.166 3.25 15.416 4 13.166 4C10.916 4 9.41602 2.5 7.16602 2.5C4.91602 2.5 4.16602 3.25 4.16602 3.25V12.25Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.16602 17.5V12.25" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 550 B

View File

@@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 17.5C14.1421 17.5 17.5 14.1421 17.5 10C17.5 5.85786 14.1421 2.5 10 2.5C5.85786 2.5 2.5 5.85786 2.5 10C2.5 14.1421 5.85786 17.5 10 17.5Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6.66602 11.666C6.66602 11.666 7.91602 13.3327 9.99935 13.3327C12.0827 13.3327 13.3327 11.666 13.3327 11.666" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 8H8.00875" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 8H12.0088" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 759 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.316 4.52475C15.9399 4.14732 15.493 3.84785 15.0008 3.64351C14.5087 3.43917 13.9811 3.33398 13.4483 3.33398C12.9154 3.33398 12.3878 3.43917 11.8957 3.64351C11.4036 3.84785 10.9567 4.14732 10.5806 4.52475L10.0033 5.10955L9.42596 4.52475C9.04984 4.14732 8.6029 3.84785 8.11079 3.64351C7.61868 3.43917 7.09108 3.33398 6.55823 3.33398C6.02538 3.33398 5.49778 3.43917 5.00567 3.64351C4.51356 3.84785 4.06663 4.14732 3.6905 4.52475C2.10107 6.11419 2.0036 8.79823 4.00539 10.8375L10.0033 16.8354L16.0011 10.8375C18.0029 8.79823 17.9054 6.11419 16.316 4.52475Z" stroke="#9CA3AF" stroke-width="1.49947" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 758 B

View File

@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.875 14.25H12.125" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.58398 17.084H11.4173" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.1887 11.416C12.3162 10.7218 12.6492 10.1835 13.1875 9.64518C13.5295 9.33033 13.801 8.94664 13.9842 8.51934C14.1673 8.09205 14.2579 7.63083 14.25 7.16602C14.25 6.03885 13.8022 4.95784 13.0052 4.16081C12.2082 3.36378 11.1272 2.91602 10 2.91602C8.87283 2.91602 7.79183 3.36378 6.9948 4.16081C6.19777 4.95784 5.75 6.03885 5.75 7.16602C5.75 7.87435 5.91292 8.7456 6.8125 9.64518C7.32537 10.1141 7.67525 10.7345 7.81125 11.416" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 857 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.9856 15.1639L12.6495 9.07732L15.2474 6.47938C16.3608 5.36598 16.732 3.88144 16.3608 3.13918C15.6186 2.76804 14.134 3.13918 13.0206 4.25258L10.4227 6.85052L4.33608 5.51443C3.96495 5.44021 3.66804 5.58866 3.51959 5.88557L3.29691 6.2567C3.14845 6.62783 3.22268 6.99897 3.51959 7.22165L7.45361 9.81959L5.96907 12.0464H3.74227L3 12.7887L5.2268 14.2732L6.71134 16.5L7.45361 15.7577V13.5309L9.68041 12.0464L12.2784 15.9804C12.501 16.2773 12.8722 16.3515 13.2433 16.2031L13.6144 16.0546C13.9113 15.832 14.0598 15.5351 13.9856 15.1639V15.1639Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 737 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.9696 18.0303C17.2625 18.3232 17.7374 18.3232 18.0302 18.0303C18.3231 17.7374 18.3231 17.2626 18.0302 16.9697L16.9696 18.0303ZM14.4052 13.3447C14.1124 13.0518 13.6375 13.0518 13.3446 13.3447C13.0517 13.6376 13.0517 14.1124 13.3446 14.4053L14.4052 13.3447ZM15.0833 9.16667C15.0833 12.4344 12.4344 15.0833 9.16667 15.0833V16.5833C13.2628 16.5833 16.5833 13.2628 16.5833 9.16667H15.0833ZM9.16667 15.0833C5.89898 15.0833 3.25 12.4344 3.25 9.16667H1.75C1.75 13.2628 5.07055 16.5833 9.16667 16.5833V15.0833ZM3.25 9.16667C3.25 5.89898 5.89898 3.25 9.16667 3.25V1.75C5.07055 1.75 1.75 5.07055 1.75 9.16667H3.25ZM9.16667 3.25C12.4344 3.25 15.0833 5.89898 15.0833 9.16667H16.5833C16.5833 5.07055 13.2628 1.75 9.16667 1.75V3.25ZM18.0302 16.9697L14.4052 13.3447L13.3446 14.4053L16.9696 18.0303L18.0302 16.9697Z" fill="#9CA3AF"/>
</svg>

After

Width:  |  Height:  |  Size: 932 B

View File

@@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.83398 16.666H14.1673" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8.33398 16.6673C12.9173 14.584 9.00065 11.334 10.834 8.33398" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.91732 7.83349C8.83398 8.50016 9.41732 9.66683 9.83398 10.9168C8.16732 11.2502 6.91732 11.2502 5.83398 10.6668C4.83398 10.1668 3.91732 9.08349 3.33398 7.16683C5.66732 6.75016 7.00065 7.16683 7.91732 7.83349V7.83349Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.7498 5.00065C11.1145 5.99362 10.7949 7.15577 10.8332 8.33398C12.4165 8.25065 13.5832 7.83398 14.4165 7.16732C15.2498 6.33398 15.7498 5.25065 15.8332 3.33398C13.5832 3.41732 12.4998 4.16732 11.7498 5.00065Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 997 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 2.5L12.3175 7.195L17.5 7.9525L13.75 11.605L14.635 16.765L10 14.3275L5.365 16.765L6.25 11.605L2.5 7.9525L7.6825 7.195L10 2.5Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 327 B

View File

@@ -0,0 +1,62 @@
import * as RadixAvatar from "@radix-ui/react-avatar"
import clsx from "clsx"
import React from "react"
import Spinner from "../spinner"
type AvatarProps = {
user?: {
img?: string
first_name?: string
last_name?: string
email?: string
}
font?: string
color?: string
isLoading?: boolean
}
const Avatar: React.FC<AvatarProps> = ({
user,
font = "inter-small-semibold",
color = "bg-violet-60",
isLoading = false,
}) => {
let username: string
if (user?.first_name && user?.last_name) {
username = user.first_name + " " + user.last_name
} else if (user?.email) {
username = user.email
} else {
username = "Medusa user"
}
return (
<RadixAvatar.Root
className={clsx(
"w-full h-full items-center justify-center overflow-hidden select-none rounded-circle",
color
)}
>
<RadixAvatar.Image
src={user?.img}
alt={username}
className="object-cover w-full h-full rounded-circle"
/>
<RadixAvatar.Fallback
className={clsx(
"w-full h-full flex items-center justify-center bg-inherit text-grey-0 rounded-circle",
font
)}
>
{isLoading ? (
<Spinner size="small" variant="primary" />
) : (
username.slice(0, 1).toUpperCase()
)}
</RadixAvatar.Fallback>
</RadixAvatar.Root>
)
}
export default Avatar

View File

@@ -0,0 +1,29 @@
import clsx from "clsx"
import React from "react"
import { useNavigate } from "react-router-dom"
import ArrowLeftIcon from "../../fundamentals/icons/arrow-left-icon"
type Props = {
path?: string
label?: string
className?: string
}
const BackButton = ({ path, label = "Go back", className }: Props) => {
const navigate = useNavigate()
return (
<button
onClick={() => {
path ? navigate(path) : navigate(-1)
}}
className={clsx("px-small py-xsmall", className)}
>
<div className="flex items-center gap-x-xsmall text-grey-50 inter-grey-40 inter-small-semibold">
<ArrowLeftIcon size={20} />
<span className="ml-1">{label}</span>
</div>
</button>
)
}
export default BackButton

View File

@@ -0,0 +1,32 @@
import clsx from "clsx"
import React, { ReactNode, useImperativeHandle } from "react"
export type CheckboxProps = React.InputHTMLAttributes<HTMLInputElement> & {
label: ReactNode
}
const Checkbox = React.forwardRef(
({ label, value, className, id, ...rest }: CheckboxProps, ref) => {
const checkboxRef = React.useRef<HTMLInputElement>(null)
useImperativeHandle(ref, () => checkboxRef.current)
return (
<label
className={clsx("flex items-center cursor-pointer", className)}
htmlFor={id}
>
<input
type="checkbox"
ref={checkboxRef}
className="form-checkbox w-[20px] h-[20px] rounded-base text-violet-60 focus:ring-0 mr-small border-grey-30"
value={value}
id={id}
{...rest}
/>
{label}
</label>
)
}
)
export default Checkbox

View File

@@ -0,0 +1,58 @@
import clsx from "clsx"
import React, { useEffect } from "react"
import useClipboard from "../../../hooks/use-clipboard"
import useNotification from "../../../hooks/use-notification"
import Button from "../../fundamentals/button"
import ClipboardCopyIcon from "../../fundamentals/icons/clipboard-copy-icon"
type CopyToClipboardProps = {
value: string
displayValue?: string
successDuration?: number
showValue?: boolean
iconSize?: number
onCopy?: () => void
}
const CopyToClipboard: React.FC<CopyToClipboardProps> = ({
value,
displayValue,
successDuration = 3000,
showValue = true,
iconSize = 20,
onCopy = () => {},
}) => {
const [isCopied, handleCopy] = useClipboard(value, {
onCopied: onCopy,
successDuration: successDuration,
})
const notification = useNotification()
useEffect(() => {
if (isCopied) {
notification("Success", "Copied!", "success")
}
}, [isCopied])
return (
<div className="flex items-center inter-small-regular text-grey-50 gap-x-xsmall">
<Button
variant="ghost"
size="small"
className={clsx("p-0 text-grey-50", {
["text-violet-60"]: isCopied,
})}
onClick={handleCopy}
>
<ClipboardCopyIcon size={iconSize} />
</Button>
{showValue && (
<span className="w-full truncate">
{displayValue ? displayValue : value}
</span>
)}
</div>
)
}
export default CopyToClipboard

View File

@@ -0,0 +1,44 @@
import { ReactDatePickerCustomHeaderProps } from "react-datepicker"
import NativeSelect from "../../molecules/native-select"
import { getYearRange, monthNames } from "./utils"
const CustomHeader = ({
date,
changeYear,
changeMonth,
}: ReactDatePickerCustomHeaderProps) => {
const month = date.getMonth()
const monthName = monthNames[month]
const year = date.getFullYear()
return (
<div className="flex w-full gap-4 items-center">
<div className="flex flex-1 items-center justify-end gap-3">
<NativeSelect
defaultValue={monthName}
onValueChange={(v) => changeMonth(monthNames.indexOf(v))}
>
{monthNames.map((month) => (
<NativeSelect.Item key={month} value={month}>
{month}
</NativeSelect.Item>
))}
</NativeSelect>
</div>
<div className="flex flex-1 items-center justify-start gap-3">
<NativeSelect
defaultValue={year.toString()}
onValueChange={(v) => changeYear(parseInt(v, 10))}
>
{getYearRange().map((year) => (
<NativeSelect.Item key={year} value={year.toString()}>
{year.toString()}
</NativeSelect.Item>
))}
</NativeSelect>
</div>
</div>
)
}
export default CustomHeader

View File

@@ -0,0 +1,115 @@
import * as PopoverPrimitive from "@radix-ui/react-popover"
import clsx from "clsx"
import moment from "moment"
import React, { useEffect, useState } from "react"
import ReactDatePicker from "react-datepicker"
import "react-datepicker/dist/react-datepicker.css"
import Button from "../../fundamentals/button"
import ArrowDownIcon from "../../fundamentals/icons/arrow-down-icon"
import InputContainer from "../../fundamentals/input-container"
import InputHeader from "../../fundamentals/input-header"
import CustomHeader from "./custom-header"
import { DateTimePickerProps } from "./types"
const getDateClassname = (d, tempDate) => {
return moment(d).format("YY,MM,DD") === moment(tempDate).format("YY,MM,DD")
? "date chosen"
: `date ${
moment(d).format("YY,MM,DD") < moment(new Date()).format("YY,MM,DD")
? "past"
: ""
}`
}
const DatePicker: React.FC<DateTimePickerProps> = ({
date,
onSubmitDate,
label = "start date",
required = false,
tooltipContent,
tooltip,
}) => {
const [tempDate, setTempDate] = useState(date)
const [isOpen, setIsOpen] = useState(false)
useEffect(() => setTempDate(date), [isOpen])
const submitDate = () => {
// update only date, month and year
const newDate = new Date(date.getTime())
newDate.setUTCDate(tempDate.getUTCDate())
newDate.setUTCMonth(tempDate.getUTCMonth())
newDate.setUTCFullYear(tempDate.getUTCFullYear())
onSubmitDate(newDate)
setIsOpen(false)
}
return (
<div className="w-full">
<PopoverPrimitive.Root open={isOpen} onOpenChange={setIsOpen}>
<PopoverPrimitive.Trigger asChild>
<button
className={clsx("w-full rounded-rounded border ", {
"shadow-input border-violet-60": isOpen,
"border-grey-20": !isOpen,
})}
>
<InputContainer className="border-0 shadown-none focus-within:shadow-none">
<div className="w-full flex text-grey-50 pr-0.5 justify-between">
<InputHeader
{...{
label,
required,
tooltipContent,
tooltip,
}}
/>
<ArrowDownIcon size={16} />
</div>
<label className="w-full text-left">
{moment(date).format("ddd, DD MMM YYYY")}
</label>
</InputContainer>
</button>
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Content
side="top"
sideOffset={8}
className="rounded-rounded px-8 border border-grey-20 bg-grey-0 w-full shadow-dropdown"
>
<CalendarComponent date={tempDate} onChange={setTempDate} />
<div className="flex w-full mb-8 mt-5">
<Button
variant="ghost"
size="medium"
onClick={() => setIsOpen(false)}
className="mr-2 w-1/3 flex justify-center border border-grey-20"
>
Cancel
</Button>
<Button
size="medium"
variant="primary"
onClick={() => submitDate()}
className="w-2/3 flex justify-center"
>{`Set ${label}`}</Button>
</div>
</PopoverPrimitive.Content>
</PopoverPrimitive.Root>
</div>
)
}
export const CalendarComponent = ({ date, onChange }) => (
<ReactDatePicker
selected={date}
inline
onChange={onChange}
calendarClassName="date-picker"
dayClassName={(d) => getDateClassname(d, date)}
renderCustomHeader={({ ...props }) => <CustomHeader {...props} />}
/>
)
export default DatePicker

View File

@@ -0,0 +1,102 @@
import * as PopoverPrimitive from "@radix-ui/react-popover"
import clsx from "clsx"
import { isNil } from "lodash"
import moment from "moment"
import React, { useEffect, useState } from "react"
import ArrowDownIcon from "../../fundamentals/icons/arrow-down-icon"
import ClockIcon from "../../fundamentals/icons/clock-icon"
import InputContainer from "../../fundamentals/input-container"
import InputHeader from "../../fundamentals/input-header"
import NumberScroller from "../number-scroller"
import { DateTimePickerProps } from "./types"
const TimePicker: React.FC<DateTimePickerProps> = ({
date,
onSubmitDate,
label = "start date",
required = false,
tooltipContent,
tooltip,
}) => {
const [selectedMinute, setSelectedMinute] = useState(
new Date(date)?.getUTCMinutes()
)
const [selectedHour, setSelectedHour] = useState(
new Date(date)?.getUTCHours()
)
useEffect(() => {
setSelectedMinute(new Date(date)?.getUTCMinutes())
setSelectedHour(new Date(date)?.getUTCHours())
}, [date])
useEffect(() => {
if (date && !isNil(selectedHour) && !isNil(selectedMinute)) {
const newDate = new Date(new Date(date).getTime())
newDate.setUTCHours(selectedHour)
newDate.setUTCMinutes(selectedMinute)
onSubmitDate(newDate)
}
}, [selectedMinute, selectedHour])
const [isOpen, setIsOpen] = useState(false)
const minuteNumbers = [...Array(60).keys()]
const hourNumbers = [...Array(24).keys()]
return (
<div className="w-full">
<PopoverPrimitive.Root open={isOpen} onOpenChange={setIsOpen}>
<PopoverPrimitive.Trigger asChild>
<button
className={clsx("w-full rounded-rounded border ", {
"shadow-input border-violet-60": isOpen,
"border-grey-20": !isOpen,
})}
>
<InputContainer className="border-0 shadown-none focus-within:shadow-none">
<div className="w-full flex text-grey-50 pr-0.5 justify-between">
<InputHeader
{...{
label,
required,
tooltipContent,
tooltip,
}}
/>
<ArrowDownIcon size={16} />
</div>
<div className="w-full items-center flex text-left text-grey-40">
<ClockIcon size={16} />
<span className="mx-1">UTC</span>
<span className="text-grey-90">
{moment.utc(date).format("HH:mm")}
</span>
</div>
</InputContainer>
</button>
</PopoverPrimitive.Trigger>
<PopoverPrimitive.Content
side="top"
sideOffset={8}
className="rounded-rounded scrollbar-hide border px-6 pt-6 pb-4 border-grey-20 bg-grey-0 w-full flex shadow-dropdown"
>
<NumberScroller
numbers={hourNumbers}
selected={selectedHour}
onSelect={(n) => setSelectedHour(n)}
className="pr-4"
/>
<NumberScroller
numbers={minuteNumbers}
selected={selectedMinute}
onSelect={(n) => setSelectedMinute(n)}
/>
<div className="absolute bottom-4 left-0 right-0 bg-gradient-to-b from-transparent to-grey-0 h-xlarge z-10" />
</PopoverPrimitive.Content>
</PopoverPrimitive.Root>
</div>
)
}
export default TimePicker

View File

@@ -0,0 +1,6 @@
import { InputHeaderProps } from "../../fundamentals/input-header"
export type DateTimePickerProps = {
date: Date
onSubmitDate: (newDate: Date) => void
} & InputHeaderProps

View File

@@ -0,0 +1,25 @@
export const range = (start, end) => {
const range: number[] = []
for (let i = start; i <= end; i++) {
range.push(i)
}
return range
}
export const getYearRange = (step = 20) =>
range(new Date().getFullYear() - step, new Date().getFullYear() + step)
export const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
]

View File

@@ -0,0 +1,47 @@
import clsx from "clsx"
import React, { useEffect, useState } from "react"
type FadeProps = {
isVisible: boolean
isFullScreen?: boolean
start?: string
transitionClass?: string
end?: string
classname?: string
children?: React.ReactNode
}
const Fade: React.FC<FadeProps> = ({
isVisible,
start,
end,
classname,
children,
isFullScreen = false,
}) => {
const [show, setShow] = useState(false)
useEffect(() => {
if (show && !isVisible) {
setTimeout(() => setShow(false), 100)
} else {
setShow(isVisible)
}
})
const classes = {
[start || "scale-[0.98] opacity-0"]: !isVisible,
[end || "scale-100 opacity-100"]: isVisible,
"absolute inset-0": show && isFullScreen,
}
return (
<div
className={clsx("transition-all duration-100 z-50", classes, classname)}
>
{show ? children : null}
</div>
)
}
export default Fade

View File

@@ -0,0 +1,105 @@
import clsx from "clsx"
import React, { useRef, useState } from "react"
type FileUploadFieldProps = {
onFileChosen: (files: File[]) => void
filetypes: string[]
errorMessage?: string
placeholder?: React.ReactElement | string
className?: string
multiple?: boolean
text?: React.ReactElement | string
}
const defaultText = (
<span>
Drop your images here, or{" "}
<span className="text-violet-60">click to browse</span>
</span>
)
const FileUploadField: React.FC<FileUploadFieldProps> = ({
onFileChosen,
filetypes,
errorMessage,
className,
text = defaultText,
placeholder = "",
multiple = false,
}) => {
const inputRef = useRef<HTMLInputElement>(null)
const [fileUploadError, setFileUploadError] = useState(false)
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const fileList = e.target.files
if (fileList) {
onFileChosen(Array.from(fileList))
}
}
const handleFileDrop = (e: React.DragEvent<HTMLDivElement>) => {
setFileUploadError(false)
e.preventDefault()
const files: File[] = []
if (e.dataTransfer.items) {
// Use DataTransferItemList interface to access the file(s)
for (let i = 0; i < e.dataTransfer.items.length; i++) {
// If dropped items aren't files, reject them
if (e.dataTransfer.items[i].kind === "file") {
const file = e.dataTransfer.items[i].getAsFile()
if (file && filetypes.indexOf(file.type) > -1) {
files.push(file)
}
}
}
} else {
// Use DataTransfer interface to access the file(s)
for (let i = 0; i < e.dataTransfer.files.length; i++) {
if (filetypes.indexOf(e.dataTransfer.files[i].type) > -1) {
files.push(e.dataTransfer.files[i])
}
}
}
if (files.length === 1) {
onFileChosen(files)
} else {
setFileUploadError(true)
}
}
return (
<div
onClick={() => inputRef?.current?.click()}
onDrop={handleFileDrop}
onDragOver={(e) => e.preventDefault()}
className={clsx(
"flex flex-col select-none inter-base-regular text-grey-50 cursor-pointer items-center justify-center w-full h-full rounded-rounded border-2 border-dashed border-grey-20 transition-colors hover:border-violet-60 hover:text-grey-40",
className
)}
>
<div className="flex flex-col items-center">
<p>{text}</p>
{placeholder}
</div>
{fileUploadError && (
<span className="text-rose-60">
{errorMessage || "Please upload an image file"}
</span>
)}
<input
ref={inputRef}
accept={filetypes.join(", ")}
multiple={multiple}
type="file"
onChange={handleFileUpload}
className="hidden"
/>
</div>
)
}
export default FileUploadField

View File

@@ -0,0 +1,26 @@
import clsx from "clsx"
import React from "react"
import TaxesIcon from "../../fundamentals/icons/taxes-icon"
import Tooltip from "../tooltip"
type Props = {
includesTax?: boolean
}
const IncludesTaxTooltip = ({ includesTax }: Props) => {
return (
<Tooltip content={includesTax ? "Tax incl. price" : "Tax excl. price"}>
<div className="w-large h-large rounded-rounded border border-grey-20 flex items-center justify-center">
<TaxesIcon
size={16}
className={clsx({
"text-grey-50": includesTax,
"text-grey-30": !includesTax,
})}
/>
</div>
</Tooltip>
)
}
export default IncludesTaxTooltip

View File

@@ -0,0 +1,73 @@
import { ErrorMessage } from "@hookform/error-message"
import clsx from "clsx"
import React from "react"
import { MultipleFieldErrors } from "react-hook-form"
import Tooltip from "../tooltip"
type InputErrorProps = {
errors?: { [x: string]: unknown }
name?: string
className?: string
}
const InputError = ({ errors, name, className }: InputErrorProps) => {
if (!errors || !name) {
return null
}
return (
<ErrorMessage
name={name}
errors={errors}
render={({ message, messages }) => {
return (
<div
className={clsx("text-rose-50 inter-small-regular mt-2", className)}
>
{messages ? (
<MultipleMessages messages={messages} />
) : (
<p>{message}</p>
)}
</div>
)
}}
/>
)
}
const MultipleMessages = ({ messages }: { messages: MultipleFieldErrors }) => {
const errors = Object.entries(messages).map(([_, message]) => message)
const displayedError = errors[0]
const remainderErrors = errors.slice(1)
return (
<div className="flex items-center gap-x-1 cursor-default">
<p>{displayedError}</p>
{remainderErrors?.length > 0 && (
<Tooltip
content={
<div className="text-rose-50 inter-small-regular">
{remainderErrors.map((e, i) => {
return (
<p key={i}>
{Array.from(Array(i + 1)).map((_) => "*")}
{e}
</p>
)
})}
</div>
}
>
<p>
+{remainderErrors.length}{" "}
{remainderErrors.length > 1 ? "errors" : "error"}
</p>
</Tooltip>
)}
</div>
)
}
export default InputError

View File

@@ -0,0 +1,32 @@
import * as React from "react"
import Spinner from "../spinner"
type LoadingContainerProps = {
isLoading: boolean
placeholder?: React.ReactElement
children: React.ReactElement | React.ReactElement[]
}
const LoadingContainer = ({
isLoading,
children,
placeholder,
...props
}: LoadingContainerProps) => {
placeholder = placeholder || <Spinner size="large" variant="secondary" />
if (isLoading) {
return (
<div
className="pt-2xlarge flex min-h-[756px] w-full items-center justify-center"
{...props}
>
{placeholder}
</div>
)
}
return children as React.ReactElement
}
export default LoadingContainer

View File

@@ -0,0 +1,62 @@
import React from "react"
import type { Toast } from "react-hot-toast"
import { toast as globalToast } from "react-hot-toast"
import AlertIcon from "../../fundamentals/icons/alert-icon"
import CheckCircleIcon from "../../fundamentals/icons/check-circle-icon"
import CrossIcon from "../../fundamentals/icons/cross-icon"
import InfoIcon from "../../fundamentals/icons/info-icon"
import XCircleIcon from "../../fundamentals/icons/x-circle-icon"
import ToasterContainer from "../toaster-container"
export type NotificationTypes = "success" | "warning" | "error" | "info"
type NotificationProps = {
toast: Toast
type: NotificationTypes
title: string
message: string
}
const Notification: React.FC<NotificationProps> = ({
toast,
type,
title,
message,
}) => {
const onDismiss = () => {
globalToast.dismiss(toast.id)
}
return (
<ToasterContainer visible={toast.visible} className="w-[380px]">
<div>{getIcon(type)}</div>
<div className="flex flex-col ml-small mr-base gap-y-2xsmall flex-grow text-white">
<span className="inter-small-semibold">{title}</span>
<span className="inter-small-regular text-grey-20">{message}</span>
</div>
<div>
<button onClick={onDismiss}>
<CrossIcon size={ICON_SIZE} className="text-grey-40" />
</button>
<span className="sr-only">Close</span>
</div>
</ToasterContainer>
)
}
const ICON_SIZE = 20
function getIcon(type: NotificationTypes) {
switch (type) {
case "success":
return <CheckCircleIcon size={ICON_SIZE} className="text-emerald-40" />
case "warning":
return <AlertIcon size={ICON_SIZE} className="text-orange-40" />
case "error":
return <XCircleIcon size={ICON_SIZE} className="text-rose-40" />
default:
return <InfoIcon size={ICON_SIZE} className="text-grey-40" />
}
}
export default Notification

View File

@@ -0,0 +1,47 @@
import clsx from "clsx"
import React from "react"
type NumberScrollerProps = {
numbers: number[]
selected: number
onSelect: (value: number) => void
} & React.HTMLAttributes<HTMLDivElement>
const NumberScroller: React.FC<NumberScrollerProps> = ({
numbers,
selected,
onSelect,
className,
...props
}) => {
return (
<div
{...props}
className={clsx(
"flex flex-col time-list h-[305px] overflow-y-auto",
className
)}
>
{numbers.map((n, i) => {
return (
<div
key={i}
className={clsx(
"w-[40px] h-[40px] last:mb-4 rounded inter-base-regular hover:bg-grey-20",
{
"bg-violet-60 hover:bg-violet-50 text-grey-0 inter-base-semibold":
n === selected,
}
)}
>
<button onClick={() => onSelect(n)} className="w-full h-full py-2">
{n.toLocaleString("en-US", { minimumIntegerDigits: 2 })}
</button>
</div>
)
})}
</div>
)
}
export default NumberScroller

View File

@@ -0,0 +1,50 @@
import React from "react"
type OSShortcutProps = {
winModifiers: string | string[]
macModifiers: string | string[]
keys: string[] | string
}
const OSShortcut = ({ winModifiers, macModifiers, keys }: OSShortcutProps) => {
const isMac =
typeof window !== "undefined" &&
navigator?.platform?.toUpperCase().indexOf("MAC") >= 0
? true
: false
let modifiers: string
if (isMac) {
if (Array.isArray(macModifiers)) {
modifiers = macModifiers.join("")
} else {
modifiers = macModifiers
}
} else {
if (Array.isArray(winModifiers)) {
modifiers = winModifiers.join(" + ")
} else {
modifiers = winModifiers
}
}
let input: string
if (Array.isArray(keys)) {
input = keys.join(" + ")
} else {
input = keys
}
return (
<div className="flex items-center text-grey-40">
<p className="m-0 inter-base-semibold">
<span className="inter-base-semibold">{modifiers} </span>
{input}
</p>
</div>
)
}
export default OSShortcut

View File

@@ -0,0 +1,20 @@
import React from "react"
type PageDescriptionProps = {
title?: string
subtitle?: string
}
const PageDescription: React.FC<PageDescriptionProps> = ({
title,
subtitle,
}) => {
return (
<div className="mb-xlarge">
<h1 className="inter-2xlarge-semibold mb-xsmall">{title}</h1>
<h2 className="inter-base-regular text-grey-50">{subtitle}</h2>
</div>
)
}
export default PageDescription

View File

@@ -0,0 +1,49 @@
import React, { useEffect } from "react"
import type { Toast } from "react-hot-toast"
import CrossIcon from "../../fundamentals/icons/cross-icon"
import XCircleIcon from "../../fundamentals/icons/x-circle-icon"
import ToasterContainer from "../toaster-container"
type SavingStateProps = {
toast: Toast
title?: string
message?: string
onDismiss: () => void
}
const ErrorState: React.FC<SavingStateProps> = ({
toast,
title = "Error",
message = "An error occured while trying to save your changes. Please try again.",
onDismiss,
}) => {
useEffect(() => {
const life = setTimeout(() => {
onDismiss()
}, 2000)
return () => {
clearTimeout(life)
}
}, [toast])
return (
<ToasterContainer visible={toast.visible} className="w-[448px]">
<div>
<XCircleIcon size={20} className="text-rose-40" />
</div>
<div className="flex flex-col ml-small mr-base gap-y-2xsmall flex-grow">
<span className="inter-small-semibold">{title}</span>
<span className="inter-small-regular text-grey-50">{message}</span>
</div>
<div>
<button onClick={onDismiss}>
<CrossIcon size={20} className="text-grey-40" />
</button>
<span className="sr-only">Close</span>
</div>
</ToasterContainer>
)
}
export default ErrorState

View File

@@ -0,0 +1,94 @@
import React, { ReactNode } from "react"
import type { Toast } from "react-hot-toast"
import { toast as globalToast } from "react-hot-toast"
import RefreshIcon from "../../fundamentals/icons/refresh-icon"
import ToasterContainer from "../toaster-container"
import ErrorState from "./error-state"
import SavingState from "./saving-state"
import SuccessState from "./success-state"
type SaveNotificationProps = {
toast: Toast
icon?: ReactNode
title?: string
message?: string
onSave: () => Promise<void>
reset: () => void
}
const SaveNotification: React.FC<SaveNotificationProps> = ({
toast,
icon,
title = "Unsaved changes",
message = "You have unsaved changes. Do you want to save and publish or discard them?",
onSave,
reset,
}) => {
const onDismiss = () => {
reset()
globalToast.dismiss(toast.id)
}
const handleSave = () => {
globalToast.custom((t) => <SavingState toast={t} />, {
id: toast.id,
})
onSave()
.then(() => {
globalToast.custom(
(t) => <SuccessState toast={t} onDismiss={onDismiss} />,
{
id: toast.id,
}
)
})
.catch((_err) => {
globalToast.custom(
(t) => <ErrorState toast={t} onDismiss={onDismiss} />,
{
id: toast.id,
}
)
})
}
return (
<ToasterContainer visible={toast.visible} className="p-0 pl-base w-[448px]">
<div className="py-base">{getIcon(icon)}</div>
<div className="flex flex-col ml-small mr-base gap-y-2xsmall flex-grow py-base">
<span className="inter-small-semibold">{title}</span>
<span className="inter-small-regular text-grey-50">{message}</span>
</div>
<div className="flex flex-col inter-small-semibold border-l border-grey-20 h-full">
<button
onClick={handleSave}
className="inter-small-semibold flex items-center justify-center h-1/2 border-b border-grey-20 px-base text-violet-60"
>
Publish
</button>
<button
className="inter-small-semibold flex items-center justify-center h-1/2 px-base"
onClick={onDismiss}
>
Discard
</button>
</div>
</ToasterContainer>
)
}
const ICON_SIZE = 20
function getIcon(icon?: any) {
if (icon) {
return React.cloneElement(icon, {
size: ICON_SIZE,
className: "text-grey-90",
})
} else {
return <RefreshIcon size={20} className="text-grey-90" />
}
}
export default SaveNotification

View File

@@ -0,0 +1,30 @@
import React from "react"
import type { Toast } from "react-hot-toast"
import Spinner from "../spinner"
import ToasterContainer from "../toaster-container"
type SavingStateProps = {
toast: Toast
title?: string
message?: string
}
const SavingState: React.FC<SavingStateProps> = ({
toast,
title = "Saving changes",
message = "Hang on, this may take a few moments.",
}) => {
return (
<ToasterContainer visible={toast.visible} className="w-[448px]">
<div>
<Spinner variant="secondary" size="large" />
</div>
<div className="flex flex-col ml-small mr-base gap-y-2xsmall flex-grow">
<span className="inter-small-semibold">{title}</span>
<span className="inter-small-regular text-grey-50">{message}</span>
</div>
</ToasterContainer>
)
}
export default SavingState

View File

@@ -0,0 +1,49 @@
import React, { useEffect } from "react"
import type { Toast } from "react-hot-toast"
import CheckCircleIcon from "../../fundamentals/icons/check-circle-icon"
import CrossIcon from "../../fundamentals/icons/cross-icon"
import ToasterContainer from "../toaster-container"
type SavingStateProps = {
toast: Toast
title?: string
message?: string
onDismiss: () => void
}
const SuccessState: React.FC<SavingStateProps> = ({
toast,
title = "Success",
message = "Your changes have been saved.",
onDismiss,
}) => {
useEffect(() => {
const life = setTimeout(() => {
onDismiss()
}, 2000)
return () => {
clearTimeout(life)
}
}, [toast])
return (
<ToasterContainer visible={toast.visible} className="w-[448px]">
<div>
<CheckCircleIcon size={20} className="text-emerald-40" />
</div>
<div className="flex flex-col ml-small mr-base gap-y-2xsmall flex-grow">
<span className="inter-small-semibold">{title}</span>
<span className="inter-small-regular text-grey-50">{message}</span>
</div>
<div>
<button onClick={onDismiss}>
<CrossIcon size={20} className="text-grey-40" />
</button>
<span className="sr-only">Close</span>
</div>
</ToasterContainer>
)
}
export default SuccessState

View File

@@ -0,0 +1,56 @@
import React from "react"
import { Link } from "react-router-dom"
import ChevronRightIcon from "../../fundamentals/icons/chevron-right-icon"
type SettingsCardProps = {
icon: React.ReactNode
heading: string
description: string
to?: string
externalLink?: string
disabled?: boolean
}
const SettingsCard: React.FC<SettingsCardProps> = ({
icon,
heading,
description,
to = null,
externalLink = null,
disabled = false,
}) => {
if (disabled) {
to = null
}
return (
<Link to={to ?? ""} className="flex flex-1 items-center">
<button
className="bg-grey-0 rounded-rounded p-large border-grey-20 group flex h-full flex-1 items-center border"
disabled={disabled}
onClick={() => {
if (externalLink) {
window.location.href = externalLink
}
}}
>
<div className="h-2xlarge w-2xlarge bg-violet-20 rounded-circle text-violet-60 group-disabled:bg-grey-10 group-disabled:text-grey-40 flex items-center justify-center">
{icon}
</div>
<div className="mx-large flex-1 text-left">
<h3 className="inter-large-semibold text-grey-90 group-disabled:text-grey-40 m-0">
{heading}
</h3>
<p className="inter-base-regular text-grey-50 group-disabled:text-grey-40 m-0">
{description}
</p>
</div>
<div className="text-grey-40 group-disabled:text-grey-30">
<ChevronRightIcon />
</div>
</button>
</Link>
)
}
export default SettingsCard

View File

@@ -0,0 +1,25 @@
import clsx from "clsx"
import { PropsWithChildren } from "react"
import { useSkeleton } from "../../../providers/skeleton-provider"
type Props = PropsWithChildren<{
isLoading?: boolean
}>
const Skeleton = ({ children, isLoading }: Props) => {
const { isLoading: providerState = false } = useSkeleton()
const state = isLoading || providerState
return (
<div
className={clsx("h-fit w-fit", {
"bg-grey-10 rounded-rounded animate-pulse [&>*]:opacity-0": state,
})}
>
{children}
</div>
)
}
export default Skeleton

View File

@@ -0,0 +1,35 @@
import clsx from "clsx"
import React from "react"
type SpinnerProps = {
size?: "large" | "medium" | "small"
variant?: "primary" | "secondary"
}
const Spinner: React.FC<SpinnerProps> = ({
size = "large",
variant = "primary",
}) => {
return (
<div
className={clsx(
"flex items-center justify-center",
{ "h-[24px] w-[24px]": size === "large" },
{ "h-[20px] w-[20px]": size === "medium" },
{ "h-[16px] w-[16px]": size === "small" }
)}
>
<div className="flex items-center justify-center relative w-full h-full">
<div
className={clsx(
"animate-ring border-2 h-4/5 w-4/5 rounded-circle border-transparent",
{ "border-t-grey-0": variant === "primary" },
{ "border-t-violet-60": variant === "secondary" }
)}
/>
</div>
</div>
)
}
export default Spinner

View File

@@ -0,0 +1,26 @@
import * as RadixSwitch from "@radix-ui/react-switch"
import clsx from "clsx"
import React from "react"
/**
* A controlled switch component atom.
*/
function Switch(props: RadixSwitch.SwitchProps) {
return (
<RadixSwitch.Root
{...props}
disabled={props.disabled}
className={clsx(
"w-8 h-[18px] rounded-full transition-bg bg-gray-300 radix-state-checked:bg-violet-60"
)}
>
<RadixSwitch.Thumb
className={clsx(
"w-2 h-2 bg-white rounded-full block transition-transform translate-x-[5px] radix-state-checked:translate-x-[19px]"
)}
/>
</RadixSwitch.Root>
)
}
export default Switch

View File

@@ -0,0 +1,19 @@
import React from "react"
import clsx from "clsx"
type TextInputProps = React.InputHTMLAttributes<HTMLInputElement>
const TextInput = React.forwardRef<HTMLInputElement, TextInputProps>(
({ className, ...props }, ref) => (
<input
ref={ref}
className={clsx(
"placeholder:inter-base-regular placeholder-grey-40 focus:outline-none",
className
)}
{...props}
/>
)
)
export default TextInput

View File

@@ -0,0 +1 @@
export { Thumbnail as default } from "./thumbnail"

View File

@@ -0,0 +1,30 @@
import clsx from "clsx"
import ImagePlaceholderIcon from "../../fundamentals/icons/image-placeholder-icon"
type Props = {
src?: string | null
className?: string
size?: "small" | "medium" | "large"
}
export const Thumbnail = ({ src, className, size = "small" }: Props) => {
return (
<div
className={clsx(
"bg-grey-5 flex items-center justify-center overflow-hidden rounded-rounded",
{
"w-[30px] h-10": size === "small",
"w-9 h-12": size === "medium",
"w-[170px] h-[226px]": size === "large",
},
className
)}
>
{src ? (
<img src={src} className="object-cover object-center flex-1" />
) : (
<ImagePlaceholderIcon />
)}
</div>
)
}

View File

@@ -0,0 +1,33 @@
import clsx from "clsx"
import React from "react"
type ToasterContainerProps = {
visible: boolean
} & React.HTMLAttributes<HTMLDivElement>
const ToasterContainer: React.FC<ToasterContainerProps> = ({
children,
visible,
className,
...rest
}) => {
return (
<div
className={clsx(
"flex items-start bg-grey-90 p-base border rounded-rounded shadow-toaster mb-xsmall last:mb-0",
{
"animate-enter": visible,
},
{
"animate-leave": !visible,
},
className
)}
{...rest}
>
{children}
</div>
)
}
export default ToasterContainer

View File

@@ -0,0 +1,58 @@
import * as RadixTooltip from "@radix-ui/react-tooltip"
import clsx from "clsx"
import React from "react"
export type TooltipProps = RadixTooltip.TooltipContentProps &
Pick<
RadixTooltip.TooltipProps,
"open" | "defaultOpen" | "onOpenChange" | "delayDuration"
> & {
content: React.ReactNode
side?: "bottom" | "left" | "top" | "right"
onClick?: React.ButtonHTMLAttributes<HTMLButtonElement>["onClick"]
}
const Tooltip = ({
children,
content,
open,
defaultOpen,
onOpenChange,
delayDuration,
className,
side,
onClick,
...props
}: TooltipProps) => {
return (
<RadixTooltip.Provider delayDuration={100}>
<RadixTooltip.Root
open={open}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}
delayDuration={delayDuration}
>
<RadixTooltip.Trigger onClick={onClick} asChild={true}>
<span>{children}</span>
</RadixTooltip.Trigger>
<RadixTooltip.Content
side={side ?? "bottom"}
sideOffset={8}
align="center"
className={clsx(
"inter-small-semibold text-grey-50",
"bg-grey-0 py-2 px-3 shadow-dropdown rounded-rounded",
"border border-solid border-grey-20",
"max-w-[220px]",
className
)}
{...props}
>
{content}
</RadixTooltip.Content>
</RadixTooltip.Root>
</RadixTooltip.Provider>
)
}
export default Tooltip

View File

@@ -0,0 +1,110 @@
import clsx from "clsx"
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react"
import TrashIcon from "../../fundamentals/icons/trash-icon"
import Spinner from "../spinner"
import Tooltip from "../tooltip"
type Props = {
onDelete: () => void
deleting?: boolean
className?: string
children?: React.ReactNode
}
const TwoStepDelete = forwardRef<HTMLButtonElement, Props>(
({ onDelete, deleting = false, children, className }: Props, ref) => {
const [armed, setArmed] = useState(false)
const innerRef = useRef<HTMLButtonElement>(null)
useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(
ref,
() => innerRef.current
)
const handleTwoStepDelete = () => {
if (!armed) {
setArmed(true)
return
}
onDelete()
setArmed(false)
}
const disarmOnClickOutside = useCallback(
(e: MouseEvent) => {
if (innerRef.current && !innerRef.current.contains(e.target as Node)) {
if (armed) {
setArmed(false)
}
}
},
[armed]
)
useEffect(() => {
document.addEventListener("mousedown", disarmOnClickOutside)
return () => {
document.removeEventListener("mousedown", disarmOnClickOutside)
}
}, [disarmOnClickOutside])
return (
<button
className={clsx(
"transition-all rounded-lg border flex items-center justify-center",
{
"bg-rose-50 border-rose-50 px-3 py-1.5": armed,
"bg-transparent border-gray-20 p-1.5": !armed,
},
{
"!bg-grey-40 !border-grey-40 !p-1.5": deleting,
},
className
)}
disabled={deleting}
onClick={handleTwoStepDelete}
ref={innerRef}
>
<div
className={clsx("text-rose-50 inter-small-semibold", {
hidden: armed || deleting,
})}
>
{children || <TrashIcon className="text-grey-50" size={20} />}
</div>
<span
className={clsx("text-white inter-small-semibold", {
hidden: !armed || deleting,
})}
>
<Tooltip
content="Are you sure?"
side="top"
sideOffset={16}
open={armed}
>
Confirm
</Tooltip>
</span>
<span
className={clsx("flex items-center justify-center", {
hidden: !deleting,
})}
>
<Spinner size="medium" />
</span>
</button>
)
}
)
export default TwoStepDelete

View File

@@ -0,0 +1,23 @@
import * as React from "react"
import { toast, ToastOptions } from "react-hot-toast"
export type ToasterProps = {
visible: boolean
children: React.ReactElement
} & ToastOptions
const Toaster = ({ visible, children, ...options }: ToasterProps) => {
React.useEffect(() => {
if (visible) {
toast.custom((t) => React.cloneElement(children, { toast: t }), {
...options,
})
} else {
toast.dismiss(options.id)
}
}, [visible, children])
return null
}
export default Toaster

View File

@@ -0,0 +1,43 @@
import clsx from "clsx"
import React from "react"
type BadgeProps = {
variant:
| "primary"
| "danger"
| "success"
| "warning"
| "ghost"
| "default"
| "disabled"
} & React.HTMLAttributes<HTMLDivElement>
const Badge: React.FC<BadgeProps> = ({
children,
variant,
onClick,
className,
...props
}) => {
const variantClassname = clsx({
["badge-primary"]: variant === "primary",
["badge-danger"]: variant === "danger",
["badge-success"]: variant === "success",
["badge-warning"]: variant === "warning",
["badge-ghost"]: variant === "ghost",
["badge-default"]: variant === "default",
["badge-disabled"]: variant === "disabled",
})
return (
<div
className={clsx("badge", variantClassname, className)}
onClick={onClick}
{...props}
>
{children}
</div>
)
}
export default Badge

View File

@@ -0,0 +1,71 @@
import clsx from "clsx"
import React, { Children } from "react"
import Spinner from "../../atoms/spinner"
export type ButtonProps = {
variant: "primary" | "secondary" | "ghost" | "danger" | "nuclear"
size?: "small" | "medium" | "large"
loading?: boolean
} & React.ButtonHTMLAttributes<HTMLButtonElement>
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
variant = "primary",
size = "large",
loading = false,
children,
...attributes
},
ref
) => {
const handleClick = (e) => {
if (!loading && attributes.onClick) {
attributes.onClick(e)
}
}
const variantClassname = clsx({
["btn-primary"]: variant === "primary",
["btn-secondary"]: variant === "secondary",
["btn-ghost"]: variant === "ghost",
["btn-danger"]: variant === "danger",
["btn-nuclear"]: variant === "nuclear",
})
const sizeClassname = clsx({
["btn-large"]: size === "large",
["btn-medium"]: size === "medium",
["btn-small"]: size === "small",
})
return (
<button
{...attributes}
className={clsx(
"btn",
variantClassname,
sizeClassname,
attributes.className
)}
disabled={attributes.disabled || loading}
ref={ref}
onClick={handleClick}
>
{loading ? (
<Spinner size={size} variant={"secondary"} />
) : (
Children.map(children, (child, i) => {
return (
<span key={i} className="mr-xsmall last:mr-0">
{child}
</span>
)
})
)}
</button>
)
}
)
export default Button

View File

@@ -0,0 +1,7 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.4444 17H4.55556C3.7 17 3 16.35 3 15.5556V5.44444C3 4.65 3.7 4 4.55556 4H15.4444C16.3 4 17 4.65 17 5.44444V15.5556C17 16.35 16.3 17 15.4444 17Z" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13 2.43994V3.43994" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 2.43994V3.43994" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10 10.7632C11.1046 10.7632 12 9.86775 12 8.76318C12 7.65861 11.1046 6.76318 10 6.76318C8.89543 6.76318 8 7.65861 8 8.76318C8 9.86775 8.89543 10.7632 10 10.7632Z" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 14.2979C12.88 13.6312 11.52 13.2979 10 13.2979C8.48 13.2979 7.12 13.6979 6 14.2979" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1013 B

View File

@@ -0,0 +1,57 @@
import React from "react"
import IconProps from "../icons/types/icon-type"
const DetailsIcon: React.FC<IconProps> = ({
size = "16",
color = "currentColor",
...attributes
}) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M15.4444 17H4.55556C3.7 17 3 16.35 3 15.5556V5.44444C3 4.65 3.7 4 4.55556 4H15.4444C16.3 4 17 4.65 17 5.44444V15.5556C17 16.35 16.3 17 15.4444 17Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M13 2.43994V3.43994"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7 2.43994V3.43994"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10 10.7632C11.1046 10.7632 12 9.86775 12 8.76318C12 7.65861 11.1046 6.76318 10 6.76318C8.89543 6.76318 8 7.65861 8 8.76318C8 9.86775 8.89543 10.7632 10 10.7632Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14 14.2979C12.88 13.6312 11.52 13.2979 10 13.2979C8.48 13.2979 7.12 13.6979 6 14.2979"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default DetailsIcon

View File

@@ -0,0 +1,21 @@
import React from "react"
import { useFeatureFlag } from "../../providers/feature-flag-provider"
export type FeatureToggleProps = {
featureFlag: string
showOnlyWhenDisabled?: boolean
children?: React.ReactNode
}
const FeatureToggle: React.FC<FeatureToggleProps> = ({
featureFlag,
showOnlyWhenDisabled = false,
children,
}) => {
const { isFeatureEnabled } = useFeatureFlag()
const showContent = isFeatureEnabled(featureFlag) === !showOnlyWhenDisabled
return showContent ? <>{children}</> : null
}
export default FeatureToggle

View File

@@ -0,0 +1,36 @@
import clsx from "clsx"
import React from "react"
import Badge from "../badge"
type IconBadgeProps = {
variant?:
| "primary"
| "danger"
| "success"
| "warning"
| "ghost"
| "default"
| "disabled"
} & React.HTMLAttributes<HTMLDivElement>
const IconBadge: React.FC<IconBadgeProps> = ({
children,
variant,
className,
...rest
}) => {
return (
<Badge
variant={variant ?? "default"}
className={clsx(
"flex items-center justify-center aspect-square w-[40px] h-[40px] border-2 border-white outline outline-1 outline-grey-20",
className
)}
{...rest}
>
{children}
</Badge>
)
}
export default IconBadge

View File

@@ -0,0 +1,43 @@
import React from "react"
import IconProps from "../types/icon-type"
const AlertIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M10 17.5C14.1421 17.5 17.5 14.1421 17.5 10C17.5 5.85786 14.1421 2.5 10 2.5C5.85786 2.5 2.5 5.85786 2.5 10C2.5 14.1421 5.85786 17.5 10 17.5Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10 6.66669V10"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10 13.3333H10.0088"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default AlertIcon

View File

@@ -0,0 +1,36 @@
import React from "react"
import IconProps from "./types/icon-type"
const ArrowDownIcon: React.FC<IconProps> = ({
size = "24px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M8 3.33331V12.6666"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.667 8L8.00033 12.6667L3.33366 8"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ArrowDownIcon

View File

@@ -0,0 +1,35 @@
import React from "react"
import IconProps from "../types/icon-type"
const ArrowLeftIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M3.75 10H16.875"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M8.125 5L3.125 10L8.125 15"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ArrowLeftIcon

View File

@@ -0,0 +1,36 @@
import React from "react"
import IconProps from "../types/icon-type"
const ArrowRightIcon: React.FC<IconProps> = ({
size = "16",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M3.33301 8H12.6663"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8 3.33331L12.6667 7.99998L8 12.6666"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ArrowRightIcon

View File

@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "../types/icon-type"
const ArrowTopRightIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M15.0129 4.98792L4.93799 15.0628M15.0129 4.98792L7.93994 4.93713M15.0129 4.98792L15.0636 12.0608"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ArrowTopRightIcon

View File

@@ -0,0 +1,36 @@
import React from "react"
import IconProps from "./types/icon-type"
const ArrowUpIcon: React.FC<IconProps> = ({
size = "24px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M8 12.6667V3.33335"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3.33301 8L7.99967 3.33333L12.6663 8"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ArrowUpIcon

View File

@@ -0,0 +1,36 @@
import React from "react"
import IconProps from "../types/icon-type"
const BackIcon: React.FC<IconProps> = ({
size = "24",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M3.33301 2.91669V7.91669H8.33301"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3.41396 11.6784C3.57168 13.1642 4.14444 13.9943 5.18922 15.0653C6.23401 16.1363 7.6093 16.8262 9.0944 17.0244C10.5795 17.2226 12.0883 16.9175 13.3787 16.1581C14.6691 15.3988 15.6663 14.2291 16.2103 12.8369C16.7542 11.4446 16.8134 9.91052 16.3783 8.48073C15.9432 7.05094 15.039 5.80836 13.8109 4.95238C12.5828 4.0964 11.1019 3.67666 9.60596 3.76051C8.10998 3.84436 6.68561 4.42693 5.56142 5.41475L3.33301 7.41474"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default BackIcon

View File

@@ -0,0 +1,42 @@
import React from "react"
import IconProps from "../types/icon-type"
const BackspaceIcon: React.FC<IconProps> = ({
size = "16",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.5 6.66675L10.1667 9.33341"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.1667 6.66675L7.5 9.33341"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.56137 11.8559L2.55004 9.49392H2.55004C1.81665 8.63293 1.81665 7.36691 2.55004 6.50592L4.56137 4.14392V4.14392C4.9991 3.62949 5.64058 3.33312 6.31604 3.33325H11.6954V3.33325C12.9682 3.33325 14 4.36509 14 5.63792C14 5.63792 14 5.63792 14 5.63792V10.3619V10.3619C14 11.6348 12.9682 12.6666 11.6954 12.6666H6.31604V12.6666C5.64058 12.6667 4.99911 12.3704 4.56137 11.8559V11.8559Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default BackspaceIcon

View File

@@ -0,0 +1,36 @@
import React from "react"
import IconProps from "../types/icon-type"
const BellIcon: React.FC<IconProps> = ({
size = "24",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M14 20C13.7968 20.3042 13.505 20.5566 13.154 20.7321C12.803 20.9076 12.4051 21 12 21C11.5949 21 11.197 20.9076 10.846 20.7321C10.495 20.5566 10.2032 20.3042 10 20"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.3333 8.2C17.3333 6.82087 16.7714 5.49823 15.7712 4.52304C14.771 3.54786 13.4145 3 12 3C10.5855 3 9.22896 3.54786 8.22876 4.52304C7.22857 5.49823 6.66667 6.82087 6.66667 8.2C6.66667 14.2667 4 16 4 16H20C20 16 17.3333 14.2667 17.3333 8.2Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default BellIcon

View File

@@ -0,0 +1,41 @@
import React from "react"
import IconProps from "../types/icon-type"
type IBellNotiIconProps = IconProps & {
accentColor?: string
}
const BellNotiIcon: React.FC<IBellNotiIconProps> = ({
size = "24px",
color = "currentColor",
accentColor = "#F43F5E",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M14 20C13.7968 20.3042 13.505 20.5566 13.154 20.7321C12.803 20.9076 12.4051 21 12 21C11.5949 21 11.197 20.9076 10.846 20.7321C10.495 20.5566 10.2032 20.3042 10 20"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M18.4735 11.7793C18.0077 11.9228 17.5129 12 17.0001 12C16.9947 12 16.9894 12 16.9841 12C17.2526 13.1526 17.6265 14.0517 18.0122 14.7412C18.1157 14.9262 18.2197 15.0955 18.3223 15.25H5.67782C5.78041 15.0955 5.88443 14.9262 5.98793 14.7412C6.72391 13.4256 7.41673 11.347 7.41673 8.2C7.41673 7.026 7.89488 5.89613 8.7524 5.06004C9.61057 4.22333 10.7784 3.75 12.0001 3.75C12.372 3.75 12.7391 3.79389 13.0938 3.87856C13.4571 3.42462 13.8978 3.03537 14.3959 2.73085C13.6453 2.41632 12.831 2.25 12.0001 2.25C10.3927 2.25 8.84747 2.87238 7.70525 3.98604C6.56238 5.10034 5.91673 6.61575 5.91673 8.2C5.91673 8.46632 5.9114 8.7235 5.90126 8.97181L3.58406 15.3759C3.31067 15.5581 3.18742 15.8975 3.28099 16.2132C3.37538 16.5316 3.66795 16.75 4.00006 16.75H20.0001C20.3322 16.75 20.6247 16.5316 20.7191 16.2132C20.8127 15.8975 20.6895 15.5582 20.4161 15.376M3.59055 15.3713L3.59037 15.3718L3.58947 15.3724L3.58787 15.3734L3.58537 15.3751L3.58406 15.3759L3.58945 15.3721C3.5898 15.3718 3.59017 15.3716 3.59055 15.3713ZM3.59055 15.3713L5.90099 8.97837C5.79921 11.4485 5.22124 13.0393 4.67886 14.0088C4.37901 14.5448 4.08562 14.8989 3.87883 15.1117C3.77524 15.2183 3.69289 15.29 3.6417 15.3316C3.61711 15.3516 3.5997 15.3646 3.59055 15.3713ZM20.4108 15.3725L20.4123 15.3734L20.4148 15.3751L20.4157 15.3757C20.4146 15.3749 20.413 15.3737 20.4107 15.3721C20.4019 15.3657 20.384 15.3524 20.3584 15.3316C20.3072 15.29 20.2249 15.2183 20.1213 15.1117C19.9145 14.8989 19.6211 14.5448 19.3213 14.0088C19.0206 13.4713 18.7089 12.7428 18.4735 11.7793M5.90099 8.97837L7.70525 3.98604L5.90126 8.97181C5.90117 8.974 5.90108 8.97619 5.90099 8.97837Z"
fill={color}
/>
<circle cx="17" cy="7" r="3" fill={accentColor} />
</svg>
)
}
export default BellNotiIcon

View File

@@ -0,0 +1,57 @@
import React from "react"
import IconProps from "../types/icon-type"
const BellOffIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M11.2971 16.7566C11.1653 16.9839 10.976 17.1726 10.7483 17.3037C10.5206 17.4349 10.2624 17.5039 9.99964 17.5039C9.73686 17.5039 9.47869 17.4349 9.25097 17.3037C9.02326 17.1726 8.83401 16.9839 8.70215 16.7566"
stroke={color}
strokeWidth="1.49999"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.9723 10.7567C14.6386 9.53526 14.4795 8.27274 14.4998 7.00671"
stroke={color}
strokeWidth="1.49999"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.69498 5.7016C5.56468 6.1243 5.49894 6.56426 5.49998 7.00659C5.49998 12.2566 3.25 13.7565 3.25 13.7565H13.7499"
stroke={color}
strokeWidth="1.49999"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.4994 7.00665C14.5006 6.19145 14.2804 5.39124 13.8622 4.69148C13.444 3.99173 12.8435 3.41871 12.125 3.03365C11.4065 2.64859 10.5969 2.46595 9.78261 2.50523C8.96836 2.54451 8.18007 2.80424 7.50195 3.25667"
stroke={color}
strokeWidth="1.49999"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.5 2.50665L17.4999 17.5065"
stroke={color}
strokeWidth="1.49999"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default BellOffIcon

View File

@@ -0,0 +1,64 @@
import React from "react"
import IconProps from "../types/icon-type"
const BuildingsIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox={`0 0 20 20`}
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M17.0141 16.5422V8.79964C17.0141 8.5943 16.9325 8.39736 16.7873 8.25216C16.6421 8.10696 16.4452 8.02539 16.2398 8.02539H13.1428"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3.07739 16.5422V4.15414C3.07739 3.94879 3.15897 3.75186 3.30417 3.60666C3.44937 3.46146 3.6463 3.37988 3.85165 3.37988H12.3684C12.5738 3.37988 12.7707 3.46146 12.9159 3.60666C13.0611 3.75186 13.1427 3.94879 13.1427 4.15414V16.5422"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.98071 6.6521H10.2391"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.98071 9.96094H10.2391"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.98087 16.5422H10.2393V13.2089H5.98087V16.5422Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.7885 16.5422H2.30347"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default BuildingsIcon

View File

@@ -0,0 +1,36 @@
import React from "react"
import IconProps from "../types/icon-type"
const CancelIcon: React.FC<IconProps> = ({
size = "16",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M10 17.5C14.1421 17.5 17.5 14.1421 17.5 10C17.5 5.85786 14.1421 2.5 10 2.5C5.85786 2.5 2.5 5.85786 2.5 10C2.5 14.1421 5.85786 17.5 10 17.5Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5 5L15 15"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default CancelIcon

View File

@@ -0,0 +1,49 @@
import React from "react"
import IconProps from "../types/icon-type"
const CartIcon: React.FC<IconProps> = ({
size = "24",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M5.70291 11.7848L4.18457 5.34766H16.6736C17.3175 5.34766 17.7893 5.89045 17.633 6.45189L16.2997 11.242C16.0969 11.9695 15.4084 12.5043 14.5776 12.579L7.83552 13.1848C6.83054 13.2745 5.91162 12.6713 5.70291 11.7848V11.7848Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.18418 5.34722L3.54123 2.68213H1.83594"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.2117 15.7988C13.7978 15.7988 13.4618 16.1349 13.4659 16.5488C13.4659 16.9628 13.8019 17.2988 14.2158 17.2988C14.6298 17.2988 14.9658 16.9628 14.9658 16.5488C14.9638 16.1349 14.6277 15.7988 14.2117 15.7988Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.20104 15.7988C6.78655 15.7988 6.45003 16.1344 6.45414 16.5478C6.45003 16.9632 6.7886 17.2988 7.20309 17.2988C7.61758 17.2988 7.9541 16.9632 7.9541 16.5499C7.9541 16.1344 7.61758 15.7988 7.20104 15.7988Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default CartIcon

View File

@@ -0,0 +1,99 @@
import React from "react"
import IconProps from "../types/icon-type"
const CashIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M3.75 7.5H2.5"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3.75 12.5H2.5"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M3.33366 10H1.66699"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.69727 4.6967C5.74616 3.64781 7.08253 2.9335 8.53739 2.64411C9.99225 2.35472 11.5002 2.50325 12.8707 3.07091C14.2411 3.63856 15.4125 4.59986 16.2366 5.83323C17.0607 7.0666 17.5006 8.51664 17.5006 10C17.5006 11.4834 17.0607 12.9334 16.2366 14.1668C15.4125 15.4001 14.2411 16.3614 12.8707 16.9291C11.5002 17.4968 9.99225 17.6453 8.53739 17.3559C7.08253 17.0665 5.74616 16.3522 4.69727 15.3033"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10 6.25V7.00737"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8.125 8.52196C8.17767 8.07456 8.40336 7.66555 8.75379 7.38246C9.10421 7.09936 9.55152 6.96468 10 7.00722"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10 13.7501V12.9927"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11.875 11.4778C11.8223 11.9252 11.5966 12.3343 11.2462 12.6174C10.8958 12.9004 10.4485 13.0351 10 12.9926"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11.8273 8.19148C11.6899 7.8243 11.4381 7.51099 11.1091 7.29776C10.7801 7.08453 10.3913 6.98266 10 7.00718"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8.17188 11.8083C8.30928 12.1755 8.56105 12.4888 8.89004 12.702C9.21904 12.9152 9.60785 13.0171 9.99913 12.9926"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8.125 8.52216C8.125 8.83316 8.23276 9.13455 8.42993 9.37506C8.6271 9.61556 8.90151 9.78033 9.20647 9.84132L10.7935 10.1587C11.0985 10.2197 11.3729 10.3845 11.5701 10.625C11.7672 10.8655 11.875 11.1669 11.875 11.4779"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default CashIcon

View File

@@ -0,0 +1,65 @@
import React from "react"
import IconProps from "./types/icon-type"
const ChannelsIcon: React.FC<IconProps> = ({
size = "24px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M17.6627 7.05C18.7811 7.05 19.6877 6.14338 19.6877 5.025C19.6877 3.90662 18.7811 3 17.6627 3C16.5443 3 15.6377 3.90662 15.6377 5.025C15.6377 6.14338 16.5443 7.05 17.6627 7.05Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.6627 14.025C18.7811 14.025 19.6877 13.1184 19.6877 12C19.6877 10.8816 18.7811 9.97498 17.6627 9.97498C16.5443 9.97498 15.6377 10.8816 15.6377 12C15.6377 13.1184 16.5443 14.025 17.6627 14.025Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M5.5123 14.025C6.63068 14.025 7.5373 13.1184 7.5373 12C7.5373 10.8816 6.63068 9.97498 5.5123 9.97498C4.39393 9.97498 3.4873 10.8816 3.4873 12C3.4873 13.1184 4.39393 14.025 5.5123 14.025Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.6627 21C18.7811 21 19.6877 20.0933 19.6877 18.975C19.6877 17.8566 18.7811 16.95 17.6627 16.95C16.5443 16.95 15.6377 17.8566 15.6377 18.975C15.6377 20.0933 16.5443 21 17.6627 21Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.6369 5.02502H13.3869C12.3924 5.02502 11.5869 5.83052 11.5869 6.82502V17.175C11.5869 18.1695 12.3924 18.975 13.3869 18.975H15.6369"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.6361 12H7.53613"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ChannelsIcon

View File

@@ -0,0 +1,28 @@
import React from "react"
import IconProps from "../types/icon-type"
const CheckCircleFillIcon: React.FC<IconProps> = ({
size = "24",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M18 10C18 14.4184 14.4184 18 10 18C5.5816 18 2 14.4184 2 10C2 5.5816 5.5816 2 10 2C14.4184 2 18 5.5816 18 10ZM13.9053 8.28033C14.1982 7.98744 14.1982 7.51256 13.9053 7.21967C13.6124 6.92678 13.1376 6.92678 12.8447 7.21967L8.875 11.1893L7.15533 9.46967C6.86244 9.17678 6.38756 9.17678 6.09467 9.46967C5.80178 9.76256 5.80178 10.2374 6.09467 10.5303L8.34467 12.7803C8.63756 13.0732 9.11244 13.0732 9.40533 12.7803L13.9053 8.28033Z"
fill={color}
/>
</svg>
)
}
export default CheckCircleFillIcon

View File

@@ -0,0 +1,36 @@
import React from "react"
import IconProps from "../types/icon-type"
const CheckCircleIcon: React.FC<IconProps> = ({
size = "24",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M10 17.5C14.1422 17.5 17.5 14.1422 17.5 10C17.5 5.85775 14.1422 2.5 10 2.5C5.85775 2.5 2.5 5.85775 2.5 10C2.5 14.1422 5.85775 17.5 10 17.5Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.5 9.99998L9.16667 11.6666L12.5 8.33331"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default CheckCircleIcon

View File

@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "./types/icon-type"
const CheckIcon: React.FC<IconProps> = ({
size = "24px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M13.3334 4L6.00008 11.3333L2.66675 8"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default CheckIcon

View File

@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "./types/icon-type"
const ChevronDownIcon: React.FC<IconProps> = ({
size = "24px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M4 6L8 10L12 6"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ChevronDownIcon

View File

@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "../types/icon-type"
const ChevronLeftIcon: React.FC<IconProps> = ({
size = "24",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M9 12L5 8L9 4"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ChevronLeftIcon

View File

@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "../types/icon-type"
const ChevronRightIcon: React.FC<IconProps> = ({
size = "24",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M6 12L10 8L6 4"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ChevronRightIcon

View File

@@ -0,0 +1,29 @@
import React from "react"
import IconProps from "./types/icon-type"
const ChevronUpIcon: React.FC<IconProps> = ({
size = "24px",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M5 12.5L10 7.5L15 12.5"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ChevronUpIcon

View File

@@ -0,0 +1,50 @@
import React from "react"
import IconProps from "../types/icon-type"
const ClipboardCopyIcon: React.FC<IconProps> = ({
size = "20",
color = "currentColor",
...attributes
}) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...attributes}
>
<path
d="M12.917 4.16669H14.3753C14.7621 4.16669 15.133 4.32277 15.4065 4.6006C15.68 4.87843 15.8337 5.25526 15.8337 5.64817V8.33335M7.08366 4.16669H5.62533C5.23855 4.16669 4.86762 4.32277 4.59413 4.6006C4.32064 4.87843 4.16699 5.25526 4.16699 5.64817V16.0185C4.16699 16.4115 4.32064 16.7883 4.59413 17.0661C4.86762 17.3439 5.23855 17.5 5.62533 17.5H14.3753C14.7621 17.5 15.133 17.3439 15.4065 17.0661C15.68 16.7883 15.8337 16.4115 15.8337 16.0185V15"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11.875 2.5H8.125C7.77982 2.5 7.5 2.8731 7.5 3.33333V5C7.5 5.46024 7.77982 5.83333 8.125 5.83333H11.875C12.2202 5.83333 12.5 5.46024 12.5 5V3.33333C12.5 2.8731 12.2202 2.5 11.875 2.5Z"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17.5 11.6667H10"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.5 9.16669L10 11.6667L12.5 14.1667"
stroke={color}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)
}
export default ClipboardCopyIcon

Some files were not shown because too many files have changed in this diff Show More