Major: Lottie, fonts, UI revamp

This commit is contained in:
2024-02-10 03:35:09 +05:30
parent fdb09a6ee8
commit f8496d5770
285 changed files with 26368 additions and 11291 deletions

3
.gitignore vendored
View File

@@ -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

View File

@@ -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)

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}

View File

@@ -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
View 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

View File

@@ -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
View File

@@ -0,0 +1,7 @@
#!/bin/sh
semver=${1:-patch}
set -e
# npm run build
npm pack --pack-destination ~/__Projects__/Common

View File

@@ -1,5 +1,5 @@
#!/bin/sh
source ./build.sh
# source ./scripts/build.sh
npm --no-git-tag-version version patch
npm publish --access public

View File

@@ -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>
)

View File

@@ -0,0 +1,3 @@
.ar-AssetSelector {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import AssetSelector from "./AssetSelector"
describe("AssetSelector", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import AssetSelector from "./AssetSelector"
export default AssetSelector

View File

@@ -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>
)

View File

@@ -41,7 +41,7 @@ const ConfigurationLoginPrompt = (
variant={ArButtonVariants.SUCCESS}
postIcon="io5/IoArrowForwardCircle"
onClick={() => {
dispatch(setRightPanelContent("LoginProvider"))
dispatch(setRightPanelContent({ name: "LoginProvider" }))
}}
/>
</div>

View File

@@ -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>
)

View File

@@ -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;
}
}

View File

@@ -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>
)
}

View File

@@ -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>

View File

@@ -108,7 +108,7 @@ const FavoritesList = (props: FavoritesListProps): JSX.Element => {
content="Login"
preIcon="ci/CiWarning"
onClick={() => {
dispatch(setRightPanelContent("LoginProvider"))
dispatch(setRightPanelContent({ name: "LoginProvider" }))
}}
/>
)}

View 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;
}
}
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import FlexTools from "./FlexTools"
describe("FlexTools", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import FlexTools from "./FlexTools"
export default FlexTools

View File

@@ -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);
}
}

View File

@@ -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>
)
}

View File

@@ -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

View File

@@ -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;
}
}
}
}

View File

@@ -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>
)
}

View File

@@ -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);
}
}

View File

@@ -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>
)

View File

@@ -0,0 +1,2 @@
.ar-IconEditor {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import IconEditor from "./IconEditor"
describe("IconEditor", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import IconEditor from "./IconEditor"
export default IconEditor

View File

@@ -0,0 +1,12 @@
.ar-IconMergeContainer {
svg {
width: 5rem;
height: 5rem;
margin-left: 2rem;
margin-right: 2rem;
path:hover {
outline: 1px dotted grey;
}
}
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import IconMergeContainer from "./IconMergeContainer"
describe("IconMergeContainer", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import IconMergeContainer from "./IconMergeContainer"
export default IconMergeContainer

View File

@@ -0,0 +1,3 @@
.ar-IconStyleSelector {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import IconStyleSelector from "./IconStyleSelector"
describe("IconStyleSelector", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import IconStyleSelector from "./IconStyleSelector"
export default IconStyleSelector

View File

@@ -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;
}
}
}

View File

@@ -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 && ( */}

View File

@@ -0,0 +1,3 @@
.ar-LandingContent {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import LandingContent from "./LandingContent"
describe("LandingContent", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import LandingContent from "./LandingContent"
export default LandingContent

View File

@@ -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>
)
}

View 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);
}
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import LottieEditor from "./LottieEditor"
describe("LottieEditor", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import LottieEditor from "./LottieEditor"
export default LottieEditor

View 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;
}
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import LottieEditorTools from "./LottieEditorTools"
describe("LottieEditorTools", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import LottieEditorTools from "./LottieEditorTools"
export default LottieEditorTools

View File

@@ -0,0 +1,3 @@
.ar-LottieLayers {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import LottieLayers from "./LottieLayers"
describe("LottieLayers", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import LottieLayers from "./LottieLayers"
export default LottieLayers

View File

@@ -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;
}
}
}
}

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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>

View 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);
}
}
}
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import Accordion from "./Accordion"
describe("Accordion", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import Accordion from "./Accordion"
export default Accordion

View File

@@ -0,0 +1,3 @@
.ar-AdvancedColorPicker {
}

View File

@@ -0,0 +1,8 @@
import React from "react"
import AdvancedColorPicker from "./AdvancedColorPicker"
describe("AdvancedColorPicker", () => {
it("renders without error", () => {
})
})

View 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

View File

@@ -0,0 +1,3 @@
import AdvancedColorPicker from "./AdvancedColorPicker"
export default AdvancedColorPicker

View File

@@ -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"

View File

@@ -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" },

View File

@@ -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 => {

View File

@@ -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}

View File

@@ -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"

View File

@@ -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)

View File

@@ -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 = [

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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

View 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

View File

@@ -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;
}
}
}

View 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

View File

@@ -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;
// }
// }
// }
}

View 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

View 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);
}
}
}

View 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

View 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 }

View File

@@ -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>()

View File

@@ -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)

View File

@@ -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"

View File

@@ -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 => {

View File

@@ -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