Refactor imports, update dependencies, add Jenkinsfile
Some checks failed
armco-org/icon-spot/pipeline/head There was a failure building this commit
Some checks failed
armco-org/icon-spot/pipeline/head There was a failure building this commit
This commit is contained in:
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Dependencies
|
||||
node_modules
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
# Swap the comments on the following lines if you don't wish to use zero-installs
|
||||
# Documentation here: https://yarnpkg.com/features/zero-installs
|
||||
!.yarn/cache
|
||||
#.pnp.*
|
||||
|
||||
# Testing
|
||||
coverage
|
||||
|
||||
# Production
|
||||
build
|
||||
|
||||
# Miscellaneous
|
||||
*.local
|
||||
.DS_Store
|
||||
.env
|
||||
|
||||
Clusters
|
||||
helper
|
||||
IconController
|
||||
IconEditor
|
||||
IconInfo
|
||||
Icons
|
||||
IconsMerge
|
||||
IconStyleSelector
|
||||
IconTile
|
||||
IconsSlice
|
||||
IconInfoSlice
|
||||
3
Jenkinsfile
vendored
Normal file
3
Jenkinsfile
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
@Library('jenkins-shared@main') _
|
||||
|
||||
kanikoPipeline(repoName: 'icon-spot', branch: env.BRANCH_NAME ?: 'main')
|
||||
1
README.md
Normal file
1
README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Armco Template for the tech stack: React, TS, Dart Sass, Redux Tookkit, react-redux, react browser routing, TS based plop generator
|
||||
36
build-tools/build.sh
Executable file
36
build-tools/build.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get the directory of the current script
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Default values
|
||||
DEV_FLAG=""
|
||||
|
||||
# Parse arguments
|
||||
for arg in "$@"
|
||||
do
|
||||
case $arg in
|
||||
--dev)
|
||||
DEV_FLAG="--dev"
|
||||
shift # Remove --dev from processing
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "[BUILD:SH] Dev flag is: $DEV_FLAG"
|
||||
echo "[BUILD:SH] Removing build if exists"
|
||||
rm -rf build
|
||||
echo "[BUILD:SH] Checking TS Types"
|
||||
npx tsc
|
||||
echo "[BUILD:SH] Initiating build..."
|
||||
# Conditionally use vite-dev.config.ts if --dev flag is present
|
||||
if [ "$DEV_FLAG" == "--dev" ]; then
|
||||
vite build --config vite-dev.config.ts
|
||||
else
|
||||
vite build
|
||||
fi
|
||||
|
||||
echo "[BUILD:SH] Running post processor scripts..."
|
||||
# Run Post processors: Update style imports in .js files, create component modules
|
||||
node "$SCRIPT_DIR/post-processor.js" build/cjs $DEV_FLAG
|
||||
node "$SCRIPT_DIR/post-processor.js" build/es $DEV_FLAG
|
||||
54
build-tools/generate-module.js
Normal file
54
build-tools/generate-module.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { promises as fs } from "fs"
|
||||
import { dirname, resolve } from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const modules = [
|
||||
"Clusters.js",
|
||||
"helper.js",
|
||||
"IconController.js",
|
||||
"IconEditor.js",
|
||||
"IconInfo.js",
|
||||
"Icons.js",
|
||||
"IconsMerge.js",
|
||||
"IconStyleSelector.js",
|
||||
"IconTile.js",
|
||||
"IconInfo.slice.js",
|
||||
"Icons.slice.js",
|
||||
]
|
||||
|
||||
const displayModuleNames = {
|
||||
"IconInfo.slice": "IconInfoSlice",
|
||||
"Icons.slice": "IconsSlice",
|
||||
}
|
||||
|
||||
async function generateModule(fileName, isDev) {
|
||||
if (modules.includes(fileName)) {
|
||||
const dir = fileName.slice(0, -3)
|
||||
const displayModuleName = displayModuleNames[dir] || dir
|
||||
const name = `@armco/icon-spot/${displayModuleName}`
|
||||
const packageJsonContent = {
|
||||
name,
|
||||
main: `../${isDev ? "build/" : ""}cjs/${dir}.js`,
|
||||
module: `../${isDev ? "build/" : ""}es/${dir}.js`,
|
||||
types: `../${isDev ? "build/" : ""}types/${dir}.d.ts`,
|
||||
}
|
||||
const dirPath = resolve(
|
||||
__dirname,
|
||||
`../${isDev ? "" : "build/"}${displayModuleName}`,
|
||||
)
|
||||
try {
|
||||
await fs.mkdir(dirPath, { recursive: true })
|
||||
await fs.writeFile(
|
||||
resolve(dirPath, "package.json"),
|
||||
JSON.stringify(packageJsonContent, null, 2),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Error processing directory ${dirPath}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default generateModule
|
||||
25
build-tools/post-processor.js
Normal file
25
build-tools/post-processor.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { readdir } from "fs/promises"
|
||||
import generateModule from "./generate-module.js"
|
||||
|
||||
async function postProcessor(dir, isDev) {
|
||||
try {
|
||||
const files = await readdir(dir)
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
await generateModule(file, isDev)
|
||||
}),
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(`Error processing directory ${dir}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
const targetDir = process.argv[2]
|
||||
const isDev = process.argv.includes("--dev")
|
||||
|
||||
if (targetDir) {
|
||||
postProcessor(targetDir, isDev)
|
||||
} else {
|
||||
console.error("Please provide the build directory to run post processor on.")
|
||||
process.exit(1)
|
||||
}
|
||||
14
index.html
Normal file
14
index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" ar-theme="th-light-1">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>React Redux App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root" style="height:100vh;"></div>
|
||||
<script type="module" src="/src/Test.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
7770
package-lock.json
generated
Normal file
7770
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
88
package.json
Normal file
88
package.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "@armco/icon-spot",
|
||||
"version": "0.0.6",
|
||||
"type": "module",
|
||||
"main": "./build/cjs/index.js",
|
||||
"module": "./build/es/index.js",
|
||||
"types": "./build/types/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development vite --config vite-run.config.ts",
|
||||
"start": "serve -s build",
|
||||
"build": "./build-tools/build.sh",
|
||||
"build:sm": "./build-tools/build.sh --dev",
|
||||
"publish:sh": "./publish.sh"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@armco/components": "^0.0.60",
|
||||
"@armco/configs": "^0.0.11",
|
||||
"@armco/icon": "^0.0.10",
|
||||
"@armco/types": "^0.0.18",
|
||||
"@armco/utils": "^0.0.29",
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.1",
|
||||
"react-router-dom": "^6.13.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"react/jsx-no-target-blank": "off"
|
||||
}
|
||||
},
|
||||
"prettier": "prettier-config-nick",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ReStruct-Corporate-Advantage/icon-spot.git"
|
||||
},
|
||||
"keywords": [
|
||||
"components",
|
||||
"atomic",
|
||||
"building-blocks",
|
||||
"foundation"
|
||||
],
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ReStruct-Corporate-Advantage/icon-spot/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ReStruct-Corporate-Advantage/icon-spot#readme",
|
||||
"devDependencies": {
|
||||
"@armco/components": "^0.0.60",
|
||||
"@armco/configs": "^0.0.15",
|
||||
"@armco/icon": "^0.0.13",
|
||||
"@armco/shared-components": "^0.0.61",
|
||||
"@armco/types": "^0.0.22",
|
||||
"@armco/utils": "^0.0.31",
|
||||
"@reduxjs/toolkit": "^2.11.2",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^18.3.26",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"ajv": "^8.17.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"glob": "^11.0.3",
|
||||
"immer": "^11.1.3",
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router-dom": "^7.11.0",
|
||||
"sass-embedded": "^1.97.1",
|
||||
"typescript": "^5.9.3",
|
||||
"uuid": "^13.0.0",
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
"vite-plugin-externalize-deps": "^0.10.0",
|
||||
"vite-plugin-lib-inject-css": "^2.2.2",
|
||||
"vitest": "^4.0.8"
|
||||
}
|
||||
}
|
||||
34
publish.sh
Executable file
34
publish.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
semver=${1:-patch}
|
||||
|
||||
set -e
|
||||
npm --no-git-tag-version version ${semver}
|
||||
npm run build
|
||||
cp package.json build/
|
||||
|
||||
# Use Node.js for portable package.json normalization
|
||||
PKG_PATH="$(pwd)/build/package.json" node - <<'EOF'
|
||||
const fs = require('fs');
|
||||
const path = process.env.PKG_PATH;
|
||||
const pkg = JSON.parse(fs.readFileSync(path, 'utf8'));
|
||||
pkg.private = false;
|
||||
delete pkg.scripts;
|
||||
delete pkg.devDependencies;
|
||||
|
||||
if (!pkg.files) pkg.files = ['*'];
|
||||
else pkg.files = pkg.files.map(x => x === 'build' ? '*' : x);
|
||||
|
||||
['main','module','types'].forEach(k => {
|
||||
if (pkg[k]) {
|
||||
pkg[k] = pkg[k]
|
||||
.replace(/^\.?\/build\//, '')
|
||||
.replace(/^build\//, '');
|
||||
}
|
||||
});
|
||||
|
||||
fs.writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
|
||||
EOF
|
||||
|
||||
cd build
|
||||
npm publish --access public --loglevel verbose
|
||||
34
src/Clusters.tsx
Executable file
34
src/Clusters.tsx
Executable file
@@ -0,0 +1,34 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { getStatic } from "@armco/utils/network"
|
||||
|
||||
const Clusters = (): JSX.Element => {
|
||||
const [clusters, setClusters] = useState()
|
||||
useEffect(() => {
|
||||
const getClusters = async () => {
|
||||
const response = await getStatic("/icon/clusters")
|
||||
setClusters(response.body)
|
||||
}
|
||||
getClusters()
|
||||
}, [])
|
||||
return (
|
||||
<div className="ar-Clusters">
|
||||
{clusters &&
|
||||
(clusters as Array<Array<string>>).map(
|
||||
(cluster: Array<string>, index: number) => {
|
||||
return (
|
||||
<div className="ar-Clusters__cluster">
|
||||
<div className="ar-Clusters__cluster-header">
|
||||
{"Cluster" + index}
|
||||
</div>
|
||||
{cluster.map((url) => (
|
||||
<img src={url} alt="cluster-icon" width="32" />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Clusters
|
||||
28
src/FavoritesList.component.scss
Executable file
28
src/FavoritesList.component.scss
Executable file
@@ -0,0 +1,28 @@
|
||||
.ar-FavoritesList {
|
||||
max-width: 0;
|
||||
transition: max-width 0.3s;
|
||||
&.show {
|
||||
max-width: 10rem;
|
||||
}
|
||||
|
||||
.ar-FavoritesList__header {
|
||||
background-color: var(--ar-color-prominent);
|
||||
|
||||
&.loggedIn {
|
||||
background-color: var(--ar-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
&.shrink {
|
||||
max-width: 4rem;
|
||||
}
|
||||
|
||||
.ar-FavoriteItem {
|
||||
width: 0;
|
||||
transition: width 0.3s linear;
|
||||
|
||||
&.show {
|
||||
width: 5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
124
src/FavoritesList.tsx
Executable file
124
src/FavoritesList.tsx
Executable file
@@ -0,0 +1,124 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useSelector } from "react-redux"
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArPopoverPositions,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
FavoritesItemProps,
|
||||
FavoritesListProps,
|
||||
IconTileProps,
|
||||
} from "./types"
|
||||
import { useLoggedIn, usePanelContent } from "@armco/utils/hooks"
|
||||
import { Button, Popover } from "@armco/shared-components"
|
||||
import Icon from "@armco/icon"
|
||||
import { getFavorites } from "./Icons.slice"
|
||||
import IconTile from "./IconTile"
|
||||
import "./FavoritesList.component.scss"
|
||||
|
||||
const FavoriteItem = (props: FavoritesItemProps): JSX.Element | null => {
|
||||
const { index, favorite } = props
|
||||
const [shown, show] = useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
show(true)
|
||||
return () => {
|
||||
show(false)
|
||||
setTimeout(() => { }, 300)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`ar-FavoriteItem d-flex flex-column${shown ? " show" : ""}`}
|
||||
>
|
||||
<IconTile key={index} icon={favorite.icon} hideFooter hideBorder />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
const FavoritesList = (props: FavoritesListProps): JSX.Element => {
|
||||
const favorites = useSelector(getFavorites)
|
||||
const { isLoggedIn } = useLoggedIn()
|
||||
const { setPanelContent: setRightPanelContent } = usePanelContent(false)
|
||||
const icons =
|
||||
favorites &&
|
||||
favorites.map((favorite: IconTileProps, index: number) => (
|
||||
<FavoriteItem index={index} favorite={favorite} />
|
||||
))
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-FavoritesList h-100${icons?.length > 0 ? " show" : ""}${isLoggedIn ? " shrink" : ""
|
||||
}`}
|
||||
>
|
||||
<Icon icon="tb/TbLayoutSidebarRightCollapse" />
|
||||
{!isLoggedIn ? (
|
||||
<Popover
|
||||
trigger={ArPopoverTriggers.HOVER}
|
||||
position={ArPopoverPositions.LEFTBOTTOM}
|
||||
version="v1"
|
||||
>
|
||||
<span
|
||||
className="ar-FavoritesList__header p-2 border-bottom d-flex justify-content-center w-100"
|
||||
slot="anchor"
|
||||
>
|
||||
<Icon
|
||||
icon="io/IoIosWarning"
|
||||
attributes={{ colors: { fillColor: "white" } }}
|
||||
/>
|
||||
</span>
|
||||
<div slot="popover" className="p-2">
|
||||
<strong className="mb-2 d-inline-block">
|
||||
You're not logged in, all of your favorites will be lost when the
|
||||
session is over.
|
||||
</strong>
|
||||
<p className="mb-0">
|
||||
Please login to ensure your changes are saved!
|
||||
</p>
|
||||
</div>
|
||||
</Popover>
|
||||
) : (
|
||||
<Popover
|
||||
trigger={ArPopoverTriggers.HOVER}
|
||||
position={ArPopoverPositions.LEFT}
|
||||
>
|
||||
<span
|
||||
className="ar-FavoritesList__header loggedIn p-2 border-bottom d-flex justify-content-center w-100"
|
||||
slot="anchor"
|
||||
>
|
||||
<Icon
|
||||
icon="io/IoIosCheckmarkCircleOutline"
|
||||
attributes={{ colors: { fillColor: "white" } }}
|
||||
/>
|
||||
</span>
|
||||
<div slot="popover" className="p-2">
|
||||
<strong className="mb-2 d-inline-block">
|
||||
Your favorites are being synchronized with your account!
|
||||
</strong>
|
||||
</div>
|
||||
</Popover>
|
||||
)}
|
||||
<div
|
||||
className={`ar-FavoritesList__favorites flex-v-center flex-column mt-3${icons?.length > 0 ? " px-2" : ""
|
||||
}`}
|
||||
>
|
||||
{icons}
|
||||
</div>
|
||||
{!isLoggedIn && (
|
||||
<Button
|
||||
classes="mx-2"
|
||||
variant={ArButtonVariants.WARNING}
|
||||
size={ArSizes.SMALL}
|
||||
content="Login"
|
||||
preIcon="ci/CiWarning"
|
||||
onClick={() => {
|
||||
setRightPanelContent({ componentName: "LoginProvider" })
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FavoritesList
|
||||
48
src/IconController.component.scss
Executable file
48
src/IconController.component.scss
Executable file
@@ -0,0 +1,48 @@
|
||||
.ar-IconController {
|
||||
.ar-IconController__main, .ar-IconController__header {
|
||||
background-color: var(--ar-bg-base);
|
||||
}
|
||||
|
||||
.ar-IconController__color-palette {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.ar-IconController__download-button, .ar-IconController__animate-button {
|
||||
background-color: orange;
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
.ar-IconController__main {
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
.ar-IconController__controls-form {
|
||||
background-color: var(--ar-bg);
|
||||
}
|
||||
|
||||
.hover-show {
|
||||
cursor: pointer;
|
||||
.ar-IconController__download-icon {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.ar-IconController__download-icon {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 8px var(--ar-shadow);
|
||||
border-radius: 3px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ar-IconController__meta-item {
|
||||
color: var(--ar-color-secondary);
|
||||
}
|
||||
}
|
||||
375
src/IconController.tsx
Executable file
375
src/IconController.tsx
Executable file
@@ -0,0 +1,375 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useSelector } from "react-redux"
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArPopoverSlots,
|
||||
IconControllerProps,
|
||||
ObjectType,
|
||||
} from "./types"
|
||||
import { API_CONFIG } from "@armco/configs/endpoints"
|
||||
import { getStatic, post } from "@armco/utils/network"
|
||||
import { usePanelContent } from "@armco/utils/hooks"
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Select,
|
||||
Suggestions,
|
||||
Tags,
|
||||
TextInput,
|
||||
} from "@armco/shared-components"
|
||||
import Icon from "@armco/icon"
|
||||
import { getIconStyles } from "./IconInfo.slice"
|
||||
import IconEditor from "./IconEditor"
|
||||
import { complement, downloadSvg } from "./helper"
|
||||
import "./IconController.component.scss"
|
||||
|
||||
const STATIC_ROOT = API_CONFIG.STATIC_HOST[process.env.NODE_ENV]
|
||||
|
||||
const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
const { group, icon, name } = props
|
||||
const [size, setSize] = useState<string>()
|
||||
const [unit, setUnit] = useState<string>("rem")
|
||||
const [similarIcons, setSimilarIcons] = useState<Array<string>>()
|
||||
|
||||
const iconStyles = useSelector(getIconStyles)
|
||||
const { setPanelContent: setRightPanelContent } = usePanelContent(false)
|
||||
const fontColor = complement(iconStyles?.bgColor || "white")
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
// const suggestions = getSuggestions(group, name)
|
||||
|
||||
useEffect(() => {
|
||||
const filename = `${group}_${name}-black.png`
|
||||
getStatic(`/static/${filename}`).then((res) => {
|
||||
const imgBlob = res.body
|
||||
const formData = new FormData()
|
||||
const file = new File([imgBlob], filename, { type: imgBlob.type })
|
||||
formData.append("file", file)
|
||||
formData.append("count", "20")
|
||||
post(
|
||||
"http://localhost:5002/api/similar-icon-paths",
|
||||
formData,
|
||||
undefined,
|
||||
{ headers: { "Content-Type": "multipart/form-data" } },
|
||||
true,
|
||||
true,
|
||||
).then((res) => {
|
||||
if (res.status === 200) {
|
||||
const iconPaths = res.body.map((str: string) =>
|
||||
str.replace(
|
||||
"../images",
|
||||
`${API_CONFIG.STATIC_HOST[process.env.NODE_ENV]}/static`,
|
||||
),
|
||||
)
|
||||
// setSimilarIcons(iconPaths)
|
||||
post(STATIC_ROOT + "/icon/png-to-svg", {
|
||||
urls: iconPaths,
|
||||
}).then((res) => {
|
||||
setSimilarIcons(res.body)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}, [name, group])
|
||||
|
||||
return (
|
||||
<div className="ar-IconController container h-100 overflow-auto">
|
||||
{group && name && (
|
||||
<Breadcrumb
|
||||
data={[
|
||||
{ label: group, route: `/icon/${group}` },
|
||||
{ label: name, route: `/icon/${group}/${name}` },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<div className="ar-IconController__header mb-3 p-3 border flex-v-center">
|
||||
<h6 className="mb-0 fw-bold">
|
||||
{group}.{name}
|
||||
</h6>
|
||||
<Button
|
||||
classes="ar-IconController__animate-button me-3 ms-auto"
|
||||
variant={ArButtonVariants.PRIMARY}
|
||||
postIcon="md/MdAnimation"
|
||||
content="Animate"
|
||||
onClick={() =>
|
||||
navigate("/icon/animate", {
|
||||
state: { icon, iconStyles, name: `${group}_${name}` },
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
classes="ar-IconController__download-button me-3"
|
||||
variant={ArButtonVariants.PRIMARY}
|
||||
postIcon="md/MdDownload"
|
||||
content="Download"
|
||||
onClick={() =>
|
||||
downloadSvg(
|
||||
(icon as ObjectType).icon as string,
|
||||
name || "download.svg",
|
||||
{
|
||||
size: "3rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
},
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Icon
|
||||
icon="io/IoIosColorPalette"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
attributes={{
|
||||
classes:
|
||||
"ar-IconController__color-palette cursor-pointer hover-shadow",
|
||||
size: "2rem",
|
||||
colors: { fillColor: "orange" },
|
||||
}}
|
||||
events={{
|
||||
onClick: () => setRightPanelContent({ component: <IconEditor /> }),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="ar-IconController__main mb-3 p-3 border">
|
||||
<div className="row">
|
||||
<div
|
||||
className="ar-IconController__icon-sizes col"
|
||||
style={{ backgroundColor: iconStyles?.bgColor }}
|
||||
>
|
||||
{group && name && (
|
||||
<div className="row h-100 border-right">
|
||||
<div className="col flex-center flex-column border-right">
|
||||
<div className="h-50 flex-center flex-column border-bottom w-100 position-relative hover-show">
|
||||
<Icon
|
||||
icon="md/MdDownload"
|
||||
attributes={{
|
||||
classes:
|
||||
"ar-IconController__download-icon position-absolute top-1 end-1",
|
||||
colors: { fillColor: "orange" },
|
||||
size: "2.5rem",
|
||||
}}
|
||||
events={{
|
||||
onClick: () =>
|
||||
downloadSvg(
|
||||
(icon as ObjectType).icon as string,
|
||||
name,
|
||||
{
|
||||
size: "3rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
},
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Icon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
attributes={{
|
||||
size: "3rem",
|
||||
colors: {
|
||||
fillColor: iconStyles?.fillColor || "royalblue",
|
||||
strokeColor: iconStyles?.strokeColor,
|
||||
},
|
||||
strokeWidth: iconStyles?.strokeWidth,
|
||||
}}
|
||||
fillPath
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
style={{ color: fontColor || "black" }}
|
||||
>
|
||||
3rem
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-50 flex-center flex-column w-100 position-relative hover-show">
|
||||
<Icon
|
||||
icon="md/MdDownload"
|
||||
attributes={{
|
||||
classes:
|
||||
"ar-IconController__download-icon position-absolute top-1 end-1",
|
||||
colors: { fillColor: "orange" },
|
||||
size: "2.5rem",
|
||||
}}
|
||||
events={{
|
||||
onClick: () =>
|
||||
downloadSvg(
|
||||
(icon as ObjectType).icon as string,
|
||||
name,
|
||||
{
|
||||
size: "7rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
},
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Icon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
attributes={{
|
||||
size: "7rem",
|
||||
colors: {
|
||||
fillColor: iconStyles?.fillColor || "royalblue",
|
||||
strokeColor: iconStyles?.strokeColor,
|
||||
},
|
||||
strokeWidth: iconStyles?.strokeWidth,
|
||||
}}
|
||||
fillPath
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
style={{ color: fontColor || "black" }}
|
||||
>
|
||||
7rem
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col flex-center flex-column position-relative hover-show">
|
||||
<Icon
|
||||
icon="md/MdDownload"
|
||||
attributes={{
|
||||
classes:
|
||||
"ar-IconController__download-icon position-absolute top-1 end-1",
|
||||
colors: { fillColor: "orange" },
|
||||
size: "2.5rem",
|
||||
}}
|
||||
events={{
|
||||
onClick: () =>
|
||||
downloadSvg((icon as ObjectType).icon as string, name, {
|
||||
size: size && unit ? size + unit : "20rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Icon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
attributes={{
|
||||
size: `${size}${unit}` || "20rem",
|
||||
colors: {
|
||||
fillColor: iconStyles?.fillColor || "royalblue",
|
||||
strokeColor: iconStyles?.strokeColor,
|
||||
},
|
||||
strokeWidth: iconStyles?.strokeWidth,
|
||||
}}
|
||||
fillPath
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
style={{ color: fontColor || "black" }}
|
||||
>
|
||||
{size && unit ? size + unit : "20rem"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="ar-IconController__controls col d-flex">
|
||||
<div className="row w-100">
|
||||
{icon && (
|
||||
<div className="ar-IconController__meta col-4">
|
||||
<h6>Icon Details</h6>
|
||||
<div className="ar-IconController__meta-item row lh-1-5">
|
||||
<div className="col">
|
||||
<strong>Created By: </strong>
|
||||
{icon.createdby as string}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ar-IconController__meta-item row lh-1-5">
|
||||
<div className="col">
|
||||
<strong>Description: </strong>
|
||||
{icon.description as string}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ar-IconController__meta-item row lh-1-5">
|
||||
<div className="col">
|
||||
<strong>Downloaded: </strong>
|
||||
{((icon.meta as ObjectType)?.downloadedTimes as number) ||
|
||||
0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ar-IconController__meta-item row lh-1-5">
|
||||
<div className="col">
|
||||
<strong>Liked: </strong>
|
||||
{((icon.meta as ObjectType)?.downloadedTimes as number) ||
|
||||
0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ar-IconController__meta-item row lh-1-5">
|
||||
<div className="col">
|
||||
<strong>Size: </strong>
|
||||
{(icon.meta as ObjectType)?.size as number}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`ar-IconController__controls-form ${icon ? "col-8" : "col-12"
|
||||
} border`}
|
||||
>
|
||||
<div className="row h-100 py-3">
|
||||
<div className="ar-IconController__controls-form-container col-12 h-25">
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<TextInput
|
||||
type="slider"
|
||||
label="Icon Size"
|
||||
min={1}
|
||||
max={30}
|
||||
onChange={(e) =>
|
||||
setSize((e.target as HTMLInputElement).value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Select
|
||||
label="Unit"
|
||||
onSelectionChanged={(e: { value: string }) =>
|
||||
setUnit(e.value)
|
||||
}
|
||||
options={[
|
||||
{ label: "rem", value: "rem" },
|
||||
{ label: "px", value: "px" },
|
||||
{ label: "vh", value: "vh" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <Tags classes="col-12 h-100 px-0" label={name} /> */}
|
||||
{icon && icon.tags && (
|
||||
<Tags
|
||||
// clickHandler={clickHandler}
|
||||
classes="col-12 h-75 px-0"
|
||||
label={name}
|
||||
tags={Object.fromEntries(
|
||||
(icon.tags as Array<string>).map((tag) => [tag, 1]),
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Similar Suggestions */}
|
||||
<Suggestions
|
||||
classes="border mb-3"
|
||||
suggestions={similarIcons}
|
||||
title={`Icons Similar to ${name}`}
|
||||
inSvgString
|
||||
/>
|
||||
{/* Recently Visited */}
|
||||
{/* <Suggestions
|
||||
classes="border mb-3"
|
||||
suggestions={similarIcons}
|
||||
title="Trending Icons"
|
||||
inSvgString
|
||||
/> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconController
|
||||
47
src/IconEditor.tsx
Executable file
47
src/IconEditor.tsx
Executable file
@@ -0,0 +1,47 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useDispatch } from "react-redux"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { IconEditorProps } from "./types"
|
||||
import { setIconStyles } from "./IconInfo.slice"
|
||||
import IconStyleSelector from "./IconStyleSelector"
|
||||
|
||||
const iconEditorTabs = [
|
||||
{
|
||||
label: "Icon",
|
||||
id: uuid(),
|
||||
},
|
||||
{
|
||||
label: "Background",
|
||||
id: uuid(),
|
||||
},
|
||||
]
|
||||
|
||||
const IconEditor = (props: IconEditorProps): JSX.Element => {
|
||||
const { layout } = props
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const [iconStyles, setIconStylesState] = useState<{
|
||||
fillColor?: string
|
||||
strokeColor?: string
|
||||
bgColor?: string
|
||||
strokeWidth?: string
|
||||
}>()
|
||||
|
||||
useEffect(() => {
|
||||
iconStyles && dispatch(setIconStyles(iconStyles))
|
||||
}, [iconStyles, dispatch])
|
||||
|
||||
return (
|
||||
<div className="ar-IconEditor h-100">
|
||||
<div className="ar-IconEditor__config-form h-100 overflow-auto">
|
||||
<IconStyleSelector
|
||||
iconStyles={iconStyles}
|
||||
setIconStyles={setIconStylesState}
|
||||
layout={layout}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconEditor
|
||||
24
src/IconInfo.slice.ts
Normal file
24
src/IconInfo.slice.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit"
|
||||
import { IconStyles } from "./types"
|
||||
|
||||
export interface IconPageState {
|
||||
iconStyles?: IconStyles
|
||||
}
|
||||
|
||||
const initialState: IconPageState = {}
|
||||
|
||||
export const iconInfoSlice = createSlice({
|
||||
name: "iconPage",
|
||||
initialState,
|
||||
reducers: {
|
||||
setIconStyles: (state, action: PayloadAction<IconStyles | undefined>) => {
|
||||
state.iconStyles = action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { setIconStyles } = iconInfoSlice.actions
|
||||
|
||||
export const getIconStyles = (state: any) => state.iconInfo.iconStyles
|
||||
|
||||
export default iconInfoSlice.reducer
|
||||
24
src/IconInfo.tsx
Executable file
24
src/IconInfo.tsx
Executable file
@@ -0,0 +1,24 @@
|
||||
import { useLocation, useParams } from "react-router-dom"
|
||||
import { Main } from "@armco/shared-components"
|
||||
import { usePanelContent } from "@armco/utils/hooks"
|
||||
import IconController from "./IconController"
|
||||
|
||||
const IconInfo = (): JSX.Element => {
|
||||
const { panelContent: rightPanelContent } = usePanelContent(false)
|
||||
const { group, name } = useParams()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<div className="ar-IconInfo h-100">
|
||||
<Main
|
||||
classes="h-100"
|
||||
mainContent={
|
||||
<IconController group={group} name={name} icon={location.state} />
|
||||
}
|
||||
rightPanelContent={rightPanelContent}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconInfo
|
||||
12
src/IconMergeContainer.component.scss
Executable file
12
src/IconMergeContainer.component.scss
Executable file
@@ -0,0 +1,12 @@
|
||||
.ar-IconMergeContainer {
|
||||
svg {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
margin-left: 2rem;
|
||||
margin-right: 2rem;
|
||||
|
||||
path:hover {
|
||||
outline: 1px dotted grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
157
src/IconMergeContainer.tsx
Executable file
157
src/IconMergeContainer.tsx
Executable file
@@ -0,0 +1,157 @@
|
||||
import { useEffect, useRef } from "react"
|
||||
import { ArrayType, IconMergeContainerProps } from "./types"
|
||||
import "./IconMergeContainer.component.scss"
|
||||
|
||||
const iconTest = [
|
||||
{
|
||||
name: "CiAlignBottom",
|
||||
icon: '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><g id="Align_Bottom"><g><path d="M3.548,20.922h16.9a.5.5,0,0,0,0-1H3.548a.5.5,0,0,0,0,1Z"></path><path d="M9,18.919H6.565a2.5,2.5,0,0,1-2.5-2.5V5.578a2.5,2.5,0,0,1,2.5-2.5H9a2.5,2.5,0,0,1,2.5,2.5V16.419A2.5,2.5,0,0,1,9,18.919ZM6.565,4.078a1.5,1.5,0,0,0-1.5,1.5V16.419a1.5,1.5,0,0,0,1.5,1.5H9a1.5,1.5,0,0,0,1.5-1.5V5.578A1.5,1.5,0,0,0,9,4.078Z"></path><path d="M17.437,18.919H15a2.5,2.5,0,0,1-2.5-2.5V10.55A2.5,2.5,0,0,1,15,8.05h2.434a2.5,2.5,0,0,1,2.5,2.5v5.869A2.5,2.5,0,0,1,17.437,18.919ZM15,9.05a1.5,1.5,0,0,0-1.5,1.5v5.869a1.5,1.5,0,0,0,1.5,1.5h2.434a1.5,1.5,0,0,0,1.5-1.5V10.55a1.5,1.5,0,0,0-1.5-1.5Z"></path></g></g></svg>',
|
||||
createdby: "Armco",
|
||||
createdAt: "2023-10-14T20:46:51.541Z",
|
||||
updatedAt: "2023-10-14T20:46:51.541Z",
|
||||
meta: {
|
||||
size: "772 bytes",
|
||||
downloadTimes: 0,
|
||||
favoriteTimes: 0,
|
||||
},
|
||||
group: "ci",
|
||||
description: "",
|
||||
tags: [
|
||||
{
|
||||
name: "Ci",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "Align",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "Bottom",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "armco",
|
||||
state: "verified",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CiAlignCenterH",
|
||||
icon: '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><g id="Align_Center-H"><path d="M17.42,4.062H12.5v-.51a.5.5,0,0,0-1,0v.51H6.58a2.507,2.507,0,0,0-2.5,2.5V9a2.5,2.5,0,0,0,2.5,2.5H11.5v1H9.06A2.507,2.507,0,0,0,6.56,15v2.44a2.507,2.507,0,0,0,2.5,2.5H11.5v.51a.5.5,0,0,0,1,0v-.51h2.43a2.5,2.5,0,0,0,2.5-2.5V15a2.5,2.5,0,0,0-2.5-2.5H12.5v-1h4.92A2.5,2.5,0,0,0,19.92,9V6.562A2.507,2.507,0,0,0,17.42,4.062ZM11.5,18.942H9.06a1.511,1.511,0,0,1-1.5-1.5V15a1.5,1.5,0,0,1,1.5-1.5H11.5Zm0-8.44H6.58A1.5,1.5,0,0,1,5.08,9V6.562a1.5,1.5,0,0,1,1.5-1.5H11.5Zm3.43,3a1.5,1.5,0,0,1,1.5,1.5v2.44a1.5,1.5,0,0,1-1.5,1.5H12.5V13.5ZM18.92,9a1.5,1.5,0,0,1-1.5,1.5H12.5V5.062h4.92a1.5,1.5,0,0,1,1.5,1.5Z"></path></g></svg>',
|
||||
createdby: "Armco",
|
||||
createdAt: "2023-10-14T20:46:51.541Z",
|
||||
updatedAt: "2023-10-14T20:46:51.541Z",
|
||||
meta: {
|
||||
size: "790 bytes",
|
||||
downloadTimes: 0,
|
||||
favoriteTimes: 0,
|
||||
},
|
||||
group: "ci",
|
||||
description: "",
|
||||
tags: [
|
||||
{
|
||||
name: "Ci",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "Align",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "Center",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "H",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "armco",
|
||||
state: "verified",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "CiAlignCenterV",
|
||||
icon: '<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><g id="Align_Center-V"><path d="M20.446,11.5h-.51V9.07a2.5,2.5,0,0,0-2.5-2.5h-2.43a2.5,2.5,0,0,0-2.5,2.5V11.5H11.5V6.58A2.5,2.5,0,0,0,9,4.08H6.566a2.5,2.5,0,0,0-2.5,2.5V11.5h-.52a.5.5,0,0,0,0,1h.52v4.92a2.5,2.5,0,0,0,2.5,2.5H9a2.5,2.5,0,0,0,2.5-2.5V12.5h1.01v2.43a2.5,2.5,0,0,0,2.5,2.5h2.43a2.5,2.5,0,0,0,2.5-2.5V12.5h.51A.5.5,0,0,0,20.446,11.5ZM10.5,17.42A1.5,1.5,0,0,1,9,18.92H6.566a1.5,1.5,0,0,1-1.5-1.5V12.5H10.5Zm0-5.92H5.066V6.58a1.5,1.5,0,0,1,1.5-1.5H9a1.5,1.5,0,0,1,1.5,1.5Zm8.44,3.43a1.5,1.5,0,0,1-1.5,1.5h-2.43a1.5,1.5,0,0,1-1.5-1.5V12.5h5.43Zm0-3.43h-5.43V9.07a1.5,1.5,0,0,1,1.5-1.5h2.43a1.5,1.5,0,0,1,1.5,1.5Z"></path></g></svg>',
|
||||
createdby: "Armco",
|
||||
createdAt: "2023-10-14T20:46:51.541Z",
|
||||
updatedAt: "2023-10-14T20:46:51.541Z",
|
||||
meta: {
|
||||
size: "784 bytes",
|
||||
downloadTimes: 0,
|
||||
favoriteTimes: 0,
|
||||
},
|
||||
group: "ci",
|
||||
description: "",
|
||||
tags: [
|
||||
{
|
||||
name: "Ci",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "Align",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "Center",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "V",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "icon",
|
||||
state: "verified",
|
||||
},
|
||||
{
|
||||
name: "armco",
|
||||
state: "verified",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const parser = new DOMParser()
|
||||
|
||||
const IconMergeContainer = (props: IconMergeContainerProps): JSX.Element => {
|
||||
// const { icons } = props
|
||||
const svgRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const childList: Array<SVGSVGElement> = []
|
||||
iconTest?.forEach((icon) => {
|
||||
const svgElement = parser
|
||||
.parseFromString(icon.icon, "image/svg+xml")
|
||||
.querySelector("svg")
|
||||
if (svgElement) {
|
||||
childList.push(svgElement)
|
||||
}
|
||||
})
|
||||
|
||||
svgRef.current?.replaceChildren(...childList)
|
||||
const paths = childList.reduce((acc: ArrayType, svgElement) => {
|
||||
const pathList = svgElement.querySelectorAll("path")
|
||||
console.log(pathList)
|
||||
pathList.forEach((pathItem) => {
|
||||
pathItem.setAttribute("draggable", "true")
|
||||
pathItem.addEventListener("dragstart", (e) => console.log(e))
|
||||
})
|
||||
return acc.concat(Array.from(pathList) as any)
|
||||
}, [])
|
||||
; (paths[0] as any).parentElement?.replaceChildren(...paths)
|
||||
}, [svgRef])
|
||||
|
||||
return <div className="ar-IconMergeContainer" ref={svgRef} />
|
||||
}
|
||||
|
||||
export default IconMergeContainer
|
||||
83
src/IconStyleSelector.tsx
Executable file
83
src/IconStyleSelector.tsx
Executable file
@@ -0,0 +1,83 @@
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { IconStyles, IconStyleSelectorProps } from "./types"
|
||||
import { AdvancedColorPicker, Slider } from "@armco/shared-components"
|
||||
|
||||
const fillColorId = uuid()
|
||||
const strokeColorId = uuid()
|
||||
const bgColorId = uuid()
|
||||
|
||||
const IconStyleSelector = (props: IconStyleSelectorProps): JSX.Element => {
|
||||
const { layout, setIconStyles } = props
|
||||
|
||||
const setIconStylesLocal = (propName: string, propValue: string) => {
|
||||
setIconStyles((currentIconStyles: IconStyles | undefined) => ({
|
||||
...currentIconStyles,
|
||||
[propName]: propValue,
|
||||
}))
|
||||
}
|
||||
|
||||
const fill = (
|
||||
<AdvancedColorPicker
|
||||
key="fill-color-selector"
|
||||
id={fillColorId}
|
||||
displaySample
|
||||
onColorSelect={(color) => setIconStylesLocal("fillColor", color)}
|
||||
title="Fill Color"
|
||||
/>
|
||||
)
|
||||
|
||||
const strokeWidth = (
|
||||
<Slider
|
||||
containerClasses="py-2 border-top border-bottom mb-4"
|
||||
label="Stroke Width"
|
||||
min={1}
|
||||
max={10}
|
||||
onChange={(e) => setIconStylesLocal("strokeWidth", e.target.value)}
|
||||
withManual
|
||||
/>
|
||||
)
|
||||
const stroke = (
|
||||
<AdvancedColorPicker
|
||||
key="stoke-color-selector"
|
||||
id={strokeColorId}
|
||||
displaySample
|
||||
onColorSelect={(color) => setIconStylesLocal("strokeColor", color)}
|
||||
title="Stroke Color"
|
||||
/>
|
||||
)
|
||||
|
||||
const bg = (
|
||||
<AdvancedColorPicker
|
||||
key="bg-color-selector"
|
||||
id={bgColorId}
|
||||
displaySample
|
||||
onColorSelect={(color) => setIconStylesLocal("bgColor", color)}
|
||||
title="Background Color"
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="ar-IconStyleSelector p-3 w-100 overflow-auto">
|
||||
{layout === "horizontal" ? (
|
||||
<>
|
||||
<div className="d-flex">
|
||||
<div className="px-3">{fill}</div>
|
||||
<div className="px-3">{stroke}</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col">{strokeWidth}</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{fill}
|
||||
{strokeWidth}
|
||||
{stroke}
|
||||
{bg}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconStyleSelector
|
||||
86
src/IconTile.component.scss
Executable file
86
src/IconTile.component.scss
Executable file
@@ -0,0 +1,86 @@
|
||||
.ar-IconTile {
|
||||
overflow: hidden;
|
||||
.font-blue {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
&.selectable .ar-IconTile__image {
|
||||
border: 3px solid var(--ar-color-border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.font-black {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.ar-IconTile__select-icon {
|
||||
top: 0.3rem;
|
||||
right: 0.3rem;
|
||||
}
|
||||
|
||||
.ar-ToolBar {
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
right: -1.5rem;
|
||||
width: 1.5rem;
|
||||
height: 100%;
|
||||
transition: right 0.3s;
|
||||
flex-direction: column;
|
||||
background-color: var(--ar-bg-invert-fade);
|
||||
border-left: 1px solid lightblue;
|
||||
|
||||
&.show {
|
||||
right: 0
|
||||
}
|
||||
&.top {
|
||||
top: -1.5rem;
|
||||
height: 1.5rem;
|
||||
width: 100%;
|
||||
transition: top 0.3s;
|
||||
&.show {
|
||||
top: 0
|
||||
}
|
||||
}
|
||||
|
||||
&.left {
|
||||
left: -1.5rem;
|
||||
width: 1.5rem;
|
||||
height: 100%;
|
||||
transition: left 0.3s;
|
||||
&.show {
|
||||
left: 0
|
||||
}
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
bottom: -1.5rem;
|
||||
height: 1.5rem;
|
||||
width: 100%;
|
||||
transition: bottom 0.3s;
|
||||
&.show {
|
||||
bottom: 0
|
||||
}
|
||||
}
|
||||
|
||||
.ar-ToolItem {
|
||||
flex: 1;
|
||||
border-top: 1px solid white;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.list {
|
||||
|
||||
}
|
||||
|
||||
&.compact {
|
||||
|
||||
}
|
||||
|
||||
.ar-IconTile__footer {
|
||||
color: grey;
|
||||
font-size: 0.6rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
158
src/IconTile.tsx
Executable file
158
src/IconTile.tsx
Executable file
@@ -0,0 +1,158 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import {
|
||||
ArThemes,
|
||||
ArIconTileTypes,
|
||||
ArIconSourceTypes,
|
||||
IconTileProps,
|
||||
} from "./types"
|
||||
import { ICON_ROOT } from "@armco/configs/endpoints"
|
||||
import { useTheme } from "@armco/utils/hooks"
|
||||
import Icon from "@armco/icon"
|
||||
import "./IconTile.component.scss"
|
||||
|
||||
const IconTile = (props: IconTileProps): JSX.Element => {
|
||||
const {
|
||||
classes,
|
||||
// fillColor,
|
||||
hideBorder,
|
||||
hideFooter,
|
||||
icon,
|
||||
iconSize,
|
||||
onClick,
|
||||
selectable,
|
||||
strokeColor,
|
||||
strokeWidth,
|
||||
// tools,
|
||||
// toolsPlacement,
|
||||
type,
|
||||
} = props
|
||||
const [hovered, setHovered] = useState<boolean>()
|
||||
const [isSelected, toggleSelected] = useState<boolean>()
|
||||
const { theme } = useTheme()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectable && isSelected) {
|
||||
toggleSelected(false)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectable])
|
||||
|
||||
// const generateTools = (tools: Array<ToolItemConfig>) => {
|
||||
// return tools.map((tool: ToolItemConfig, index: number) => {
|
||||
// const anchor = (
|
||||
// <Icon
|
||||
// slot={ArPopoverSlots.ANCHOR}
|
||||
// {...{
|
||||
// ...tool.iconProps,
|
||||
// onClick: tool.onClick,
|
||||
// }}
|
||||
// />
|
||||
// )
|
||||
// return (
|
||||
// <span
|
||||
// key={"tool-item-" + icon.name + "-" + index}
|
||||
// className="ar-ToolItem flex-center"
|
||||
// onClick={(e) => {
|
||||
// e.stopPropagation()
|
||||
// }}
|
||||
// >
|
||||
// {tool.children ? (
|
||||
// <Popover
|
||||
// // trigger={ArPopoverTriggers.HOVER}
|
||||
// position={ArPopoverPositions.RIGHTBOTTOM}
|
||||
// version="v1"
|
||||
// >
|
||||
// {anchor}
|
||||
// <div slot={ArPopoverSlots.POPOVER}>
|
||||
// {generateTools(tool.children)}
|
||||
// </div>
|
||||
// </Popover>
|
||||
// ) : (
|
||||
// anchor
|
||||
// )}
|
||||
// </span>
|
||||
// )
|
||||
// })
|
||||
// }
|
||||
|
||||
// const iconTools = tools && (
|
||||
// <div
|
||||
// className={`ar-ToolBar position-absolute${
|
||||
// toolsPlacement ? " " + toolsPlacement : ""
|
||||
// }${selectable ? " d-none" : " d-flex"}${hovered ? " show" : ""}${
|
||||
// type ? " " + type : ""
|
||||
// }`}
|
||||
// >
|
||||
// {generateTools(tools)}
|
||||
// </div>
|
||||
// )
|
||||
|
||||
return (
|
||||
<span
|
||||
className={`ar-IconTile d-inline-block mx-2${hideBorder ? "" : " border"
|
||||
}${classes ? " " + classes : ""}${type === ArIconTileTypes.LIST ? " d-flex" : ""
|
||||
}${selectable ? " selectable" : ""}`}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
onClick={() => onClick && onClick(icon)}
|
||||
>
|
||||
<div
|
||||
className={`ar-IconTile__image position-relative h-100${type === ArIconTileTypes.LIST ? " flex-v-center px-1" : " flex-center"
|
||||
}`}
|
||||
onClick={() =>
|
||||
selectable
|
||||
? toggleSelected(!isSelected)
|
||||
: navigate(`/icon/${icon.group}/${icon.name}`, { state: icon })
|
||||
}
|
||||
>
|
||||
{selectable && (
|
||||
<Icon
|
||||
icon={
|
||||
isSelected ? "im/ImCheckboxChecked" : "im/ImCheckboxUnchecked"
|
||||
}
|
||||
attributes={{
|
||||
classes: "ar-IconTile__select-icon position-absolute",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
// key={icon.name}
|
||||
icon={{ source: btoa(icon.icon), type: ArIconSourceTypes.b64 }}
|
||||
attributes={{
|
||||
size: iconSize,
|
||||
colors: {
|
||||
fillColor: hovered
|
||||
? "royalblue"
|
||||
: theme === ArThemes.LIGHT1
|
||||
? "grey"
|
||||
: "white",
|
||||
strokeColor,
|
||||
},
|
||||
strokeWidth,
|
||||
}}
|
||||
fillPath
|
||||
hoverShadow
|
||||
/>
|
||||
{/* {type !== ArIconTileTypes.LIST && iconTools} */}
|
||||
</div>
|
||||
{!hideFooter && (
|
||||
<footer
|
||||
className={`ar-IconTile__footer fw-bold text-nowrap overflow-hidden flex-center px-2 ${hovered ? "font-blue btn-link" : "font-black"
|
||||
}${type === ArIconTileTypes.LIST ? " flex-grow-1" : ""}`}
|
||||
onClick={() => {
|
||||
const iconLink = `${ICON_ROOT}/${icon.group}/${icon.name}`
|
||||
selectable
|
||||
? toggleSelected(!isSelected)
|
||||
: navigator.clipboard.writeText(iconLink)
|
||||
}}
|
||||
>
|
||||
{icon.name}
|
||||
</footer>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconTile
|
||||
34
src/Icons.slice.ts
Normal file
34
src/Icons.slice.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { PayloadAction, createSlice, Slice } from "@reduxjs/toolkit"
|
||||
import { IconTileProps } from "./types"
|
||||
|
||||
export interface IconsPageState {
|
||||
favorites: Array<IconTileProps>
|
||||
selectedTag?: string
|
||||
}
|
||||
|
||||
const initialState: IconsPageState = {
|
||||
favorites: [],
|
||||
}
|
||||
|
||||
export const iconsSlice: Slice<IconsPageState> = createSlice({
|
||||
name: "iconsPage",
|
||||
initialState,
|
||||
reducers: {
|
||||
setFavorites: (state, action: PayloadAction<IconTileProps>) => {
|
||||
state.favorites = [...state.favorites, action.payload]
|
||||
},
|
||||
removeFavorite: (state, action: PayloadAction<IconTileProps>) => {
|
||||
state.favorites.splice(state.favorites.indexOf(action.payload))
|
||||
},
|
||||
selectTag: (state, action: PayloadAction<string>) => {
|
||||
state.selectedTag = action.payload
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const { selectTag, setFavorites, removeFavorite } = iconsSlice.actions
|
||||
|
||||
export const getFavorites = (state: any) => state.icons.favorites
|
||||
export const getSelectedTag = (state: any) => state.icons.selectedTag
|
||||
|
||||
export default iconsSlice.reducer
|
||||
59
src/Icons.tsx
Executable file
59
src/Icons.tsx
Executable file
@@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useDispatch } from "react-redux"
|
||||
import { FilterState, IconResponse, ObjectType } from "./types"
|
||||
import { usePanelContent } from "@armco/utils/hooks"
|
||||
import { getStatic } from "@armco/utils/network"
|
||||
import { Filters, Main } from "@armco/shared-components"
|
||||
import { selectTag } from "./Icons.slice"
|
||||
import IconsList from "./IconsList"
|
||||
|
||||
const fetchData = async (api: string, cb: Function) => {
|
||||
const response: { status: number; body: Array<IconResponse> } =
|
||||
await getStatic(api, null, null)
|
||||
cb && cb(response.body)
|
||||
}
|
||||
|
||||
const Icons = (): JSX.Element => {
|
||||
const [tags, setTags] = useState<ObjectType>()
|
||||
const [filters, setFilters] = useState<FilterState | undefined>({
|
||||
count: {
|
||||
value: "1to1000",
|
||||
data: { startIndex: 0, endIndex: 999 },
|
||||
},
|
||||
})
|
||||
const { panelContent: rightPanelContent } = usePanelContent(false)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
const variant = "gt100"
|
||||
const parseTags = (tagReponse: ObjectType) => {
|
||||
setTags(tagReponse[variant] as ObjectType)
|
||||
}
|
||||
!tags && fetchData(`/icon/tag/all?variants=${variant}`, parseTags)
|
||||
}, [tags])
|
||||
|
||||
const clickHandler = (e: any) => {
|
||||
dispatch(selectTag(e.point.name))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ar-Icons d-flex flex-column h-100">
|
||||
<Main
|
||||
contentClasses="p-2"
|
||||
drawerContent={
|
||||
<Filters
|
||||
config={{ tags }}
|
||||
clickHandler={clickHandler}
|
||||
initialFilters={filters}
|
||||
onFilterChange={setFilters}
|
||||
/>
|
||||
}
|
||||
mainContent={<IconsList />}
|
||||
rightPanelContent={rightPanelContent}
|
||||
rightPanelHeader="Favorites"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Icons
|
||||
67
src/IconsList.component.scss
Executable file
67
src/IconsList.component.scss
Executable file
@@ -0,0 +1,67 @@
|
||||
.ar-IconsList {
|
||||
.ar-IconsList__header-pagination-search-upload {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-colora);
|
||||
}
|
||||
|
||||
.ar-IconList__selection-manager {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-colora);
|
||||
transition: all 0.3s;
|
||||
|
||||
&.hide {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ar-IconsList__icon-tile-container {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-color);
|
||||
}
|
||||
|
||||
.ar-IconTile {
|
||||
cursor: pointer;
|
||||
width: 7rem;
|
||||
height: 7rem;
|
||||
transition: width 0.3s, padding 0.3s;
|
||||
|
||||
&.compact {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
&.list {
|
||||
width: 8rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ar-bg-hover);
|
||||
}
|
||||
|
||||
footer {
|
||||
height: 1.9rem;
|
||||
border-top: 1px solid var(--bs-border-color);
|
||||
background-color: var(--ar-bg-base);
|
||||
}
|
||||
}
|
||||
|
||||
.ar-IconsList__upload-button {
|
||||
background-color: orange;
|
||||
border-color: orange;
|
||||
}
|
||||
|
||||
.ar-IconsList__color-palette {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
.ar-IconTile {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
442
src/IconsList.tsx
Executable file
442
src/IconsList.tsx
Executable file
@@ -0,0 +1,442 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useDispatch, useSelector } from "react-redux"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArIconTileTypes,
|
||||
ArLoaderTypes,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
FunctionType,
|
||||
IconResponse,
|
||||
IconStyles,
|
||||
IconTileProps,
|
||||
IconsListProps,
|
||||
ObjectType,
|
||||
SearchItem,
|
||||
SegmentData,
|
||||
} from "./types"
|
||||
import { API_CONFIG, ENDPOINTS } from "@armco/configs/endpoints"
|
||||
import { useNotification, usePanelContent } from "@armco/utils/hooks"
|
||||
import { get } from "@armco/utils/network"
|
||||
import { debounce } from "@armco/utils/helper"
|
||||
import Icon from "@armco/icon"
|
||||
import {
|
||||
Button,
|
||||
Loader,
|
||||
MenuButton,
|
||||
Pagination,
|
||||
Pillbox,
|
||||
Popover,
|
||||
SearchField,
|
||||
SegmentedControl,
|
||||
} from "@armco/shared-components"
|
||||
import {
|
||||
getFavorites,
|
||||
getSelectedTag,
|
||||
removeFavorite,
|
||||
setFavorites,
|
||||
} from "./Icons.slice"
|
||||
import { getIconStyles, setIconStyles } from "./IconInfo.slice"
|
||||
import IconStyleSelector from "./IconStyleSelector"
|
||||
import IconTile from "./IconTile"
|
||||
import "./IconsList.component.scss"
|
||||
|
||||
const fetchIconsPage = (
|
||||
limit: number,
|
||||
from: number,
|
||||
filters: ObjectType,
|
||||
dataSetter: FunctionType,
|
||||
pageSetter: FunctionType,
|
||||
setLoading: FunctionType,
|
||||
) => {
|
||||
const pageApi =
|
||||
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
|
||||
ENDPOINTS.STATIC.ICON.ROOT +
|
||||
ENDPOINTS.STATIC.ICON.PAGE
|
||||
const queryParams: ObjectType = { pageSize: limit, from, ...filters }
|
||||
get(pageApi, queryParams)
|
||||
.then((response) => {
|
||||
if (response && response.status === 200) {
|
||||
if (response.body && dataSetter) {
|
||||
dataSetter(response.body)
|
||||
pageSetter(response.body.slice(0, 100))
|
||||
}
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
// const { variant } = props
|
||||
const [searchText, setSearchText] = useState<string | undefined>()
|
||||
const [icons, setIcons] = useState<Array<IconResponse>>()
|
||||
const [page, setPage] = useState<Array<IconResponse>>()
|
||||
const [view, setView] = useState<SegmentData>()
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const [isSelectMode, toggleSelectMode] = useState<boolean>()
|
||||
const [selectedIcons, setSelectedIcons] = useState<Array<IconResponse>>()
|
||||
const { setPanelContent: setRightPanelContent } = usePanelContent(false)
|
||||
const { notify } = useNotification()
|
||||
const dispatch = useDispatch()
|
||||
const favorites = useSelector(getFavorites)
|
||||
const selectedTag = useSelector(getSelectedTag) as string | undefined
|
||||
const iconStyles = useSelector(getIconStyles) as IconStyles | undefined
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
setView({ name: ArIconTileTypes.COMFY })
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (favorites) {
|
||||
setRightPanelContent({
|
||||
componentName: favorites.length > 0 ? "FavoritesList" : "",
|
||||
})
|
||||
}
|
||||
}, [favorites, setRightPanelContent])
|
||||
|
||||
useEffect(() => {
|
||||
const filters: ObjectType = {}
|
||||
if (selectedTag) {
|
||||
filters.tags = selectedTag
|
||||
}
|
||||
if (searchText) {
|
||||
filters.search = searchText
|
||||
}
|
||||
setLoading(true)
|
||||
fetchIconsPage(2000, 0, filters, setIcons, setPage, setLoading)
|
||||
}, [selectedTag, searchText])
|
||||
|
||||
const onIconTileClick = (iconProps: IconResponse) => {
|
||||
isSelectMode
|
||||
? setSelectedIcons((currentIcons) => {
|
||||
currentIcons = [...(currentIcons || [])]
|
||||
const existingIconIndex = currentIcons?.findIndex(
|
||||
(currentIcon) => currentIcon.name === iconProps.name,
|
||||
)
|
||||
if (existingIconIndex > -1) {
|
||||
currentIcons.splice(existingIconIndex, 1)
|
||||
} else {
|
||||
currentIcons.push(iconProps)
|
||||
}
|
||||
return currentIcons
|
||||
})
|
||||
: view?.name === ArIconTileTypes.COMPACT &&
|
||||
notify({
|
||||
show: true,
|
||||
message: `Icon link for ${iconProps.name} copied`,
|
||||
uid: uuid(),
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ar-IconsList h-100 w-100 overflow-auto position-relative d-flex flex-column">
|
||||
{!icons && (
|
||||
<Loader label="Loading Icons..." type={ArLoaderTypes.SHAPES} />
|
||||
)}
|
||||
<div className="ar-IconsList__header-pagination-search-upload py-2 px-3 mb-2 border">
|
||||
<div className="row">
|
||||
<div className="col d-none d-md-flex align-items-center">
|
||||
<h6 className="mb-0 h-100 flex-center px-3 border-right">
|
||||
<MenuButton
|
||||
buttonProps={{
|
||||
variant: ArButtonVariants.LINK,
|
||||
content: "Categories",
|
||||
size: ArSizes.SMALL,
|
||||
}}
|
||||
splitOptions={[
|
||||
{
|
||||
label: "All",
|
||||
},
|
||||
{
|
||||
label: "Business",
|
||||
},
|
||||
{
|
||||
label: "Medical",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</h6>
|
||||
</div>
|
||||
<span className="col flex-v-center justify-content-end">
|
||||
<Popover version="v2">
|
||||
<Icon
|
||||
icon="io/IoIosColorPalette"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
attributes={{
|
||||
classes:
|
||||
"ar-IconsList__color-palette cursor-pointer ms-auto hover-shadow me-3",
|
||||
colors: {
|
||||
fillColor: "orange",
|
||||
},
|
||||
size: "2rem",
|
||||
}}
|
||||
events={{ onClick: () => { } }}
|
||||
/>
|
||||
<IconStyleSelector
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
setIconStyles={(iconStylesUpdater) => {
|
||||
if (typeof iconStylesUpdater === "function") {
|
||||
dispatch(setIconStyles(iconStylesUpdater(iconStyles)))
|
||||
} else {
|
||||
dispatch(setIconStyles(iconStylesUpdater))
|
||||
}
|
||||
}}
|
||||
layout="horizontal"
|
||||
/>
|
||||
</Popover>
|
||||
<Button
|
||||
classes="h-100 float-end me-3"
|
||||
content="Create"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonVariants.LINKHOVEREFFECT}
|
||||
preIcon="io5/IoCreateOutline"
|
||||
onClick={() => toggleSelectMode(true)}
|
||||
/>
|
||||
<Button
|
||||
classes="ar-IconsList__upload-button h-100 float-end"
|
||||
content="Upload"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonVariants.PRIMARY}
|
||||
/>
|
||||
<SegmentedControl
|
||||
classes="d-none d-sm-inline ms-3"
|
||||
hasUniformSegments
|
||||
segments={[
|
||||
{
|
||||
name: "comfy",
|
||||
icon: "md/MdViewComfy",
|
||||
tooltip: "View Comfortable",
|
||||
},
|
||||
{
|
||||
name: "compact",
|
||||
icon: "md/MdViewCompact",
|
||||
tooltip: "View Compact",
|
||||
},
|
||||
{
|
||||
name: "list",
|
||||
icon: "io5/IoList",
|
||||
tooltip: "Quick Peak",
|
||||
},
|
||||
]}
|
||||
onChange={(view) => setView(view as SegmentData)}
|
||||
/>
|
||||
</span>
|
||||
<div className="col d-none d-md-flex flex-v-center">
|
||||
<SearchField
|
||||
classes="bg-white"
|
||||
placeholder="Search by name, tags, description"
|
||||
onChange={debounce(
|
||||
(event: ChangeEvent<HTMLInputElement>) =>
|
||||
setSearchText(event.target.value),
|
||||
1000,
|
||||
)}
|
||||
items={
|
||||
icons
|
||||
? icons.map(
|
||||
(icon): SearchItem => ({
|
||||
label: icon.name,
|
||||
data: icon,
|
||||
}),
|
||||
)
|
||||
: []
|
||||
}
|
||||
hidePopup
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`ar-IconList__selection-manager d-flex${isSelectMode ? " mb-2 py-2 px-3 border" : " hide"
|
||||
}`}
|
||||
>
|
||||
{selectedIcons && selectedIcons.length > 0 && (
|
||||
<Pillbox
|
||||
data={(selectedIcons || []).map((icon) => ({
|
||||
label: icon.name,
|
||||
deletable: true,
|
||||
}))}
|
||||
onChange={() => { }}
|
||||
/>
|
||||
)}
|
||||
<div className="ms-auto d-flex">
|
||||
<Button
|
||||
classes="me-3"
|
||||
content="Cancel"
|
||||
variant={ArButtonVariants.SECONDARY}
|
||||
size={ArSizes.SMALL}
|
||||
onClick={() => {
|
||||
toggleSelectMode(false)
|
||||
setSelectedIcons(undefined)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
content="Done"
|
||||
variant={ArButtonVariants.PRIMARY}
|
||||
size={ArSizes.SMALL}
|
||||
onClick={() =>
|
||||
navigate("/icons/merge-icons", { state: selectedIcons })
|
||||
}
|
||||
disabled={!selectedIcons || selectedIcons.length === 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{page && (
|
||||
<div className="ar-IconsList__icon-tile-container-wrapper d-flex flex-grow-1 justify-content-between mh-100 overflow-auto">
|
||||
<div
|
||||
className="ar-IconsList__icon-tile-container py-2 px-3 border d-grid w-100"
|
||||
style={{
|
||||
gridTemplateColumns: "repeat(10, minmax(8rem, auto))",
|
||||
gridAutoRows: "max(8rem)",
|
||||
}}
|
||||
>
|
||||
{loading && (
|
||||
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
|
||||
)}
|
||||
{page.map((icon, index) => (
|
||||
<Popover
|
||||
trigger={ArPopoverTriggers.HOVER}
|
||||
key={"icon-tile-popover-" + index}
|
||||
classes="mb-3"
|
||||
version="v2"
|
||||
>
|
||||
{!view || view.name !== ArIconTileTypes.LIST ? (
|
||||
<span slot={ArPopoverSlots.POPOVER}>
|
||||
<b className="d-block">{icon.group}</b>
|
||||
{icon.name
|
||||
?.match(/[A-Z][a-z]+/g)
|
||||
?.slice(1)
|
||||
.join(" ")}
|
||||
</span>
|
||||
) : null}
|
||||
<IconTile
|
||||
type={
|
||||
view && view.name
|
||||
? (view.name as ArIconTileTypes)
|
||||
: ArIconTileTypes.COMFY
|
||||
}
|
||||
hideBorder={view?.name !== ArIconTileTypes.LIST}
|
||||
classes={view?.name}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
key={icon + "-" + index}
|
||||
icon={icon}
|
||||
iconSize={
|
||||
view?.name === ArIconTileTypes.LIST ? "1rem" : "2rem"
|
||||
}
|
||||
onClick={onIconTileClick}
|
||||
tools={[
|
||||
{
|
||||
iconProps: {
|
||||
icon: "sl.SlOptions",
|
||||
attributes: {
|
||||
colors: {
|
||||
fillColor: "white",
|
||||
hoverFillColor: "lightblue",
|
||||
},
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
iconProps: {
|
||||
icon: "io.IoIosShareAlt",
|
||||
attributes: {
|
||||
colors: { hoverFillColor: "lightblue" },
|
||||
},
|
||||
},
|
||||
name: "share",
|
||||
onClick: () => { },
|
||||
},
|
||||
],
|
||||
name: "more-options",
|
||||
onClick: () => { },
|
||||
},
|
||||
{
|
||||
iconProps: {
|
||||
icon: "ai/AiFillLike",
|
||||
attributes: {
|
||||
colors: {
|
||||
fillColor: "white",
|
||||
hoverFillColor: "lightblue",
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "like",
|
||||
onClick: () => { },
|
||||
},
|
||||
{
|
||||
iconProps: {
|
||||
icon: "md/MdFavorite",
|
||||
attributes: {
|
||||
colors: {
|
||||
fillColor: "white",
|
||||
hoverFillColor: "lightblue",
|
||||
toggleFillColor: "red",
|
||||
},
|
||||
},
|
||||
toggled: false,
|
||||
},
|
||||
name: "favorite",
|
||||
onClick: () => {
|
||||
const favorite = favorites.find(
|
||||
(searchedIcon: IconTileProps) =>
|
||||
icon.name === searchedIcon.icon.name,
|
||||
)
|
||||
if (!favorites || !favorite) {
|
||||
dispatch(setFavorites({ icon }))
|
||||
notify({
|
||||
message: "Icon Added to your favorites",
|
||||
show: true,
|
||||
uid: uuid(),
|
||||
})
|
||||
} else {
|
||||
dispatch(removeFavorite({ icon }))
|
||||
notify({
|
||||
message: "Icon removed from your favorites",
|
||||
show: true,
|
||||
uid: uuid(),
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
]}
|
||||
hideFooter={
|
||||
(view && (view.name as ArIconTileTypes)) !==
|
||||
ArIconTileTypes.LIST
|
||||
}
|
||||
selectable={isSelectMode}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
</Popover>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* {slices && ( */}
|
||||
<Pagination
|
||||
classes="my-3 flex-center"
|
||||
data={icons}
|
||||
maxPillsToShow={5}
|
||||
pageSetter={setPage}
|
||||
// trigger={ArPageTriggers.SCROLL}
|
||||
count={1}
|
||||
load={100}
|
||||
dataFetcher={(load, count) =>
|
||||
fetchIconsPage(load, count, {}, setIcons, setPage, setLoading)
|
||||
}
|
||||
/>
|
||||
{/* )} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconsList
|
||||
20
src/IconsMerge.tsx
Normal file
20
src/IconsMerge.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { useLocation } from "react-router-dom"
|
||||
import { usePanelContent } from "@armco/utils/hooks"
|
||||
import { Main } from "@armco/shared-components"
|
||||
import IconMergeContainer from "./IconMergeContainer"
|
||||
|
||||
const IconsMerge = (): JSX.Element => {
|
||||
const { panelContent: rightPanelContent } = usePanelContent(false)
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<div className="ar-IconsMerge">
|
||||
<Main
|
||||
mainContent={<IconMergeContainer icons={location.state} />}
|
||||
rightPanelContent={rightPanelContent}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconsMerge
|
||||
64
src/Test.tsx
Normal file
64
src/Test.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { useEffect } from "react"
|
||||
import ReactDOM from "react-dom/client"
|
||||
import {
|
||||
BrowserRouter,
|
||||
Navigate,
|
||||
RouteObject,
|
||||
useRoutes,
|
||||
} from "react-router-dom"
|
||||
import { Provider } from "react-redux"
|
||||
import { ArProvider } from "@armco/utils/providers"
|
||||
import { useTheme } from "@armco/utils/hooks"
|
||||
import { store } from "./store"
|
||||
import Icons from "./Icons"
|
||||
import IconsMerge from "./IconsMerge"
|
||||
import IconInfo from "./IconInfo"
|
||||
import Clusters from "./Clusters"
|
||||
import { ArThemes } from "./types"
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement)
|
||||
const routes: Array<RouteObject & { class?: string }> = [
|
||||
{
|
||||
path: "/",
|
||||
class: "icons",
|
||||
element: <Navigate to="/icons" />,
|
||||
},
|
||||
{
|
||||
path: "/icons",
|
||||
class: "icons",
|
||||
element: <Icons />,
|
||||
},
|
||||
{
|
||||
path: "/icons/merge-icons",
|
||||
class: "merge-icons",
|
||||
element: <IconsMerge />,
|
||||
},
|
||||
{
|
||||
path: "/icon/:group/:name",
|
||||
class: "icon",
|
||||
element: <IconInfo />,
|
||||
},
|
||||
{
|
||||
path: "/clusters",
|
||||
class: "clusters",
|
||||
element: <Clusters />,
|
||||
},
|
||||
]
|
||||
|
||||
const Router = () => {
|
||||
const { setTheme } = useTheme()
|
||||
useEffect(() => setTheme(ArThemes.LIGHT1), [setTheme])
|
||||
return useRoutes(routes)
|
||||
}
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<Provider store={store}>
|
||||
<ArProvider>
|
||||
<Router />
|
||||
</ArProvider>
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
46
src/helper.ts
Normal file
46
src/helper.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { IconProps } from "./types"
|
||||
import { applyStyles } from "@armco/icon/helper"
|
||||
|
||||
const parser = new DOMParser()
|
||||
|
||||
export function downloadSvg(
|
||||
svgString: string,
|
||||
svgName: string,
|
||||
svgProps?: IconProps,
|
||||
) {
|
||||
const svgElement = parser
|
||||
.parseFromString(svgString, "image/svg+xml")
|
||||
.querySelector("svg")
|
||||
if (svgElement) {
|
||||
applyStyles(svgElement, { ...(svgProps || {}), fillPath: true })
|
||||
svgString = svgElement.outerHTML
|
||||
}
|
||||
const base64doc = btoa(unescape(encodeURIComponent(svgString)))
|
||||
const a = document.createElement("a")
|
||||
const e = new MouseEvent("click")
|
||||
a.download = svgName + ".svg"
|
||||
a.href = "data:image/svg+xml;base64," + base64doc
|
||||
a.dispatchEvent(e)
|
||||
}
|
||||
|
||||
export const complement = (hex?: string) => {
|
||||
if (hex) {
|
||||
const hexParts = hex.substring(1).toLowerCase().split("")
|
||||
const hexMap = { a: 10, b: 11, c: 12, d: 13, e: 14, f: 15 }
|
||||
const reverseMap = { 10: "a", 11: "b", 12: "c", 13: "d", 14: "e", 15: "f" }
|
||||
let complementValue = "#"
|
||||
hexParts.forEach((part: string) => {
|
||||
let num = +part
|
||||
if (isNaN(num)) {
|
||||
num = hexMap[part as keyof object]
|
||||
}
|
||||
const complementNumeric = 15 - num
|
||||
complementValue +=
|
||||
complementNumeric > 9
|
||||
? reverseMap[complementNumeric as keyof object]
|
||||
: complementNumeric
|
||||
})
|
||||
return complementValue
|
||||
}
|
||||
return "#fff"
|
||||
}
|
||||
4
src/index.tsx
Normal file
4
src/index.tsx
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as Clusters } from "./Clusters"
|
||||
export { default as IconInfo } from "./IconInfo"
|
||||
export { default as IconsMerge } from "./IconsMerge"
|
||||
export { default } from "./Icons"
|
||||
71
src/react-app-env.d.ts
vendored
Normal file
71
src/react-app-env.d.ts
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
/// <reference types="node" />
|
||||
/// <reference types="react" />
|
||||
/// <reference types="react-dom" />
|
||||
|
||||
declare namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
readonly NODE_ENV: "development" | "production"
|
||||
readonly PUBLIC_URL: string
|
||||
}
|
||||
}
|
||||
|
||||
declare module "*.avif" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.bmp" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.gif" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.jpg" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.jpeg" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.png" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.webp" {
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.svg" {
|
||||
import * as React from "react"
|
||||
|
||||
export const ReactComponent: React.FunctionComponent<
|
||||
React.SVGProps<SVGSVGElement> & { title?: string }
|
||||
>
|
||||
|
||||
const src: string
|
||||
export default src
|
||||
}
|
||||
|
||||
declare module "*.module.css" {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module "*.module.scss" {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module "*.module.sass" {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
13
src/store.ts
Normal file
13
src/store.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { configureStore } from "@reduxjs/toolkit"
|
||||
import icons from "./Icons.slice"
|
||||
import iconInfo from "./IconInfo.slice"
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
icons,
|
||||
iconInfo,
|
||||
},
|
||||
devTools: {
|
||||
name: "icon-spot", // Specify the name of your store for Redux DevTools
|
||||
},
|
||||
})
|
||||
105
src/types.ts
Normal file
105
src/types.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { MouseEventHandler, ReactNode, Dispatch, SetStateAction } from "react"
|
||||
import { IconProps, IconResponse as BaseIconResponse, IconStyles } from "@armco/icon"
|
||||
|
||||
export type { IconProps, IconStyles } from "@armco/icon"
|
||||
export type { ObjectType, FunctionType, ArrayType } from "@armco/types"
|
||||
export {
|
||||
ArButtonVariants,
|
||||
ArLoaderTypes,
|
||||
ArPopoverPositions,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
RecusionConditionTypes,
|
||||
} from "@armco/shared-components/enums"
|
||||
export type {
|
||||
FilterState,
|
||||
SearchItem,
|
||||
SegmentData,
|
||||
} from "@armco/shared-components/entity"
|
||||
export {
|
||||
ArIconSourceTypes,
|
||||
ArIconTileTypes,
|
||||
} from "@armco/icon/enums"
|
||||
|
||||
// ArThemes - defined locally since @armco/utils/enums path may not be accessible
|
||||
export enum ArThemes {
|
||||
LIGHT1 = "th-light-1",
|
||||
DARK1 = "th-dark-1",
|
||||
}
|
||||
|
||||
// Extended IconResponse with additional fields used in icon-spot
|
||||
export interface IconResponse extends BaseIconResponse {
|
||||
createdby?: string
|
||||
meta?: {
|
||||
downloadedTimes?: number
|
||||
likedTimes?: number
|
||||
size?: number
|
||||
[key: string]: unknown
|
||||
}
|
||||
[key: string]: unknown // Allow index access for ObjectType compatibility
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// icon-spot local component prop types
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
export interface IconTileProps {
|
||||
classes?: string
|
||||
fillColor?: string
|
||||
hideBorder?: boolean
|
||||
hideFooter?: boolean
|
||||
icon: IconResponse
|
||||
iconSize?: string
|
||||
onClick?: (icon: IconResponse) => void
|
||||
selectable?: boolean
|
||||
slot?: string
|
||||
strokeColor?: string
|
||||
strokeWidth?: string
|
||||
tools?: Array<ToolItemConfig>
|
||||
toolsPlacement?: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
export interface ToolItemConfig {
|
||||
children?: Array<ToolItemConfig>
|
||||
iconProps: IconProps
|
||||
onClick: MouseEventHandler<HTMLImageElement>
|
||||
name: string
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
export interface FavoritesItemProps {
|
||||
index: number
|
||||
favorite: IconTileProps
|
||||
}
|
||||
|
||||
export interface FavoritesListProps {
|
||||
classes?: string
|
||||
}
|
||||
|
||||
export interface IconControllerProps {
|
||||
group?: string
|
||||
icon?: IconResponse
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface IconsListProps {
|
||||
filters?: Record<string, unknown>
|
||||
tags?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface IconStyleSelectorProps {
|
||||
iconStyles?: IconStyles
|
||||
layout?: "horizontal" | "vertical"
|
||||
setIconStyles: Dispatch<SetStateAction<IconStyles | undefined>>
|
||||
slot?: string
|
||||
}
|
||||
|
||||
export interface IconEditorProps {
|
||||
layout?: "horizontal" | "vertical"
|
||||
}
|
||||
|
||||
export interface IconMergeContainerProps {
|
||||
icons?: Array<IconResponse>
|
||||
}
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
42
vite-dev.config.ts
Normal file
42
vite-dev.config.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), libInjectCss(), dts({ outDir: "build/types" })],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
|
||||
},
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
26
vite-run.config.ts
Normal file
26
vite-run.config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import { config } from "dotenv"
|
||||
|
||||
config()
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
||||
},
|
||||
plugins: [react()],
|
||||
server: {
|
||||
open: true,
|
||||
},
|
||||
build: {
|
||||
outDir: "build",
|
||||
sourcemap: true,
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "src/setupTests",
|
||||
mockReset: true,
|
||||
},
|
||||
})
|
||||
45
vite.config.ts
Normal file
45
vite.config.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { resolve } from "path"
|
||||
import { glob } from "glob"
|
||||
import { defineConfig } from "vitest/config"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import dts from "vite-plugin-dts"
|
||||
import { libInjectCss } from "vite-plugin-lib-inject-css"
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
libInjectCss(),
|
||||
dts({ outDir: "build/types", exclude: ["**/Test.tsx", "**/*.test.tsx"] }),
|
||||
],
|
||||
build: {
|
||||
outDir: "build",
|
||||
lib: {
|
||||
entry: glob.sync(resolve(__dirname, "src/**/!(*.d|Test).{ts,tsx}")),
|
||||
},
|
||||
rollupOptions: {
|
||||
treeshake: true,
|
||||
external: [
|
||||
new RegExp("react*"),
|
||||
new RegExp("highcharts*"),
|
||||
new RegExp("@armco/*"),
|
||||
"d3",
|
||||
"uuid",
|
||||
],
|
||||
output: [
|
||||
{
|
||||
format: "es",
|
||||
dir: "build/es",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
{
|
||||
format: "cjs",
|
||||
dir: "build/cjs",
|
||||
entryFileNames: "[name].js",
|
||||
chunkFileNames: "[name]-chunk.js",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user