Major: Lottie, fonts, UI revamp
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,8 @@ coverage
|
||||
# Production
|
||||
build
|
||||
dist
|
||||
lib
|
||||
public
|
||||
|
||||
# Miscellaneous
|
||||
*.local
|
||||
@@ -35,6 +37,7 @@ analyse.html
|
||||
stats.html
|
||||
|
||||
*.pem
|
||||
*.d.ts
|
||||
|
||||
analyticsrc.json
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
block loader
|
||||
tree list search
|
||||
tree selected item highlight issue - if two sub items have same name both get highlighted on select (add namespace to determine item to select)
|
||||
treelist showPopUp is buggy - test in components tree to check (Search Component used in TreeList)
|
||||
make drawer collapsible
|
||||
icon filters
|
||||
optimize icons - currently all are fetched worth 28mb, should fetch less (~100 icons - decide first 100 or most popular 100, etc.),
|
||||
Add description of all the components in components viewer
|
||||
Implement component tools on components viewer to select props
|
||||
|
||||
Priority icons page
|
||||
|
||||
add toolbar component
|
||||
- 2 variants
|
||||
- floating (flat can be imposed anywhere)
|
||||
- sticky (shows up on hover or button click of parent element - icon tile in this case)
|
||||
- triggers for sticky
|
||||
- button
|
||||
- hover on
|
||||
tools
|
||||
- download
|
||||
- view
|
||||
- share
|
||||
- Favorite
|
||||
- View similar
|
||||
|
||||
Ability to upload icons (require sign up)
|
||||
|
||||
- Tab bar should have navigate buttons when tabbar width exceeds parent width
|
||||
- Implement table with controls to visualize side-by-side
|
||||
- Add on the right edge a set of half visible buttons for bringing up user guide, feedback, favorites etc. on hover or click (configurable)
|
||||
15
build.sh
15
build.sh
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
COMPONENT_CATEGORIES=( index atoms molecules )
|
||||
FORMATS=( es cjs )
|
||||
|
||||
for format in "${FORMATS[@]}"
|
||||
do
|
||||
# Order is important else index overrides atoms and molecules
|
||||
for component_type in "${COMPONENT_CATEGORIES[@]}"
|
||||
do
|
||||
COMPONENT_CATEGORY=$component_type FORMAT=$format npm run build:publish
|
||||
done
|
||||
done
|
||||
|
||||
npm run build:types
|
||||
23138
package-lock.json
generated
23138
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
54
package.json
54
package.json
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "@armco/armory-react-components",
|
||||
"description": "React Component Library for Armco's stack of products and services",
|
||||
"version": "0.0.21",
|
||||
"version": "0.0.22",
|
||||
"type": "module",
|
||||
"author": "Armco (@restruct-corporate-advantage)",
|
||||
"scripts": {
|
||||
"compile": "tsc -b ./tsconfig-build.types.json",
|
||||
"dev": "vite",
|
||||
"start": "NODE_ENV=production vite",
|
||||
"shbuild": "./build.sh",
|
||||
"build": "tsc && vite build",
|
||||
"build:publish": "vite --config vite-publish.config.ts build",
|
||||
"build:types": "tsc --project ./tsconfig-publish.json",
|
||||
"build": "tsc --p ./tsconfig-build.json && vite build",
|
||||
"build:clean": "rm -rf ./build",
|
||||
"build:types": "NODE_ENV=production tsc --project ./tsconfig-publish.json",
|
||||
"build:sh": "./scripts/build.sh",
|
||||
"generate": "plop",
|
||||
"atom": "plop atom",
|
||||
"molecule": "plop molecule",
|
||||
@@ -22,14 +23,17 @@
|
||||
"lint": "eslint .",
|
||||
"type-check": "tsc",
|
||||
"publish:dry": "npm publish --dry-run",
|
||||
"publish:local": "./scripts/publish-local.sh",
|
||||
"publish:public": "npm publish --access public",
|
||||
"shpublish": "./publish.sh",
|
||||
"shpublish": "./scripts/publish.sh",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@armco/analytics": "^0.2.5",
|
||||
"@armco/armory-react-components": "^0.0.20",
|
||||
"@armco/armory-react-components": "^0.0.22",
|
||||
"@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",
|
||||
@@ -39,6 +43,8 @@
|
||||
"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",
|
||||
@@ -66,14 +72,20 @@
|
||||
"@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/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",
|
||||
@@ -86,8 +98,14 @@
|
||||
"storybook": "^7.0.23",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.0.0",
|
||||
"vite-plugin-dts": "^3.7.1",
|
||||
"vite-plugin-lib-inject-css": "^1.3.0",
|
||||
"vitest": "^0.30.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-redux": "^8.0.1",
|
||||
"react-router-dom": "^6.13.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
@@ -103,14 +121,9 @@
|
||||
}
|
||||
},
|
||||
"prettier": "prettier-config-nick",
|
||||
"main": "cjs/index.js",
|
||||
"module": "es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./es/index.js",
|
||||
"require": "./cjs/index.js"
|
||||
}
|
||||
},
|
||||
"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"
|
||||
@@ -126,16 +139,11 @@
|
||||
"foundation"
|
||||
],
|
||||
"files": [
|
||||
"build/cjs",
|
||||
"build/es"
|
||||
"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",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
"homepage": "https://github.com/ReStruct-Corporate-Advantage/armory-react-components#readme"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { {{pascalCase name}}Props } from "../../../types/components.interface"
|
||||
import { {{pascalCase name}}Props } from ".."
|
||||
import "./{{pascalCase name}}.component.scss"
|
||||
|
||||
const {{pascalCase name}} = (props: {{pascalCase name}}Props): JSX.Element => {
|
||||
|
||||
67
scripts/build.sh
Executable file
67
scripts/build.sh
Executable file
@@ -0,0 +1,67 @@
|
||||
#!/bin/sh
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
copy_files() {
|
||||
local source_dir="$1"
|
||||
local destination_dir="$2"
|
||||
local files_to_copy=(".tsx" ".component.scss")
|
||||
|
||||
find "$source_dir" -type d -mindepth 1 -maxdepth 1 | while read -r directory; do
|
||||
echo "Found directory: $directory"
|
||||
for file in "${files_to_copy[@]}"; do
|
||||
source_file="$directory/$(basename "$directory")$file"
|
||||
destination_file="$destination_dir/$(basename "$directory")$file"
|
||||
echo "Copying $source_file to $destination_file"
|
||||
cp "$source_file" "$destination_file"
|
||||
echo "Copied $source_file to $destination_file"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
search_replace_in_files() {
|
||||
local directory="$1"
|
||||
local search_string="$2"
|
||||
local replace_string="$3"
|
||||
|
||||
# Use find to locate TypeScript and SCSS files in the directory
|
||||
find "$directory" -type f \( -name "*.tsx" -o -name "*.scss" \) -exec sh -c '
|
||||
for file do
|
||||
echo "Processed: $file"
|
||||
perl -pi -e "s|$search_string|$replace_string|g" "$file"
|
||||
done
|
||||
' sh {} +
|
||||
|
||||
echo "Search and replace in TypeScript and SCSS files completed."
|
||||
}
|
||||
|
||||
|
||||
# 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
|
||||
# cp ../src/app/hooks.ts "../lib"
|
||||
# cp -r ../src/app/pages "../lib"
|
||||
# cp ../src/react-app-env.d.ts ../lib
|
||||
# cp ../src/vite-env.d.ts ../lib
|
||||
# cp ../tsconfig.json ../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/EventForm.tsx "../lib"
|
||||
# cp ../src/app/components/atoms/Calendar/MonthNavigator.tsx "../lib"
|
||||
# cp ../src/app/components/atoms/Calendar/helper.ts "../lib"
|
||||
# cp ../src/app/store.ts "../lib"
|
||||
# search_replace_in_files "../lib" "../../../types" "./types"
|
||||
# search_replace_in_files "../lib" "\.\./\.\." "\."
|
||||
# search_replace_in_files "../lib" "\.\./utils" "utils"
|
||||
# search_replace_in_files "../lib" "\.\./config" "config"
|
||||
# search_replace_in_files "../lib" "\.\./static" "static"
|
||||
# search_replace_in_files "../lib" "\.\." "\."
|
||||
# search_replace_in_files "../lib" "\(\.args" "\(\.\.\.args"
|
||||
npm run build
|
||||
@@ -3,7 +3,7 @@
|
||||
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 component "$i"
|
||||
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")
|
||||
7
scripts/publish-local.sh
Executable file
7
scripts/publish-local.sh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
semver=${1:-patch}
|
||||
|
||||
set -e
|
||||
# npm run build
|
||||
npm pack --pack-destination ~/__Projects__/Common
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
source ./build.sh
|
||||
# source ./scripts/build.sh
|
||||
npm --no-git-tag-version version patch
|
||||
npm publish --access public
|
||||
@@ -62,16 +62,18 @@ const Router = (props: RouterProps) => {
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
dispatch(setRightPanelContent(""))
|
||||
dispatch(setRightPanelContent({ name: "" }))
|
||||
dispatch(setUser(e.data.data.body.user))
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const matchedRouteElement = useRoutes(ROUTES)
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Header />
|
||||
{useRoutes(ROUTES)}
|
||||
<Header routeDetails={matchedRouteElement?.props.match} />
|
||||
{matchedRouteElement}
|
||||
{alertInfo && <Alert {...alertInfo} />}
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
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,16 +1,20 @@
|
||||
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 COMPONENTS from "../../config/components"
|
||||
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 &&
|
||||
@@ -19,12 +23,13 @@ const ComponentList = (props: ComponentListProps): JSX.Element => {
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className="ar-ComponentList h-100 w-100 overflow-auto mx-2">
|
||||
<div className="ar-ComponentList h-100 w-100 mx-2">
|
||||
<TreeList
|
||||
onItemSelect={handleComponentSelect}
|
||||
data={formattedTreeData}
|
||||
title="Component List"
|
||||
firstExpanded={true}
|
||||
theme={theme}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -41,7 +41,7 @@ const ConfigurationLoginPrompt = (
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
postIcon="io5/IoArrowForwardCircle"
|
||||
onClick={() => {
|
||||
dispatch(setRightPanelContent("LoginProvider"))
|
||||
dispatch(setRightPanelContent({ name: "LoginProvider" }))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ interface ContentProps {
|
||||
const Content = (props: ContentProps): JSX.Element => {
|
||||
const { children, classes } = props
|
||||
return (
|
||||
<div className={`ar-Content${classes ? " " + classes : ""}`}>
|
||||
<div className={`ar-Content flex-grow-1${classes ? " " + classes : ""}`}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,38 @@
|
||||
.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,31 +1,65 @@
|
||||
import { useState } from "react"
|
||||
import { DrawerProps } from "../../types/components.interface"
|
||||
import "./Drawer.component.scss"
|
||||
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"
|
||||
|
||||
const isMobile = Helper.isMobile()
|
||||
let clickedSelf: boolean
|
||||
|
||||
const Drawer = (props: DrawerProps): JSX.Element => {
|
||||
const { children, classes, isCollapsible } = props
|
||||
const [collapsed, setCollapsed] = useState<boolean>()
|
||||
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={`ar-Drawer${classes ? " " + classes : ""}${
|
||||
className={`ar-Drawer overflow-auto${classes ? " " + classes : ""}${
|
||||
isCollapsible ? " position-relative" : ""
|
||||
}${collapsed ? " collapsed" : ""}`}
|
||||
}${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="position-absolute top-1 end-1 cursor-pointer"
|
||||
classes="ar-Drawer__expander position-absolute cursor-pointer d-none d-sm-inline"
|
||||
color={theme === ArThemes.DARK1 ? "white" : "black"}
|
||||
icon={
|
||||
collapsed
|
||||
drawerState?.collapsed
|
||||
? "tb/TbLayoutSidebarLeftExpand"
|
||||
: "tb/TbLayoutSidebarLeftCollapse"
|
||||
}
|
||||
size="1.5rem"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
onClick={() =>
|
||||
dispatch(setDrawerState({ collapsed: !drawerState?.collapsed }))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!collapsed && children}
|
||||
{!drawerState?.collapsed && children}
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Alert, Button, List, Popover, Toggle } from ".."
|
||||
import { FrameContentDefinition } from "../../types/entity.interface"
|
||||
import { FrameContentProps } from "../../types/components.interface"
|
||||
import { ArButtonVariants, ArPopoverSlots } from "../../types/enums"
|
||||
import { ComponentDescription } from "../../types/componentlist.interface"
|
||||
import Helper from "../../utils/helper"
|
||||
import COMPONENTS from "../../config/components"
|
||||
import * as images from "../../static/images"
|
||||
@@ -239,7 +240,7 @@ const Editor = (): JSX.Element | string => {
|
||||
toggleOnName="Dark"
|
||||
toggleOffName="Light"
|
||||
onChange={(checked: boolean) =>
|
||||
setTheme(checked ? "th-light-1" : "th-dark-1")
|
||||
setTheme(checked ? "th-dark-1" : "th-light-1")
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -108,7 +108,7 @@ const FavoritesList = (props: FavoritesListProps): JSX.Element => {
|
||||
content="Login"
|
||||
preIcon="ci/CiWarning"
|
||||
onClick={() => {
|
||||
dispatch(setRightPanelContent("LoginProvider"))
|
||||
dispatch(setRightPanelContent({ name: "LoginProvider" }))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
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
|
||||
@@ -1,3 +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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,99 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { FontsListProps } from "../../types/components.interface"
|
||||
import { ArrayType } from "../../types/types"
|
||||
import { ArrayType, FunctionType, ObjectType } from "../../types/types"
|
||||
import { ArButtonVariants, ArLoaderTypes, ArSizes } from "../../types/enums"
|
||||
import { Button, Loader, Search } from ".."
|
||||
import { Helper } from "../../utils"
|
||||
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(() => {}, [])
|
||||
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-pagination-search-upload py-2 px-3 mb-2 border">
|
||||
<div className="ar-FontsList__header-search-upload py-2 px-3 mb-2 border">
|
||||
<div className="row">
|
||||
<div className="col-4 d-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>
|
||||
<span className="col-4 d-none d-md-flex flex-v-center justify-content-end">
|
||||
<Button
|
||||
classes="h-100 float-end me-3"
|
||||
content="Create"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonVariants.LINK}
|
||||
preIcon="io5/IoCreateOutline"
|
||||
/>
|
||||
<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, tags, description"
|
||||
placeholder="Search by name, style, description"
|
||||
onChange={Helper.debounce(
|
||||
(event: ChangeEvent<HTMLInputElement>) => null,
|
||||
// onSearchChanged(event.target.value),
|
||||
(event: ChangeEvent<HTMLInputElement>) =>
|
||||
setSearchText(event.target.value),
|
||||
1000,
|
||||
)}
|
||||
data={
|
||||
@@ -81,12 +112,27 @@ const FontsList = (props: FontsListProps): JSX.Element => {
|
||||
</div>
|
||||
</div>
|
||||
{page && (
|
||||
<div className="ar-FontsList__icon-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap flex-grow-1">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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="ar-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
|
||||
|
||||
@@ -7,9 +7,33 @@
|
||||
.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__separator {
|
||||
border-right: var(--ar-border);
|
||||
.ar-Header__app-search {
|
||||
transition: width 0.3s;
|
||||
@media screen and (max-width: 576px) {
|
||||
flex: 1;
|
||||
|
||||
.ar-Search .ar-TextInput {
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
}
|
||||
.ar-Search .ar-Button {
|
||||
border-top-right-radius: 0.5rem;
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,96 +1,150 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useState } from "react"
|
||||
import { useLocation, useNavigate } from "react-router-dom"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { TabBar, UserOptions } from ".."
|
||||
import { ArTabType } from "../../types/enums"
|
||||
import { TabProps } from "../../types/components.interface"
|
||||
import { AppLogo } from "../../static/images"
|
||||
import { ReactComponent as AppName } from "../../static/images/Omnia Alkatra.svg"
|
||||
import { useAppDispatch, useAppSelector } from "../../hooks"
|
||||
import {
|
||||
getCurrentTheme,
|
||||
getDrawerState,
|
||||
setDrawerState,
|
||||
setTheme,
|
||||
} from "../../store"
|
||||
import { FlexTools, LoadableIcon, Search } from ".."
|
||||
import { DrawerProps, HeaderProps } from "../../types/components.interface"
|
||||
import { ObjectType } from "../../types/types"
|
||||
import { ArThemes } from "../../types/enums"
|
||||
// import { AppLogo } from "../../static/images"
|
||||
// import { ReactComponent as AppName } from "../../static/images/Omnia Alkatra.svg"
|
||||
import "./Header.component.scss"
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
label: "Components",
|
||||
id: uuid(),
|
||||
isActive: true,
|
||||
route: "/components",
|
||||
icon: "cg.CgComponents",
|
||||
},
|
||||
{
|
||||
label: "Icons",
|
||||
id: uuid(),
|
||||
isActive: false,
|
||||
route: "/icons",
|
||||
icon: "md.MdOutlineInsertEmoticon",
|
||||
},
|
||||
{
|
||||
label: "Fonts",
|
||||
id: uuid(),
|
||||
isActive: false,
|
||||
route: "/fonts",
|
||||
icon: "bi.BiFontFamily",
|
||||
},
|
||||
{
|
||||
label: "Configurations",
|
||||
id: uuid(),
|
||||
isActive: false,
|
||||
route: "/config",
|
||||
icon: "fc.FcDataConfiguration",
|
||||
},
|
||||
{
|
||||
label: "Tasks",
|
||||
id: uuid(),
|
||||
isActive: false,
|
||||
route: "/tasks",
|
||||
icon: "ri.RiPlayList2Fill",
|
||||
},
|
||||
]
|
||||
// const tabs = [
|
||||
// {
|
||||
// label: "Components",
|
||||
// id: uuid(),
|
||||
// isActive: true,
|
||||
// route: "/components",
|
||||
// icon: "cg.CgComponents",
|
||||
// },
|
||||
// {
|
||||
// label: "Icons",
|
||||
// id: uuid(),
|
||||
// isActive: false,
|
||||
// route: "/icons",
|
||||
// icon: "md.MdOutlineInsertEmoticon",
|
||||
// },
|
||||
// {
|
||||
// label: "Fonts",
|
||||
// id: uuid(),
|
||||
// isActive: false,
|
||||
// route: "/fonts",
|
||||
// icon: "bi.BiFontFamily",
|
||||
// },
|
||||
// {
|
||||
// label: "Configurations",
|
||||
// id: uuid(),
|
||||
// isActive: false,
|
||||
// route: "/config",
|
||||
// icon: "fc.FcDataConfiguration",
|
||||
// },
|
||||
// {
|
||||
// label: "Tasks",
|
||||
// id: uuid(),
|
||||
// isActive: false,
|
||||
// route: "/tasks",
|
||||
// icon: "ri.RiPlayList2Fill",
|
||||
// },
|
||||
// ]
|
||||
|
||||
const userOptions = [
|
||||
[{ name: "Profile" }, { name: "Settings" }, { name: "My Stuff" }],
|
||||
[{ name: "+ Organization" }, { name: "+ Upload Icon" }],
|
||||
]
|
||||
|
||||
const Header = (): JSX.Element => {
|
||||
const Header = (props: HeaderProps): JSX.Element => {
|
||||
const { routeDetails } = props
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [activeTabId, setActiveTabId] = useState<string>()
|
||||
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
|
||||
|
||||
useEffect(() => {
|
||||
const matchedTab =
|
||||
tabs && tabs.find((tab) => location.pathname.startsWith(tab.route))
|
||||
if (matchedTab) {
|
||||
setActiveTabId(matchedTab.id)
|
||||
}
|
||||
}, [location])
|
||||
|
||||
const separator = <span className="ar-Header__separator h-100 me-2" />
|
||||
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" onClick={() => navigate("/")}>
|
||||
<img
|
||||
<div
|
||||
className="ar-Header__app-logo h-100 flex-v-center me-3 d-none d-sm-flex"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
{/* <img
|
||||
src={AppLogo}
|
||||
alt="App Logo"
|
||||
className="ar-Header__brand mx-2"
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
<AppName className="ar-Header__app-name h-100" width="50" />
|
||||
/> */}
|
||||
{/* <AppName className="ar-Header__app-name h-100" width="50" /> */}
|
||||
<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>
|
||||
<TabBar
|
||||
classes="ms-auto"
|
||||
data={tabs.map((tab: TabProps) => {
|
||||
tab.isActive = activeTabId === tab.id
|
||||
return tab
|
||||
})}
|
||||
variant={ArTabType.MODERN}
|
||||
onTabSelected={(id: string, data: TabProps) => {
|
||||
data?.data?.route && navigate(data.data.route)
|
||||
{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"
|
||||
}`}
|
||||
>
|
||||
<Search
|
||||
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"}
|
||||
/>
|
||||
{separator}
|
||||
<UserOptions classes="h-100 flex-center px-2" options={userOptions} />
|
||||
<div
|
||||
className={`h-100 d-none d-sm-inline${
|
||||
isLanding ? " w-100" : " ms-auto"
|
||||
}`}
|
||||
>
|
||||
<FlexTools isLanding={isLanding} route={route as string} />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,46 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { useState } from "react"
|
||||
import { IconControllerProps } from "../../types/components.interface"
|
||||
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,
|
||||
ColorSelector,
|
||||
Button,
|
||||
Dropdown,
|
||||
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) => {
|
||||
@@ -33,22 +42,56 @@ const complement = (hex?: string) => {
|
||||
return "#fff"
|
||||
}
|
||||
|
||||
// const getSuggestions = (group, name, icons) => {
|
||||
// const response: {
|
||||
// group?: Array<ObjectType>
|
||||
// [key: string]: Array<ObjectType> | undefined
|
||||
// } = {}
|
||||
// }
|
||||
const STATIC_ROOT = API_CONFIG.STATIC_HOST[process.env.NODE_ENV]
|
||||
|
||||
const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
const { group, name } = props
|
||||
const [iconColor, setIconColor] = useState<string>()
|
||||
const [backgroundColor, setBackGroundColor] = useState<string>()
|
||||
const { group, icon, name } = props
|
||||
const [size, setSize] = useState<string>()
|
||||
const [unit, setUnit] = useState<string>("rem")
|
||||
const fontColor = complement(backgroundColor || "white")
|
||||
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 && (
|
||||
@@ -59,26 +102,82 @@ const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<div className="ar-IconController__header mb-3 p-3 border">
|
||||
<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: backgroundColor }}
|
||||
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">
|
||||
<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={iconColor || "black"}
|
||||
color={iconStyles?.strokeColor || "royalblue"}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
@@ -87,12 +186,32 @@ const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
3rem
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-50 flex-center flex-column w-100">
|
||||
<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={iconColor || "black"}
|
||||
color={iconStyles?.strokeColor || "royalblue"}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
@@ -102,12 +221,32 @@ const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col flex-center flex-column">
|
||||
<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={iconColor || "black"}
|
||||
color={iconStyles?.strokeColor || "royalblue"}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
<span
|
||||
className="fw-bold"
|
||||
@@ -121,16 +260,48 @@ const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
</div>
|
||||
<div className="ar-IconController__controls col d-flex">
|
||||
<div className="row w-100">
|
||||
<div className="col-2 flex-h-center">
|
||||
<ColorSelector onChange={setIconColor} label="Color" />
|
||||
</div>
|
||||
<div className="col-2 flex-h-center">
|
||||
<ColorSelector
|
||||
onChange={setBackGroundColor}
|
||||
label="Background"
|
||||
/>
|
||||
</div>
|
||||
<div className="ar-IconController__controls-form col-8 border">
|
||||
{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">
|
||||
@@ -161,7 +332,19 @@ const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tags classes="col-12 h-100 px-0" label={name} />
|
||||
{/* <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>
|
||||
@@ -170,11 +353,16 @@ const IconController = (props: IconControllerProps): JSX.Element => {
|
||||
</div>
|
||||
<Suggestions
|
||||
classes="border mb-3"
|
||||
suggestions={[{ group: "ci", name: "CiAlarmOn" }]}
|
||||
suggestions={similarIcons}
|
||||
title={`Icons Similar to ${name}`}
|
||||
inSvgString
|
||||
/>
|
||||
<Suggestions
|
||||
classes="border"
|
||||
suggestions={[{ group: "ci", name: "CiAlarmOn" }]}
|
||||
classes="border mb-3"
|
||||
suggestions={similarIcons}
|
||||
title="Trending Icons"
|
||||
inSvgString
|
||||
// suggestions={recentlyVisited}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
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
|
||||
@@ -2,7 +2,19 @@
|
||||
.ar-IconsList__header-pagination-search-upload {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-colora);
|
||||
background-color: var(--ar-bg-base);
|
||||
}
|
||||
|
||||
.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 {
|
||||
@@ -36,4 +48,20 @@
|
||||
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,4 +1,5 @@
|
||||
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"
|
||||
@@ -8,11 +9,18 @@ import {
|
||||
removeFavorite,
|
||||
setFavorites,
|
||||
} from "../../pages/IconsPage/IconsPage.slice"
|
||||
import {
|
||||
getIconStyles,
|
||||
setIconStyles,
|
||||
} from "../../pages/IconPage/IconPage.slice"
|
||||
import {
|
||||
Button,
|
||||
IconStyleSelector,
|
||||
IconTile,
|
||||
LoadableIcon,
|
||||
Loader,
|
||||
Pagination,
|
||||
Pillbox,
|
||||
Popover,
|
||||
Search,
|
||||
SegmentedControl,
|
||||
@@ -21,11 +29,11 @@ import {
|
||||
ArButtonVariants,
|
||||
ArIconTileTypes,
|
||||
ArLoaderTypes,
|
||||
ArPageTriggers,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
} from "../../types/enums"
|
||||
import { IconStyles, SearchItem } from "../../types/entity.interface"
|
||||
import { IconTileProps, IconsListProps } from "../../types/components.interface"
|
||||
import { FunctionType, ObjectType, SegmentType } from "../../types/types"
|
||||
|
||||
@@ -71,14 +79,20 @@ const fetchIconsPage = (
|
||||
}
|
||||
|
||||
const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
const { onSearchChanged, searchString } = props
|
||||
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<SegmentType>()
|
||||
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 })
|
||||
@@ -87,7 +101,9 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
useEffect(() => {
|
||||
if (favorites) {
|
||||
dispatch(
|
||||
setRightPanelContent(favorites.length > 0 ? "FavoritesList" : ""),
|
||||
setRightPanelContent(
|
||||
favorites.length > 0 ? { name: "FavoritesList" } : { name: "" },
|
||||
),
|
||||
)
|
||||
}
|
||||
}, [favorites, dispatch])
|
||||
@@ -97,21 +113,35 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
if (selectedTag) {
|
||||
filters.tags = selectedTag
|
||||
}
|
||||
if (searchString) {
|
||||
filters.search = searchString
|
||||
if (searchText) {
|
||||
filters.search = searchText
|
||||
}
|
||||
setLoading(true)
|
||||
fetchIconsPage(2000, 0, filters, setIcons, setPage, setLoading)
|
||||
}, [selectedTag, searchString])
|
||||
}, [selectedTag, searchText])
|
||||
|
||||
const onIconTileClick = (iconProps: IconResponse) => {
|
||||
dispatch(
|
||||
notify({
|
||||
show: true,
|
||||
message: `Icon link for ${iconProps.name} copied`,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
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 (
|
||||
@@ -121,7 +151,7 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
)}
|
||||
<div className="ar-IconsList__header-pagination-search-upload py-2 px-3 mb-2 border">
|
||||
<div className="row">
|
||||
<div className="col-4 d-flex align-items-center">
|
||||
<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}
|
||||
@@ -141,21 +171,41 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
/>
|
||||
</h6>
|
||||
</div>
|
||||
<span className="col-4 d-none d-md-flex flex-v-center justify-content-end">
|
||||
<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.LINK}
|
||||
variant={ArButtonVariants.LINKHOVEREFFECT}
|
||||
preIcon="io5/IoCreateOutline"
|
||||
onClick={() => toggleSelectMode(true)}
|
||||
/>
|
||||
<Button
|
||||
classes="h-100 float-end me-3"
|
||||
classes="ar-IconsList__upload-button h-100 float-end"
|
||||
content="Upload"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
variant={ArButtonVariants.PRIMARY}
|
||||
/>
|
||||
<SegmentedControl
|
||||
classes="d-none d-sm-inline ms-3"
|
||||
segments={[
|
||||
{
|
||||
isIcon: true,
|
||||
@@ -179,13 +229,13 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
onChange={(view) => setView(view as SegmentType)}
|
||||
/>
|
||||
</span>
|
||||
<div className="col-4 offset-4 offset-md-0 flex-v-center">
|
||||
<div className="col d-none d-md-flex flex-v-center">
|
||||
<Search
|
||||
classes="bg-white"
|
||||
placeholder="Search by name, tags, description"
|
||||
onChange={Helper.debounce(
|
||||
(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onSearchChanged(event.target.value),
|
||||
setSearchText(event.target.value),
|
||||
1000,
|
||||
)}
|
||||
data={
|
||||
@@ -202,91 +252,153 @@ const IconsList = (props: IconsListProps): JSX.Element => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`ar-IconList__selection-manager d-flex${
|
||||
isSelectMode ? " mb-2 py-2 px-3 border" : " hide"
|
||||
}`}
|
||||
>
|
||||
{selectedIcons && selectedIcons.length > 0 && (
|
||||
<Pillbox
|
||||
data={(selectedIcons || []).map((icon) => ({
|
||||
label: icon.name,
|
||||
deletable: true,
|
||||
}))}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)}
|
||||
<div className="ms-auto d-flex">
|
||||
<Button
|
||||
classes="me-3"
|
||||
content="Cancel"
|
||||
variant={ArButtonVariants.SECONDARY}
|
||||
size={ArSizes.SMALL}
|
||||
onClick={() => {
|
||||
toggleSelectMode(false)
|
||||
setSelectedIcons(undefined)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
content="Done"
|
||||
variant={ArButtonVariants.PRIMARY}
|
||||
size={ArSizes.SMALL}
|
||||
onClick={() =>
|
||||
navigate("/icons/merge-icons", { state: selectedIcons })
|
||||
}
|
||||
disabled={!selectedIcons || selectedIcons.length === 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{page && (
|
||||
<div className="ar-IconsList__icon-tile-container py-2 px-3 border d-flex justify-content-between flex-wrap flex-grow-1">
|
||||
{loading && (
|
||||
<Loader label="Applying filters..." type={ArLoaderTypes.CIRCLE} />
|
||||
)}
|
||||
{page.map((icon, index) => (
|
||||
<Popover trigger={ArPopoverTriggers.HOVER}>
|
||||
{!view || view.name !== ArIconTileTypes.LIST ? (
|
||||
<span slot={ArPopoverSlots.POPOVER}>{icon.name}</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: "io.IoIosShareAlt",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
<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: () => {},
|
||||
},
|
||||
name: "share",
|
||||
onClick: () => {},
|
||||
},
|
||||
{
|
||||
iconProps: {
|
||||
icon: "ai/AiFillLike",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
{
|
||||
iconProps: {
|
||||
icon: "ai/AiFillLike",
|
||||
color: "white",
|
||||
hoverColor: "lightblue",
|
||||
},
|
||||
name: "like",
|
||||
onClick: () => {},
|
||||
},
|
||||
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(),
|
||||
}),
|
||||
{
|
||||
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,
|
||||
)
|
||||
} else {
|
||||
dispatch(removeFavorite({ icon }))
|
||||
dispatch(
|
||||
notify({
|
||||
message: "Icon removed from your favorites",
|
||||
show: true,
|
||||
uid: uuid(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
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
|
||||
}
|
||||
/>
|
||||
</Popover>
|
||||
))}
|
||||
]}
|
||||
hideFooter={
|
||||
(view && (view.name as ArIconTileTypes)) !==
|
||||
ArIconTileTypes.LIST
|
||||
}
|
||||
selectable={isSelectMode}
|
||||
fillColor={iconStyles?.fillColor}
|
||||
strokeColor={iconStyles?.strokeColor}
|
||||
strokeWidth={iconStyles?.strokeWidth}
|
||||
/>
|
||||
</Popover>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* {slices && ( */}
|
||||
|
||||
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
|
||||
@@ -1,14 +1,29 @@
|
||||
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 (
|
||||
<iframe
|
||||
// src="https://iam.notabuck.com"
|
||||
src="http://localhost:3001"
|
||||
title="IAM"
|
||||
className="ar-LoginProvider h-100"
|
||||
/>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
10
src/app/components/LottieEditor/LottieEditor.component.scss
Executable file
10
src/app/components/LottieEditor/LottieEditor.component.scss
Executable file
@@ -0,0 +1,10 @@
|
||||
.ar-LottieEditor {
|
||||
.ar-LottieEditor__header-download {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-colora);
|
||||
}
|
||||
.ar-LottieEditor__lottie-preview {
|
||||
background-color: var(--ar-bg);
|
||||
color: var(--ar-color);
|
||||
}
|
||||
}
|
||||
8
src/app/components/LottieEditor/LottieEditor.test.ts
Executable file
8
src/app/components/LottieEditor/LottieEditor.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import LottieEditor from "./LottieEditor"
|
||||
|
||||
describe("LottieEditor", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
110
src/app/components/LottieEditor/LottieEditor.tsx
Executable file
110
src/app/components/LottieEditor/LottieEditor.tsx
Executable file
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { useLocation } from "react-router-dom"
|
||||
import { Controls, Player } from "@lottiefiles/react-lottie-player"
|
||||
import { Button } from ".."
|
||||
import { LottieEditorProps } from "../../types/components.interface"
|
||||
import { Animation } from "../../types/lottie"
|
||||
import LottieHelper from "../../utils/lottieHelper"
|
||||
import { ArButtonVariants } from "../../types/enums"
|
||||
import { Helper, Network } from "../../utils"
|
||||
import API_CONFIG from "../../config/api-config"
|
||||
import { ENDPOINTS } from "../../config/constants"
|
||||
import "./LottieEditor.component.scss"
|
||||
|
||||
const LottieEditor = (props: LottieEditorProps): JSX.Element => {
|
||||
const { selectedPresets } = props
|
||||
const location = useLocation()
|
||||
const icon = location.state?.icon
|
||||
const iconStyles = location.state?.iconStyles
|
||||
const [lottieJson, setLottieJson] = useState<Animation>()
|
||||
|
||||
useEffect(() => {
|
||||
const base = LottieHelper.renderStaticImage(icon, iconStyles)
|
||||
if (!selectedPresets || selectedPresets.length === 0) {
|
||||
setLottieJson(base)
|
||||
} else {
|
||||
selectedPresets.forEach(
|
||||
(preset) =>
|
||||
preset.property &&
|
||||
preset.raw &&
|
||||
LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: preset.property,
|
||||
juice: preset.raw,
|
||||
base,
|
||||
}),
|
||||
)
|
||||
setLottieJson(base)
|
||||
}
|
||||
}, [selectedPresets])
|
||||
|
||||
return (
|
||||
<div className="ar-LottieEditor h-100 w-100 overflow-auto position-relative d-flex flex-column">
|
||||
<div className="ar-LottieEditor__header-download py-2 px-3 mb-2 border">
|
||||
<div className="row">
|
||||
<div className="col d-none d-md-flex align-items-center">
|
||||
<Button
|
||||
classes="ms-auto h-100"
|
||||
content="Download"
|
||||
variant={ArButtonVariants.WARNING}
|
||||
splitOptions={[
|
||||
{
|
||||
label: "MP4",
|
||||
onClick: () => {
|
||||
Network.post(
|
||||
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
|
||||
ENDPOINTS.STATIC.LOTTIE.ROOT +
|
||||
ENDPOINTS.STATIC.LOTTIE.TOMP4,
|
||||
{ lottie: lottieJson, name: location.state?.name },
|
||||
).then((res) => {
|
||||
if (res.status === 200) {
|
||||
Helper.download(res.body, location.state?.name + ".mp4")
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "GIF",
|
||||
onClick: () => {
|
||||
Network.post(
|
||||
API_CONFIG.STATIC_HOST[process.env.NODE_ENV] +
|
||||
ENDPOINTS.STATIC.LOTTIE.ROOT +
|
||||
ENDPOINTS.STATIC.LOTTIE.TOGIF,
|
||||
{ lottie: lottieJson, name: location.state?.name },
|
||||
).then((res) => {
|
||||
if (res.status === 200) {
|
||||
Helper.download(res.body, location.state?.name + ".gif")
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "PNG",
|
||||
},
|
||||
{
|
||||
label: "JSON",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ar-LottieEditor__lottie-preview-wrapper d-flex flex-grow-1 justify-content-between">
|
||||
<div className="ar-LottieEditor__lottie-preview py-2 px-3 border flex-center w-100">
|
||||
<Player
|
||||
autoplay
|
||||
loop
|
||||
src={lottieJson as object}
|
||||
style={{ height: "300px", width: "300px" }}
|
||||
>
|
||||
<Controls
|
||||
visible={true}
|
||||
buttons={["play", "repeat", "frame", "debug"]}
|
||||
/>
|
||||
</Player>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LottieEditor
|
||||
3
src/app/components/LottieEditor/index.ts
Executable file
3
src/app/components/LottieEditor/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import LottieEditor from "./LottieEditor"
|
||||
|
||||
export default LottieEditor
|
||||
18
src/app/components/LottieEditorTools/LottieEditorTools.component.scss
Executable file
18
src/app/components/LottieEditorTools/LottieEditorTools.component.scss
Executable file
@@ -0,0 +1,18 @@
|
||||
.ar-LottieEditorTools {
|
||||
width: calc(20rem + 2px + 1rem); // add border and padding
|
||||
|
||||
.ar-LottieEditorTools__header__close-button {
|
||||
font-size: 0.7rem !important;
|
||||
color: rgb(154, 92, 92) !important;
|
||||
transition: color 0.4s;
|
||||
|
||||
&:hover {
|
||||
color: rgb(242, 51, 51) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ar-LottieEditorTools__tab-content {
|
||||
height: calc(100% - 2rem - 1px - 8px); // subtract header height, bottom border and bottom margin
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
8
src/app/components/LottieEditorTools/LottieEditorTools.test.ts
Executable file
8
src/app/components/LottieEditorTools/LottieEditorTools.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import LottieEditorTools from "./LottieEditorTools"
|
||||
|
||||
describe("LottieEditorTools", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
334
src/app/components/LottieEditorTools/LottieEditorTools.tsx
Executable file
334
src/app/components/LottieEditorTools/LottieEditorTools.tsx
Executable file
@@ -0,0 +1,334 @@
|
||||
import { ReactNode, memo, useEffect, useState } from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { setRightPanelContent } from "../../store"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { Accordion, Button, LottieTile, Pillbox, TabBar } from ".."
|
||||
import {
|
||||
AccordionExpansionPanelProps,
|
||||
LottieEditorToolsProps,
|
||||
TabProps,
|
||||
} from "../../types/components.interface"
|
||||
import {
|
||||
ArAccordionStyles,
|
||||
ArAccordionVariants,
|
||||
ArAnimationProperty,
|
||||
ArButtonVariants,
|
||||
ArSizes,
|
||||
ArTabType,
|
||||
} from "../../types/enums"
|
||||
import { PresetDefinition } from "../../types/entity.interface"
|
||||
import LottieHelper from "../../utils/lottieHelper"
|
||||
import {
|
||||
scaleDown,
|
||||
scaleUp,
|
||||
slideInFromTop,
|
||||
slideInFromLeft,
|
||||
fadeOut,
|
||||
slideOutToLeft,
|
||||
slideOutToTop,
|
||||
slideInFromBottom,
|
||||
slideOutToBottom,
|
||||
slideInFromRight,
|
||||
slideOutToRight,
|
||||
fadeIn,
|
||||
rotateClockWise,
|
||||
rotateAntiClockWise,
|
||||
} from "../../static/LottieConfigs/presets"
|
||||
import "./LottieEditorTools.component.scss"
|
||||
|
||||
const editorToolsTabItems = [
|
||||
{
|
||||
label: "Customize",
|
||||
id: uuid(),
|
||||
},
|
||||
{
|
||||
label: "Presets",
|
||||
id: uuid(),
|
||||
isActive: true,
|
||||
},
|
||||
]
|
||||
|
||||
export const presets: Array<{
|
||||
id: string
|
||||
title: string
|
||||
items: Array<PresetDefinition>
|
||||
content: ReactNode
|
||||
}> = [
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Slide In",
|
||||
content: null,
|
||||
items: [
|
||||
{
|
||||
name: "slideinfromleft",
|
||||
from: "left",
|
||||
raw: slideInFromLeft,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideInFromLeft,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "slideinfromright",
|
||||
from: "right",
|
||||
raw: slideInFromRight,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideInFromRight,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "slideinfromtop",
|
||||
from: "top",
|
||||
raw: slideInFromTop,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideInFromTop,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "slideinfrombottom",
|
||||
from: "bottom",
|
||||
raw: slideInFromBottom,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideInFromBottom,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Slide Out",
|
||||
content: null,
|
||||
items: [
|
||||
{
|
||||
name: "slideouttoleft",
|
||||
to: "left",
|
||||
raw: slideOutToLeft,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideOutToLeft,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "slideouttoright",
|
||||
to: "right",
|
||||
raw: slideOutToRight,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideOutToRight,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "slideouttotop",
|
||||
to: "top",
|
||||
raw: slideOutToTop,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideOutToTop,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "slideouttobottom",
|
||||
to: "bottom",
|
||||
raw: slideOutToBottom,
|
||||
property: ArAnimationProperty.POSITION,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.POSITION,
|
||||
juice: slideOutToBottom,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Fade",
|
||||
content: null,
|
||||
items: [
|
||||
{
|
||||
name: "fadein",
|
||||
raw: fadeIn,
|
||||
property: ArAnimationProperty.OPACITY,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.OPACITY,
|
||||
juice: fadeIn,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "fadeout",
|
||||
raw: fadeOut,
|
||||
property: ArAnimationProperty.OPACITY,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.OPACITY,
|
||||
juice: fadeOut,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Rotate",
|
||||
content: null,
|
||||
items: [
|
||||
{
|
||||
name: "rotateClockWise",
|
||||
raw: rotateClockWise,
|
||||
property: ArAnimationProperty.ROTATE,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.ROTATE,
|
||||
juice: rotateClockWise,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "rotateAntiClockWise",
|
||||
raw: rotateAntiClockWise,
|
||||
property: ArAnimationProperty.ROTATE,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.ROTATE,
|
||||
juice: rotateAntiClockWise,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Scale",
|
||||
content: null,
|
||||
items: [
|
||||
{
|
||||
name: "expand",
|
||||
raw: scaleUp,
|
||||
property: ArAnimationProperty.SCALE,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.SCALE,
|
||||
juice: scaleUp,
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "shrink",
|
||||
raw: scaleDown,
|
||||
property: ArAnimationProperty.SCALE,
|
||||
lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
property: ArAnimationProperty.SCALE,
|
||||
juice: scaleDown,
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// name: "slidein",
|
||||
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
// property: "",
|
||||
// juice: slideInFromLeft,
|
||||
// }),
|
||||
// },
|
||||
// {
|
||||
// name: "slidein",
|
||||
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
// property: "",
|
||||
// juice: slideInFromLeft,
|
||||
// }),
|
||||
// },
|
||||
// {
|
||||
// name: "slidein",
|
||||
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
// property: "",
|
||||
// juice: slideInFromLeft,
|
||||
// }),
|
||||
// },
|
||||
// {
|
||||
// name: "slidein",
|
||||
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
// property: "",
|
||||
// juice: slideInFromLeft,
|
||||
// }),
|
||||
// },
|
||||
// {
|
||||
// name: "slidein",
|
||||
// lottie: LottieHelper.injectAnimationInBaseLottieBasic({
|
||||
// property: "",
|
||||
// juice: slideInFromLeft,
|
||||
// }),
|
||||
// },
|
||||
]
|
||||
|
||||
const LottieEditorTools = (props: LottieEditorToolsProps): JSX.Element => {
|
||||
const { setSelectedPresets } = props
|
||||
const [lottieTools, setLottieTools] = useState<
|
||||
Array<AccordionExpansionPanelProps>
|
||||
>([])
|
||||
const dispatch = useAppDispatch()
|
||||
const [activeTab, setActiveTab] = useState<TabProps>(editorToolsTabItems[1])
|
||||
|
||||
useEffect(() => {
|
||||
setLottieTools([
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Effects",
|
||||
content: <Pillbox data={[]} onChange={() => {}} />,
|
||||
},
|
||||
])
|
||||
}, [])
|
||||
|
||||
presets.forEach(
|
||||
(preset) =>
|
||||
(preset.content = (
|
||||
<div className="py-2">
|
||||
{preset.items.map((preset, index) => (
|
||||
<LottieTile
|
||||
key={"lottie-preset-" + index}
|
||||
preset={preset as PresetDefinition}
|
||||
setSelectedPresets={setSelectedPresets}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)),
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="ar-LottieEditorTools h-100 px-2">
|
||||
<h6 className="ar-LottieEditorTools__header border-bottom d-flex">
|
||||
<TabBar
|
||||
classes="ar-LottieEditorTools__header__tab-bar"
|
||||
data={editorToolsTabItems}
|
||||
variant={ArTabType.MINIMAL}
|
||||
onTabSelected={(id, data) => setActiveTab(data)}
|
||||
/>
|
||||
<Button
|
||||
classes="ar-LottieEditorTools__header__close-button ms-auto"
|
||||
size={ArSizes.SMALL}
|
||||
variant={ArButtonVariants.LINK}
|
||||
content="Close"
|
||||
onClick={() => dispatch(setRightPanelContent({ name: "" }))}
|
||||
/>
|
||||
</h6>
|
||||
<div className="ar-LottieEditorTools__tab-content">
|
||||
{activeTab?.id === editorToolsTabItems[1].id ? (
|
||||
<Accordion
|
||||
data={presets}
|
||||
appearance={ArAccordionStyles.STACKED}
|
||||
variant={ArAccordionVariants.LIMITED}
|
||||
firstExpanded
|
||||
/>
|
||||
) : (
|
||||
<Accordion
|
||||
data={lottieTools}
|
||||
appearance={ArAccordionStyles.STACKED}
|
||||
variant={ArAccordionVariants.LIMITED}
|
||||
firstExpanded
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LottieEditorTools
|
||||
3
src/app/components/LottieEditorTools/index.ts
Executable file
3
src/app/components/LottieEditorTools/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import LottieEditorTools from "./LottieEditorTools"
|
||||
|
||||
export default LottieEditorTools
|
||||
3
src/app/components/LottieLayers/LottieLayers.component.scss
Executable file
3
src/app/components/LottieLayers/LottieLayers.component.scss
Executable file
@@ -0,0 +1,3 @@
|
||||
.ar-LottieLayers {
|
||||
|
||||
}
|
||||
8
src/app/components/LottieLayers/LottieLayers.test.ts
Executable file
8
src/app/components/LottieLayers/LottieLayers.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import LottieLayers from "./LottieLayers"
|
||||
|
||||
describe("LottieLayers", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
32
src/app/components/LottieLayers/LottieLayers.tsx
Executable file
32
src/app/components/LottieLayers/LottieLayers.tsx
Executable file
@@ -0,0 +1,32 @@
|
||||
import { LottieLayersProps } from "../../types/components.interface"
|
||||
import { Button, Popover } from ".."
|
||||
import TreeList from "../atoms/TreeList"
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArPopoverSlots,
|
||||
ArPopoverTriggers,
|
||||
ArSizes,
|
||||
} from "../../types/enums"
|
||||
import "./LottieLayers.component.scss"
|
||||
|
||||
const LottieLayers = (props: LottieLayersProps): JSX.Element => {
|
||||
return (
|
||||
<div className="ar-LottieLayers py-1 px-2 w-100">
|
||||
{/* <Popover trigger={ArPopoverTriggers.HOVER}>
|
||||
<Button
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
content="Add"
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
size={ArSizes.XSMALL}
|
||||
/>
|
||||
<div slot={ArPopoverSlots.POPOVER}>
|
||||
Add a new layer or one of the lottie configurations from the panel
|
||||
towards the right
|
||||
</div>
|
||||
</Popover> */}
|
||||
<TreeList data={[]} noSearch />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LottieLayers
|
||||
3
src/app/components/LottieLayers/index.ts
Executable file
3
src/app/components/LottieLayers/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import LottieLayers from "./LottieLayers"
|
||||
|
||||
export default LottieLayers
|
||||
@@ -5,8 +5,20 @@
|
||||
&.collapsed {
|
||||
width: 3.5rem;
|
||||
}
|
||||
& + .ar-Content {
|
||||
// width: 85%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
.ar-Drawer {
|
||||
position: fixed !important;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 75%;
|
||||
z-index: 1;
|
||||
background-color: var(--ar-bg);
|
||||
|
||||
&.collapsed {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ import { ErrorBoundary, SidePanel } from ".."
|
||||
import { ArPlacement } from "../../types/enums"
|
||||
import { MainProps } from "../../types/components.interface"
|
||||
import "./Main.component.scss"
|
||||
import { ReactNode } from "react"
|
||||
|
||||
const Main = (props: MainProps): JSX.Element => {
|
||||
const {
|
||||
contentClasses,
|
||||
drawerContent,
|
||||
mainContent,
|
||||
hideSidepanelCloseButton,
|
||||
rightPanelContent,
|
||||
leftPanelContent,
|
||||
rightPanelHeader,
|
||||
@@ -24,13 +24,16 @@ const Main = (props: MainProps): JSX.Element => {
|
||||
</Drawer>
|
||||
)}
|
||||
<SidePanel
|
||||
key="left-panel"
|
||||
header={leftPanelHeader || "Header Name"}
|
||||
placement={ArPlacement.LEFT}
|
||||
componentName={leftPanelContent as string}
|
||||
componentName={leftPanelContent ? leftPanelContent.name : ""}
|
||||
componentProps={leftPanelContent ? leftPanelContent.props : {}}
|
||||
hideCloseButton={hideSidepanelCloseButton}
|
||||
/>
|
||||
<ErrorBoundary>
|
||||
<Content
|
||||
classes={`flex-center flex-grow-1 position-relative${
|
||||
classes={`flex-center flex-grow-1 position-relative overflow-auto${
|
||||
contentClasses ? " " + contentClasses : ""
|
||||
}`}
|
||||
>
|
||||
@@ -38,8 +41,11 @@ const Main = (props: MainProps): JSX.Element => {
|
||||
</Content>
|
||||
</ErrorBoundary>
|
||||
<SidePanel
|
||||
key="left-panel"
|
||||
header={rightPanelHeader}
|
||||
componentName={rightPanelContent as string}
|
||||
componentName={rightPanelContent ? rightPanelContent.name : ""}
|
||||
componentProps={rightPanelContent ? rightPanelContent.props : {}}
|
||||
hideCloseButton={hideSidepanelCloseButton}
|
||||
/>
|
||||
</main>
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Suspense, lazy } from "react"
|
||||
import { Suspense, lazy, memo } from "react"
|
||||
import { useAppDispatch } from "../../hooks"
|
||||
import { setRightPanelContent } from "../../store"
|
||||
import { Button } from ".."
|
||||
import { SidePanelProps } from "../../types/components.interface"
|
||||
import { ArPlacement } from "../../types/enums"
|
||||
import { ArButtonVariants, ArPlacement } from "../../types/enums"
|
||||
import { ComponentImport } from "../../types/entity.interface"
|
||||
import "./SidePanel.component.scss"
|
||||
|
||||
@@ -8,11 +11,21 @@ const componentImport = {
|
||||
LoginProvider: () => import("../LoginProvider"),
|
||||
FavoritesList: () => import("../FavoritesList"),
|
||||
TaskViewer: () => import("../TaskViewer"),
|
||||
IconEditor: () => import("../IconEditor"),
|
||||
LottieEditorTools: () => import("../LottieEditorTools"),
|
||||
// Add more components and keys as needed
|
||||
}
|
||||
|
||||
const SidePanel = (props: SidePanelProps): JSX.Element | null => {
|
||||
const { isFloating, componentName, componentProps, header, placement } = props
|
||||
const SidePanel = memo((props: SidePanelProps): JSX.Element | null => {
|
||||
const {
|
||||
isFloating,
|
||||
componentName,
|
||||
componentProps,
|
||||
hideCloseButton,
|
||||
header,
|
||||
placement,
|
||||
} = props
|
||||
const dispatch = useAppDispatch()
|
||||
const SelectedComponent =
|
||||
componentName &&
|
||||
lazy(() => (componentImport as ComponentImport)[componentName]())
|
||||
@@ -24,21 +37,29 @@ const SidePanel = (props: SidePanelProps): JSX.Element | null => {
|
||||
? "right"
|
||||
: "left"
|
||||
return SelectedComponent ? (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<div
|
||||
className={`ar-SidePanel h-100 d-flex flex-column${
|
||||
isFloating ? " floating position-absolute top-0" : ""
|
||||
}${" " + placementClass}${componentName ? " " + componentName : ""}`}
|
||||
>
|
||||
{header && (
|
||||
<div className="ar-SidePanel__header row h6 p-3 flex-v-center">
|
||||
<div className="col">{header}</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`ar-SidePanel h-100 d-flex flex-column overflow-auto${
|
||||
isFloating ? " floating position-absolute top-0" : " position-relative"
|
||||
}${" " + placementClass}${componentName ? " " + componentName : ""}`}
|
||||
>
|
||||
{header && (
|
||||
<div className="ar-SidePanel__header row h6 p-3 flex-v-center">
|
||||
<div className="col">{header}</div>
|
||||
</div>
|
||||
)}
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<SelectedComponent {...componentProps} />
|
||||
</div>
|
||||
</Suspense>
|
||||
</Suspense>
|
||||
{!hideCloseButton && (
|
||||
<Button
|
||||
content="Close"
|
||||
classes="position-absolute top-0 end-0"
|
||||
variant={ArButtonVariants.LINK}
|
||||
onClick={() => dispatch(setRightPanelContent({ name: "" }))}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
})
|
||||
|
||||
export default SidePanel
|
||||
|
||||
@@ -54,7 +54,7 @@ const TaskLoginPrompt = (props: TaskLoginPromptProps): JSX.Element => {
|
||||
variant={ArButtonVariants.SUCCESS}
|
||||
postIcon="io5/IoArrowForwardCircle"
|
||||
onClick={() => {
|
||||
dispatch(setRightPanelContent("LoginProvider"))
|
||||
dispatch(setRightPanelContent({ name: "LoginProvider" }))
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
36
src/app/components/atoms/Accordion/Accordion.component.scss
Executable file
36
src/app/components/atoms/Accordion/Accordion.component.scss
Executable file
@@ -0,0 +1,36 @@
|
||||
.ar-Accordion {
|
||||
.ar-AccordionExpansionPanel {
|
||||
.ar-AccordionExpansionPanel__content {
|
||||
max-height: 0;
|
||||
transition: max-height 0.3s;
|
||||
overflow: auto;
|
||||
}
|
||||
&.expanded {
|
||||
.ar-AccordionExpansionPanel__title-expand-icon {
|
||||
transform: rotateZ(90deg);
|
||||
}
|
||||
.ar-AccordionExpansionPanel__content {
|
||||
max-height: 100vh;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) .ar-AccordionExpansionPanel__title, .ar-AccordionExpansionPanel__content {
|
||||
border-bottom: var(--ar-border);
|
||||
}
|
||||
|
||||
.ar-AccordionExpansionPanel__title-expand-icon {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
}
|
||||
|
||||
&.stacked {
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
border: var(--ar-border);
|
||||
.ar-AccordionExpansionPanel {
|
||||
.ar-AccordionExpansionPanel__title {
|
||||
background-color: var(--ar-bg-mild);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/app/components/atoms/Accordion/Accordion.test.ts
Executable file
8
src/app/components/atoms/Accordion/Accordion.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import Accordion from "./Accordion"
|
||||
|
||||
describe("Accordion", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
162
src/app/components/atoms/Accordion/Accordion.tsx
Executable file
162
src/app/components/atoms/Accordion/Accordion.tsx
Executable file
@@ -0,0 +1,162 @@
|
||||
import { ReactNode, useEffect, useState } from "react"
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { LoadableIcon } from ".."
|
||||
import { AccordionExpansionPanelProps, AccordionProps } from ".."
|
||||
import { ArAccordionVariants, ArThemes } from "../../../types/enums"
|
||||
import "./Accordion.component.scss"
|
||||
|
||||
const dummyData = [
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Slide In",
|
||||
content:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut laoreet tellus ante, et gravida massa egestas ac. Maecenas venenatis dui a eros maximus, vitae fringilla metus ultrices. Etiam at lacinia magna. Vestibulum sagittis felis sed diam ullamcorper, vel pharetra quam facilisis. Suspendisse bibendum ante id risus interdum, vel sodales felis egestas. Mauris vitae dui gravida, dictum sem id, accumsan tellus. Aliquam sodales quam efficitur, congue justo a, consectetur ante. Nunc euismod augue ac ante condimentum tincidunt. Quisque bibendum semper velit. Cras sit amet imperdiet dolor, ac aliquet mi. Morbi elementum magna eros, in venenatis nunc laoreet eget. Etiam at lorem suscipit, interdum augue id, scelerisque risus. Suspendisse at nisi lorem. Morbi libero erat, vestibulum at pulvinar at, ultrices ac turpis. Etiam ac leo gravida, semper quam in, tempus ipsum. Phasellus finibus rhoncus cursus. Maecenas dapibus ex ut congue eleifend. Sed luctus consectetur quam, at malesuada enim condimentum non.",
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Slide Out",
|
||||
content: (
|
||||
<input
|
||||
type="text"
|
||||
className="p-3 m-3"
|
||||
title="dummy"
|
||||
placeholder="Accordion content..."
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Fade",
|
||||
content:
|
||||
"Donec varius lorem vel orci aliquam, nec venenatis turpis commodo. Ut ac diam nisi. Integer at lorem ac ligula vehicula porta. Proin tempor lorem a nunc auctor, rhoncus porta elit auctor. Duis imperdiet dictum luctus. Sed eget orci mattis, sagittis quam ac, congue arcu. Praesent blandit mattis sem, eget rutrum neque auctor id. Vestibulum eu augue ut nisl tempus iaculis. Nullam id tortor sed ante tincidunt consectetur et eu odio. Aliquam accumsan turpis ac dictum interdum.",
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Rotate",
|
||||
content:
|
||||
"Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Donec metus augue, tempus vitae neque in, accumsan faucibus velit. Quisque felis lorem, tristique nec finibus consequat, viverra quis nibh. Donec arcu mi, placerat eget pretium non, molestie vitae leo. Nunc cursus id mi quis malesuada.",
|
||||
},
|
||||
{
|
||||
id: uuid(),
|
||||
title: "Scale",
|
||||
content:
|
||||
"Integer dapibus aliquet bibendum. Quisque id lobortis dolor, in bibendum enim. Cras mollis iaculis metus et tincidunt. Maecenas fermentum porttitor faucibus. Suspendisse feugiat ac enim nec vulputate. Aliquam egestas ante ac tempor blandit. Etiam vehicula, dui sed facilisis rhoncus, quam tortor efficitur sapien, eu imperdiet nisi magna et dui. Nulla a justo odio.",
|
||||
},
|
||||
]
|
||||
|
||||
const AccordionExpansionPanel = (
|
||||
props: AccordionExpansionPanelProps,
|
||||
): ReactNode => {
|
||||
const {
|
||||
classes,
|
||||
content,
|
||||
expanded: expandedPre,
|
||||
id,
|
||||
isRemovable,
|
||||
setExpandedId,
|
||||
theme,
|
||||
title,
|
||||
} = props
|
||||
const [expanded, setExpanded] = useState<boolean>()
|
||||
|
||||
useEffect(() => {
|
||||
setExpanded(expandedPre)
|
||||
}, [expandedPre])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-AccordionExpansionPanel${classes ? " " + classes : ""}${
|
||||
expanded ? " expanded" : ""
|
||||
}`}
|
||||
id={id}
|
||||
>
|
||||
<div
|
||||
className="ar-AccordionExpansionPanel__title flex-v-center px-2 py-1 cursor-pointer"
|
||||
onClick={() => {
|
||||
setExpanded(!expanded)
|
||||
setExpandedId && setExpandedId(id)
|
||||
}}
|
||||
>
|
||||
<span className="ar-AccordionExpansionPanel__title-expand-icon p-2">
|
||||
<LoadableIcon
|
||||
color={ArThemes.DARK1 === theme ? "white" : "black"}
|
||||
icon="md.MdKeyboardArrowRight"
|
||||
/>
|
||||
</span>
|
||||
{title}
|
||||
{isRemovable && (
|
||||
<LoadableIcon
|
||||
color={ArThemes.DARK1 === theme ? "white" : "black"}
|
||||
classes="ms-auto"
|
||||
icon="md.MdClose"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="ar-AccordionExpansionPanel__content">
|
||||
{typeof content === "string" ? (
|
||||
<div className="p-3">{content}</div>
|
||||
) : (
|
||||
content
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Accordion = (props: AccordionProps): JSX.Element => {
|
||||
const {
|
||||
appearance,
|
||||
classes,
|
||||
data,
|
||||
demo,
|
||||
firstExpanded,
|
||||
isRemovable,
|
||||
theme,
|
||||
variant,
|
||||
} = props
|
||||
const [expanded, setExpanded] = useState<string>()
|
||||
const [localData, setLocalData] =
|
||||
useState<Array<AccordionExpansionPanelProps>>()
|
||||
const useData = data || (demo && dummyData)
|
||||
|
||||
useEffect(() => {
|
||||
if (useData) {
|
||||
if (firstExpanded) {
|
||||
useData[0] && (useData[0].expanded = true)
|
||||
setExpanded(useData[0].id)
|
||||
}
|
||||
setLocalData(useData)
|
||||
}
|
||||
}, [useData, firstExpanded])
|
||||
|
||||
useEffect(() => {
|
||||
if (variant === ArAccordionVariants.LIMITED) {
|
||||
const dataClone = [...useData]
|
||||
dataClone.forEach((item) => (item.expanded = expanded === item.id))
|
||||
setLocalData(dataClone)
|
||||
}
|
||||
}, [expanded, variant, useData])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-Accordion${classes ? " " + classes : ""}${
|
||||
appearance ? " " + appearance : ""
|
||||
}`}
|
||||
>
|
||||
{localData?.map((item, index) => {
|
||||
item.setExpandedId = setExpanded
|
||||
item.isRemovable =
|
||||
item.isRemovable !== undefined ? item.isRemovable : !!isRemovable
|
||||
return (
|
||||
<AccordionExpansionPanel
|
||||
key={"accordion-expansion-panel-" + index}
|
||||
theme={theme}
|
||||
{...item}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Accordion
|
||||
3
src/app/components/atoms/Accordion/index.ts
Executable file
3
src/app/components/atoms/Accordion/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import Accordion from "./Accordion"
|
||||
|
||||
export default Accordion
|
||||
@@ -0,0 +1,3 @@
|
||||
.ar-AdvancedColorPicker {
|
||||
|
||||
}
|
||||
8
src/app/components/atoms/AdvancedColorPicker/AdvancedColorPicker.test.ts
Executable file
8
src/app/components/atoms/AdvancedColorPicker/AdvancedColorPicker.test.ts
Executable file
@@ -0,0 +1,8 @@
|
||||
import React from "react"
|
||||
import AdvancedColorPicker from "./AdvancedColorPicker"
|
||||
|
||||
describe("AdvancedColorPicker", () => {
|
||||
it("renders without error", () => {
|
||||
|
||||
})
|
||||
})
|
||||
117
src/app/components/atoms/AdvancedColorPicker/AdvancedColorPicker.tsx
Executable file
117
src/app/components/atoms/AdvancedColorPicker/AdvancedColorPicker.tsx
Executable file
@@ -0,0 +1,117 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { Slider } from "../.."
|
||||
import { AdvancedColorPickerProps } from ".."
|
||||
import { DomHelper } from "../../../utils"
|
||||
import colorPickerConfig from "../../../config/ColorPicker"
|
||||
import imageColorMap from "../../../static/images/img_colormap.gif"
|
||||
import "./AdvancedColorPicker.component.scss"
|
||||
|
||||
const AdvancedColorPicker = (props: AdvancedColorPickerProps): JSX.Element => {
|
||||
const { demo, displaySample, id, onColorSelect, title } = props
|
||||
const [color, setColor] = useState<string>()
|
||||
const [hue, setHue] = useState<number>()
|
||||
const [saturation, setSaturation] = useState<number>()
|
||||
const [lightness, setLightness] = useState<number>()
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
hue !== undefined &&
|
||||
saturation !== undefined &&
|
||||
lightness !== undefined
|
||||
) {
|
||||
const colorInHex = DomHelper.hslToHex(hue, saturation, lightness)
|
||||
setColor(colorInHex)
|
||||
onColorSelect && onColorSelect(colorInHex)
|
||||
}
|
||||
}, [hue, saturation, lightness])
|
||||
|
||||
const getColorNameHsl = () => {
|
||||
return hue !== undefined &&
|
||||
saturation !== undefined &&
|
||||
lightness !== undefined
|
||||
? "hsl(" +
|
||||
Math.round(hue * 255) +
|
||||
", " +
|
||||
Math.round(saturation * 100) +
|
||||
"%, " +
|
||||
Math.round(lightness * 100) +
|
||||
"%)"
|
||||
: ""
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ar-AdvancedColorPicker mb-4">
|
||||
{title && (
|
||||
<strong className="border-bottom mb-3 d-flex pb-2">{title}</strong>
|
||||
)}
|
||||
<img
|
||||
className="flex-h-center"
|
||||
src={imageColorMap}
|
||||
useMap={`#colormap-${id}`}
|
||||
alt={`colormap-${id}`}
|
||||
/>
|
||||
<map id={`colormap-${id}`} name={`colormap-${id}`}>
|
||||
{colorPickerConfig.map((config, index) => {
|
||||
return (
|
||||
<area
|
||||
key={id + "-area-item-" + index}
|
||||
className="cursor-pointer"
|
||||
shape="poly"
|
||||
coords={config.coord}
|
||||
onClick={() => {
|
||||
setColor(config.color)
|
||||
const [h, s, l] = DomHelper.hexToHsl(config.color, true)
|
||||
setHue(h as number)
|
||||
setSaturation(s as number)
|
||||
setLightness(l as number)
|
||||
// onColorSelect && onColorSelect(config.color)
|
||||
}}
|
||||
alt={config.color}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</map>
|
||||
{displaySample && color && (
|
||||
<div className="my-4 row">
|
||||
<div
|
||||
className="col-12 mb-3"
|
||||
style={{ backgroundColor: getColorNameHsl(), height: "1rem" }}
|
||||
title="Sample"
|
||||
/>
|
||||
<strong className="col-12">{getColorNameHsl()}</strong>
|
||||
</div>
|
||||
)}
|
||||
<div className="ar-AdvancedColorPicker__hsl-editor mt-4 mb-2">
|
||||
<Slider
|
||||
label="Hue"
|
||||
min={0}
|
||||
max={255}
|
||||
onChange={(e) => setHue(parseInt(e.target.value) / 255)}
|
||||
withManual
|
||||
/>
|
||||
<Slider
|
||||
label="Saturation"
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={(e) => setSaturation(parseInt(e.target.value) / 100)}
|
||||
withManual
|
||||
/>
|
||||
<Slider
|
||||
label="Lightness"
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={(e) => setLightness(parseInt(e.target.value) / 100)}
|
||||
withManual
|
||||
/>
|
||||
</div>
|
||||
{demo && (
|
||||
<div
|
||||
className="ar-AdvancedColorPicker__demo"
|
||||
style={{ width: "5rem", height: "2rem", backgroundColor: color }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AdvancedColorPicker
|
||||
3
src/app/components/atoms/AdvancedColorPicker/index.ts
Executable file
3
src/app/components/atoms/AdvancedColorPicker/index.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import AdvancedColorPicker from "./AdvancedColorPicker"
|
||||
|
||||
export default AdvancedColorPicker
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import Icon from "../Icon"
|
||||
import { AlertProps } from "../../../types/components.interface"
|
||||
import { AlertProps } from ".."
|
||||
import { ICON_ROOT } from "../../../config/constants"
|
||||
import "./Alert.component.scss"
|
||||
import LoadableIcon from "../LoadableIcon"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useEffect, useRef } from "react"
|
||||
import * as d3 from "d3"
|
||||
import { ArVizProps } from "../../../types/components.interface"
|
||||
import { ArVizProps } from ".."
|
||||
import { ArVisualizationTypes } from "../../../types/enums"
|
||||
import { generateBubbleChart } from "../../../utils/chartGenerators"
|
||||
import "./ArViz.component.scss"
|
||||
import BubbleChart from "../BubbleChart"
|
||||
import { ObjectType } from "../../../types/types"
|
||||
import "./ArViz.component.scss"
|
||||
|
||||
const dataDummy = [
|
||||
{ source: "Item 1", val: 1350, color: "#C9D6DF" },
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Icon } from "../.."
|
||||
import { ICON_ROOT } from "../../../config/constants"
|
||||
import { BadgeProps } from "../../../types/components.interface"
|
||||
import { BadgeProps } from ".."
|
||||
import "./Badge.component.scss"
|
||||
|
||||
const Badge = (props: BadgeProps): JSX.Element => {
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import {
|
||||
BreadCrumbData,
|
||||
BreadcrumbProps,
|
||||
} from "../../../types/components.interface"
|
||||
import "./Breadcrumb.component.scss"
|
||||
import Button from "../Button"
|
||||
import { BreadcrumbProps, Button } from ".."
|
||||
import { BreadCrumbData } from "../../../types/entity.interface"
|
||||
import { ArButtonVariants, ArSizes } from "../../../types/enums"
|
||||
import "./Breadcrumb.component.scss"
|
||||
|
||||
const Breadcrumb = (props: BreadcrumbProps): JSX.Element => {
|
||||
const { classes, data, separator, size, onClick } = props
|
||||
@@ -29,6 +26,7 @@ const Breadcrumb = (props: BreadcrumbProps): JSX.Element => {
|
||||
{nodes.map((node: BreadCrumbData, index: number) => (
|
||||
<>
|
||||
<Button
|
||||
key={"breadcrumb-part-" + index}
|
||||
classes="d-inline-block"
|
||||
content={node.label}
|
||||
size={size || ArSizes.SMALL}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { v4 as uuid } from "uuid"
|
||||
import { Alert, Link, LoadableIcon } from "../.."
|
||||
import { BrowserIncompatibilityProps } from "../../../types/components.interface"
|
||||
import { BrowserIncompatibilityProps } from ".."
|
||||
import Helper from "../../../utils/helper"
|
||||
import "./BrowserIncompatibility.component.scss"
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useRef } from "react"
|
||||
import Highcharts from "highcharts"
|
||||
import HighchartsMore from "highcharts/highcharts-more"
|
||||
import { BubbleChartProps } from "../../../types/components.interface"
|
||||
import "./BubbleChart.component.scss"
|
||||
import { BubbleChartProps } from ".."
|
||||
import { ObjectType } from "../../../types/types"
|
||||
import "./BubbleChart.component.scss"
|
||||
|
||||
HighchartsMore(Highcharts)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react"
|
||||
import d3 from "d3"
|
||||
import { BubbleVizProps } from "../../../types/components.interface"
|
||||
import { BubbleVizProps } from ".."
|
||||
import "./BubbleViz.component.scss"
|
||||
|
||||
const data = [
|
||||
|
||||
@@ -3,11 +3,17 @@
|
||||
padding: 0.5rem 0.9rem;
|
||||
border-radius: 0.2rem;
|
||||
background-color: var(--ar-color-primary-faded);
|
||||
color: var(--ar-color-font-invert);
|
||||
color: var(--ar-white);
|
||||
border: 1px solid var(--ar-color-primary);
|
||||
transition: all 0.5s;
|
||||
font-size: 1rem;
|
||||
|
||||
&:disabled, &.ar-secondary:disabled {
|
||||
color: var(--ar-color-disabled);
|
||||
border-color: var(--ar-color-disabled);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.ar-Button__icon {
|
||||
border-right: var(--ar-bg);
|
||||
}
|
||||
@@ -42,6 +48,7 @@
|
||||
margin-left: -0.0625rem;
|
||||
border-bottom-right-radius: 0.125rem;
|
||||
border-top-right-radius: 0.125rem;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.ar-primary {
|
||||
@@ -98,6 +105,15 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.ar-warning {
|
||||
background-color: var(--ar-color-font-warning-faded);
|
||||
color: var(--ar-color-font-invert);
|
||||
border-color: var(--ar-color-font-warning);
|
||||
&:hover {
|
||||
background-color: var(--ar-color-font-warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +158,9 @@
|
||||
background-color: var(--ar-color-font-warning-faded);
|
||||
color: var(--ar-color-font-invert);
|
||||
border-color: var(--ar-color-font-warning);
|
||||
&+.ar-Popover {
|
||||
--ar-color-highlight-2: var(--ar-color-font-warning);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--ar-color-font-warning);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { List, Popover, LoadableIcon } from "../.."
|
||||
import { ButtonProps } from "../../../types/components.interface"
|
||||
import { ButtonProps } from ".."
|
||||
import {
|
||||
ArButtonTypes,
|
||||
ArButtonVariants,
|
||||
@@ -62,7 +62,11 @@ const Button = (props: ButtonProps) => {
|
||||
{content || (!preIcon && !postIcon ? "Button" : "")}
|
||||
{postIcon && (
|
||||
<span className="ar-Button__icon ms-2">
|
||||
<LoadableIcon icon={postIcon} color={setColor} />
|
||||
<LoadableIcon
|
||||
icon={postIcon}
|
||||
color={setColor}
|
||||
skipPathColorFill={true}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
@@ -76,7 +80,19 @@ const Button = (props: ButtonProps) => {
|
||||
title="Extra Button Options"
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
>
|
||||
<LoadableIcon icon="io/IoIosArrowDown" />
|
||||
<LoadableIcon
|
||||
icon="io/IoIosArrowDown"
|
||||
color={
|
||||
!variant ||
|
||||
[
|
||||
ArButtonVariants.LINK,
|
||||
ArButtonVariants.LINKHOVEREFFECT,
|
||||
ArButtonVariants.LINKNATIVE,
|
||||
].indexOf(variant) > -1
|
||||
? "black"
|
||||
: "white"
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
<List data={splitOptions} slot={ArPopoverSlots.POPOVER} />
|
||||
</Popover>
|
||||
|
||||
@@ -1,3 +1,53 @@
|
||||
@use "./MonthSelector.component.scss";
|
||||
@use "./JustCalendar.component.scss";
|
||||
@use "./MonthNavigator.component.scss";
|
||||
|
||||
|
||||
.ar-Calendar {
|
||||
|
||||
--ar-footer-height: 3rem;
|
||||
&.is-mini {
|
||||
--ar-footer-height: 2rem;
|
||||
width: calc(2.5rem * 7 + 1rem) !important;
|
||||
height: calc(3rem + (2.5rem * 6) + 3rem + 1px) !important;
|
||||
}
|
||||
|
||||
&__cal-event-decade-month-year-selector {
|
||||
|
||||
& > * {
|
||||
transition: left 0.3s;
|
||||
}
|
||||
|
||||
.ar-MonthSelector, .ar-EventForm {
|
||||
left: 100%;
|
||||
}
|
||||
.ar-JustCalendar {
|
||||
left: 0;
|
||||
}
|
||||
&.to-month-year, &.to-event-form {
|
||||
.ar-JustCalendar {
|
||||
left: -100%
|
||||
}
|
||||
}
|
||||
&.to-month-year {
|
||||
.ar-MonthSelector {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
&.to-event-form {
|
||||
.ar-EventForm {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ar-Calendar__cal-event-decade-month-year-selector {
|
||||
height: calc(100% - var(--ar-footer-height));
|
||||
}
|
||||
.ar-Calendar__footer {
|
||||
transition: height 0.3s;
|
||||
height: var(--ar-footer-height);
|
||||
|
||||
.ar-Calendar__status {
|
||||
color: var(--ar-color-obscure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,279 @@
|
||||
import React from "react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { Button, Modal } from "../.."
|
||||
import MonthSelector from "./MonthSelector"
|
||||
import JustCalendar from "./JustCalendar"
|
||||
import EventForm from "./EventForm"
|
||||
import MonthNavigator from "./MonthNavigator"
|
||||
import { CalendarProps } from ".."
|
||||
import {
|
||||
ArButtonVariants,
|
||||
ArCalViews,
|
||||
ArMonthSelectorViews,
|
||||
ArSizes,
|
||||
} from "../../../types/enums"
|
||||
import { CalendarDate } from "../../../types/entity.interface"
|
||||
import CalHelper from "./helper"
|
||||
import "./Calendar.component.scss"
|
||||
|
||||
interface CalendarProps {}
|
||||
const calHelper = new CalHelper({ siblingMonths: true, weekStart: 1 })
|
||||
|
||||
const Calendar = (props: any): JSX.Element => {
|
||||
return <div className="ar-Calendar">In Component Calendar</div>
|
||||
const today = new Date()
|
||||
|
||||
const Calendar = (props: CalendarProps): JSX.Element => {
|
||||
const {
|
||||
allowEventSetting,
|
||||
classes,
|
||||
events,
|
||||
customDayEventSetter,
|
||||
hasTimeControls,
|
||||
isSingleSelect,
|
||||
maxDate,
|
||||
minDate,
|
||||
miniMode,
|
||||
onDateSelected,
|
||||
startDate,
|
||||
endDate,
|
||||
theme,
|
||||
} = props
|
||||
const [calendar, setCalendar] = useState<Array<CalendarDate | false>>()
|
||||
const [refDate, setRefDate] = useState<Date>(today)
|
||||
const [startDateLocal, setStartDate] = useState<CalendarDate | null>()
|
||||
const [endDateLocal, setEndDate] = useState<CalendarDate | null>()
|
||||
const [eventDate, setEventDate] = useState<CalendarDate | null>()
|
||||
const [hovered, setHovered] = useState<CalendarDate | null>()
|
||||
const [currentView, setCurrentView] = useState<ArCalViews>(
|
||||
ArCalViews.CALENDAR,
|
||||
)
|
||||
const [currentMonthNavView, setCurrentMonthNavView] =
|
||||
useState<ArMonthSelectorViews | null>()
|
||||
|
||||
const currentDate = (startDateLocal || {
|
||||
day: today.getDay(),
|
||||
month: today.getMonth(),
|
||||
year: today.getFullYear(),
|
||||
}) as CalendarDate
|
||||
|
||||
const [currentDecade, setCurrentDecade] = useState<number | undefined>(
|
||||
currentDate?.year && Math.floor(currentDate.year / 10) * 10,
|
||||
)
|
||||
const [currentYear, setCurrentYear] = useState<number | undefined>(
|
||||
currentDate?.year,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (startDate) {
|
||||
setStartDate({
|
||||
year: (startDate as Date).getFullYear(),
|
||||
month: (startDate as Date).getMonth(),
|
||||
day: (startDate as Date).getDay(),
|
||||
})
|
||||
}
|
||||
}, [startDate])
|
||||
|
||||
useEffect(() => {
|
||||
if (endDate) {
|
||||
setEndDate({
|
||||
year: (endDate as Date).getFullYear(),
|
||||
month: (endDate as Date).getMonth(),
|
||||
day: (endDate as Date).getDay(),
|
||||
})
|
||||
}
|
||||
}, [endDate])
|
||||
|
||||
useEffect(() => {
|
||||
if (refDate) {
|
||||
const year = (refDate as Date).getFullYear()
|
||||
const month = (refDate as Date).getMonth()
|
||||
const calendar = calHelper.getCalendar(year, month)
|
||||
setCalendar(calendar)
|
||||
}
|
||||
}, [refDate])
|
||||
|
||||
const onDateSelectedLocal = (date: CalendarDate, isSpecialOp?: boolean) => {
|
||||
if (!startDateLocal || isSingleSelect) {
|
||||
setStartDate(date)
|
||||
calHelper.setStartDate(date)
|
||||
onDateSelected &&
|
||||
onDateSelected(isSingleSelect ? date : { startDate: date })
|
||||
return
|
||||
}
|
||||
if (startDateLocal && endDateLocal) {
|
||||
if (
|
||||
CalHelper.compare(startDateLocal, date) === -1 &&
|
||||
CalHelper.compare(date, endDateLocal) === -1
|
||||
) {
|
||||
calHelper.setEndDate(date)
|
||||
setEndDate(date)
|
||||
onDateSelected &&
|
||||
onDateSelected(
|
||||
isSingleSelect
|
||||
? date
|
||||
: { startDate: startDateLocal, endDate: date },
|
||||
)
|
||||
} else {
|
||||
calHelper.setEndDate(null)
|
||||
setEndDate(null)
|
||||
calHelper.setStartDate(date)
|
||||
setStartDate(date)
|
||||
onDateSelected &&
|
||||
onDateSelected(
|
||||
isSingleSelect ? date : { startDate: date, endDate: null },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (CalHelper.compare(date, startDateLocal) === -1) {
|
||||
setEndDate(startDateLocal)
|
||||
calHelper.setEndDate(startDateLocal)
|
||||
setStartDate(date)
|
||||
calHelper.setStartDate(date)
|
||||
onDateSelected &&
|
||||
onDateSelected(
|
||||
isSingleSelect
|
||||
? date
|
||||
: { startDate: date, endDate: startDateLocal },
|
||||
)
|
||||
} else {
|
||||
setEndDate(date)
|
||||
calHelper.setEndDate(date)
|
||||
onDateSelected &&
|
||||
onDateSelected(
|
||||
isSingleSelect
|
||||
? date
|
||||
: { startDate: startDateLocal, endDate: date },
|
||||
)
|
||||
}
|
||||
setHovered(null)
|
||||
}
|
||||
if (isSpecialOp && allowEventSetting) {
|
||||
setEventDate(date)
|
||||
setCurrentView(ArCalViews.EVENT_FORM)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-Calendar w-100 h-100 d-flex flex-column${
|
||||
hasTimeControls ? " has-time-controls" : ""
|
||||
}${classes ? " " + classes : ""}`}
|
||||
>
|
||||
{refDate && (
|
||||
<MonthNavigator
|
||||
currentDecade={currentDecade}
|
||||
currentMonthNavView={currentMonthNavView}
|
||||
currentView={currentView}
|
||||
currentYear={currentYear}
|
||||
miniMode={miniMode}
|
||||
refDate={refDate}
|
||||
setCurrentDecade={setCurrentDecade}
|
||||
setCurrentMonthNavView={setCurrentMonthNavView}
|
||||
setCurrentView={setCurrentView}
|
||||
setCurrentYear={setCurrentYear}
|
||||
setRefDate={setRefDate}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`ar-Calendar__cal-event-decade-month-year-selector d-flex flex-1 position-relative overflow-hidden border-bottom${
|
||||
currentView === ArCalViews.MONTH_YEAR_SELECTOR ? " to-month-year" : ""
|
||||
}${currentView === ArCalViews.EVENT_FORM ? " to-event-form" : ""}${
|
||||
miniMode ? " is-mini" : ""
|
||||
}`}
|
||||
>
|
||||
<JustCalendar
|
||||
allowEventSetting={allowEventSetting}
|
||||
calHelper={calHelper}
|
||||
calendar={calendar}
|
||||
endDate={endDateLocal as CalendarDate}
|
||||
hovered={hovered as CalendarDate}
|
||||
isSingleSelect={isSingleSelect}
|
||||
miniMode={miniMode}
|
||||
onDateSelected={onDateSelectedLocal}
|
||||
setHovered={setHovered}
|
||||
startDate={startDateLocal as CalendarDate}
|
||||
/>
|
||||
<MonthSelector
|
||||
currentDecade={currentDecade}
|
||||
currentMonthNavView={currentMonthNavView}
|
||||
currentView={currentView}
|
||||
currentYear={currentYear}
|
||||
miniMode={miniMode}
|
||||
onMonthSelect={(date: Date) => {
|
||||
setCurrentView(ArCalViews.CALENDAR)
|
||||
setRefDate(date)
|
||||
}}
|
||||
setCurrentDecade={setCurrentDecade}
|
||||
setCurrentMonthNavView={setCurrentMonthNavView}
|
||||
setCurrentView={setCurrentView}
|
||||
setCurrentYear={setCurrentYear}
|
||||
/>
|
||||
{eventDate && (
|
||||
<EventForm
|
||||
events={events}
|
||||
miniMode={miniMode}
|
||||
selectedDate={eventDate as CalendarDate}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`ar-Calendar__footer flex-v-center${
|
||||
miniMode ? " p-2" : " px-3 py-2"
|
||||
}${
|
||||
currentView === ArCalViews.EVENT_FORM ? " h-0 overflow-hidden" : ""
|
||||
}`}
|
||||
>
|
||||
{startDateLocal && !miniMode && (
|
||||
<div className="ar-Calendar__status justify-self-start">
|
||||
<span className="fw-bold">
|
||||
{CalHelper.toString(startDateLocal)}
|
||||
{endDateLocal && " - " + CalHelper.toString(endDateLocal)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="ar-Calendar__controls ms-auto d-flex">
|
||||
<Button
|
||||
content="Clear"
|
||||
classes={miniMode ? "me-2" : "me-3"}
|
||||
size={miniMode ? ArSizes.XSMALL : ArSizes.SMALL}
|
||||
variant={ArButtonVariants.SECONDARY}
|
||||
onClick={() => {
|
||||
setStartDate(null)
|
||||
setEndDate(null)
|
||||
calHelper.setStartDate(null)
|
||||
calHelper.setEndDate(null)
|
||||
}}
|
||||
disabled={!startDateLocal}
|
||||
/>
|
||||
<Button
|
||||
content="Today"
|
||||
size={miniMode ? ArSizes.XSMALL : ArSizes.SMALL}
|
||||
onClick={() => {
|
||||
const date = today
|
||||
const calDate = {
|
||||
day: date.getDay(),
|
||||
month: date.getMonth(),
|
||||
year: date.getFullYear(),
|
||||
}
|
||||
setStartDate(calDate)
|
||||
setEndDate(null)
|
||||
calHelper.setStartDate(calDate)
|
||||
calHelper.setEndDate(null)
|
||||
setRefDate(date)
|
||||
setCurrentDecade(Math.floor(calDate.year / 10) * 10)
|
||||
setCurrentYear(calDate.year)
|
||||
setCurrentMonthNavView(null)
|
||||
setCurrentView(ArCalViews.CALENDAR)
|
||||
onDateSelected &&
|
||||
onDateSelected(
|
||||
isSingleSelect
|
||||
? calDate
|
||||
: { startDate: calDate, endDate: null },
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Calendar
|
||||
|
||||
21
src/app/components/atoms/Calendar/EventForm.tsx
Normal file
21
src/app/components/atoms/Calendar/EventForm.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { EventFormProps } from ".."
|
||||
import { pad } from "./helper"
|
||||
|
||||
const EventForm = (props: EventFormProps) => {
|
||||
const { events, miniMode, selectedDate } = props
|
||||
return (
|
||||
<div className="ar-EventForm position-absolute h-100 w-100">
|
||||
<div className="ar-EventForm__header border-bottom py-2 px-3">
|
||||
<span className="fw-bold">
|
||||
{pad(selectedDate.day) +
|
||||
" - " +
|
||||
pad(selectedDate.month + 1) +
|
||||
" - " +
|
||||
selectedDate.year}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default EventForm
|
||||
@@ -0,0 +1,47 @@
|
||||
.ar-JustCalendar {
|
||||
.ar-JustCalendar__day {
|
||||
width: calc(100% / 7);
|
||||
.ar-JustCalendar__day__date {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:not(.ar-JustCalendar__weekday):hover {
|
||||
background-color: var(--ar-bg-hover);
|
||||
.ar-JustCalendar__day__date {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
&.ar-JustCalendar__sibling-month {
|
||||
color: var(--ar-color-disabled);
|
||||
&.selected {
|
||||
.ar-JustCalendar__day__date {
|
||||
color: var(--ar-color-disabled-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.selected {
|
||||
.ar-JustCalendar__day__date {
|
||||
background-color: var(--ar-bg-selected-4-dark);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&.selectable {
|
||||
background-color: var(--ar-bg-hover);
|
||||
}
|
||||
&__badge {
|
||||
top: 0.1rem;
|
||||
right: 0;
|
||||
}
|
||||
// &.today {
|
||||
// background-color: var(--ar-bg-selected-3-faded);
|
||||
// }
|
||||
}
|
||||
&.is-mini {
|
||||
width: 18rem;
|
||||
.ar-JustCalendar__day {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
src/app/components/atoms/Calendar/JustCalendar.tsx
Normal file
127
src/app/components/atoms/Calendar/JustCalendar.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import { Badge, Button, Popover } from "../.."
|
||||
import { JustCalendarProps } from ".."
|
||||
import { CalendarDate } from "../../../types/entity.interface"
|
||||
import { ArBadgeType, ArPopoverSlots, ArSizes } from "../../../types/enums"
|
||||
|
||||
const weekDays = [
|
||||
{
|
||||
name: "Monday",
|
||||
short: "Mo",
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
name: "Tuesday",
|
||||
short: "Tu",
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
name: "Wednesday",
|
||||
short: "We",
|
||||
index: 2,
|
||||
},
|
||||
{
|
||||
name: "Thursday",
|
||||
short: "Th",
|
||||
index: 3,
|
||||
},
|
||||
{
|
||||
name: "Friday",
|
||||
short: "Fr",
|
||||
index: 4,
|
||||
},
|
||||
{
|
||||
name: "Saturday",
|
||||
short: "Sa",
|
||||
index: 5,
|
||||
},
|
||||
{
|
||||
name: "Sunday",
|
||||
short: "Su",
|
||||
index: 6,
|
||||
},
|
||||
]
|
||||
const areSame = (date: Date | CalendarDate, calDate: CalendarDate) => {
|
||||
if ("day" in date) {
|
||||
date = date as CalendarDate
|
||||
return (
|
||||
calDate.day === date.day &&
|
||||
calDate.month === date.month &&
|
||||
calDate.year === date.year
|
||||
)
|
||||
} else {
|
||||
date = date as Date
|
||||
return (
|
||||
calDate.day === date.getDate() &&
|
||||
calDate.month === date.getMonth() &&
|
||||
calDate.year === date.getFullYear()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const JustCalendar = (props: JustCalendarProps) => {
|
||||
const {
|
||||
allowEventSetting,
|
||||
calendar,
|
||||
calHelper,
|
||||
endDate,
|
||||
hovered,
|
||||
isSingleSelect,
|
||||
miniMode,
|
||||
setHovered,
|
||||
onDateSelected,
|
||||
startDate,
|
||||
} = props
|
||||
return (
|
||||
<ul
|
||||
className={`ar-JustCalendar list-unstyled d-flex flex-wrap flex-1 position-relative mb-0 ${
|
||||
miniMode ? "is-mini p-1" : "p-3"
|
||||
}`}
|
||||
onMouseLeave={() => setHovered(null)}
|
||||
>
|
||||
{weekDays.map((weekday) => (
|
||||
<li className="ar-JustCalendar__day ar-Calendar__weekday fw-bold flex-center">
|
||||
<span className="ar-JustCalendar__day__short">{weekday.short}</span>
|
||||
</li>
|
||||
))}
|
||||
{calendar?.map((date) => {
|
||||
const isToday = date && areSame(new Date(), date)
|
||||
const liElement = date ? (
|
||||
<li
|
||||
slot={ArPopoverSlots.ANCHOR}
|
||||
className={`ar-JustCalendar__day flex-center cursor-pointer${
|
||||
date.siblingMonth ? " ar-JustCalendar__sibling-month" : ""
|
||||
}${calHelper?.isDateSelected(date) ? " selected" : ""}${
|
||||
isToday ? " today" : ""
|
||||
}${calHelper?.isDateSelected(date, hovered) ? " selectable" : ""}`}
|
||||
onClick={() => onDateSelected(date)}
|
||||
onMouseOver={() => {
|
||||
if (
|
||||
!!calHelper?.startDate &&
|
||||
!calHelper.endDate &&
|
||||
!isSingleSelect
|
||||
) {
|
||||
setHovered(date)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="ar-JustCalendar__day__date flex-center position-relative">
|
||||
{isToday && (
|
||||
<Badge
|
||||
type={ArBadgeType.COMPLETE}
|
||||
size={ArSizes.SMALL}
|
||||
classes="ar-JustCalendar__day__badge position-absolute"
|
||||
/>
|
||||
)}
|
||||
{date.day}
|
||||
</span>
|
||||
</li>
|
||||
) : (
|
||||
<li />
|
||||
)
|
||||
return liElement
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default JustCalendar
|
||||
@@ -0,0 +1,72 @@
|
||||
.ar-MonthNavigator {
|
||||
&__month {
|
||||
font-weight: bold;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
&__nav-button {
|
||||
transition: width 0.3s;
|
||||
width: calc(100% / 5);
|
||||
padding: 0.5rem 0;
|
||||
&:hover {
|
||||
background-color: var(--ar-bg-hover);
|
||||
}
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
background-color: var(--ar-color-disabled-3);
|
||||
color: var(--ar-color-disabled-2);
|
||||
}
|
||||
}
|
||||
&.is-mini {
|
||||
.ar-MonthNavigator {
|
||||
&__month {
|
||||
width: 48%;
|
||||
}
|
||||
&__nav-button:not(.ar-MonthNavigator__month) {
|
||||
width: 13%;
|
||||
}
|
||||
}
|
||||
&.to-month-year {
|
||||
.ar-MonthNavigator {
|
||||
&__month {
|
||||
width: 70%;
|
||||
}
|
||||
&__nav-button:first-child, &__nav-button:last-child {
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
// &.to-month-selector {
|
||||
// .ar-MonthNavigator {
|
||||
// &__nav-button {
|
||||
// width: 100%;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
&.to-month-year {
|
||||
.ar-MonthNavigator {
|
||||
&__nav-button {
|
||||
width: calc(100% / 3);
|
||||
}
|
||||
&__nav-button:first-child, &__nav-button:last-child {
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
// &.to-month-selector {
|
||||
// .ar-MonthNavigator {
|
||||
// &__nav-button {
|
||||
// width: 100%;
|
||||
// }
|
||||
// &__nav-button:nth-child(2), &__nav-button:nth-child(4) {
|
||||
// width: 0;
|
||||
// overflow: hidden;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
165
src/app/components/atoms/Calendar/MonthNavigator.tsx
Normal file
165
src/app/components/atoms/Calendar/MonthNavigator.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import LoadableIcon from "../LoadableIcon"
|
||||
import { MonthNavigatorProps } from ".."
|
||||
import {
|
||||
ArCalViews,
|
||||
ArMonthSelectorViews,
|
||||
ArThemes,
|
||||
} from "../../../types/enums"
|
||||
import CalHelper from "./helper"
|
||||
import { MONTH_INDEX } from "../../../config/constants"
|
||||
|
||||
const getSelectedDecade = (refDate: Date, selectedDecade?: number) => {
|
||||
const useYear = selectedDecade || refDate.getFullYear()
|
||||
const start = Math.floor(useYear / 10) * 10
|
||||
const end = start + 10
|
||||
|
||||
return start + 1 + " - " + end
|
||||
}
|
||||
|
||||
const getSelectedCentury = (refDate: Date, selectedDecade?: number) => {
|
||||
const useDecade = selectedDecade || refDate.getFullYear()
|
||||
const start = Math.floor(useDecade / 100) * 100
|
||||
const end = start + 100
|
||||
|
||||
return start + 1 + " - " + end
|
||||
}
|
||||
|
||||
const MonthNavigator = (props: MonthNavigatorProps) => {
|
||||
const {
|
||||
currentDecade,
|
||||
currentMonthNavView,
|
||||
currentView,
|
||||
currentYear,
|
||||
miniMode,
|
||||
refDate,
|
||||
setCurrentDecade,
|
||||
setCurrentMonthNavView,
|
||||
setCurrentView,
|
||||
setCurrentYear,
|
||||
setRefDate,
|
||||
theme,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`ar-MonthNavigator border-bottom d-flex${
|
||||
miniMode ? " is-mini px-0 py-1" : " px-3 py-2"
|
||||
}${
|
||||
currentView === ArCalViews.MONTH_YEAR_SELECTOR ? " to-month-year" : ""
|
||||
}${
|
||||
ArMonthSelectorViews.MONTH === currentMonthNavView
|
||||
? " to-month-selector"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="d-flex w-100">
|
||||
<div
|
||||
className="ar-MonthNavigator__nav-button flex-center cursor-pointer"
|
||||
onClick={() => setRefDate(CalHelper.getPreviousYear(refDate))}
|
||||
>
|
||||
<LoadableIcon
|
||||
icon="ai/AiOutlineDoubleLeft"
|
||||
color={theme === ArThemes.DARK1 ? "white" : "black"}
|
||||
size="1rem"
|
||||
strokeWidth={miniMode ? "30" : "100"}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="ar-MonthNavigator__nav-button flex-center cursor-pointer"
|
||||
onClick={() => {
|
||||
if (currentView === ArCalViews.CALENDAR) {
|
||||
setRefDate(CalHelper.getPreviousMonth(refDate))
|
||||
} else {
|
||||
currentMonthNavView === ArMonthSelectorViews.MONTH
|
||||
? setCurrentYear((currentYear || refDate.getFullYear()) - 1)
|
||||
: setCurrentDecade(
|
||||
(currentDecade || 2000) -
|
||||
(currentMonthNavView === ArMonthSelectorViews.DECADE
|
||||
? 100
|
||||
: 10),
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LoadableIcon
|
||||
icon="ai/AiOutlineLeft"
|
||||
color={theme === ArThemes.DARK1 ? "white" : "black"}
|
||||
size="1rem"
|
||||
strokeWidth={miniMode ? "30" : "100"}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`ar-MonthNavigator__nav-button ar-MonthNavigator__month d-inline-flex flex-center cursor-pointer overflow-auto${
|
||||
currentMonthNavView === ArMonthSelectorViews.DECADE
|
||||
? " disabled"
|
||||
: ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (currentView === ArCalViews.CALENDAR) {
|
||||
setCurrentView(ArCalViews.MONTH_YEAR_SELECTOR)
|
||||
}
|
||||
if (currentMonthNavView === ArMonthSelectorViews.MONTH) {
|
||||
setCurrentMonthNavView(ArMonthSelectorViews.YEAR)
|
||||
} else if (currentMonthNavView === ArMonthSelectorViews.YEAR) {
|
||||
setCurrentMonthNavView(ArMonthSelectorViews.DECADE)
|
||||
} else {
|
||||
setCurrentMonthNavView(ArMonthSelectorViews.MONTH)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentView === ArCalViews.MONTH_YEAR_SELECTOR ? (
|
||||
currentMonthNavView === ArMonthSelectorViews.MONTH ? (
|
||||
currentYear || refDate.getFullYear()
|
||||
) : currentMonthNavView === ArMonthSelectorViews.YEAR ? (
|
||||
getSelectedDecade(refDate, currentDecade)
|
||||
) : (
|
||||
getSelectedCentury(refDate, currentDecade)
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<span className="me-1">{MONTH_INDEX[refDate.getMonth()]}</span>
|
||||
<span>{refDate.getFullYear()}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className="ar-MonthNavigator__nav-button flex-center cursor-pointer"
|
||||
onClick={() => {
|
||||
if (currentView === ArCalViews.CALENDAR) {
|
||||
setRefDate(CalHelper.getNextMonth(refDate))
|
||||
} else {
|
||||
currentMonthNavView === ArMonthSelectorViews.MONTH
|
||||
? setCurrentYear((currentYear || refDate.getFullYear()) + 1)
|
||||
: setCurrentDecade(
|
||||
(currentDecade || 2000) +
|
||||
(currentMonthNavView === ArMonthSelectorViews.DECADE
|
||||
? 100
|
||||
: 10),
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<LoadableIcon
|
||||
icon="ai.AiOutlineRight"
|
||||
color={theme === ArThemes.DARK1 ? "white" : "black"}
|
||||
size="1rem"
|
||||
strokeWidth={miniMode ? "30" : "100"}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="ar-MonthNavigator__nav-button flex-center cursor-pointer"
|
||||
onClick={() => setRefDate(CalHelper.getNextYear(refDate))}
|
||||
>
|
||||
<LoadableIcon
|
||||
icon="ai.AiOutlineDoubleRight"
|
||||
color={theme === ArThemes.DARK1 ? "white" : "black"}
|
||||
size="1rem"
|
||||
strokeWidth={miniMode ? "30" : "100"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MonthNavigator
|
||||
18
src/app/components/atoms/Calendar/MonthSelector.component.scss
Executable file
18
src/app/components/atoms/Calendar/MonthSelector.component.scss
Executable file
@@ -0,0 +1,18 @@
|
||||
.ar-MonthSelector {
|
||||
& > * {
|
||||
transition: left 0.3s;
|
||||
}
|
||||
&__decade-selector, &__year-selector, &__month-selector {
|
||||
&__decade-padder, &__year-padder, &__month-padder {
|
||||
width: calc(100% / 4);
|
||||
}
|
||||
&__decade:hover, &__year:hover, &__month:hover {
|
||||
background-color: var(--ar-bg-hover);
|
||||
}
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
&__decade-selector__decade-padder, &__year-selector__year-padder, &__month-selector__month-padder {
|
||||
width: calc(100% / 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
141
src/app/components/atoms/Calendar/MonthSelector.tsx
Normal file
141
src/app/components/atoms/Calendar/MonthSelector.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
import { MONTH_INDEX } from "../../../config/constants"
|
||||
import { MonthSelectorProps } from ".."
|
||||
import { ArCalViews, ArMonthSelectorViews } from "../../../types/enums"
|
||||
import { sub } from "./helper"
|
||||
import "./MonthSelector.component.scss"
|
||||
|
||||
const generateDecades = (currentDecade: number) => {
|
||||
let startDecade = +(("" + currentDecade).substring(0, 2) + "00")
|
||||
const tillDecade = startDecade + 100
|
||||
const decades = []
|
||||
while (startDecade < tillDecade) {
|
||||
decades.push({ start: startDecade + 1, end: startDecade + 10 })
|
||||
startDecade += 10
|
||||
}
|
||||
return decades
|
||||
}
|
||||
|
||||
const generateYears = (currentYear: number) => {
|
||||
let startYear = currentYear + 1
|
||||
const tillYear = startYear + 10
|
||||
const years = []
|
||||
while (startYear < tillYear) {
|
||||
years.push(startYear)
|
||||
startYear++
|
||||
}
|
||||
return years
|
||||
}
|
||||
|
||||
const MonthSelector = (props: MonthSelectorProps) => {
|
||||
const {
|
||||
currentDecade,
|
||||
currentMonthNavView,
|
||||
currentYear,
|
||||
miniMode,
|
||||
onMonthSelect,
|
||||
setCurrentDecade,
|
||||
setCurrentMonthNavView,
|
||||
setCurrentView,
|
||||
setCurrentYear,
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className="ar-MonthSelector position-absolute h-100 d-flex w-100 overflow-hidden">
|
||||
<div
|
||||
className={`ar-MonthSelector__decade-selector position-absolute h-100 w-100 d-flex flex-wrap${
|
||||
currentMonthNavView === ArMonthSelectorViews.DECADE
|
||||
? " start-0"
|
||||
: " start-100"
|
||||
}`}
|
||||
>
|
||||
{currentDecade &&
|
||||
generateDecades(currentDecade).map((decade) => {
|
||||
return (
|
||||
<div
|
||||
className={`ar-MonthSelector__decade-selector__decade-padder d-flex ${
|
||||
miniMode ? "p-0" : "p-3"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setCurrentDecade(decade.start - 1)
|
||||
setCurrentMonthNavView(ArMonthSelectorViews.YEAR)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`ar-MonthSelector__decade-selector__decade flex-center flex-1 cursor-pointer${
|
||||
!miniMode ? " border border-radius" : ""
|
||||
}`}
|
||||
>
|
||||
{decade.start + " - " + sub(decade.end)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`ar-MonthSelector__year-selector position-absolute w-100 h-100 d-flex flex-wrap${
|
||||
currentMonthNavView === ArMonthSelectorViews.YEAR
|
||||
? " start-0"
|
||||
: currentMonthNavView === ArMonthSelectorViews.MONTH
|
||||
? " start-100"
|
||||
: " start-n100"
|
||||
}`}
|
||||
>
|
||||
{currentDecade &&
|
||||
generateYears(currentDecade).map((year) => {
|
||||
return (
|
||||
<div
|
||||
className={`ar-MonthSelector__year-selector__year-padder d-flex ${
|
||||
miniMode ? "p-0" : "p-3"
|
||||
}`}
|
||||
onClick={() => {
|
||||
setCurrentYear(year)
|
||||
setCurrentMonthNavView(ArMonthSelectorViews.MONTH)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`ar-MonthSelector__year-selector__year flex-center flex-1 cursor-pointer${
|
||||
!miniMode ? " border border-radius" : ""
|
||||
}`}
|
||||
>
|
||||
{year}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div
|
||||
className={`ar-MonthSelector__month-selector position-absolute w-100 h-100 d-flex flex-wrap${
|
||||
currentMonthNavView === ArMonthSelectorViews.MONTH
|
||||
? " start-0"
|
||||
: " start-n100"
|
||||
}`}
|
||||
>
|
||||
{MONTH_INDEX.map((month, index) => (
|
||||
<div
|
||||
className={`ar-MonthSelector__month-selector__month-padder d-flex ${
|
||||
miniMode ? "p-0" : "p-3"
|
||||
}`}
|
||||
onClick={() => {
|
||||
const date = new Date()
|
||||
date.setMonth(index)
|
||||
date.setFullYear(+(currentYear || date.getFullYear()))
|
||||
onMonthSelect(date)
|
||||
setCurrentMonthNavView(null)
|
||||
setCurrentView(ArCalViews.CALENDAR)
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`ar-MonthSelector__month-selector__month flex-center flex-1 cursor-pointer${
|
||||
!miniMode ? " border border-radius" : ""
|
||||
}`}
|
||||
>
|
||||
{month}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MonthSelector
|
||||
494
src/app/components/atoms/Calendar/helper.ts
Normal file
494
src/app/components/atoms/Calendar/helper.ts
Normal file
@@ -0,0 +1,494 @@
|
||||
import { MONTH_INDEX } from "../../../config/constants"
|
||||
import { CalendarDate } from "../../../types/entity.interface"
|
||||
import { ArDateFormats } from "../../../types/enums"
|
||||
|
||||
export interface CalendarOptions {
|
||||
/**
|
||||
* Date object indicating the selected start date
|
||||
*/
|
||||
startDate?: CalendarDate | null
|
||||
|
||||
/**
|
||||
* Date object indicating the selected end date
|
||||
*/
|
||||
endDate?: CalendarDate | null
|
||||
|
||||
/**
|
||||
* Calculate dates from sibling months (before and after the current month, based on weekStart)
|
||||
*/
|
||||
siblingMonths?: boolean
|
||||
|
||||
/**
|
||||
* Calculate the week days
|
||||
*/
|
||||
weekNumbers?: boolean
|
||||
|
||||
/**
|
||||
* Day of the week to start the calendar, respects `Date.prototype.getDay` (defaults to `0`, Sunday)
|
||||
*/
|
||||
weekStart?: number
|
||||
}
|
||||
|
||||
export const pad = (num: number, separator?: string) => {
|
||||
return ("" + num).padStart(2, "0") + (separator ? separator : "")
|
||||
}
|
||||
|
||||
export const sub = (num: number, separator?: string) => {
|
||||
return ("" + num).substring(2) + (separator ? separator : "")
|
||||
}
|
||||
|
||||
const str = (num: number, trim?: boolean, separator?: string) => {
|
||||
const month = MONTH_INDEX[num]
|
||||
return (trim ? month.substring(0, 4) : month) + (separator ? separator : "")
|
||||
}
|
||||
|
||||
export type CalendarInstance = Calendar
|
||||
/**
|
||||
* Calendar object
|
||||
*/
|
||||
class Calendar {
|
||||
startDate: CalendarDate | null
|
||||
endDate: CalendarDate | null
|
||||
siblingMonths: boolean
|
||||
weekNumbers: boolean
|
||||
weekStart: number
|
||||
|
||||
/**
|
||||
* Calendar constructor
|
||||
*
|
||||
* @param options Calendar options
|
||||
*/
|
||||
constructor({
|
||||
startDate = null,
|
||||
endDate = null,
|
||||
siblingMonths = false,
|
||||
weekNumbers = false,
|
||||
weekStart = 0,
|
||||
}: CalendarOptions = {}) {
|
||||
this.startDate = startDate
|
||||
this.endDate = endDate
|
||||
this.siblingMonths = siblingMonths
|
||||
this.weekNumbers = weekNumbers
|
||||
this.weekStart = weekStart
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate a calendar month
|
||||
*
|
||||
* @param year Year
|
||||
* @param month Month [0-11]
|
||||
* @return Calendar days
|
||||
*/
|
||||
getCalendar(year: number, month: number) {
|
||||
const date = new Date(Date.UTC(year, month, 1, 0, 0, 0, 0))
|
||||
|
||||
year = date.getFullYear()
|
||||
month = date.getMonth()
|
||||
|
||||
const calendar: (CalendarDate | false)[] = []
|
||||
const firstDay = date.getDay()
|
||||
const firstDate = -((7 - this.weekStart + firstDay) % 7)
|
||||
const lastDate = Calendar.daysInMonth(year, month)
|
||||
const lastDay = (lastDate - firstDate) % 7
|
||||
const lastDatePreviousMonth = Calendar.daysInMonth(year, month - 1)
|
||||
|
||||
let i = firstDate
|
||||
let currentDay
|
||||
let currentDate
|
||||
let currentDateObject: CalendarDate | false = false
|
||||
let currentWeekNumber = null
|
||||
let otherMonth
|
||||
let otherYear
|
||||
|
||||
const max = lastDate - i + (lastDay !== 0 ? 7 - lastDay : 0) + firstDate
|
||||
|
||||
while (i < max) {
|
||||
currentDate = i + 1
|
||||
currentDay = ((i < 1 ? 7 + i : i) + firstDay) % 7
|
||||
if (currentDate < 1 || currentDate > lastDate) {
|
||||
if (this.siblingMonths) {
|
||||
if (currentDate < 1) {
|
||||
otherMonth = month - 1
|
||||
otherYear = year
|
||||
if (otherMonth < 0) {
|
||||
otherMonth = 11
|
||||
otherYear--
|
||||
}
|
||||
currentDate = lastDatePreviousMonth + currentDate
|
||||
} else if (currentDate > lastDate) {
|
||||
otherMonth = month + 1
|
||||
otherYear = year
|
||||
if (otherMonth > 11) {
|
||||
otherMonth = 0
|
||||
otherYear++
|
||||
}
|
||||
currentDate = i - lastDate + 1
|
||||
}
|
||||
|
||||
if (otherMonth !== undefined && otherYear !== undefined) {
|
||||
currentDateObject = {
|
||||
day: currentDate,
|
||||
weekDay: currentDay,
|
||||
month: otherMonth,
|
||||
year: otherYear,
|
||||
siblingMonth: true,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
currentDateObject = false
|
||||
}
|
||||
} else {
|
||||
currentDateObject = {
|
||||
day: currentDate,
|
||||
weekDay: currentDay,
|
||||
month: month,
|
||||
year: year,
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDateObject && this.weekNumbers) {
|
||||
if (currentWeekNumber === null) {
|
||||
currentWeekNumber = Calendar.calculateWeekNumber(currentDateObject)
|
||||
} else if (currentDay === 1 && currentWeekNumber === 52) {
|
||||
currentWeekNumber = 1
|
||||
} else if (currentDay === 1) {
|
||||
currentWeekNumber++
|
||||
}
|
||||
currentDateObject.weekNumber = currentWeekNumber
|
||||
}
|
||||
|
||||
if (currentDateObject && this.startDate) {
|
||||
currentDateObject.selected = this.isDateSelected(currentDateObject)
|
||||
}
|
||||
|
||||
calendar.push(currentDateObject)
|
||||
i++
|
||||
}
|
||||
|
||||
return calendar
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a date is selected
|
||||
*
|
||||
* @param date Date object
|
||||
* @return Selected status of the date
|
||||
*/
|
||||
isDateSelected(date: CalendarDate, endDate?: CalendarDate | null) {
|
||||
// Hack to disregard this.endDate in further calculations for finding selectable candidates (hovered)
|
||||
// by explicitly sending a "null" endDate, if endDate is not sent at all (undefined) then only function behaves as
|
||||
// initially intended and considers endDate for calculations
|
||||
if (!this.startDate || endDate === null) {
|
||||
return false
|
||||
}
|
||||
let startDate = this.startDate
|
||||
if (endDate) {
|
||||
if (Calendar.compare(this.startDate, endDate) === 1) {
|
||||
startDate = endDate
|
||||
endDate = this.startDate
|
||||
}
|
||||
} else {
|
||||
endDate = this.endDate
|
||||
}
|
||||
if (
|
||||
date.year === startDate.year &&
|
||||
date.month === startDate.month &&
|
||||
date.day === startDate.day
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (!endDate) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
date.year === startDate.year &&
|
||||
date.month === startDate.month &&
|
||||
date.day < startDate.day
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
date.year === endDate.year &&
|
||||
date.month === endDate.month &&
|
||||
date.day > endDate.day
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (date.year === startDate.year && date.month < startDate.month) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (date.year === endDate.year && date.month > endDate.month) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (date.year < startDate.year) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (date.year > endDate.year) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected period start
|
||||
*
|
||||
* @param date Date object
|
||||
*/
|
||||
setStartDate(date: CalendarDate | null) {
|
||||
this.startDate = date
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected period end
|
||||
*
|
||||
* @param date Date object
|
||||
*/
|
||||
setEndDate(date: CalendarDate | null) {
|
||||
this.endDate = date
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets one selected date
|
||||
*
|
||||
* @param date Date object
|
||||
*/
|
||||
setDate(date: CalendarDate) {
|
||||
return this.setStartDate(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the difference between two dates (date1 - date2), in days
|
||||
*
|
||||
* @param dateLeft Date object
|
||||
* @param dateRight Date object
|
||||
* @return Days between the dates
|
||||
*/
|
||||
static diff(dateLeft: CalendarDate, dateRight: CalendarDate) {
|
||||
const dateLeftDate = new Date(
|
||||
Date.UTC(dateLeft.year, dateLeft.month, dateLeft.day, 0, 0, 0, 0),
|
||||
)
|
||||
const dateRightDate = new Date(
|
||||
Date.UTC(dateRight.year, dateRight.month, dateRight.day, 0, 0, 0, 0),
|
||||
)
|
||||
return Math.ceil(
|
||||
(dateLeftDate.getTime() - dateRightDate.getTime()) / 86400000,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the interval between two dates
|
||||
*
|
||||
* @param dateLeft Date object
|
||||
* @param dateRight Date object
|
||||
* @return Number of days between dates
|
||||
*/
|
||||
static interval(dateLeft: CalendarDate, dateRight: CalendarDate) {
|
||||
return Math.abs(Calendar.diff(dateLeft, dateRight)) + 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly compare two dates
|
||||
*
|
||||
* @param dateLeft Left `CalendarDate` object
|
||||
* @param dateRight Right `CalendarDate` object
|
||||
* @return Comparison result: -1 (left < right), 0 (equal) or 1 (left > right)
|
||||
*/
|
||||
static compare(dateLeft: CalendarDate, dateRight: CalendarDate) {
|
||||
if (
|
||||
typeof dateLeft !== "object" ||
|
||||
typeof dateRight !== "object" ||
|
||||
dateLeft === null ||
|
||||
dateRight === null
|
||||
) {
|
||||
throw new TypeError("dates must be objects")
|
||||
}
|
||||
|
||||
if (dateLeft.year < dateRight.year) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (dateLeft.year > dateRight.year) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (dateLeft.month < dateRight.month) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (dateLeft.month > dateRight.month) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (dateLeft.day < dateRight.day) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (dateLeft.day > dateRight.day) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of days in a month
|
||||
*
|
||||
* @param year Year
|
||||
* @param month Month [0-11]
|
||||
* @return Length of the month
|
||||
*/
|
||||
static daysInMonth(year: number, month: number) {
|
||||
return new Date(year, month + 1, 0).getDate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates if a given year is a leap year
|
||||
*
|
||||
* @param year Year
|
||||
* @return Leap year or not
|
||||
*/
|
||||
static isLeapYear(year: number) {
|
||||
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the week number for a given date
|
||||
*
|
||||
* @param date Date object
|
||||
* @return Week number
|
||||
*/
|
||||
// Adapted from http://techblog.procurios.nl/k/news/view/33796/14863/calculate-iso-8601-week-and-year-in-javascript.html
|
||||
static calculateWeekNumber(date: CalendarDate) {
|
||||
// Creates the requested date
|
||||
const current = new Date(
|
||||
Date.UTC(date.year, date.month, date.day, 0, 0, 0, 0),
|
||||
)
|
||||
|
||||
// Create a copy of the object
|
||||
const target = new Date(current.valueOf())
|
||||
|
||||
// ISO week date weeks start on monday so correct the day number
|
||||
const dayNr = (current.getUTCDay() + 6) % 7
|
||||
|
||||
// ISO 8601 states that week 1 is the week with the first thursday of that
|
||||
// year. Set the target date to the thursday in the target week.
|
||||
target.setUTCDate(target.getUTCDate() - dayNr + 3)
|
||||
|
||||
// Store the millisecond value of the target date
|
||||
const firstThursday = target.valueOf()
|
||||
|
||||
// Set the target to the first thursday of the year
|
||||
|
||||
// First set the target to january first
|
||||
target.setUTCMonth(0, 1)
|
||||
|
||||
// Not a thursday? Correct the date to the next thursday
|
||||
if (target.getUTCDay() !== 4) {
|
||||
target.setUTCMonth(0, 1 + ((4 - target.getUTCDay() + 7) % 7))
|
||||
}
|
||||
|
||||
// The week number is the number of weeks between the first thursday of the
|
||||
// year and the thursday in the target week.
|
||||
// 604800000 = 7 * 24 * 3600 * 1000
|
||||
return 1 + Math.ceil((firstThursday - target.getTime()) / 604800000)
|
||||
}
|
||||
|
||||
static toString(
|
||||
date: CalendarDate,
|
||||
separator: string = "/",
|
||||
format: string = "DDMMYYYY",
|
||||
) {
|
||||
const day = pad(date.day, separator)
|
||||
const month = pad(date.month + 1, separator)
|
||||
const year = date.year
|
||||
|
||||
switch (format) {
|
||||
case ArDateFormats.MMDDYY:
|
||||
return month + day + sub(year)
|
||||
case ArDateFormats.DDMMYYYY:
|
||||
return day + month + year
|
||||
case ArDateFormats.MMDDYYYY:
|
||||
return month + day + year
|
||||
case ArDateFormats.DDMMMYY:
|
||||
return day + str(date.month + 1, true, separator) + sub(year)
|
||||
case ArDateFormats.DDMMMYYYY:
|
||||
return day + str(date.month + 1, true, separator) + year
|
||||
case ArDateFormats.MMMDDYY:
|
||||
return str(date.month + 1, true, separator) + day + sub(year)
|
||||
case ArDateFormats.MMMDDYYYY:
|
||||
return str(date.month + 1, true, separator) + day + year
|
||||
case ArDateFormats.YYMMDD:
|
||||
return sub(year) + month + day
|
||||
case ArDateFormats.YYMMMDD:
|
||||
return sub(year) + str(date.month + 1, true, separator) + day
|
||||
case ArDateFormats.YYYYMMDD:
|
||||
return year + month + day
|
||||
case ArDateFormats.YYYYMMMDD:
|
||||
return year + str(date.month + 1, true, separator) + day
|
||||
case ArDateFormats.DDMMYY:
|
||||
default:
|
||||
return day + month + sub(year)
|
||||
}
|
||||
}
|
||||
|
||||
static getPreviousMonth(date: Date) {
|
||||
const month = date.getMonth()
|
||||
const year = date.getFullYear()
|
||||
|
||||
const previousMonth = month === 0 ? 11 : month - 1
|
||||
const previousYear = month === 0 ? year - 1 : year
|
||||
|
||||
const returnDate = new Date()
|
||||
returnDate.setDate(date.getDate())
|
||||
returnDate.setMonth(previousMonth)
|
||||
returnDate.setFullYear(previousYear)
|
||||
|
||||
return returnDate
|
||||
}
|
||||
|
||||
static getNextMonth(date: Date) {
|
||||
const month = date.getMonth()
|
||||
const year = date.getFullYear()
|
||||
|
||||
const previousMonth = month === 11 ? 0 : month + 1
|
||||
const previousYear = month === 11 ? year + 1 : year
|
||||
|
||||
const returnDate = new Date()
|
||||
returnDate.setDate(date.getDate())
|
||||
returnDate.setMonth(previousMonth)
|
||||
returnDate.setFullYear(previousYear)
|
||||
|
||||
return returnDate
|
||||
}
|
||||
|
||||
static getPreviousYear(date: Date) {
|
||||
const year = date.getFullYear()
|
||||
const returnDate = new Date()
|
||||
returnDate.setDate(date.getDate())
|
||||
returnDate.setMonth(date.getMonth())
|
||||
returnDate.setFullYear(year === 1 ? 1 : year - 1)
|
||||
return returnDate
|
||||
}
|
||||
|
||||
static getNextYear(date: Date) {
|
||||
const year = date.getFullYear()
|
||||
const returnDate = new Date()
|
||||
returnDate.setDate(date.getDate())
|
||||
returnDate.setMonth(date.getMonth())
|
||||
returnDate.setFullYear(year + 1)
|
||||
return returnDate
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the Calendar
|
||||
*/
|
||||
export { Calendar as default }
|
||||
@@ -1,15 +1,8 @@
|
||||
import React, { useState } from "react"
|
||||
import { Toggle } from "../.."
|
||||
import { CheckboxProps } from ".."
|
||||
import "./Checkbox.component.scss"
|
||||
|
||||
type CheckBoxTypes = "TOGGLE" | "CHECKBOX"
|
||||
interface CheckboxProps {
|
||||
id?: string
|
||||
label?: string
|
||||
type?: CheckBoxTypes
|
||||
onChange: Function
|
||||
}
|
||||
|
||||
const Checkbox = (props: CheckboxProps): JSX.Element => {
|
||||
const { label, id, onChange } = props
|
||||
const [checked, setChecked] = useState<boolean>()
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { useState } from "react"
|
||||
import { ColorPickerProps } from ".."
|
||||
import Helper from "../../../utils/helper"
|
||||
import "./ColorPicker.component.scss"
|
||||
|
||||
interface ColorPickerProps {
|
||||
id?: string
|
||||
label?: string
|
||||
onChange?: Function
|
||||
value?: string
|
||||
}
|
||||
|
||||
const ColorPicker = (props: ColorPickerProps): JSX.Element => {
|
||||
const { id, label, onChange, value: externalValue } = props
|
||||
const [value, setValue] = useState<string | undefined>(externalValue)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from "react"
|
||||
import { ColorSelectorProps } from "../../../types/components.interface"
|
||||
import { ColorSelectorProps } from ".."
|
||||
import "./ColorSelector.component.scss"
|
||||
import TextInput from "../TextInput"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react"
|
||||
import { InlineMenu } from "../.."
|
||||
import { ContextMenuProps } from "../../../types/components.interface"
|
||||
import { ContextMenuProps } from ".."
|
||||
import "./ContextMenu.component.scss"
|
||||
|
||||
const ContextMenu = (props: ContextMenuProps): JSX.Element => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ChangeEvent, useEffect, useState } from "react"
|
||||
import { CronTabProps } from "../../../types/components.interface"
|
||||
import { CronTabProps } from ".."
|
||||
import TextInput from "../TextInput"
|
||||
import LoadableIcon from "../LoadableIcon"
|
||||
import "./CronTab.component.scss"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user