4
.dockerignore
Normal file
4
.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
plop-templates
|
||||
plopfile.cjs
|
||||
.github
|
||||
0
.github/workflows.yml
vendored
Normal file
0
.github/workflows.yml
vendored
Normal file
14
.gitignore
vendored
14
.gitignore
vendored
@@ -26,7 +26,19 @@ coverage
|
||||
# Production
|
||||
build
|
||||
dist
|
||||
lib
|
||||
public
|
||||
|
||||
# Miscellaneous
|
||||
*.local
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
analyse.html
|
||||
stats.html
|
||||
|
||||
*.pem
|
||||
*.d.ts
|
||||
|
||||
analyticsrc.json
|
||||
|
||||
Date
|
||||
20
.storybook/main.ts
Normal file
20
.storybook/main.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { StorybookConfig } from "@storybook/react-vite"
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
addons: [
|
||||
"@storybook/addon-links",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-interactions",
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: "tag",
|
||||
},
|
||||
core: {
|
||||
disableTelemetry: true,
|
||||
},
|
||||
}
|
||||
export default config
|
||||
15
.storybook/preview.ts
Normal file
15
.storybook/preview.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { Preview } from "@storybook/react"
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
# pull the Node.js Docker image as dependencies
|
||||
# ---- Dependencies Node ----
|
||||
FROM node:16-alpine AS build
|
||||
# copy the package.json files from local machine to the workdir in container
|
||||
COPY package.json .
|
||||
# install node packages
|
||||
RUN npm set progress=false && npm config set depth 0
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# #
|
||||
# # ---- Test ----
|
||||
# # run linters, setup and tests
|
||||
# FROM dependencies AS test
|
||||
# COPY . .
|
||||
# RUN npm run lint && npm run test
|
||||
# copy app sources
|
||||
COPY ./src ./src
|
||||
COPY tsconfig.json vite.config.ts analyticsrc.json index.html /
|
||||
RUN npm run build
|
||||
|
||||
FROM node:16-alpine
|
||||
# copy the generated modules and all other files to the container
|
||||
WORKDIR /usr/src/app
|
||||
RUN npm install -g serve
|
||||
COPY cert.pem /etc/ssl/certificates/cert.pem
|
||||
COPY chain.pem /etc/ssl/certificates/chain.pem
|
||||
COPY fullchain.pem /etc/ssl/certificates/fullchain.pem
|
||||
COPY privkey.pem /etc/ssl/certificates/privkey.pem
|
||||
COPY --from=build build .
|
||||
# our app is running on port 3000 within the container, so need to expose it
|
||||
EXPOSE 3000
|
||||
# the command that starts our app
|
||||
CMD ["serve", "-s", "."]
|
||||
58
build-tools/injectLibCss.ts
Normal file
58
build-tools/injectLibCss.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import fs from "fs"
|
||||
import { resolve } from "path"
|
||||
import { PluginOption } from "vite"
|
||||
import { LibCssOptions } from "./injectLibCss.d"
|
||||
|
||||
let viteConfig
|
||||
|
||||
const injectLibCss = function (options: LibCssOptions = {}): PluginOption {
|
||||
return {
|
||||
name: "ar-lib-css",
|
||||
apply: "build",
|
||||
enforce: "post",
|
||||
|
||||
configResolved(resolvedConfig) {
|
||||
viteConfig = resolvedConfig
|
||||
},
|
||||
|
||||
writeBundle(option, bundle) {
|
||||
if (!viteConfig.build || !viteConfig.build.lib) {
|
||||
// only for lib build
|
||||
console.warn("vite-plugin-libcss only works in lib mode.")
|
||||
return
|
||||
}
|
||||
if (option.format !== "es") {
|
||||
// only for es built
|
||||
return
|
||||
}
|
||||
const files = Object.keys(bundle)
|
||||
const cssFile = files.find((v) => v.endsWith(".css"))
|
||||
if (!cssFile) {
|
||||
return
|
||||
}
|
||||
for (const file of files) {
|
||||
if (!(bundle[file] as any).isEntry) {
|
||||
// only for entry
|
||||
continue
|
||||
}
|
||||
if (
|
||||
options.exclude &&
|
||||
(options.exclude as Array<string>).findIndex((ex) =>
|
||||
file.startsWith(ex + "/"),
|
||||
) > -1
|
||||
) {
|
||||
// check if the file matches the include pattern
|
||||
continue
|
||||
}
|
||||
const outDir = viteConfig.build.outDir || "dist"
|
||||
const filePath = resolve(viteConfig.root, outDir, file)
|
||||
const data = fs.readFileSync(filePath, {
|
||||
encoding: "utf8",
|
||||
})
|
||||
fs.writeFileSync(filePath, `import './${cssFile}';\n${data}`)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default injectLibCss
|
||||
13
bulkgen.sh
13
bulkgen.sh
@@ -1,13 +0,0 @@
|
||||
#!/bin/zsh
|
||||
|
||||
declare -a arr=("Alert", "Badge" "Breadcrumb" "BrowserIncompatibility" "Button" "Checkbox" "ColorPicker" "ContextMenu" "DateInput" "DatePicker" "DateRange" "Calendar" "PickerRange" "DetailsPanel" "Dialog" "FacetedFilter" "InlineMenu" "LabelValue" "LearnLink" "Link" "List" "Modal" "Notification" "NumericStepper" "Pagination" "Picklist" "Pill" "Pillbox" "Popover" "ProgressIndicator" "ProgressStepper" "Radio" "SearchField" "SecondaryNavigation" "SegmentedControl" "Select" "Slider" "Splitter" "Tab" "TabBar" "Table" "Tag" "TextArea" "TextInput" "Mask" "TimeEntry" "Toggle" "Toolbar" "Tooltip" "TypeAhead" "Uploader" "Widget")
|
||||
for i in "${arr[@]}"
|
||||
do
|
||||
npm run component "$i"
|
||||
done
|
||||
|
||||
declare -a arr=("AboutUs" "Application" "Banner" "Benefits" "Blog" "Brands" "Breadcrumbs" "CTA" "Card" "Careers" "Contact" "Content" "Cookies" "Dashboard" "Download" "Ecomm_Orders" "Ecomm_Products" "Empty" "FAQ" "Features" "Footer" "Form" "Gallery" "GraphTiles" "Graph" "HTTPCode" "Hero" "HowItWorks" "InstaPhotos" "Integrations" "LogoClouds" "Newsletter" "Notifications" "Portfolio" "Pricing" "ProductInfo" "Projects" "Reviews" "RichText" "Search" "Services" "SignInUp" "Snackbar" "Stats" "Steps" "Team" "Testimonials" "Toast" "Users")
|
||||
for i in "${arr[@]}"
|
||||
do
|
||||
npm run molecule "$i"
|
||||
done
|
||||
@@ -4,11 +4,11 @@
|
||||
<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>
|
||||
<title>Armory React Repository</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div class="theme-light" id="root"></div>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
29149
package-lock.json
generated
29149
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
84
package.json
84
package.json
@@ -1,16 +1,15 @@
|
||||
{
|
||||
"name": "@armco/armory-react-components",
|
||||
"description": "React Component Library for Armco's stack of products and services",
|
||||
"version": "0.0.8",
|
||||
"version": "0.0.28",
|
||||
"type": "module",
|
||||
"author": "Armco (@restruct-corporate-advantage)",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"start": "vite",
|
||||
"build": "tsc --project ./tsconfig-vite.json && vite build",
|
||||
"build:publish": "npm run build:esm && npm run build:cjs",
|
||||
"build:esm": "tsc",
|
||||
"build:cjs": "tsc --module commonjs --outDir dist/cjs",
|
||||
"start": "NODE_ENV=production vite",
|
||||
"build": "tsc && vite build",
|
||||
"build:publish": "./scripts/build.sh",
|
||||
"build:publish:compile": "tsc --p ./tsconfig-build.json && vite build --config vite-publish.config.ts",
|
||||
"generate": "plop",
|
||||
"atom": "plop atom",
|
||||
"molecule": "plop molecule",
|
||||
@@ -22,47 +21,93 @@
|
||||
"lint": "eslint .",
|
||||
"type-check": "tsc",
|
||||
"publish:dry": "npm publish --dry-run",
|
||||
"publish:public": "npm publish --access public",
|
||||
"prepublishOnly": "npm run build:publish && npm --no-git-tag-version version patch"
|
||||
"publish:local": "./scripts/publish-local.sh",
|
||||
"publish:public": "./scripts/publish.sh",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@armco/armory-react-components": "^0.0.8",
|
||||
"@armco/analytics": "^0.2.5",
|
||||
"@armco/svg-canvas": "^0.1.3",
|
||||
"@lottiefiles/react-lottie-player": "^3.5.3",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"@storybook/cli": "^7.0.23",
|
||||
"bootstrap": "^5.3.0",
|
||||
"react": "^18.2.0",
|
||||
"classnames": "^2.3.2",
|
||||
"d3": "^7.8.5",
|
||||
"highcharts": "^11.2.0",
|
||||
"highlight.js": "^11.8.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"lottie-react": "^2.4.0",
|
||||
"lottie-web": "^5.12.2",
|
||||
"moment": "^2.29.4",
|
||||
"react-app-polyfill": "^3.0.0",
|
||||
"react-bootstrap": "^2.7.4",
|
||||
"react-dev-utils": "^12.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.0.1",
|
||||
"react-router-dom": "^6.13.0"
|
||||
"react-router-dom": "^6.13.0",
|
||||
"react-table": "^7.8.0",
|
||||
"svgpath": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vite-plugin-svgr": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@storybook/addon-essentials": "^7.0.23",
|
||||
"@storybook/addon-interactions": "^7.0.23",
|
||||
"@storybook/addon-links": "^7.0.23",
|
||||
"@storybook/blocks": "^7.0.23",
|
||||
"@storybook/react": "^7.0.23",
|
||||
"@storybook/react-vite": "^7.0.23",
|
||||
"@storybook/testing-library": "^0.0.14-next.2",
|
||||
"@testing-library/dom": "^9.2.0",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.2.5",
|
||||
"@types/bootstrap": "^5.2.6",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/js-cookie": "^3.0.3",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react-table": "^7.7.19",
|
||||
"@types/testing-library__jest-dom": "^5.14.5",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"chalk": "^5.3.0",
|
||||
"cherry-pick": "^0.5.0",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"eslint": "^8.0.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-storybook": "^0.6.12",
|
||||
"execa": "^8.0.1",
|
||||
"fs-extra": "^11.2.0",
|
||||
"glob": "^10.3.10",
|
||||
"jsdom": "^21.1.0",
|
||||
"plop": "^3.1.2",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-config-nick": "^1.0.2",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.63.4",
|
||||
"storybook": "^7.0.23",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-dts": "^3.7.1",
|
||||
"vitest": "^0.30.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-redux": "^8.0.1",
|
||||
"react-router-dom": "^6.13.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
"react-app/jest",
|
||||
"plugin:storybook/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier"
|
||||
@@ -73,7 +118,9 @@
|
||||
}
|
||||
},
|
||||
"prettier": "prettier-config-nick",
|
||||
"main": "index.tsx",
|
||||
"types": "./build/index.d.ts",
|
||||
"main": "./build/index.js",
|
||||
"module": "./build/index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ReStruct-Corporate-Advantage/armory-react-components.git"
|
||||
@@ -89,14 +136,11 @@
|
||||
"foundation"
|
||||
],
|
||||
"files": [
|
||||
"dist"
|
||||
"build"
|
||||
],
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ReStruct-Corporate-Advantage/armory-react-components/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ReStruct-Corporate-Advantage/armory-react-components#readme",
|
||||
"peerDependencies": {
|
||||
"react": ">=16"
|
||||
}
|
||||
"homepage": "https://github.com/ReStruct-Corporate-Advantage/armory-react-components#readme"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.c-{{pascalCase name}} {
|
||||
.ar-{{pascalCase name}} {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { {{pascalCase name}}Props } from ".."
|
||||
import "./{{pascalCase name}}.component.scss"
|
||||
|
||||
interface {{pascalCase name}}Props {}
|
||||
|
||||
const {{pascalCase name}} = (props: {{pascalCase name}}Props): JSX.Element => {
|
||||
return <div className="c-{{pascalCase name}}">In Component {{pascalCase name}}</div>
|
||||
return <div className="ar-{{pascalCase name}}">In Component {{pascalCase name}}</div>
|
||||
}
|
||||
|
||||
export default {{pascalCase name}}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.c-{{pascalCase name}} {
|
||||
.ar-{{pascalCase name}} {
|
||||
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ export interface {{pascalCase name}}State {}
|
||||
|
||||
const initialState: {{pascalCase name}}State = {}
|
||||
|
||||
export const {{snakeCase name}}Slice = createSlice({
|
||||
name: "{{snakeCase name}}",
|
||||
export const {{camelCase name}}Slice = createSlice({
|
||||
name: "{{camelCase name}}",
|
||||
initialState,
|
||||
reducers: {
|
||||
increment: (state) => {},
|
||||
@@ -13,6 +13,6 @@ export const {{snakeCase name}}Slice = createSlice({
|
||||
extraReducers: (builder) => {},
|
||||
})
|
||||
|
||||
export const { increment } = {{snakeCase name}}Slice.actions
|
||||
export const { increment } = {{camelCase name}}Slice.actions
|
||||
|
||||
export default {{snakeCase name}}Slice.reducer
|
||||
export default {{camelCase name}}Slice.reducer
|
||||
|
||||
@@ -3,7 +3,7 @@ import "./{{pascalCase name}}.page.scss"
|
||||
interface {{pascalCase name}}Props {}
|
||||
|
||||
const {{pascalCase name}} = (props: {{pascalCase name}}Props): JSX.Element => {
|
||||
return <div className="c-{{pascalCase name}}">In Page {{pascalCase name}}</div>
|
||||
return <div className="ar-{{pascalCase name}}">In Page {{pascalCase name}}</div>
|
||||
}
|
||||
|
||||
export default {{pascalCase name}}
|
||||
|
||||
2
plop-templates/components.interface.ts.hbs
Normal file
2
plop-templates/components.interface.ts.hbs
Normal file
@@ -0,0 +1,2 @@
|
||||
/* PLOP_INJECT_INTERFACE */
|
||||
|
||||
18
plopfile.cjs
18
plopfile.cjs
@@ -47,6 +47,12 @@ module.exports = (plop) => {
|
||||
pattern: `/* PLOP_INJECT_EXPORT */`,
|
||||
template: `\t{{pascalCase name}},`,
|
||||
},
|
||||
{
|
||||
type: "append",
|
||||
path: "src/app/types/components.interface.ts",
|
||||
pattern: `/* PLOP_INJECT_INTERFACE */`,
|
||||
template: `export interface {{pascalCase name}}Props extends BaseProps {\n}\n`,
|
||||
},
|
||||
],
|
||||
})
|
||||
plop.setGenerator("atom", {
|
||||
@@ -97,6 +103,12 @@ module.exports = (plop) => {
|
||||
pattern: `/* PLOP_INJECT_EXPORT */`,
|
||||
template: `\t{{pascalCase name}},`,
|
||||
},
|
||||
{
|
||||
type: "append",
|
||||
path: "src/app/types/components.interface.ts",
|
||||
pattern: `/* PLOP_INJECT_INTERFACE */`,
|
||||
template: `export interface {{pascalCase name}}Props extends BaseProps {\n}\n`,
|
||||
},
|
||||
],
|
||||
})
|
||||
plop.setGenerator("molecule", {
|
||||
@@ -147,6 +159,12 @@ module.exports = (plop) => {
|
||||
pattern: `/* PLOP_INJECT_EXPORT */`,
|
||||
template: `\t{{pascalCase name}},`,
|
||||
},
|
||||
{
|
||||
type: "append",
|
||||
path: "src/app/types/components.interface.ts",
|
||||
pattern: `/* PLOP_INJECT_INTERFACE */`,
|
||||
template: `export interface {{pascalCase name}}Props extends BaseProps {\n}\n`,
|
||||
},
|
||||
],
|
||||
})
|
||||
plop.setGenerator("page", {
|
||||
|
||||
131
scripts/build.sh
Executable file
131
scripts/build.sh
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/bin/sh
|
||||
|
||||
copy_files() {
|
||||
local source_dir="$1"
|
||||
local destination_dir="$2"
|
||||
local files_to_copy=".tsx|.scss"
|
||||
|
||||
echo "Copying files from $source_dir to $destination_dir"
|
||||
find "$source_dir" -type f \( -name "*.tsx" -o -name "*.scss" \) | while read -r file; do
|
||||
# Check if the file has one of the specified extensions
|
||||
for ext in $(echo "$files_to_copy" | tr '|' '\n'); do
|
||||
if echo "$file" | grep -q "$ext$"; then
|
||||
destination_file="$destination_dir/$(basename "$file")"
|
||||
# Create destination directory if it doesn't exist
|
||||
mkdir -p "$(dirname "$destination_file")"
|
||||
# echo "Copying $file to $destination_file"
|
||||
cp "$file" "$destination_file"
|
||||
echo "Copied $file to $destination_file"
|
||||
break # Exit the loop after finding a match
|
||||
fi
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
search_replace_in_files() {
|
||||
local directory_or_file="$1"
|
||||
local search_string="$2"
|
||||
local replace_string="$3"
|
||||
local -a dir_names=("${@:4}") # Accept array of directory names as arguments
|
||||
|
||||
echo "Initiate search and replace in files $search_string $replace_string"
|
||||
|
||||
# Step 1: Check if the argument is a file
|
||||
if [ -f "$directory_or_file" ]; then
|
||||
# Process the single file
|
||||
perl -pi -e "s|$search_string|$replace_string|g" "$directory_or_file"
|
||||
else
|
||||
# Step 2: Get all files recursively matching the condition (only .tsx and .scss)
|
||||
files=$(find "$directory_or_file" -type f \( -name "*.tsx" -o -name "*.scss" -o -name "*.ts" \))
|
||||
# Step 3: Iterate over each file and perform search and replace
|
||||
for file in $files; do
|
||||
# Step 4: Iterate over each directory name in the array
|
||||
for dir_name in "${dir_names[@]}"; do
|
||||
# Step 5: Check if the file name starts with any of the specified directories
|
||||
if [[ "$file" == *"/$dir_name/"* ]]; then
|
||||
echo "Skipping file $file as it starts with '$dir_name/'"
|
||||
continue 2 # Continue to the next iteration of the outer loop
|
||||
fi
|
||||
done
|
||||
# Step 6: Perform search and replace in each file using perl
|
||||
perl -pi -e "s|$search_string|$replace_string|g" "$file"
|
||||
done
|
||||
fi
|
||||
|
||||
echo "Search and replace in TypeScript and SCSS files completed."
|
||||
}
|
||||
|
||||
# Function to split the string into an array based on a delimiter
|
||||
split_string() {
|
||||
local string="$1"
|
||||
local delimiter="$2"
|
||||
IFS="$delimiter" read -r -a array <<< "$string"
|
||||
echo "${array[@]}"
|
||||
}
|
||||
|
||||
rm -rf ./lib
|
||||
mkdir -p "./lib"
|
||||
|
||||
file_types=("types" "utils" "config" "static" "store" "hooks")
|
||||
ignore_dirs=("utils config static" "types" "utils" "utils" "" "")
|
||||
|
||||
copy_files "src/app/components/atoms" "lib"
|
||||
copy_files "src/app/components/molecules" "lib"
|
||||
cp src/app/components/atoms/index.tsx lib/atoms.ts
|
||||
cp src/app/components/molecules/index.tsx lib/molecules.ts
|
||||
cp src/app/components/components.ts lib/index.ts
|
||||
cp -r src/app/types lib
|
||||
cp -r src/app/utils lib
|
||||
cp -r src/app/config lib
|
||||
cp -r src/app/static lib
|
||||
rm -rf lib/static/styles/*
|
||||
cp src/app/static/styles/_mixins.scss lib/static/styles/mixins.scss
|
||||
cp src/app/static/styles/_variables.scss lib/static/styles/variables.scss
|
||||
cp src/app/static/styles/index.scss lib/static/styles/index.scss
|
||||
cp src/app/hooks.ts "lib"
|
||||
cp -r src/app/hooks "lib"
|
||||
cp src/react-app-env.d.ts lib
|
||||
cp src/vite-env.d.ts lib
|
||||
cp package.json lib
|
||||
# cp src/app/components/atoms/Calendar/JustCalendar.tsx "lib"
|
||||
# cp src/app/components/atoms/Calendar/MonthSelector.tsx "lib"
|
||||
# cp src/app/components/atoms/Calendar/MonthSelector.component.scss "lib"
|
||||
# cp src/app/components/atoms/Calendar/JustCalendar.component.scss "lib"
|
||||
# cp src/app/components/atoms/Calendar/EventForm.tsx "lib"
|
||||
# cp src/app/components/atoms/Calendar/MonthNavigator.tsx "lib"
|
||||
# cp src/app/components/atoms/Calendar/MonthNavigator.component.scss "lib"
|
||||
cp src/app/components/atoms/Calendar/helper.ts "lib"
|
||||
# cp src/app/components/molecules/Carousel/Thumbs.tsx "lib"
|
||||
cp src/app/components/molecules/Carousel/cssClasses.ts "lib"
|
||||
cp src/app/components/molecules/Carousel/animations.ts "lib"
|
||||
cp src/app/store.ts "lib"
|
||||
|
||||
for i in $(seq 0 $((${#file_types[@]} - 1))); do
|
||||
echo ${file_types[i]} ${ignore_dirs[i]}
|
||||
ignore_dirs_array=($(split_string "${ignore_dirs[i]}" " "))
|
||||
search_replace_in_files "lib" "\.\./\.\./\.\./${file_types[i]}" "./${file_types[i]}" "${ignore_dirs_array[@]}"
|
||||
search_replace_in_files "lib" "\.\./\.\./${file_types[i]}" "./${file_types[i]}" "${ignore_dirs_array[@]}"
|
||||
search_replace_in_files "lib" "\.\./${file_types[i]}" "./${file_types[i]}" "${ignore_dirs_array[@]}"
|
||||
done
|
||||
|
||||
search_replace_in_files "lib" "\.\./components/molecules" ".."
|
||||
search_replace_in_files "lib" "\.\./components/atoms" ".."
|
||||
search_replace_in_files "lib/types/types.ts" "Carousel/Carousel" "Carousel"
|
||||
search_replace_in_files "lib/DatePicker.tsx" "\.\./Calendar/helper" "./helper" "types"
|
||||
search_replace_in_files "lib/DateRangePicker.tsx" "\.\./Calendar/helper" "./helper" "types"
|
||||
search_replace_in_files "lib/package.json" "\./build/index" "index"
|
||||
search_replace_in_files "lib/types/components.interface.ts" "\.\./Calendar/helper" "../helper"
|
||||
search_replace_in_files "lib" "\"\.\./\.\.\"" "\"..\""
|
||||
search_replace_in_files "lib/index.ts" "\.\./static/styles" "./static/styles"
|
||||
search_replace_in_files "lib/atoms.ts" "\./SegmentedControl/Segment" "./Segment"
|
||||
|
||||
sed '/componentsViewerPage/d' lib/store.ts > temp_file && mv temp_file lib/store.ts
|
||||
sed '/iconsPage/d' lib/store.ts > temp_file && mv temp_file lib/store.ts
|
||||
sed '/iconPage/d' lib/store.ts > temp_file && mv temp_file lib/store.ts
|
||||
sed '/tasksPage/d' lib/store.ts > temp_file && mv temp_file lib/store.ts
|
||||
|
||||
search_replace_in_files "lib" "\"\.\.\"" "\"\.\""
|
||||
|
||||
rm -rf build
|
||||
npm run build:publish:compile
|
||||
13
scripts/bulkgen.sh
Normal file
13
scripts/bulkgen.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/zsh
|
||||
|
||||
declare -a arr=("Alert", "Badge" "Breadcrumb" "BrowserIncompatibility" "Button" "Checkbox" "ColorPicker" "ContextMenu" "DateInput" "DatePicker" "Date" "Calendar" "PickerRange" "DetailsPanel" "Dialog" "FacetedFilter" "Icon" "InlineMenu" "LabelValue" "LearnLink" "Link" "List" "Modal" "Notification" "NumericStepper" "Pagination" "Picklist" "Pill" "Pillbox" "Popover" "ProgressIndicator" "ProgressStepper" "Radio" "SearchField" "SecondaryNavigation" "SegmentedControl" "Select" "Slider" "Splitter" "Tab" "TabBar" "Table" "Tag" "TextArea" "TextInput" "Mask" "TimeEntry" "Toggle" "Toolbar" "Tooltip" "TypeAhead" "Uploader" "Widget")
|
||||
for i in "${arr[@]}"
|
||||
do
|
||||
npm run atom "$i"
|
||||
done
|
||||
|
||||
declare -a arr=("AboutUs" "Application" "Carousel" "Banner" "Benefits" "Blog" "Brands" "Breadcrumbs" "CTA" "Card" "Careers" "Contact" "Content" "Cookies" "Dashboard" "Download" "Ecomm_Orders" "Ecomm_Products" "Empty" "FAQ" "Features" "Footer" "Form" "Gallery" "GraphTiles" "Graph" "HTTPCode" "Hero" "HowItWorks" "InstaPhotos" "Integrations" "LogoClouds" "Newsletter" "Notifications" "Portfolio" "Pricing" "ProductInfo" "Projects" "Reviews" "RichText" "Search" "Services" "SignInUp" "Snackbar" "Stats" "Steps" "Swiper" "Team" "Testimonials" "Toast" "Users")
|
||||
for i in "${arr[@]}"
|
||||
do
|
||||
npm run molecule "$i"
|
||||
done
|
||||
5
scripts/publish-local.sh
Executable file
5
scripts/publish-local.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
source ./scripts/build.sh
|
||||
npm pack --pack-destination ~/__Projects__/Common
|
||||
8
scripts/publish.sh
Executable file
8
scripts/publish.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
semver=${1:-patch}
|
||||
|
||||
set -e
|
||||
source ./scripts/build.sh
|
||||
npm --no-git-tag-version version ${semver}
|
||||
npm publish --access public
|
||||
@@ -14,7 +14,6 @@ class ErrorBoundary extends Component<Props, State> {
|
||||
}
|
||||
|
||||
public static getDerivedStateFromError(_: Error): State {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,82 @@
|
||||
import { Suspense, useEffect } from "react"
|
||||
import { useRoutes } from "react-router-dom"
|
||||
import * as pages from "./pages"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { AlertProps } from "./types/components.interface"
|
||||
import Header from "./components/Header"
|
||||
import { useAppDispatch, useAppSelector } from "./hooks"
|
||||
import {
|
||||
notification,
|
||||
notify,
|
||||
setLoggedIn,
|
||||
setRightPanelContent,
|
||||
setUser,
|
||||
} from "./store"
|
||||
import { Alert } from "./components"
|
||||
import { ArAlertType } from "./types/enums"
|
||||
import Helper from "./utils/helper"
|
||||
import { Network } from "./utils"
|
||||
import ROUTES from "./routes"
|
||||
import { ENDPOINTS } from "./config/constants"
|
||||
import API_CONFIG from "./config/api-config"
|
||||
|
||||
Helper.populateComponentsInRoutes(ROUTES, pages)
|
||||
Helper.populatePagesInRoutes(ROUTES)
|
||||
|
||||
interface RouterProps {}
|
||||
|
||||
const Router = (props: RouterProps) => useRoutes(ROUTES)
|
||||
const Router = (props: RouterProps) => {
|
||||
const alertInfo = useAppSelector<AlertProps | undefined>(notification)
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useEffect(() => {
|
||||
document
|
||||
.getElementsByTagName("html")[0]
|
||||
.setAttribute("ar-theme", "th-light-1")
|
||||
Network.get(
|
||||
API_CONFIG.IAM[process.env.NODE_ENV] +
|
||||
ENDPOINTS.USERS.ROOT +
|
||||
ENDPOINTS.USERS.CHECK,
|
||||
)
|
||||
.then((response) => {
|
||||
if (response && response.status === 200) {
|
||||
dispatch(setLoggedIn(true))
|
||||
if (response.body) {
|
||||
dispatch(setUser(response.body))
|
||||
}
|
||||
} else {
|
||||
dispatch(setLoggedIn(false))
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.status === 403) {
|
||||
dispatch(setLoggedIn(false))
|
||||
}
|
||||
})
|
||||
window.onmessage = (e) => {
|
||||
if (e.data && e.data.event === "LOGGED_IN") {
|
||||
dispatch(setLoggedIn(true))
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Logged in successfully!",
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
dispatch(setRightPanelContent({ name: "" }))
|
||||
dispatch(setUser(e.data.data.body.user))
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const matchedRouteElement = useRoutes(ROUTES)
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Header routeDetails={matchedRouteElement?.props.match} />
|
||||
{matchedRouteElement}
|
||||
{alertInfo && <Alert {...alertInfo} />}
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
export default Router
|
||||
|
||||
3
src/app/components/AssetSelector/AssetSelector.component.scss
Executable file
3
src/app/components/AssetSelector/AssetSelector.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-AssetSelector {
|
||||
|
||||
}
|
||||
8
src/app/components/AssetSelector/AssetSelector.test.ts
Executable file
8
src/app/components/AssetSelector/AssetSelector.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import AssetSelector from "./AssetSelector"
|
||||
|
||||
describe("AssetSelector", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
13
src/app/components/AssetSelector/AssetSelector.tsx
Executable file
13
src/app/components/AssetSelector/AssetSelector.tsx
Executable file
@@ -0,0 +1,13 @@
|
||||
import { AssetSelectorProps } from "../../types/components.interface"
|
||||
import IconsList from "../IconsList"
|
||||
import "./AssetSelector.component.scss"
|
||||
|
||||
const AssetSelector = (props: AssetSelectorProps): JSX.Element => {
|
||||
return (
|
||||
<div className="ar-AssetSelector">
|
||||
<IconsList variant="compact" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AssetSelector
|
||||
3
src/app/components/AssetSelector/index.ts
Executable file
3
src/app/components/AssetSelector/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import AssetSelector from "./AssetSelector"
|
||||
|
||||
export default AssetSelector
|
||||
@@ -1,3 +1,3 @@
|
||||
.c-ComponentList {
|
||||
.ar-ComponentList {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useAppSelector } from "../../hooks"
|
||||
import { getCurrentTheme } from "../../store"
|
||||
import { TreeList } from ".."
|
||||
import { TreeListData } from "../../types/entity.interface"
|
||||
import Adapter from "../../utils/adapters"
|
||||
import Network from "../../utils/network"
|
||||
import COMPONENTS from "../../config/components"
|
||||
import "./ComponentList.component.scss"
|
||||
|
||||
interface ComponentListProps {}
|
||||
|
||||
const formattedTreeData = Adapter.adaptToTree(COMPONENTS)
|
||||
|
||||
const ComponentList = (props: ComponentListProps): JSX.Element => {
|
||||
const formattedTreeData = Adapter.adaptToTree(COMPONENTS)
|
||||
const navigate = useNavigate()
|
||||
const theme = useAppSelector<string>(getCurrentTheme)
|
||||
const handleComponentSelect = (treeNode: TreeListData) => {
|
||||
const params = treeNode.data?.props
|
||||
treeNode.data?.component &&
|
||||
navigate(
|
||||
Network.stringifyUrl(`/components/${treeNode.data?.component}`, params),
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="c-ComponentList h-100 w-100 overflow-auto">
|
||||
<div className="ar-ComponentList h-100 w-100 mx-2">
|
||||
<TreeList
|
||||
onItemSelect={handleComponentSelect}
|
||||
data={formattedTreeData}
|
||||
title="Component List"
|
||||
firstExpanded={true}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
3
src/app/components/ConfigRowItem/ConfigRowItem.component.scss
Executable file
3
src/app/components/ConfigRowItem/ConfigRowItem.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-ConfigRowItem {
|
||||
|
||||
}
|
||||
8
src/app/components/ConfigRowItem/ConfigRowItem.test.ts
Executable file
8
src/app/components/ConfigRowItem/ConfigRowItem.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigRowItem from "./ConfigRowItem"
|
||||
|
||||
describe("ConfigRowItem", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
98
src/app/components/ConfigRowItem/ConfigRowItem.tsx
Executable file
98
src/app/components/ConfigRowItem/ConfigRowItem.tsx
Executable file
@@ -0,0 +1,98 @@
|
||||
import { ChangeEvent, useState } from "react"
|
||||
import { ConfigRowItemProps } from "../../types/components.interface"
|
||||
import { Button, LoadableIcon, TextInput } from ".."
|
||||
import "./ConfigRowItem.component.scss"
|
||||
import { ArButtonVariants } from "../../types/enums"
|
||||
|
||||
const ConfigRowItem = (props: ConfigRowItemProps): JSX.Element => {
|
||||
const { config, disabled, isNew, onAdd, onUpdate, onDelete } = props
|
||||
const [key, setKey] = useState<string>(config?.key || "")
|
||||
const [value, setValue] = useState<string>(config?.value || "")
|
||||
const [edited, setEdited] = useState<boolean>()
|
||||
|
||||
const configIsSubmittable = (isNew || edited) && key && value
|
||||
return (
|
||||
<div className="ar-ConfigRowItem row">
|
||||
<div className="col-3">
|
||||
<TextInput
|
||||
value={key}
|
||||
disabled={disabled && !edited}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setKey(e.target.value)
|
||||
}
|
||||
placeholder="Config key"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<TextInput
|
||||
placeholder="Enter a value, text/json etc."
|
||||
disabled={disabled && !edited}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
setValue(e.target.value)
|
||||
}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
{isNew ? (
|
||||
<div className="col-1 flex-v-center">
|
||||
<Button
|
||||
content="Add"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
preIcon="io/IoMdAdd"
|
||||
onClick={() => {
|
||||
configIsSubmittable && onAdd && onAdd(key, value)
|
||||
setKey("")
|
||||
setValue("")
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="col-2 flex-v-center">
|
||||
<span
|
||||
className={`me-3${
|
||||
configIsSubmittable ? " cursor-pointer" : " pe-none"
|
||||
}`}
|
||||
>
|
||||
<LoadableIcon
|
||||
icon="fa/FaCheck"
|
||||
color={configIsSubmittable ? "green" : "rgba(0, 128, 0, 0.3)"}
|
||||
onClick={() => {
|
||||
config?._id &&
|
||||
configIsSubmittable &&
|
||||
onUpdate &&
|
||||
onUpdate(config?._id, key, value)
|
||||
setKey("")
|
||||
setValue("")
|
||||
}}
|
||||
hoverShadow
|
||||
/>
|
||||
</span>
|
||||
<span className={`me-3${edited ? " pe-none" : " cursor-pointer"}`}>
|
||||
<LoadableIcon
|
||||
icon={edited ? "rx/RxCross2" : "md/MdModeEditOutline"}
|
||||
color={isNew || edited ? "rgba(165, 42, 42, 0.3)" : "brown"}
|
||||
onClick={() => (edited ? setEdited(false) : setEdited(true))}
|
||||
/>
|
||||
</span>
|
||||
<span className={`me-3 ${edited ? "pe-none" : "cursor-pointer"}`}>
|
||||
<LoadableIcon
|
||||
icon="md/MdAdd"
|
||||
width="1.3rem"
|
||||
color={!configIsSubmittable ? "green" : "rgba(0, 128, 0, 0.3)"}
|
||||
onClick={() => onDelete && onDelete(config?._id)}
|
||||
/>
|
||||
</span>
|
||||
<span className={edited ? "pe-none" : "cursor-pointer"}>
|
||||
<LoadableIcon
|
||||
icon="ri/RiDeleteBin6Line"
|
||||
color={isNew || edited ? "rgba(128, 0, 0, 0.3)" : "red"}
|
||||
onClick={() => onDelete && onDelete(config?._id)}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigRowItem
|
||||
3
src/app/components/ConfigRowItem/index.ts
Executable file
3
src/app/components/ConfigRowItem/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigRowItem from "./ConfigRowItem"
|
||||
|
||||
export default ConfigRowItem
|
||||
3
src/app/components/ConfigurationList/ConfigurationList.component.scss
Executable file
3
src/app/components/ConfigurationList/ConfigurationList.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-ConfigurationList {
|
||||
|
||||
}
|
||||
8
src/app/components/ConfigurationList/ConfigurationList.test.ts
Executable file
8
src/app/components/ConfigurationList/ConfigurationList.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationList from "./ConfigurationList"
|
||||
|
||||
describe("ConfigurationList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
30
src/app/components/ConfigurationList/ConfigurationList.tsx
Executable file
30
src/app/components/ConfigurationList/ConfigurationList.tsx
Executable file
@@ -0,0 +1,30 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { TreeList } from ".."
|
||||
import { ConfigurationListProps } from "../../types/components.interface"
|
||||
import { TreeListData } from "../../types/entity.interface"
|
||||
import { Network } from "../../utils"
|
||||
import "./ConfigurationList.component.scss"
|
||||
|
||||
const ConfigurationList = (props: ConfigurationListProps): JSX.Element => {
|
||||
const { list } = props
|
||||
const navigate = useNavigate()
|
||||
const handleComponentSelect = (treeNode: TreeListData) => {
|
||||
const params = treeNode.data?.props
|
||||
treeNode.data?.component &&
|
||||
navigate(
|
||||
Network.stringifyUrl(`/components/${treeNode.data?.component}`, params),
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="ar-ConfigurationList w-100 p-2">
|
||||
<TreeList
|
||||
onItemSelect={handleComponentSelect}
|
||||
data={list || []}
|
||||
title="Configurations"
|
||||
firstExpanded={true}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationList
|
||||
3
src/app/components/ConfigurationList/index.ts
Executable file
3
src/app/components/ConfigurationList/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationList from "./ConfigurationList"
|
||||
|
||||
export default ConfigurationList
|
||||
@@ -0,0 +1,10 @@
|
||||
.ar-ConfigurationLoginPrompt {
|
||||
width: 50vw;
|
||||
background-color: var(--ar-bg);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
small {
|
||||
color: var(--ar-color-secondary);
|
||||
line-height: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationLoginPrompt from "./ConfigurationLoginPrompt"
|
||||
|
||||
describe("ConfigurationLoginPrompt", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
51
src/app/components/ConfigurationLoginPrompt/ConfigurationLoginPrompt.tsx
Executable file
51
src/app/components/ConfigurationLoginPrompt/ConfigurationLoginPrompt.tsx
Executable file
@@ -0,0 +1,51 @@
|
||||
import { setRightPanelContent } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Button, LoadableIcon } from ".."
|
||||
import { ConfigurationLoginPromptProps } from "../../types/components.interface"
|
||||
import { ArButtonVariants } from "../../types/enums"
|
||||
import "./ConfigurationLoginPrompt.component.scss"
|
||||
|
||||
const ConfigurationLoginPrompt = (
|
||||
props: ConfigurationLoginPromptProps,
|
||||
): JSX.Element => {
|
||||
const dispatch = useAppDispatch()
|
||||
return (
|
||||
<div className="ar-ConfigurationLoginPrompt p-3 border">
|
||||
<div className="flex-h-center flex-column">
|
||||
<h6 className="flex-v-center" style={{ color: "#ffbf00" }}>
|
||||
<LoadableIcon
|
||||
classes="me-2"
|
||||
icon="fa/FaExclamationTriangle"
|
||||
color="#ffbf00"
|
||||
/>
|
||||
Please login to continue
|
||||
</h6>
|
||||
<small>
|
||||
This is the configurations feature where you can create and save
|
||||
configurations as key-value pairs.
|
||||
</small>
|
||||
<small>
|
||||
These key-value pairs can then be accessed using an endpoint inside
|
||||
your application.
|
||||
</small>
|
||||
<small className="mb-3">
|
||||
<strong>
|
||||
In order to be able to save and secure these configurations, you
|
||||
need to create an account, if one doesn't exist already and login
|
||||
using the same. Please use the button below to continue.
|
||||
</strong>
|
||||
</small>
|
||||
</div>
|
||||
<Button
|
||||
content="Login"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
postIcon="io5/IoArrowForwardCircle"
|
||||
onClick={() => {
|
||||
dispatch(setRightPanelContent({ name: "LoginProvider" }))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationLoginPrompt
|
||||
3
src/app/components/ConfigurationLoginPrompt/index.ts
Executable file
3
src/app/components/ConfigurationLoginPrompt/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationLoginPrompt from "./ConfigurationLoginPrompt"
|
||||
|
||||
export default ConfigurationLoginPrompt
|
||||
@@ -0,0 +1,3 @@
|
||||
.ar-ConfigurationNoConfigPrompt {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationNoConfigPrompt from "./ConfigurationNoConfigPrompt"
|
||||
|
||||
describe("ConfigurationNoConfigPrompt", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { ReactNode, useState } from "react"
|
||||
import { setModalState } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Button, LoadableIcon, NamespaceOrgForm, Popover } from ".."
|
||||
import { ConfigurationNoConfigPromptProps } from "../../types/components.interface"
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
} from "../../types/enums"
|
||||
import "./ConfigurationNoConfigPrompt.component.scss"
|
||||
|
||||
const ConfigurationNoConfigPrompt = (
|
||||
props: ConfigurationNoConfigPromptProps,
|
||||
): ReactNode => {
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
return (
|
||||
<div className="ar-ConfigurationNoConfigPrompt flex-center flex-column border px-3 py-5 w-50">
|
||||
<LoadableIcon
|
||||
classes="mb-4"
|
||||
icon="gr/GrConfigure"
|
||||
size="7rem"
|
||||
color="lightgrey"
|
||||
/>
|
||||
<p className="ar-ConfigurationNoConfigPrompt__message mx-5">
|
||||
You don't have any configurations created yet, you can start by
|
||||
selecting one of the options below
|
||||
</p>
|
||||
<div className="ar-ConfigurationNoConfigPrompt__buttons d-flex">
|
||||
<Popover trigger={ArPopoverTriggers.HOVER}>
|
||||
<Button
|
||||
classes="me-3"
|
||||
content="+ Space"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceOrgForm context="namespace" />,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
className="text-wrap"
|
||||
style={{ maxWidth: "10rem" }}
|
||||
>
|
||||
<p>This is where you keep your configurations.</p>
|
||||
<p className="mb-3">
|
||||
Use namespaces in your project/s to avoid fetching all the
|
||||
configurations for multiple projects.
|
||||
</p>
|
||||
<b>Namespace is required to create a configuration</b>
|
||||
</div>
|
||||
</Popover>
|
||||
<Popover trigger={ArPopoverTriggers.HOVER}>
|
||||
<Button
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
content="+ Organization"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceOrgForm context="org" />,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
<div
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
className="text-wrap"
|
||||
style={{ maxWidth: "10rem" }}
|
||||
>
|
||||
<p>
|
||||
You may skip creating an organization as one will be created for
|
||||
you if you don't.
|
||||
</p>
|
||||
<p>
|
||||
Or you may choose to create one. This will allow you to control
|
||||
severl aspects of an organization at creation time itself
|
||||
</p>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationNoConfigPrompt
|
||||
3
src/app/components/ConfigurationNoConfigPrompt/index.ts
Executable file
3
src/app/components/ConfigurationNoConfigPrompt/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationNoConfigPrompt from "./ConfigurationNoConfigPrompt"
|
||||
|
||||
export default ConfigurationNoConfigPrompt
|
||||
13
src/app/components/ConfigurationViewer/ConfigurationViewer.component.scss
Executable file
13
src/app/components/ConfigurationViewer/ConfigurationViewer.component.scss
Executable file
@@ -0,0 +1,13 @@
|
||||
.ar-ConfigurationViewer {
|
||||
background-color: var(--ar-bg);
|
||||
.ar-ConfigurationViewer__header {
|
||||
background-color: var(--ar-bg-base);
|
||||
border-bottom: 1px solid var(--ar-color-layout-border);
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
border: none;
|
||||
background: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
8
src/app/components/ConfigurationViewer/ConfigurationViewer.test.ts
Executable file
8
src/app/components/ConfigurationViewer/ConfigurationViewer.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import ConfigurationViewer from "./ConfigurationViewer"
|
||||
|
||||
describe("ConfigurationViewer", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
205
src/app/components/ConfigurationViewer/ConfigurationViewer.tsx
Executable file
205
src/app/components/ConfigurationViewer/ConfigurationViewer.tsx
Executable file
@@ -0,0 +1,205 @@
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { notify, setModalState } from "../../store"
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
ConfigRowItem,
|
||||
InlineMenu,
|
||||
LoadableIcon,
|
||||
NamespaceInfoBox,
|
||||
NamespaceOrgForm,
|
||||
Popover,
|
||||
} from ".."
|
||||
import {
|
||||
ArAlertType,
|
||||
ArButtonVariants,
|
||||
ArPopoverPositions,
|
||||
ArPopoverSlots,
|
||||
ArSizes,
|
||||
} from "../../types/enums"
|
||||
import { ObjectType } from "../../types/types"
|
||||
import { ConfigurationViewerProps } from "../../types/components.interface"
|
||||
import { Network } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "./ConfigurationViewer.component.scss"
|
||||
|
||||
const ConfigurationViewer = (props: ConfigurationViewerProps): JSX.Element => {
|
||||
const { namespace, fetchNamespaces } = props
|
||||
const configurations = namespace?.configs
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
const addConfig = (key: string, value: string, _id?: string) => {
|
||||
const payload: ObjectType = {
|
||||
key,
|
||||
value,
|
||||
namespace: namespace._id,
|
||||
version: "v1",
|
||||
}
|
||||
_id && (payload._id = _id)
|
||||
Network.post(
|
||||
API_CONFIG.CONFIG[process.env.NODE_ENV] +
|
||||
ENDPOINTS.CONFIG.ROOT +
|
||||
ENDPOINTS.CONFIG.SAVE +
|
||||
namespace._id,
|
||||
payload,
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
fetchNamespaces()
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Added a new config successfully",
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Failed to add new config",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const deleteConfig = (_id?: string) => {
|
||||
Network.get(
|
||||
API_CONFIG.CONFIG[process.env.NODE_ENV] +
|
||||
ENDPOINTS.CONFIG.ROOT +
|
||||
ENDPOINTS.CONFIG.DELETE +
|
||||
"/" +
|
||||
namespace._id +
|
||||
"/" +
|
||||
_id,
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.status === 200) {
|
||||
fetchNamespaces()
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Removed config successfully",
|
||||
type: ArAlertType.SUCCESS,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: "Failed to delete config",
|
||||
type: ArAlertType.ERROR,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
return (
|
||||
<div className="ar-ConfigurationViewer d-flex flex-column w-100 h-100">
|
||||
<div className="ar-ConfigurationViewer__header px-3 py-2 w-100">
|
||||
<Breadcrumb
|
||||
classes="d-inline-block"
|
||||
data={[{ label: namespace.name, route: `/config/${namespace.name}` }]}
|
||||
/>
|
||||
<Button
|
||||
classes="float-end"
|
||||
content="Edit"
|
||||
color="brown"
|
||||
preIcon="fa/FaRegEdit"
|
||||
variant={ArButtonVariants.LINK}
|
||||
size={ArSizes.SMALL}
|
||||
/>
|
||||
<Popover
|
||||
classes="float-end"
|
||||
// trigger={ArPopoverTriggers.HOVER}
|
||||
position={ArPopoverPositions.BOTTOMLEFT}
|
||||
>
|
||||
<Button
|
||||
content="Add"
|
||||
color="green"
|
||||
preIcon="md/MdAddCircleOutline"
|
||||
variant={ArButtonVariants.LINK}
|
||||
size={ArSizes.SMALL}
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
/>
|
||||
<InlineMenu
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
data={{
|
||||
Org: {},
|
||||
Namespace: {
|
||||
onClick: () =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceOrgForm context="namespace" />,
|
||||
}),
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Popover>
|
||||
<LoadableIcon
|
||||
classes="cursor-pointer"
|
||||
icon="io/IoMdInformationCircle"
|
||||
color="#0d6efd"
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
setModalState({
|
||||
show: true,
|
||||
isSticky: true,
|
||||
content: <NamespaceInfoBox namespace={namespace} />,
|
||||
}),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="ar-ConfigurationViewer__content flex-1 d-flex px-3 py-2 w-100">
|
||||
<div className="d-flex flex-1 flex-column">
|
||||
<ConfigRowItem onAdd={addConfig} isNew />
|
||||
<div className="my-3 border" />
|
||||
{configurations && configurations.length > 0 ? (
|
||||
configurations.map((configuration) => {
|
||||
return (
|
||||
<ConfigRowItem
|
||||
onUpdate={(_id: string, key: string, value: string) =>
|
||||
addConfig(key, value, _id)
|
||||
}
|
||||
onDelete={deleteConfig}
|
||||
config={configuration}
|
||||
disabled
|
||||
/>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<div className="row flex-1">
|
||||
<div className="col flex-center fw-bold">
|
||||
Nothing here, start by adding configurations above
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ar-ConfigurationViewer__footer px-3 py-2 w-100">
|
||||
<Button
|
||||
classes="float-end"
|
||||
content="Submit"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
size={ArSizes.SMALL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConfigurationViewer
|
||||
3
src/app/components/ConfigurationViewer/index.ts
Executable file
3
src/app/components/ConfigurationViewer/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import ConfigurationViewer from "./ConfigurationViewer"
|
||||
|
||||
export default ConfigurationViewer
|
||||
@@ -1,5 +1,3 @@
|
||||
@import "../../static/styles/variables";
|
||||
|
||||
.c-Content {
|
||||
background-color: var(--ar-bg-base);
|
||||
.ar-Content {
|
||||
background-color: var(--ar-bg-secondary);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import React from "react"
|
||||
import { ReactNode } from "react"
|
||||
import "./Content.component.scss"
|
||||
|
||||
interface ContentProps {
|
||||
children?: JSX.Element | Array<JSX.Element>
|
||||
children?: ReactNode
|
||||
classes?: string
|
||||
}
|
||||
|
||||
const Content = (props: ContentProps): JSX.Element => {
|
||||
const { children, classes } = props
|
||||
return (
|
||||
<div className={`c-Content${classes ? " " + classes : ""}`}>{children}</div>
|
||||
<div className={`ar-Content flex-grow-1${classes ? " " + classes : ""}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
.c-Drawer {
|
||||
border-right: 1px solid #eee;
|
||||
.ar-Drawer {
|
||||
border-right: 1px solid var(--ar-color-layout-border);
|
||||
&.collapsed {
|
||||
.ar-Drawer__expander {
|
||||
right: 1rem;
|
||||
}
|
||||
}
|
||||
.ar-Drawer__expander {
|
||||
top: 4px;
|
||||
right: 6px;
|
||||
}
|
||||
/* width */
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #bbb;
|
||||
border-radius: 2px;
|
||||
&:hover {
|
||||
background: #888;
|
||||
}
|
||||
}
|
||||
|
||||
& + .ar-Content {
|
||||
width: 85%;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,65 @@
|
||||
import ComponentList from "../ComponentList/ComponentList"
|
||||
import { useEffect, useRef } from "react"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import { getCurrentTheme, getDrawerState, setDrawerState } from "../../store"
|
||||
import LoadableIcon from "../atoms/LoadableIcon"
|
||||
import { DrawerProps } from "../../types/components.interface"
|
||||
import { ArThemes } from "../../types/enums"
|
||||
import { Helper } from "../../utils"
|
||||
import "./Drawer.component.scss"
|
||||
|
||||
interface DrawerProps {
|
||||
children?: JSX.Element | Array<JSX.Element>
|
||||
classes?: string
|
||||
}
|
||||
const isMobile = Helper.isMobile()
|
||||
let clickedSelf: boolean
|
||||
|
||||
const Drawer = (props: DrawerProps): JSX.Element => {
|
||||
const { children, classes } = props
|
||||
const { children, classes, isCollapsible } = props
|
||||
const drawerRef = useRef<HTMLDivElement>(null)
|
||||
const dispatch = useAppDispatch()
|
||||
const drawerState = useAppSelector<DrawerProps | undefined>(getDrawerState)
|
||||
const theme = useAppSelector<string>(getCurrentTheme)
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile) {
|
||||
if (!drawerState?.collapsed) {
|
||||
drawerRef.current?.focus()
|
||||
}
|
||||
}
|
||||
}, [drawerState])
|
||||
|
||||
return (
|
||||
<aside className={`c-Drawer${classes ? " " + classes : ""}`}>
|
||||
{children}
|
||||
<aside
|
||||
className={`ar-Drawer overflow-auto${classes ? " " + classes : ""}${
|
||||
isCollapsible ? " position-relative" : ""
|
||||
}${drawerState?.collapsed ? " collapsed" : ""}`}
|
||||
tabIndex={-1}
|
||||
ref={drawerRef}
|
||||
onMouseDown={() => {
|
||||
if (isMobile) {
|
||||
clickedSelf = true
|
||||
setTimeout(() => (clickedSelf = false), 0)
|
||||
}
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (!clickedSelf && isMobile) {
|
||||
dispatch(setDrawerState({ collapsed: true }))
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isCollapsible && (
|
||||
<LoadableIcon
|
||||
classes="ar-Drawer__expander position-absolute cursor-pointer d-none d-sm-inline"
|
||||
color={theme === ArThemes.DARK1 ? "white" : "black"}
|
||||
icon={
|
||||
drawerState?.collapsed
|
||||
? "tb/TbLayoutSidebarLeftExpand"
|
||||
: "tb/TbLayoutSidebarLeftCollapse"
|
||||
}
|
||||
size="1.5rem"
|
||||
onClick={() =>
|
||||
dispatch(setDrawerState({ collapsed: !drawerState?.collapsed }))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!drawerState?.collapsed && children}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
.c-Editor {
|
||||
|
||||
.ar-Editor {
|
||||
border: 1px solid var(--ar-color-layout-border);
|
||||
.ar-Editor__tools {
|
||||
background-color: var(--ar-bg-base);
|
||||
border-bottom: 1px solid var(--ar-color-layout-border);
|
||||
}
|
||||
}
|
||||
|
||||
.ar-Editor__frame__main {
|
||||
background-color: var(--ar-bg-tertiary);
|
||||
&.background {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,300 @@
|
||||
import { ReactNode, useEffect, useState } from "react"
|
||||
import { useLocation, useNavigate } from "react-router-dom"
|
||||
import { createPortal } from "react-dom"
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
FrameContentDefinition,
|
||||
FrameContentProps,
|
||||
Toggle,
|
||||
Helper,
|
||||
ArSizes,
|
||||
ObjectType,
|
||||
Network,
|
||||
Dropdown,
|
||||
PRIMITIVES,
|
||||
ArDropdownVariants,
|
||||
} from ".."
|
||||
import { ComponentDescription } from "../../types/componentlist.interface"
|
||||
import StyleHelper from "./StyleHelper"
|
||||
import COMPONENTS from "../../config/components"
|
||||
import * as images from "../../static/images"
|
||||
import "./Editor.component.scss"
|
||||
|
||||
interface EditorProps {}
|
||||
const children = {
|
||||
bg: (
|
||||
<div
|
||||
className="ar-Editor__frame__dummy-child"
|
||||
style={{ height: "100vh", width: "100vw" }}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<span slot="content" style={{ height: "10rem", width: "10rem" }}>
|
||||
Popover Content
|
||||
</span>
|
||||
),
|
||||
popover: (
|
||||
<div slot="popover" style={{ height: "10rem", width: "10rem" }}>
|
||||
Popover Content
|
||||
</div>
|
||||
),
|
||||
anchor: <Button content="Click Me!" slot="anchor" />,
|
||||
}
|
||||
const FrameContent = (
|
||||
props: FrameContentProps,
|
||||
): JSX.Element | string | null => {
|
||||
const { contentDefinition, notificationProps } = props
|
||||
const { component, props: componentProps, data } = contentDefinition || {}
|
||||
const background: string = data?.test?.background
|
||||
const SelectedComponent = component
|
||||
let childRenders: Array<JSX.Element> = []
|
||||
if (data?.type === "HOC") {
|
||||
childRenders = data.children.map(
|
||||
(name: string) => children[name as keyof object],
|
||||
)
|
||||
}
|
||||
return SelectedComponent ? (
|
||||
<div
|
||||
className={`ar-Editor__frame__main h-100 w-100 flex-center p-4${
|
||||
background ? " background" : ""
|
||||
}`}
|
||||
style={{
|
||||
// @ts-ignore
|
||||
backgroundImage: background ? `url(${images[background]})` : "",
|
||||
}}
|
||||
>
|
||||
{data && data.type === "HOC" ? (
|
||||
<SelectedComponent {...componentProps}>
|
||||
{childRenders}
|
||||
</SelectedComponent>
|
||||
) : (
|
||||
<SelectedComponent {...componentProps} />
|
||||
)}
|
||||
{notificationProps && <Alert {...notificationProps} />}
|
||||
</div>
|
||||
) : (
|
||||
"Select an item from the side panel to view here"
|
||||
)
|
||||
}
|
||||
|
||||
const Editor = (props: EditorProps): JSX.Element => {
|
||||
return <div className="c-Editor">In Component Editor</div>
|
||||
const Editor = (): JSX.Element | string => {
|
||||
const [contentRef, setContentRef] = useState<HTMLIFrameElement | null>(null)
|
||||
const [notificationProps, setNotificationProps] = useState<any>(null)
|
||||
const [selectedComponentDefinition, setSelectedComponentDefinition] =
|
||||
useState<FrameContentDefinition | null>(null)
|
||||
const [theme, setTheme] = useState<string>("th-light-1")
|
||||
const [portal, setPortal] = useState<ReactNode>()
|
||||
const [selectedProps, updateSelectedProps] = useState<ObjectType | null>()
|
||||
const [component, setComponent] = useState<string>()
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
window.onmessage = (e) => {
|
||||
"ar" in e.data && setNotificationProps(e.data?.ar)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (theme) {
|
||||
const iframeHtml =
|
||||
contentRef?.contentWindow?.document?.getElementsByTagName("html")[0]
|
||||
iframeHtml && iframeHtml.setAttribute("ar-theme", theme)
|
||||
const iframeBody = iframeHtml?.getElementsByTagName("body")[0]
|
||||
iframeBody && (iframeBody.style.backgroundColor = "var(--ar-bg)")
|
||||
}
|
||||
}, [contentRef, theme])
|
||||
|
||||
useEffect(() => {
|
||||
const locationPathArr = location.pathname
|
||||
.split("/")
|
||||
.filter((p: string) => p)
|
||||
const componentName =
|
||||
locationPathArr.length > 1 && locationPathArr[locationPathArr.length - 1]
|
||||
if (componentName) {
|
||||
setComponent(componentName)
|
||||
const { selectedItem, hierarchy } = Helper.findComponentDescription(
|
||||
componentName,
|
||||
COMPONENTS,
|
||||
)
|
||||
if (selectedItem && hierarchy) {
|
||||
let props: { [key: string]: string | number | boolean | undefined } = {
|
||||
demo: true,
|
||||
}
|
||||
if (location.search) {
|
||||
const params = location.search.substring(1)?.split("&")
|
||||
if (params) {
|
||||
params.forEach((prop) => {
|
||||
const keyValue = prop.split("=")
|
||||
const name = keyValue && keyValue[0]
|
||||
let value: string | boolean = keyValue && keyValue[1]
|
||||
value = value === "true" ? true : decodeURIComponent(value)
|
||||
value =
|
||||
value === "false" ? false : decodeURIComponent(value as string)
|
||||
if (prop && value && selectedItem) {
|
||||
props[name] = value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if (
|
||||
!selectedComponentDefinition ||
|
||||
selectedComponentDefinition.componentName !== componentName ||
|
||||
JSON.stringify(selectedComponentDefinition.props) !==
|
||||
JSON.stringify(props)
|
||||
) {
|
||||
const SelectedComponent = Helper.importComponent(
|
||||
componentName,
|
||||
hierarchy?.toLowerCase(),
|
||||
)
|
||||
setSelectedComponentDefinition({
|
||||
componentName,
|
||||
component: SelectedComponent,
|
||||
props,
|
||||
data: selectedItem,
|
||||
hierarchy,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [location])
|
||||
|
||||
useEffect(() => {
|
||||
selectedProps && navigate(Network.stringifyUrl("/", selectedProps).slice(1))
|
||||
}, [selectedProps])
|
||||
|
||||
useEffect(() => {
|
||||
updateSelectedProps(null)
|
||||
}, [component])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
contentRef &&
|
||||
selectedComponentDefinition &&
|
||||
process.env.NODE_ENV !== "production"
|
||||
) {
|
||||
StyleHelper.injectComponentStyleInFrame(
|
||||
selectedComponentDefinition.data as ComponentDescription,
|
||||
selectedComponentDefinition.hierarchy,
|
||||
contentRef,
|
||||
)
|
||||
}
|
||||
}, [contentRef, selectedComponentDefinition])
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef) {
|
||||
const bsLink =
|
||||
contentRef?.contentWindow?.document.getElementsByTagName("link")
|
||||
if (!bsLink || bsLink.length === 0) {
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
StyleHelper.injectLinkInFrame(
|
||||
["index", "ComponentsViewerPage"],
|
||||
contentRef,
|
||||
)
|
||||
} else {
|
||||
StyleHelper.injectBootstrapInFrame(contentRef)
|
||||
StyleHelper.injectVariablesInFrame(contentRef)
|
||||
StyleHelper.injectGlobalInFrame(contentRef)
|
||||
StyleHelper.injectEditorStyleInFrame(contentRef)
|
||||
StyleHelper.injectComponentStyleInFrame(
|
||||
{ name: "Alert" },
|
||||
"ATOMS",
|
||||
contentRef,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [contentRef])
|
||||
|
||||
const mountNode = contentRef?.contentWindow?.document?.body
|
||||
|
||||
useEffect(() => {
|
||||
if (mountNode) {
|
||||
if (selectedComponentDefinition && selectedComponentDefinition.props) {
|
||||
selectedComponentDefinition.props.theme = theme
|
||||
}
|
||||
setPortal(
|
||||
createPortal(
|
||||
<FrameContent
|
||||
contentDefinition={selectedComponentDefinition}
|
||||
notificationProps={notificationProps}
|
||||
/>,
|
||||
mountNode,
|
||||
),
|
||||
)
|
||||
}
|
||||
}, [selectedComponentDefinition, mountNode, notificationProps, theme])
|
||||
|
||||
const currentItemVariants = selectedComponentDefinition?.data.variants || []
|
||||
return (
|
||||
<div className="ar-Editor w-100 h-100 d-flex flex-column">
|
||||
<div className="ar-Editor__tools px-3 py-2">
|
||||
<div className="row justify-content-end">
|
||||
<div className="col flex-v-center justify-content-end">
|
||||
<div className="ar-Editor__prop-selector flex-v-center border-right me-3">
|
||||
{Object.keys(currentItemVariants)
|
||||
.filter((prop: string) => prop !== "demo")
|
||||
.map((prop: any, index: number, arr) => {
|
||||
const propsValues = currentItemVariants[prop]
|
||||
const isBoolean =
|
||||
propsValues.length === 2 &&
|
||||
propsValues.indexOf(true) > -1 &&
|
||||
propsValues.indexOf(false) > -1
|
||||
return isBoolean ? (
|
||||
<Toggle
|
||||
key={"prop-toggle-" + index + "-" + prop}
|
||||
classes="me-3"
|
||||
label={Helper.toReadable(prop)}
|
||||
onChange={(isChecked: boolean) =>
|
||||
updateSelectedProps({
|
||||
...(selectedProps || {}),
|
||||
[prop]: isChecked,
|
||||
})
|
||||
}
|
||||
size={ArSizes.SMALL}
|
||||
hideStatus
|
||||
/>
|
||||
) : (
|
||||
<Dropdown
|
||||
classes="me-2"
|
||||
options={propsValues.map((propValue: PRIMITIVES) => ({
|
||||
name: propValue,
|
||||
}))}
|
||||
onSelectionChanged={(obj) => {
|
||||
updateSelectedProps({
|
||||
...(selectedProps || {}),
|
||||
[prop]: obj.value,
|
||||
})
|
||||
}}
|
||||
variant={ArDropdownVariants.SELECTIONSASPILLS}
|
||||
placeholder={Helper.toReadable(prop)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<span className="float-end">
|
||||
<Toggle
|
||||
toggleOnName="Dark"
|
||||
toggleOffName="Light"
|
||||
onChange={(checked: boolean) =>
|
||||
setTheme(checked ? "th-dark-1" : "th-light-1")
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<iframe
|
||||
className="ar-Editor__frame w-100 h-100"
|
||||
ref={setContentRef}
|
||||
title="Armco Component Viewer"
|
||||
>
|
||||
{portal}
|
||||
</iframe>
|
||||
{/* <div className="ar-Editor__props-container d-inline-block h-100"></div> */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Editor
|
||||
|
||||
133
src/app/components/Editor/StyleHelper.ts
Normal file
133
src/app/components/Editor/StyleHelper.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { ComponentDescription } from "../../types/componentlist.interface"
|
||||
|
||||
//// Editor Frame Style related helpers
|
||||
class StyleHelper {
|
||||
static injectGlobalInFrame(frame: HTMLIFrameElement | null) {
|
||||
const clipCallback = (text: string) => {
|
||||
return {
|
||||
clipStart: text && text.indexOf("html {\\n font-size: 16px"),
|
||||
}
|
||||
}
|
||||
StyleHelper.injectScssInFrame(
|
||||
"/src/app/static/styles/_global.scss",
|
||||
frame,
|
||||
undefined,
|
||||
clipCallback,
|
||||
)
|
||||
}
|
||||
|
||||
static injectBootstrapInFrame(frame: HTMLIFrameElement | null) {
|
||||
StyleHelper.injectLinkInFrame(
|
||||
"/src/app/static/styles/bootstrap.min.css",
|
||||
frame,
|
||||
)
|
||||
}
|
||||
|
||||
static injectVariablesInFrame(frame: HTMLIFrameElement | null) {
|
||||
StyleHelper.injectScssInFrame(
|
||||
"/src/app/static/styles/_variables.scss",
|
||||
frame,
|
||||
)
|
||||
}
|
||||
|
||||
static injectEditorStyleInFrame(frame: HTMLIFrameElement | null) {
|
||||
StyleHelper.injectScssInFrame(
|
||||
"/src/app/components/Editor/Editor.component.scss",
|
||||
frame,
|
||||
)
|
||||
}
|
||||
|
||||
static injectComponentStyleInFrame(
|
||||
selectedComponent: ComponentDescription,
|
||||
hierarchy: string,
|
||||
frame: HTMLIFrameElement | null,
|
||||
) {
|
||||
if (hierarchy && selectedComponent && selectedComponent.name) {
|
||||
const componentStyleElement =
|
||||
frame?.contentWindow?.document.querySelector("style.component-style")
|
||||
componentStyleElement && componentStyleElement.remove()
|
||||
const dependencyComponentsToInject = selectedComponent.uses
|
||||
? [
|
||||
{ name: selectedComponent.name, hierarchy },
|
||||
...selectedComponent.uses,
|
||||
]
|
||||
: [{ name: selectedComponent.name, hierarchy }]
|
||||
dependencyComponentsToInject.forEach(
|
||||
(component: { [key: string]: string }) => {
|
||||
const scssPath = `/src/app/components/${component.hierarchy.toLowerCase()}/${
|
||||
component.name
|
||||
}/${component.name}.component.scss`
|
||||
StyleHelper.injectScssInFrame(scssPath, frame, component)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static injectScssInFrame(
|
||||
scssPath: string,
|
||||
frame: HTMLIFrameElement | null,
|
||||
component?: { [key: string]: string },
|
||||
clipCallback?: any,
|
||||
) {
|
||||
import("sass").then((module) => {
|
||||
const { compileString } = module
|
||||
fetch(scssPath)
|
||||
.then((r) => r.text())
|
||||
.then((text) => {
|
||||
const { clipStart, clipEnd } = clipCallback
|
||||
? clipCallback(text)
|
||||
: () => ({})
|
||||
text = text
|
||||
.substring(
|
||||
clipStart ||
|
||||
text.indexOf("__vite__css = ") + '__vite__css = "'.length,
|
||||
clipEnd || text.indexOf("__vite__updateStyle(") - 2,
|
||||
)
|
||||
.replace(/\\n/g, "")
|
||||
.replace(/\\"/g, '"')
|
||||
const css = compileString(text).css
|
||||
StyleHelper.injectStyleInFrame(css, frame, component)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
static injectStyleInFrame(
|
||||
css: string,
|
||||
frame: HTMLIFrameElement | null,
|
||||
component?: { [key: string]: string },
|
||||
) {
|
||||
const style = document.createElement("style")
|
||||
if (component && component.name) {
|
||||
style.setAttribute("id", component.name)
|
||||
style.setAttribute("class", "component-style")
|
||||
}
|
||||
style.appendChild(document.createTextNode(css))
|
||||
frame?.contentWindow?.document?.head.appendChild(style)
|
||||
}
|
||||
|
||||
static injectLinkInFrame(
|
||||
link: string | Array<string>,
|
||||
frame: HTMLIFrameElement | null,
|
||||
) {
|
||||
if (Array.isArray(link)) {
|
||||
const links = document.querySelectorAll("link[rel='stylesheet']")
|
||||
link.forEach((item: string) => {
|
||||
Array.from(links).forEach((selectedLink) => {
|
||||
const cssUrl = selectedLink.getAttribute("href")
|
||||
if (cssUrl && cssUrl.startsWith(`/assets/${item}`)) {
|
||||
const clone = selectedLink.cloneNode(true)
|
||||
frame?.contentWindow?.document?.head.appendChild(clone)
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
const cssLink = document.createElement("link")
|
||||
cssLink.href = link
|
||||
cssLink.rel = "stylesheet"
|
||||
cssLink.type = "text/css"
|
||||
frame?.contentWindow?.document?.head.appendChild(cssLink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default StyleHelper
|
||||
28
src/app/components/FavoritesList/FavoritesList.component.scss
Executable file
28
src/app/components/FavoritesList/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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/app/components/FavoritesList/FavoritesList.test.ts
Executable file
8
src/app/components/FavoritesList/FavoritesList.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import FavoritesList from "./FavoritesList"
|
||||
|
||||
describe("FavoritesList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
119
src/app/components/FavoritesList/FavoritesList.tsx
Executable file
119
src/app/components/FavoritesList/FavoritesList.tsx
Executable file
@@ -0,0 +1,119 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import { getFavorites } from "../../pages/IconsPage/IconsPage.slice"
|
||||
import { getLoggedIn, setRightPanelContent } from "../../store"
|
||||
import { Button, IconTile, LoadableIcon, Popover } from ".."
|
||||
import {
|
||||
FavoritesItemProps,
|
||||
FavoritesListProps,
|
||||
IconTileProps,
|
||||
} from "../../types/components.interface"
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArPopoverPositions,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
} from "../../types/enums"
|
||||
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 = useAppSelector(getFavorites)
|
||||
const isLoggedIn = useAppSelector<boolean | undefined>(getLoggedIn)
|
||||
const dispatch = useAppDispatch()
|
||||
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" : ""
|
||||
}`}
|
||||
>
|
||||
<LoadableIcon icon="tb/TbLayoutSidebarRightCollapse" />
|
||||
{!isLoggedIn ? (
|
||||
<Popover
|
||||
trigger={ArPopoverTriggers.HOVER}
|
||||
position={ArPopoverPositions.LEFTBOTTOM}
|
||||
>
|
||||
<span
|
||||
className="ar-FavoritesList__header p-2 border-bottom d-flex justify-content-center w-100"
|
||||
slot="anchor"
|
||||
>
|
||||
<LoadableIcon icon="io/IoIosWarning" color="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.LEFTBOTTOM}
|
||||
>
|
||||
<span
|
||||
className="ar-FavoritesList__header loggedIn p-2 border-bottom d-flex justify-content-center w-100"
|
||||
slot="anchor"
|
||||
>
|
||||
<LoadableIcon icon="io/IoIosCheckmarkCircleOutline" color="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={() => {
|
||||
dispatch(setRightPanelContent({ name: "LoginProvider" }))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FavoritesList
|
||||
3
src/app/components/FavoritesList/index.ts
Executable file
3
src/app/components/FavoritesList/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import FavoritesList from "./FavoritesList"
|
||||
|
||||
export default FavoritesList
|
||||
26
src/app/components/FlexTools/FlexTools.component.scss
Executable file
26
src/app/components/FlexTools/FlexTools.component.scss
Executable file
@@ -0,0 +1,26 @@
|
||||
.ar-FlexTools {
|
||||
.col {
|
||||
border-right: var(--ar-border);
|
||||
}
|
||||
.ar-FlexTools__separator {
|
||||
border-right: var(--ar-border);
|
||||
}
|
||||
|
||||
.ar-FlexTools__brand {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.ar-FlexTools__app-name {
|
||||
font-family: "Allerta Stencil";
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
|
||||
.ar-FlexTools__app-name-i {
|
||||
color: red;
|
||||
}
|
||||
.ar-FlexTools__app-name-o {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
8
src/app/components/FlexTools/FlexTools.test.ts
Executable file
8
src/app/components/FlexTools/FlexTools.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import FlexTools from "./FlexTools"
|
||||
|
||||
describe("FlexTools", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
129
src/app/components/FlexTools/FlexTools.tsx
Executable file
129
src/app/components/FlexTools/FlexTools.tsx
Executable file
@@ -0,0 +1,129 @@
|
||||
import { useEffect } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useAppSelector } from "../../hooks"
|
||||
import { getCurrentTheme } from "../../store"
|
||||
import {
|
||||
AppAndToolsSelector,
|
||||
LoadableIcon,
|
||||
Popover,
|
||||
TabBar,
|
||||
UserOptions,
|
||||
} from ".."
|
||||
import {
|
||||
ArAnimations,
|
||||
ArPopoverPositions,
|
||||
ArPopoverSlots,
|
||||
ArTabType,
|
||||
ArThemes,
|
||||
} from "../../types/enums"
|
||||
import {
|
||||
FlexToolsProps,
|
||||
TabBarProps,
|
||||
TabProps,
|
||||
} from "../../types/components.interface"
|
||||
import { Helper } from "../../utils"
|
||||
import navigator from "../../config/navigator"
|
||||
import "./FlexTools.component.scss"
|
||||
|
||||
const userOptions = [
|
||||
[{ name: "Profile" }, { name: "Settings" }, { name: "My Stuff" }],
|
||||
[{ name: "+ Organization" }, { name: "+ Upload Icon" }],
|
||||
]
|
||||
|
||||
const isMobile = Helper.isMobile()
|
||||
|
||||
const FlexTools = (props: FlexToolsProps): JSX.Element => {
|
||||
const { isLanding, route } = props
|
||||
const theme = useAppSelector<string>(getCurrentTheme)
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
navigator.forEach((item) => {
|
||||
navigator &&
|
||||
item.items?.forEach(
|
||||
(subItem) =>
|
||||
(subItem.onClick = () => subItem.url && navigate(subItem.url)),
|
||||
)
|
||||
})
|
||||
}, [navigate])
|
||||
|
||||
const onTabSelected = (id: string, tab: TabProps) => {
|
||||
tab?.data?.url && navigate(tab.data.url as string)
|
||||
}
|
||||
|
||||
const separator = <span className="ar-FlexTools__separator h-100 me-2" />
|
||||
const tabBarProps: TabBarProps = {
|
||||
data: navigator,
|
||||
variant: ArTabType.MODERN,
|
||||
onTabSelected,
|
||||
}
|
||||
if (route === "landing") {
|
||||
tabBarProps.activeId = ""
|
||||
}
|
||||
const appSelector = isLanding ? (
|
||||
<TabBar {...tabBarProps} />
|
||||
) : (
|
||||
<Popover
|
||||
classes={isMobile ? "" : "me-3"}
|
||||
position={
|
||||
isMobile ? ArPopoverPositions.TOPCENTER : ArPopoverPositions.BOTTOMLEFT
|
||||
}
|
||||
animation={ArAnimations.FADEINOUT}
|
||||
transition
|
||||
>
|
||||
<LoadableIcon
|
||||
classes="cursor-pointer"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
icon="io5.IoApps"
|
||||
size="1.5rem"
|
||||
color={theme === ArThemes.DARK1 ? "lightgrey" : "grey"}
|
||||
hoverColor="orange"
|
||||
/>
|
||||
<AppAndToolsSelector slot={ArPopoverSlots.POPOVER} data={navigator} />
|
||||
</Popover>
|
||||
)
|
||||
|
||||
const userOps = (
|
||||
<UserOptions
|
||||
classes={`h-100 flex-center px-2${isLanding ? " ms-auto" : ""}`}
|
||||
options={userOptions}
|
||||
isLanding={isLanding}
|
||||
theme={theme}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<div className="ar-FlexTools h-100 flex-center w-100">
|
||||
{!isMobile ? (
|
||||
<>
|
||||
{appSelector}
|
||||
{!isLanding && separator}
|
||||
{userOps}
|
||||
</>
|
||||
) : (
|
||||
<div className="row w-100">
|
||||
<div className="col flex-center" onClick={() => navigate("/")}>
|
||||
{/* <img
|
||||
src={AppLogo}
|
||||
alt="App Logo"
|
||||
className="ar-FlexTools__brand mx-2"
|
||||
width="32"
|
||||
height="32"
|
||||
onClick={() => navigate("/")}
|
||||
/> */}
|
||||
<span className="ar-FlexTools__app-name h-100 flex-center">
|
||||
Stuffle.
|
||||
<span className="ar-FlexTools__app-name-i">i</span>
|
||||
<span className="ar-FlexTools__app-name-o">o</span>
|
||||
</span>
|
||||
</div>
|
||||
<div className="ar-FlexTools__app-selector col flex-center">
|
||||
{appSelector}
|
||||
</div>
|
||||
<div className="col flex-center">{userOps}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FlexTools
|
||||
3
src/app/components/FlexTools/index.ts
Executable file
3
src/app/components/FlexTools/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import FlexTools from "./FlexTools"
|
||||
|
||||
export default FlexTools
|
||||
12
src/app/components/FontsList/FontsList.component.scss
Executable file
12
src/app/components/FontsList/FontsList.component.scss
Executable file
@@ -0,0 +1,12 @@
|
||||
.ar-FontsList {
|
||||
.ar-FontsList__header-search-upload {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-colora);
|
||||
background-color: var(--ar-bg-base);
|
||||
}
|
||||
|
||||
.ar-FontsList__font-tile-container {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-color);
|
||||
}
|
||||
}
|
||||
8
src/app/components/FontsList/FontsList.test.ts
Executable file
8
src/app/components/FontsList/FontsList.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import FontsList from "./FontsList"
|
||||
|
||||
describe("FontsList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
140
src/app/components/FontsList/FontsList.tsx
Executable file
140
src/app/components/FontsList/FontsList.tsx
Executable file
@@ -0,0 +1,140 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { FontsListProps } from "../../types/components.interface"
|
||||
import { ArrayType, FunctionType, ObjectType } from "../../types/types"
|
||||
import { ArButtonVariants, ArLoaderTypes, ArSizes } from "../../types/enums"
|
||||
import { Button, FontTile, Loader, Pagination, Search, Toggle } from ".."
|
||||
import { Helper, Network } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "./FontsList.component.scss"
|
||||
|
||||
const fetchFontsPage = (
|
||||
limit: number,
|
||||
from: number,
|
||||
filters: ObjectType,
|
||||
dataSetter: FunctionType,
|
||||
pageSetter: FunctionType,
|
||||
setLoading: FunctionType,
|
||||
) => {
|
||||
const pageApi =
|
||||
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
|
||||
ENDPOINTS.STATIC.FONT.ROOT +
|
||||
ENDPOINTS.STATIC.FONT.PAGE
|
||||
const queryParams: ObjectType = { pageSize: limit, from, ...filters }
|
||||
Network.get(pageApi, queryParams)
|
||||
.then((response) => {
|
||||
if (response && response.status === 200) {
|
||||
if (response.body && dataSetter) {
|
||||
const content = JSON.parse(response.body)
|
||||
const styleElement = document.createElement("style")
|
||||
styleElement.type = "text/css"
|
||||
styleElement.appendChild(
|
||||
document.createTextNode(content.returnFontGroups),
|
||||
)
|
||||
document.head.appendChild(styleElement)
|
||||
|
||||
dataSetter(content.fontNames)
|
||||
pageSetter(content.fontNames.slice(0, 30))
|
||||
}
|
||||
}
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
const FontsList = (props: FontsListProps): JSX.Element => {
|
||||
const [searchText, setSearchText] = useState<string | undefined>()
|
||||
const [fonts, setFonts] = useState<ArrayType>()
|
||||
const [page, setPage] = useState<ArrayType>()
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
|
||||
// TODO: Fetch Fonts
|
||||
useEffect(() => {
|
||||
const filters: ObjectType = {}
|
||||
setLoading(true)
|
||||
fetchFontsPage(30, 0, filters, setFonts, setPage, setLoading)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const filters: ObjectType = {}
|
||||
if (searchText) {
|
||||
filters.search = searchText
|
||||
}
|
||||
setLoading(true)
|
||||
fetchFontsPage(200, 0, filters, setFonts, setPage, setLoading)
|
||||
}, [searchText])
|
||||
|
||||
return (
|
||||
<div className="ar-FontsList h-100 w-100">
|
||||
{!fonts && (
|
||||
<Loader label="Loading Fonts..." type={ArLoaderTypes.SHAPES} />
|
||||
)}
|
||||
<div className="ar-FontsList__header-search-upload py-2 px-3 mb-2 border">
|
||||
<div className="row">
|
||||
<span className="offset-4 col-4 d-none d-md-flex flex-v-center justify-content-end">
|
||||
<Button
|
||||
classes="h-100 float-end me-3"
|
||||
content="Upload"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
/>
|
||||
<Toggle
|
||||
onChange={() => {}}
|
||||
toggleOnName="Compact"
|
||||
toggleOffName="Compact"
|
||||
/>
|
||||
</span>
|
||||
<div className="col-4 offset-4 offset-md-0 flex-v-center">
|
||||
<Search
|
||||
classes="bg-white"
|
||||
placeholder="Search by name, style, description"
|
||||
onChange={Helper.debounce(
|
||||
(event: ChangeEvent<HTMLInputElement>) =>
|
||||
setSearchText(event.target.value),
|
||||
1000,
|
||||
)}
|
||||
data={
|
||||
[]
|
||||
// fonts
|
||||
// ? fonts.map(
|
||||
// (font): SearchItem => ({
|
||||
// label: font.name,
|
||||
// data: font,
|
||||
// }),
|
||||
// )
|
||||
// : []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{page && (
|
||||
<div className="ar-FontsList__font-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap flex-grow-1">
|
||||
{loading && (
|
||||
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
|
||||
)}
|
||||
{(page as Array<string>).map((font) => (
|
||||
<FontTile font={font} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Pagination
|
||||
classes="my-3 flex-center"
|
||||
data={fonts}
|
||||
maxPillsToShow={5}
|
||||
pageSetter={setPage}
|
||||
// trigger={ArPageTriggers.SCROLL}
|
||||
count={1}
|
||||
load={100}
|
||||
// dataFetcher={(load, count) =>
|
||||
// fetchIconsPage(load, count, {}, setIcons, setPage, setLoading)
|
||||
// }
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FontsList
|
||||
3
src/app/components/FontsList/index.ts
Executable file
3
src/app/components/FontsList/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import FontsList from "./FontsList"
|
||||
|
||||
export default FontsList
|
||||
@@ -1,6 +1,4 @@
|
||||
@import "../../static/styles/variables";
|
||||
|
||||
.c-Footer {
|
||||
.ar-Footer {
|
||||
background-color: var(--ar-bg-base);
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,36 @@
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import { getCurrentTheme, setTheme } from "../../store"
|
||||
import { FlexTools, Toggle } from ".."
|
||||
import { Helper } from "../../utils"
|
||||
import "./Footer.component.scss"
|
||||
|
||||
interface FooterProps {}
|
||||
|
||||
const isMobile = Helper.isMobile()
|
||||
|
||||
const Footer = (props: FooterProps): JSX.Element => {
|
||||
return <footer className="c-Footer w-100 mt-auto"></footer>
|
||||
const theme = useAppSelector<string>(getCurrentTheme)
|
||||
const dispatch = useAppDispatch()
|
||||
return (
|
||||
<footer className="ar-Footer w-100 d-flex py-1">
|
||||
{!isMobile ? (
|
||||
<Toggle
|
||||
classes="ms-auto"
|
||||
toggleOffName="Go Dark"
|
||||
toggleOnName="Go Dark"
|
||||
onChange={(isChecked: boolean) => {
|
||||
const nextTheme = isChecked ? "th-dark-1" : "th-light-1"
|
||||
dispatch(setTheme(nextTheme))
|
||||
document
|
||||
.getElementsByTagName("html")[0]
|
||||
.setAttribute("ar-theme", nextTheme)
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FlexTools />
|
||||
)}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Footer
|
||||
|
||||
@@ -1,6 +1,39 @@
|
||||
@import "../../static/styles/variables";
|
||||
|
||||
.c-Header {
|
||||
.ar-Header {
|
||||
background-color: var(--ar-bg-base);
|
||||
border-bottom: 1px solid #eee;
|
||||
border-bottom: 1px solid var(--ar-color-layout-border);
|
||||
|
||||
.ar-Header__app-logo {
|
||||
cursor: pointer;
|
||||
.ar-Header__brand {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.ar-Header__app-name {
|
||||
font-family: "Allerta Stencil";
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
|
||||
.ar-Header__app-name-i {
|
||||
color: red;
|
||||
}
|
||||
.ar-Header__app-name-o {
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ar-Header__app-search {
|
||||
transition: width 0.3s;
|
||||
@media screen and (max-width: 576px) {
|
||||
flex: 1;
|
||||
|
||||
.ar-SearchField .ar-TextInput {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
}
|
||||
.ar-SearchField .ar-Button {
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,104 @@
|
||||
import { useState } from "react"
|
||||
import { useLocation, useNavigate } from "react-router-dom"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import {
|
||||
getCurrentTheme,
|
||||
getDrawerState,
|
||||
setDrawerState,
|
||||
setTheme,
|
||||
} from "../../store"
|
||||
import { FlexTools, LoadableIcon, SearchField } from ".."
|
||||
import { DrawerProps, HeaderProps } from "../../types/components.interface"
|
||||
import { ObjectType } from "../../types/types"
|
||||
import { ArThemes } from "../../types/enums"
|
||||
import "./Header.component.scss"
|
||||
|
||||
interface HeaderProps {}
|
||||
|
||||
const Header = (props: HeaderProps): JSX.Element => {
|
||||
return <header className="c-Header w-100"></header>
|
||||
const { routeDetails } = props
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [searchFocussed, setSearchFocussed] = useState<boolean>()
|
||||
const drawerState = useAppSelector<DrawerProps | undefined>(getDrawerState)
|
||||
const dispatch = useAppDispatch()
|
||||
const theme = useAppSelector<string>(getCurrentTheme)
|
||||
const route = (routeDetails?.route as ObjectType)?.class
|
||||
const isLanding =
|
||||
!routeDetails ||
|
||||
["landing", "assets", "playground", "tools-and-services"].indexOf(
|
||||
route as string,
|
||||
) > -1
|
||||
|
||||
const showExpander =
|
||||
location.pathname.startsWith("/components") ||
|
||||
location.pathname.startsWith("/icons")
|
||||
|
||||
return (
|
||||
<header className="ar-Header w-100 flex-v-center px-2">
|
||||
<div
|
||||
className="ar-Header__app-logo h-100 flex-v-center me-3 d-none d-sm-flex"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
<span className="ar-Header__app-name h-100 flex-center">
|
||||
Stuffle.
|
||||
<span className="ar-Header__app-name-i">i</span>
|
||||
<span className="ar-Header__app-name-o">o</span>
|
||||
</span>
|
||||
</div>
|
||||
{showExpander && (
|
||||
<LoadableIcon
|
||||
classes="ar-Drawer__expander d-inline d-sm-none me-3"
|
||||
icon={
|
||||
drawerState?.collapsed
|
||||
? "tb/TbLayoutSidebarLeftExpand"
|
||||
: "tb/TbLayoutSidebarLeftCollapse"
|
||||
}
|
||||
size="2rem"
|
||||
onClick={() =>
|
||||
dispatch(setDrawerState({ collapsed: !drawerState?.collapsed }))
|
||||
}
|
||||
color={theme === "th-dark-1" ? "lightgrey" : "black"}
|
||||
/>
|
||||
)}
|
||||
{!isLanding && (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
onFocus={() => setSearchFocussed(true)}
|
||||
onBlur={() => setSearchFocussed(false)}
|
||||
className={`ar-Header__app-search h-100 flex-v-center me-3 ${
|
||||
searchFocussed ? "w-50" : "w-25"
|
||||
}`}
|
||||
>
|
||||
<SearchField
|
||||
data={[]}
|
||||
onChange={() => {}}
|
||||
placeholder="Search icons, components, fonts..."
|
||||
showPopUp
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<LoadableIcon
|
||||
classes="d-inline d-sm-none ms-auto"
|
||||
icon="cg.CgDarkMode"
|
||||
size="2rem"
|
||||
onClick={() => {
|
||||
const nextTheme =
|
||||
theme === ArThemes.LIGHT1 ? ArThemes.DARK1 : ArThemes.LIGHT1
|
||||
dispatch(setTheme(nextTheme))
|
||||
document
|
||||
.getElementsByTagName("html")[0]
|
||||
.setAttribute("ar-theme", nextTheme)
|
||||
}}
|
||||
color={theme === ArThemes.DARK1 ? "lightgrey" : "black"}
|
||||
/>
|
||||
<div
|
||||
className={`h-100 d-none d-sm-inline${
|
||||
isLanding ? " w-100" : " ms-auto"
|
||||
}`}
|
||||
>
|
||||
<FlexTools isLanding={isLanding} route={route as string} />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
|
||||
48
src/app/components/IconController/IconController.component.scss
Executable file
48
src/app/components/IconController/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);
|
||||
}
|
||||
}
|
||||
8
src/app/components/IconController/IconController.test.ts
Executable file
8
src/app/components/IconController/IconController.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import IconController from "./IconController"
|
||||
|
||||
describe("IconController", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
373
src/app/components/IconController/IconController.tsx
Executable file
373
src/app/components/IconController/IconController.tsx
Executable file
@@ -0,0 +1,373 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Player, Controls } from "@lottiefiles/react-lottie-player"
|
||||
import { setRightPanelContent } from "../../store"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import { getIconStyles } from "../../pages/IconPage/IconPage.slice"
|
||||
import {
|
||||
Breadcrumb,
|
||||
Button,
|
||||
Select,
|
||||
LoadableIcon,
|
||||
Suggestions,
|
||||
Tags,
|
||||
TextInput,
|
||||
} from ".."
|
||||
import { IconControllerProps } from "../../types/components.interface"
|
||||
import { ArButtonVariants, ArPopoverSlots } from "../../types/enums"
|
||||
import { ObjectType } from "../../types/types"
|
||||
import { DomHelper, Network } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import "./IconController.component.scss"
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
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 dispatch = useAppDispatch()
|
||||
const iconStyles = useAppSelector(getIconStyles)
|
||||
const fontColor = complement(iconStyles?.bgColor || "white")
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
// const suggestions = getSuggestions(group, name)
|
||||
|
||||
useEffect(() => {
|
||||
const filename = `${group}_${name}-black.png`
|
||||
Network.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")
|
||||
Network.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)
|
||||
Network.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 d-flex">
|
||||
<h6 className="mb-0 fw-bold">
|
||||
{group}.{name}
|
||||
</h6>
|
||||
<Button
|
||||
classes="ar-IconController__animate-button ms-auto me-3"
|
||||
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={() =>
|
||||
DomHelper.downloadSvg(
|
||||
(icon as ObjectType).icon as string,
|
||||
name || "download.svg",
|
||||
{
|
||||
size: "3rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
},
|
||||
)
|
||||
}
|
||||
/>
|
||||
<LoadableIcon
|
||||
classes="ar-IconController__color-palette cursor-pointer hover-shadow"
|
||||
icon="io/IoIosColorPalette"
|
||||
color="orange"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
size="2rem"
|
||||
onClick={() => dispatch(setRightPanelContent({ name: "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">
|
||||
<LoadableIcon
|
||||
classes="ar-IconController__download-icon position-absolute top-1 end-1"
|
||||
icon="md/MdDownload"
|
||||
color="orange"
|
||||
size="2.5rem"
|
||||
onClick={() =>
|
||||
DomHelper.downloadSvg(
|
||||
(icon as ObjectType).icon as string,
|
||||
name,
|
||||
{
|
||||
size: "3rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
},
|
||||
)
|
||||
}
|
||||
/>
|
||||
<LoadableIcon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
size="3rem"
|
||||
color={iconStyles?.strokeColor || "royalblue"}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
<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">
|
||||
<LoadableIcon
|
||||
classes="ar-IconController__download-icon position-absolute top-1 end-1"
|
||||
icon="md/MdDownload"
|
||||
color="orange"
|
||||
size="2.5rem"
|
||||
onClick={() =>
|
||||
DomHelper.downloadSvg(
|
||||
(icon as ObjectType).icon as string,
|
||||
name,
|
||||
{
|
||||
size: "7rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
},
|
||||
)
|
||||
}
|
||||
/>
|
||||
<LoadableIcon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
size="7rem"
|
||||
color={iconStyles?.strokeColor || "royalblue"}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
style={{ color: fontColor || "black" }}
|
||||
>
|
||||
7rem
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col flex-center flex-column position-relative hover-show">
|
||||
<LoadableIcon
|
||||
classes="ar-IconController__download-icon position-absolute top-1 end-1"
|
||||
icon="md/MdDownload"
|
||||
color="orange"
|
||||
size="2.5rem"
|
||||
onClick={() =>
|
||||
DomHelper.downloadSvg(
|
||||
(icon as ObjectType).icon as string,
|
||||
name,
|
||||
{
|
||||
size: size && unit ? size + unit : "20rem",
|
||||
...iconStyles,
|
||||
color: iconStyles?.strokeColor || "royalblue",
|
||||
},
|
||||
)
|
||||
}
|
||||
/>
|
||||
<LoadableIcon
|
||||
key={`${group}/${name}`}
|
||||
icon={`${group}/${name}`}
|
||||
size={`${size}${unit}` || "20rem"}
|
||||
color={iconStyles?.strokeColor || "royalblue"}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
<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={[
|
||||
// @ts-ignore
|
||||
{ label: "rem", value: "rem" },
|
||||
// @ts-ignore
|
||||
{ label: "px", value: "px" },
|
||||
// @ts-ignore
|
||||
{ label: "vh", value: "vh" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* <Tags classes="col-12 h-100 px-0" label={name} /> */}
|
||||
{icon && (
|
||||
<Tags
|
||||
// clickHandler={clickHandler}
|
||||
classes="col-12 h-75 px-0"
|
||||
label={name}
|
||||
tags={Object.fromEntries(
|
||||
(
|
||||
icon.tags as Array<{ name: string; verified: string }>
|
||||
).map((key) => [key.name, 1]),
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Suggestions
|
||||
classes="border mb-3"
|
||||
suggestions={similarIcons}
|
||||
title={`Icons Similar to ${name}`}
|
||||
inSvgString
|
||||
/>
|
||||
<Suggestions
|
||||
classes="border mb-3"
|
||||
suggestions={similarIcons}
|
||||
title="Trending Icons"
|
||||
inSvgString
|
||||
// suggestions={recentlyVisited}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconController
|
||||
3
src/app/components/IconController/index.ts
Executable file
3
src/app/components/IconController/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import IconController from "./IconController"
|
||||
|
||||
export default IconController
|
||||
2
src/app/components/IconEditor/IconEditor.component.scss
Executable file
2
src/app/components/IconEditor/IconEditor.component.scss
Executable file
@@ -0,0 +1,2 @@
|
||||
.ar-IconEditor {
|
||||
}
|
||||
8
src/app/components/IconEditor/IconEditor.test.ts
Executable file
8
src/app/components/IconEditor/IconEditor.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import IconEditor from "./IconEditor"
|
||||
|
||||
describe("IconEditor", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
47
src/app/components/IconEditor/IconEditor.tsx
Executable file
47
src/app/components/IconEditor/IconEditor.tsx
Executable file
@@ -0,0 +1,47 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { setIconStyles } from "../../pages/IconPage/IconPage.slice"
|
||||
import { TabBar, IconStyleSelector } from ".."
|
||||
import { IconEditorProps, TabProps } from "../../types/components.interface"
|
||||
import "./IconEditor.component.scss"
|
||||
|
||||
const iconEditorTabs = [
|
||||
{
|
||||
label: "Icon",
|
||||
id: uuid(),
|
||||
},
|
||||
{
|
||||
label: "Background",
|
||||
id: uuid(),
|
||||
},
|
||||
]
|
||||
|
||||
const IconEditor = (props: IconEditorProps): JSX.Element => {
|
||||
const { layout } = props
|
||||
const [iconStyles, setIconStylesState] = useState<{
|
||||
fillColor?: string
|
||||
strokeColor?: string
|
||||
bgColor?: string
|
||||
strokeWidth?: string
|
||||
}>()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
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
|
||||
3
src/app/components/IconEditor/index.ts
Executable file
3
src/app/components/IconEditor/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import IconEditor from "./IconEditor"
|
||||
|
||||
export default IconEditor
|
||||
12
src/app/components/IconMergeContainer/IconMergeContainer.component.scss
Executable file
12
src/app/components/IconMergeContainer/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;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/app/components/IconMergeContainer/IconMergeContainer.test.ts
Executable file
8
src/app/components/IconMergeContainer/IconMergeContainer.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import IconMergeContainer from "./IconMergeContainer"
|
||||
|
||||
describe("IconMergeContainer", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
154
src/app/components/IconMergeContainer/IconMergeContainer.tsx
Executable file
154
src/app/components/IconMergeContainer/IconMergeContainer.tsx
Executable file
@@ -0,0 +1,154 @@
|
||||
import { useEffect, useRef } from "react"
|
||||
import { IconMergeContainerProps } from "../../types/components.interface"
|
||||
import { DomHelper } from "../../utils"
|
||||
import "./IconMergeContainer.component.scss"
|
||||
import { ArrayType, ObjectType } from "../../types/types"
|
||||
|
||||
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 IconMergeContainer = (props: IconMergeContainerProps): JSX.Element => {
|
||||
const { icons } = props
|
||||
const svgRef = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const childList: Array<SVGSVGElement> = []
|
||||
iconTest?.forEach((icon) => {
|
||||
const svgElement = DomHelper.svgStringToElement(icon.icon)
|
||||
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
|
||||
3
src/app/components/IconMergeContainer/index.ts
Executable file
3
src/app/components/IconMergeContainer/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import IconMergeContainer from "./IconMergeContainer"
|
||||
|
||||
export default IconMergeContainer
|
||||
3
src/app/components/IconStyleSelector/IconStyleSelector.component.scss
Executable file
3
src/app/components/IconStyleSelector/IconStyleSelector.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-IconStyleSelector {
|
||||
|
||||
}
|
||||
8
src/app/components/IconStyleSelector/IconStyleSelector.test.ts
Executable file
8
src/app/components/IconStyleSelector/IconStyleSelector.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import IconStyleSelector from "./IconStyleSelector"
|
||||
|
||||
describe("IconStyleSelector", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
87
src/app/components/IconStyleSelector/IconStyleSelector.tsx
Executable file
87
src/app/components/IconStyleSelector/IconStyleSelector.tsx
Executable file
@@ -0,0 +1,87 @@
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { IconStyleSelectorProps } from "../../types/components.interface"
|
||||
import { IconStyles } from "../../types/entity.interface"
|
||||
import AdvancedColorPicker from "../atoms/AdvancedColorPicker"
|
||||
import { Slider } from ".."
|
||||
import "./IconStyleSelector.component.scss"
|
||||
|
||||
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) => {
|
||||
console.log(propName)
|
||||
setIconStyles((currentIconStyles: IconStyles) => ({
|
||||
...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">
|
||||
{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
|
||||
3
src/app/components/IconStyleSelector/index.ts
Executable file
3
src/app/components/IconStyleSelector/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import IconStyleSelector from "./IconStyleSelector"
|
||||
|
||||
export default IconStyleSelector
|
||||
@@ -1,32 +1,67 @@
|
||||
.c-IconsList {
|
||||
.c-IconsList__header-pagination-search {
|
||||
.ar-IconsList {
|
||||
.ar-IconsList__header-pagination-search-upload {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-colora);
|
||||
background-color: var(--ar-bg-secondary);
|
||||
}
|
||||
|
||||
.c-IconsList__icon-tile-container {
|
||||
.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);
|
||||
}
|
||||
|
||||
.c-IconTile {
|
||||
.ar-IconTile {
|
||||
cursor: pointer;
|
||||
width: 7rem;
|
||||
height: 8.5rem;
|
||||
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);
|
||||
}
|
||||
|
||||
.c-IconTile__image {
|
||||
height: 6.5rem;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import React from "react"
|
||||
import IconsList from "./IconsList"
|
||||
|
||||
describe("IconsList", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
it("renders without error", () => {})
|
||||
})
|
||||
|
||||
@@ -1,67 +1,246 @@
|
||||
import { ChangeEvent, useState } from "react"
|
||||
import { Pagination, Search } from ".."
|
||||
import Helper from "../../utils/helper"
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import { notify, setRightPanelContent } from "../../store"
|
||||
import {
|
||||
getFavorites,
|
||||
getSelectedTag,
|
||||
removeFavorite,
|
||||
setFavorites,
|
||||
} from "../../pages/IconsPage/IconsPage.slice"
|
||||
import {
|
||||
getIconStyles,
|
||||
setIconStyles,
|
||||
} from "../../pages/IconPage/IconPage.slice"
|
||||
import {
|
||||
Button,
|
||||
IconStyleSelector,
|
||||
IconTile,
|
||||
LoadableIcon,
|
||||
Loader,
|
||||
Pagination,
|
||||
Pillbox,
|
||||
Popover,
|
||||
SearchField,
|
||||
SegmentedControl,
|
||||
} from ".."
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArIconTileTypes,
|
||||
ArLoaderTypes,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
} from "../../types/enums"
|
||||
import {
|
||||
IconStyles,
|
||||
SearchItem,
|
||||
SegmentData,
|
||||
} from "../../types/entity.interface"
|
||||
import { IconTileProps, IconsListProps } from "../../types/components.interface"
|
||||
import { FunctionType, ObjectType } from "../../types/types"
|
||||
import { IconResponse } from "../../types/iconresponse.interface"
|
||||
|
||||
import { Helper, Network } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "./IconsList.component.scss"
|
||||
|
||||
interface IconsListProps {
|
||||
icons?: Array<IconResponse>
|
||||
onSearchChanged: Function
|
||||
}
|
||||
|
||||
interface IconTileProps {
|
||||
icon: IconResponse
|
||||
}
|
||||
|
||||
const IconTile = (props: IconTileProps): JSX.Element => {
|
||||
const { icon } = props
|
||||
|
||||
return (
|
||||
<span
|
||||
className="c-IconTile d-inline-block mx-2 mb-3 border"
|
||||
onClick={() => {
|
||||
const iconLink = `http://localhost:8080/icon/${icon.group}/${icon.name}`
|
||||
navigator.clipboard.writeText(iconLink)
|
||||
// TODO: replace below line with a dispatcher to show notification
|
||||
// alert("Copied link to clipboard: " + iconLink)
|
||||
}}
|
||||
>
|
||||
<div className="c-IconTile__image text-center">
|
||||
<img
|
||||
className="h-100"
|
||||
src={`data:image/svg+xml;utf8,${icon.svg}`}
|
||||
alt="icon"
|
||||
/>
|
||||
</div>
|
||||
<footer className="fw-bold text-nowrap overflow-hidden flex-center">
|
||||
{icon.name}
|
||||
</footer>
|
||||
</span>
|
||||
)
|
||||
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 }
|
||||
// if (
|
||||
// filters &&
|
||||
// typeof filters === "object" &&
|
||||
// Object.keys(filters).length > 0
|
||||
// ) {
|
||||
// queryParams.filters = filters
|
||||
// }
|
||||
Network.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 { icons, onSearchChanged } = props
|
||||
const slices =
|
||||
icons &&
|
||||
icons.length > 0 &&
|
||||
Helper.generateSlices(icons.length, 100, "index")
|
||||
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 dispatch = useAppDispatch()
|
||||
const favorites = useAppSelector(getFavorites)
|
||||
const selectedTag = useAppSelector<string | undefined>(getSelectedTag)
|
||||
const iconStyles = useAppSelector<IconStyles | undefined>(getIconStyles)
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
useEffect(() => {
|
||||
setView({ name: ArIconTileTypes.COMFY })
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (favorites) {
|
||||
dispatch(
|
||||
setRightPanelContent(
|
||||
favorites.length > 0 ? { name: "FavoritesList" } : { name: "" },
|
||||
),
|
||||
)
|
||||
}
|
||||
}, [favorites, dispatch])
|
||||
|
||||
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 &&
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: `Icon link for ${iconProps.name} copied`,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="c-IconsList h-100 w-100 overflow-auto p-3">
|
||||
<div className="c-IconsList__header-pagination-search py-2 px-3 mb-3 border">
|
||||
<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-2 d-flex align-items-center">
|
||||
<h6 className="mb-0">Icon Repository</h6>
|
||||
<div className="col d-none d-md-flex align-items-center">
|
||||
<h6 className="mb-0 h-100 flex-center px-3 border-right">
|
||||
<Button
|
||||
variant={ArButtonVariants.LINK}
|
||||
content="Categories"
|
||||
size={ArSizes.SMALL}
|
||||
splitOptions={[
|
||||
{
|
||||
label: "All",
|
||||
},
|
||||
{
|
||||
label: "Business",
|
||||
},
|
||||
{
|
||||
label: "Medical",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</h6>
|
||||
</div>
|
||||
<div className="col-7 flex-center">
|
||||
{slices && <Pagination data={slices} maxPillsToShow={5} />}
|
||||
</div>
|
||||
<div className="col-3">
|
||||
<Search
|
||||
classes="bg-white h-100"
|
||||
<span className="col flex-v-center justify-content-end">
|
||||
<Popover>
|
||||
<LoadableIcon
|
||||
classes="ar-IconsList__color-palette cursor-pointer ms-auto hover-shadow me-3"
|
||||
icon="io/IoIosColorPalette"
|
||||
color="orange"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
size="2rem"
|
||||
onClick={() => {}}
|
||||
/>
|
||||
<IconStyleSelector
|
||||
slot={ArPopoverSlots.POPOVER}
|
||||
setIconStyles={(iconStylesUpdater) => {
|
||||
const updatedStyles = iconStylesUpdater(iconStyles)
|
||||
dispatch(setIconStyles(updatedStyles))
|
||||
}}
|
||||
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={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onSearchChanged(event.target.value)
|
||||
}
|
||||
onChange={Helper.debounce(
|
||||
(event: ChangeEvent<HTMLInputElement>) =>
|
||||
setSearchText(event.target.value),
|
||||
1000,
|
||||
)}
|
||||
data={
|
||||
icons
|
||||
? icons.map(
|
||||
@@ -76,12 +255,169 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="c-IconsList__icon-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap">
|
||||
{icons &&
|
||||
icons
|
||||
.slice(0, 100)
|
||||
.map((icon, index) => <IconTile key={index} icon={icon} />)}
|
||||
<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">
|
||||
<div className="ar-IconsList__icon-tile-container py-2 px-3 border d-flex flex-wrap w-100">
|
||||
{loading && (
|
||||
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
|
||||
)}
|
||||
{page.map((icon, index) => (
|
||||
<Popover
|
||||
trigger={ArPopoverTriggers.HOVER}
|
||||
key={"icon-tile-popover-" + index}
|
||||
>
|
||||
{!view || view.name !== ArIconTileTypes.LIST ? (
|
||||
<span slot={ArPopoverSlots.POPOVER}>
|
||||
{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={index}
|
||||
icon={icon}
|
||||
iconSize={
|
||||
view?.name === ArIconTileTypes.LIST ? "1rem" : "2rem"
|
||||
}
|
||||
onClick={onIconTileClick}
|
||||
tools={[
|
||||
{
|
||||
iconProps: {
|
||||
icon: "sl.SlOptions",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
iconProps: {
|
||||
icon: "io.IoIosShareAlt",
|
||||
hoverColor: "lightblue",
|
||||
},
|
||||
name: "share",
|
||||
onClick: () => {},
|
||||
},
|
||||
],
|
||||
name: "more-options",
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
iconProps: {
|
||||
icon: "ai/AiFillLike",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
},
|
||||
name: "like",
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
iconProps: {
|
||||
icon: "md/MdFavorite",
|
||||
hoverColor: "lightblue",
|
||||
toggleColor: "red",
|
||||
color: "white",
|
||||
toggled: false,
|
||||
},
|
||||
name: "favorite",
|
||||
onClick: () => {
|
||||
const favorite = favorites.find(
|
||||
(searchedIcon: IconTileProps) =>
|
||||
icon.name === searchedIcon.icon.name,
|
||||
)
|
||||
if (!favorites || !favorite) {
|
||||
dispatch(setFavorites({ icon }))
|
||||
dispatch(
|
||||
notify({
|
||||
message: "Icon Added to your favorites",
|
||||
show: true,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
dispatch(removeFavorite({ icon }))
|
||||
dispatch(
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
3
src/app/components/LandingContent/LandingContent.component.scss
Executable file
3
src/app/components/LandingContent/LandingContent.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-LandingContent {
|
||||
|
||||
}
|
||||
8
src/app/components/LandingContent/LandingContent.test.ts
Executable file
8
src/app/components/LandingContent/LandingContent.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import LandingContent from "./LandingContent"
|
||||
|
||||
describe("LandingContent", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
82
src/app/components/LandingContent/LandingContent.tsx
Executable file
82
src/app/components/LandingContent/LandingContent.tsx
Executable file
@@ -0,0 +1,82 @@
|
||||
import { Hero, ProductDescriptionTile } from ".."
|
||||
import { LandingContentProps } from "../../types/components.interface"
|
||||
import { ArPlacement } from "../../types/enums"
|
||||
import "./LandingContent.component.scss"
|
||||
|
||||
const toolsAndProducts = [
|
||||
{
|
||||
name: "Icons & Fonts",
|
||||
description: [
|
||||
"Dive into a world of creativity with our diverse icon assets and fonts.",
|
||||
],
|
||||
image: "",
|
||||
imagePlacement: ArPlacement.RIGHT,
|
||||
},
|
||||
{
|
||||
name: "Task Manager and Config Manager",
|
||||
description: [
|
||||
"Streamline project management and configuration tasks effortlessly.",
|
||||
"Master project management and configurations with our intuitive tools.",
|
||||
],
|
||||
image: "",
|
||||
imagePlacement: ArPlacement.RIGHT,
|
||||
},
|
||||
{
|
||||
name: "Components Library",
|
||||
description: [
|
||||
"Indulge in Unmatched Creativity with Our Opinionated and highly customizable Component Library",
|
||||
"Discover a component library like no other, meticulously crafted and opinionated for optimal performance. Our library isn't just about components; it's a playground for creators. Immerse yourself in the richness of our carefully curated elements, each designed with a distinct perspective on aesthetics and functionality.",
|
||||
"Explore a library that doesn't just follow trends but sets them. Elevate your projects with components that speak your language, and let your creativity soar in a space that celebrates individuality. Welcome to a component library where opinion meets innovation, and your designs become a masterpiece.",
|
||||
],
|
||||
image: "",
|
||||
imagePlacement: ArPlacement.RIGHT,
|
||||
},
|
||||
{
|
||||
name: "Analytics Collector and Viewer",
|
||||
description: [
|
||||
"Showcase the analytics capabilities for data-driven decisions.",
|
||||
"Turn data into insights with our integrated analytics tools.",
|
||||
],
|
||||
image: "",
|
||||
imagePlacement: ArPlacement.RIGHT,
|
||||
},
|
||||
{
|
||||
name: "IAM",
|
||||
description: [
|
||||
"Explain the identity and access management features.",
|
||||
"Manage identities and access with ease using our robust IAM features.",
|
||||
],
|
||||
image: "",
|
||||
imagePlacement: ArPlacement.RIGHT,
|
||||
},
|
||||
{
|
||||
name: "APIs",
|
||||
description: [
|
||||
"Highlight the flexibility of APIs for integration into various workflows.",
|
||||
"Harness the flexibility of APIs for seamless integration into your development workflow.",
|
||||
],
|
||||
image: "",
|
||||
imagePlacement: ArPlacement.RIGHT,
|
||||
},
|
||||
]
|
||||
|
||||
const LandingContent = (props: LandingContentProps): JSX.Element => {
|
||||
return (
|
||||
<div className="ar-LandingContent">
|
||||
<Hero classes="mb-3" />
|
||||
<div className="ar-LandingContent__tiles-container container">
|
||||
{toolsAndProducts.map((item) => {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<ProductDescriptionTile classes="mb-3" {...item} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LandingContent
|
||||
3
src/app/components/LandingContent/index.ts
Executable file
3
src/app/components/LandingContent/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import LandingContent from "./LandingContent"
|
||||
|
||||
export default LandingContent
|
||||
3
src/app/components/LoginProvider/LoginProvider.component.scss
Executable file
3
src/app/components/LoginProvider/LoginProvider.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-LoginProvider {
|
||||
width: 30rem;
|
||||
}
|
||||
8
src/app/components/LoginProvider/LoginProvider.test.ts
Executable file
8
src/app/components/LoginProvider/LoginProvider.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import LoginProvider from "./LoginProvider"
|
||||
|
||||
describe("LoginProvider", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
30
src/app/components/LoginProvider/LoginProvider.tsx
Executable file
30
src/app/components/LoginProvider/LoginProvider.tsx
Executable file
@@ -0,0 +1,30 @@
|
||||
import { setRightPanelContent } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Button } from ".."
|
||||
import { LoginProviderProps } from "../../types/components.interface"
|
||||
import { ArButtonVariants, ArSizes } from "../../types/enums"
|
||||
import WEB_CONFIG from "../../config/web-config"
|
||||
import "./LoginProvider.component.scss"
|
||||
|
||||
const LoginProvider = (props: LoginProviderProps): JSX.Element => {
|
||||
const { url } = props
|
||||
const dispatch = useAppDispatch()
|
||||
return (
|
||||
<div className="ar-LoginProvider position-relative h-100">
|
||||
<iframe
|
||||
src={url || WEB_CONFIG.IAM[process.env.NODE_ENV]}
|
||||
title="IAM"
|
||||
className="ar-LoginProvider__frame h-100 w-100"
|
||||
/>
|
||||
<Button
|
||||
variant={ArButtonVariants.LINKHOVEREFFECT}
|
||||
size={ArSizes.SMALL}
|
||||
classes="position-absolute top-0 end-0"
|
||||
content="Close"
|
||||
onClick={() => dispatch(setRightPanelContent({ name: "" }))}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginProvider
|
||||
3
src/app/components/LoginProvider/index.ts
Executable file
3
src/app/components/LoginProvider/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import LoginProvider from "./LoginProvider"
|
||||
|
||||
export default LoginProvider
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user