| import React from 'react'; | ||
| import './dependencies/style/style.css'; | ||
| import { EvaluateFailure, GaurdStatus } from '../../utils/DyvixGuard'; | ||
| import gsap from 'gsap'; | ||
| import { useGSAP } from '@gsap/react'; | ||
| import { Validatebtn } from './validation'; | ||
| import Version from '../../../package.json'; | ||
| function DyvixButton({ | ||
| children = 'Click Me', | ||
| animation = 'fade', | ||
| className = '', | ||
| theme = '!/', | ||
| background, | ||
| color, | ||
| onClick, | ||
| style, | ||
| ...rest | ||
| }) { | ||
| const btnRef = React.useRef(null); | ||
| const [configs, SetConfig] = React.useState({}); | ||
| const instanceId = React.useId(); | ||
| const currentTheme = configs['theme']; | ||
| const currentAnimation = animation | ||
| ? configs['animation'] | ||
| : null; | ||
| function handleClick() { | ||
| if (typeof onClick === 'function') { | ||
| onClick(); | ||
| } | ||
| } | ||
| className = `dyvix-button${currentTheme ? ` ${currentTheme.class}` : ''}${className !== '' ? ` ${className}` : ''}`; | ||
| React.useEffect(()=>{ | ||
| async function validate() { | ||
| const validator = await Validatebtn(animation, theme, SetConfig, instanceId); | ||
| if (validator.status === GaurdStatus.Error) { | ||
| return EvaluateFailure(validator.error, validator.status); | ||
| } | ||
| } | ||
| validate(); | ||
| return () => { | ||
| const key = `DYVIX_${Version['version']}_Button_theme_${instanceId}`; | ||
| const ele = document.getElementById(key); | ||
| if (ele) ele.remove(); | ||
| }; | ||
| },[theme, animation]); | ||
| useGSAP(() => { | ||
| if (!btnRef.current || !currentAnimation) return; | ||
| gsap.fromTo(btnRef.current, currentAnimation.from, { | ||
| ...currentAnimation.to, | ||
| duration: currentAnimation['default-duration'], | ||
| ease: currentAnimation.ease | ||
| }); | ||
| }, [currentAnimation]); | ||
| const props = { | ||
| className: className, | ||
| disabled: rest.disabled, | ||
| style: { | ||
| ...(background && { background: background }), | ||
| ...(color && { color: color }), | ||
| ...style | ||
| } | ||
| }; | ||
| return ( | ||
| <div className="dyvix-btn-wrapper" ref={btnRef} {...rest}> | ||
| <button {...props} onClick={handleClick}> | ||
| {children} | ||
| </button> | ||
| </div> | ||
| ); | ||
| } | ||
| export default DyvixButton; |
| .dyvix-btn-wrapper { | ||
| width: fit-content; | ||
| height: fit-content; | ||
| } | ||
| .dyvix-button { | ||
| position: relative; | ||
| background: #0d0d0f; | ||
| border: 1px solid rgba(255, 255, 255, 0.08); | ||
| padding: 10px 22px; | ||
| color: #d3e9ff; | ||
| border-radius: 6px; | ||
| cursor: pointer; | ||
| overflow: hidden; | ||
| font-weight: 500; | ||
| font-family: 'Geist', sans-serif; | ||
| letter-spacing: -0.02em; | ||
| text-rendering: optimizeLegibility; | ||
| -webkit-font-smoothing: antialiased; | ||
| user-select: none; | ||
| -moz-user-select: none; | ||
| -webkit-user-select: none; | ||
| box-shadow: inset 0 1px 0 0 rgba(255, 255, 255, 0.05); | ||
| transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | ||
| } | ||
| .dyvix-button:hover { | ||
| background: #161618; | ||
| border-color: rgba(255, 255, 255, 0.2); | ||
| color: #fff; | ||
| transform: translateY(-1px); | ||
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); | ||
| } | ||
| .dyvix-button:active { | ||
| background: #161618; | ||
| border-color: rgb(255, 255, 255); | ||
| transform: translateY(2px); | ||
| box-shadow: 0 0 10px rgba(255, 255, 255, 0.3); | ||
| } |
| .dyvix-button-lens { | ||
| background: radial-gradient(circle, #000000 0%, #1a1a1a 100%); | ||
| box-shadow: 0 0 30px #b903e2; | ||
| -webkit-box-shadow: 0 0 30px #b903e2; | ||
| -moz-box-shadow: 0 0 30px #b903e2; | ||
| border: #450343 3px double; | ||
| border-radius: 10rem; | ||
| transition: all 0.3s ease-in-out; | ||
| } | ||
| .dyvix-button-lens:hover { | ||
| border: #000000 3px double; | ||
| transform: scale(1.09); | ||
| } | ||
| .dyvix-button-lens:active { | ||
| border: #000000 5px double; | ||
| transform: scale(1.09); | ||
| } | ||
| .dyvix-button-neon { | ||
| background: #4deeea; | ||
| background: radial-gradient( | ||
| circle, | ||
| rgba(77, 238, 234, 1) 0%, | ||
| rgba(112, 186, 239, 1) 43%, | ||
| rgba(240, 0, 255, 1) 100% | ||
| ); | ||
| box-shadow: 0px 0px 20px 2px rgba(252, 46, 255, 0.4); | ||
| border: none; | ||
| color: white; | ||
| font-weight: bold; | ||
| border-radius: 4rem; | ||
| transition: | ||
| transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), | ||
| border-radius 0.2s ease, | ||
| box-shadow 0.3s ease, | ||
| background 0.3s ease; | ||
| } | ||
| .dyvix-button-neon:hover { | ||
| border-radius: 2rem; | ||
| transform: scale(1.02); | ||
| background: #1c4241; | ||
| background: radial-gradient( | ||
| circle, | ||
| rgba(112, 186, 239, 1) 0%, | ||
| rgba(77, 238, 234, 1) 43%, | ||
| rgba(240, 0, 255, 1) 90% | ||
| ); | ||
| box-shadow: 0px 0px 56px 4px rgba(252,61,255,1); | ||
| } | ||
| .dyvix-button-neon:active { | ||
| background-color: #5eeeec; | ||
| background: radial-gradient( | ||
| circle, | ||
| rgb(10, 152, 150) 0%, | ||
| rgb(110, 167, 207) 43%, | ||
| rgb(84, 4, 90) 100% | ||
| ); | ||
| transform: scale(0.97); | ||
| } | ||
| .dyvix-button-aurora { | ||
| background: #14e81e; | ||
| background: linear-gradient( | ||
| 99deg, | ||
| rgba(20, 232, 30, 1) 0%, | ||
| rgba(0, 234, 141, 1) 21%, | ||
| rgba(1, 177, 179, 1) 46%, | ||
| rgba(1, 126, 213, 1) 67%, | ||
| rgba(181, 61, 255, 1) 100% | ||
| ); | ||
| border-radius: 3rem; | ||
| border: 1px solid rgba(165, 164, 164, 0.1); | ||
| box-shadow: 0px 0px 75px 0px rgba(1, 176, 179, 0.9); | ||
| transition: | ||
| box-shadow 0.3s ease-in-out, | ||
| transform 0.3s ease-in-out, | ||
| border-radius 0.2s ease-in-out, | ||
| background-color 0.1s ease-in-out; | ||
| } | ||
| .dyvix-button-aurora:hover { | ||
| border-radius: 5rem; | ||
| background: #14e81e; | ||
| background: linear-gradient( | ||
| 99deg, | ||
| rgb(93, 189, 98) 0%, | ||
| rgba(0, 234, 141, 1) 21%, | ||
| rgba(1, 177, 179, 1) 46%, | ||
| rgb(100, 187, 250) 67%, | ||
| rgba(181, 61, 255, 1) 100% | ||
| ); | ||
| transform: scale(1.04); | ||
| box-shadow: 0px 0px 50px 10px rgba(1, 176, 179, 0.8); | ||
| } | ||
| .dyvix-button-aurora:active { | ||
| border-radius: 5rem; | ||
| background: linear-gradient( | ||
| 99deg, | ||
| rgb(165, 255, 169) 0%, | ||
| rgb(166, 248, 215) 21%, | ||
| rgb(189, 250, 251) 46%, | ||
| #b7ddf8 67%, | ||
| rgb(227, 181, 255) 100% | ||
| ); | ||
| transform: scale(0.98); | ||
| filter: brightness(1.2); | ||
| box-shadow: 0px 0px 5px 8px rgba(1, 176, 179, 1); | ||
| } |
| [ | ||
| { | ||
| "theme": "Singularity", | ||
| "class": "dyvix-button-lens", | ||
| "default-animation": "bubble" | ||
| }, | ||
| { | ||
| "theme": "Neon", | ||
| "class": "dyvix-button-neon", | ||
| "default-animation": "pulse" | ||
| }, | ||
| { | ||
| "theme": "Aurora", | ||
| "class": "dyvix-button-aurora", | ||
| "default-animation": "aurora" | ||
| } | ||
| ] |
| import { | ||
| EvaluateFailure, | ||
| GaurdStatus, | ||
| allowsNull | ||
| } from '../../utils/DyvixGuard'; | ||
| import { ValidatAndLoadJSON } from '../../utils/Smart Json Caching/SJCManager'; | ||
| const component = "Button" | ||
| const CacheMapping = { | ||
| theme: { | ||
| jsonpath: '../../components/button/dependencies/themes.json', | ||
| csspath: '../../components/button/dependencies/style/themes.css' | ||
| }, | ||
| animation: { | ||
| jsonpath: '../../components/animations.json', | ||
| csspath: null | ||
| } | ||
| }; | ||
| export async function Validatebtn(animation, theme, callback, instance) { | ||
| const normalizedAnimation = animation?.trim().toLowerCase(); | ||
| const normalizedTheme = | ||
| theme?.trim().charAt(0).toUpperCase() + theme.trim().slice(1); | ||
| const [isAnimation, isTheme] = await Promise.all([ | ||
| ValidatAndLoadJSON( | ||
| CacheMapping, | ||
| normalizedAnimation, | ||
| callback, | ||
| 'animation', | ||
| component | ||
| ), | ||
| ValidatAndLoadJSON( | ||
| CacheMapping, | ||
| normalizedTheme, | ||
| callback, | ||
| 'theme', | ||
| component, | ||
| instance | ||
| ) | ||
| ]); | ||
| if ( | ||
| normalizedAnimation !== null && | ||
| !isAnimation.status | ||
| ) { | ||
| return { | ||
| status: GaurdStatus.Error, | ||
| error: 'Please provide a valid animation.' | ||
| }; | ||
| } | ||
| if (normalizedTheme !== '!/' && !isTheme.status) { | ||
| return { | ||
| status: GaurdStatus.Error, | ||
| error: 'Please provide a valid theme.' | ||
| }; | ||
| } | ||
| return { status: GaurdStatus.Success }; | ||
| } |
| import { EvaluateFailure, GaurdStatus } from '../DyvixGuard'; | ||
| import Version from '../../../package.json'; | ||
| export const CACHETYPE = { CSS: 'css', Default: 'default' }; | ||
| const VERSION = Version['version']; | ||
| export async function SJCManager( | ||
| jsonpath, | ||
| csspath, | ||
| type, | ||
| component, | ||
| utility, | ||
| jsonKey, | ||
| jsonclasskey = '', | ||
| instance | ||
| ) { | ||
| let result = null; | ||
| const key = generateCacheKey(component, utility); | ||
| result = await cachelayerOne( | ||
| jsonpath, | ||
| csspath, | ||
| type, | ||
| component, | ||
| utility, | ||
| jsonKey, | ||
| jsonclasskey, | ||
| key | ||
| ); | ||
| if (result === null) | ||
| result = await cachelayerTwo( | ||
| jsonpath, | ||
| csspath, | ||
| type, | ||
| component, | ||
| utility, | ||
| jsonKey, | ||
| jsonclasskey, | ||
| key | ||
| ); | ||
| if (result === null) | ||
| result = await cachelayerThree( | ||
| jsonpath, | ||
| csspath, | ||
| type, | ||
| component, | ||
| utility, | ||
| jsonKey, | ||
| jsonclasskey, | ||
| key | ||
| ); | ||
| if (result === null) return result; | ||
| if (result?.CSS !== undefined && result?.CSS !== null) { | ||
| InjectCSS(result.CSS, key, instance); | ||
| } | ||
| return result.JSON; | ||
| } | ||
| async function cachelayerThree( | ||
| jsonpath, | ||
| csspath, | ||
| type, | ||
| component, | ||
| utility, | ||
| jsonKey, | ||
| jsonclasskey, | ||
| key | ||
| ) { | ||
| let JsonArray = null; | ||
| let rawCSS = null; | ||
| let cssResult = null; | ||
| let jsonResult = null; | ||
| let keys = [key + '_L1', key + '_L2', key + '_L3']; | ||
| if (localStorage.getItem(keys[2])) { | ||
| const cachedData = JSON.parse(localStorage.getItem(keys[2])); | ||
| JsonArray = cachedData.JSON; | ||
| rawCSS = cachedData.CSS; | ||
| } else { | ||
| const rawJSONText = await extractFile(jsonpath); | ||
| JsonArray = JSON.parse(rawJSONText); | ||
| if (type === CACHETYPE.CSS) { | ||
| rawCSS = await extractFile(csspath); | ||
| } | ||
| } | ||
| jsonResult = JsonArray.find((e) => e[utility] === jsonKey); | ||
| let value = { | ||
| ...(rawCSS !== null && { CSS: rawCSS }), | ||
| ...(JsonArray !== null && { JSON: JsonArray }) | ||
| }; | ||
| localStorage.setItem(keys[2], JSON.stringify(value)); | ||
| if (!jsonResult) { | ||
| return null; | ||
| } | ||
| cssResult = await extractCSSClass(jsonResult[jsonclasskey], null, rawCSS); | ||
| let result = { | ||
| ...(cssResult !== null && { CSS: cssResult }), | ||
| ...(jsonResult !== null && { JSON: jsonResult }) | ||
| }; | ||
| const rawL2Cache = localStorage.getItem(keys[1]); | ||
| const existingL2cache = rawL2Cache ? JSON.parse(rawL2Cache) : {}; | ||
| existingL2cache[jsonKey] = result; | ||
| localStorage.setItem(keys[1], JSON.stringify(existingL2cache)); | ||
| const rawL1Cache = localStorage.getItem(keys[0]); | ||
| const existingL1cache = rawL1Cache ? JSON.parse(rawL1Cache) : {}; | ||
| existingL1cache[jsonKey] = { | ||
| ...result, | ||
| expires: Date.now() + 30 * 24 * 60 * 60 * 1000 | ||
| }; | ||
| localStorage.setItem(keys[0], JSON.stringify(existingL1cache)); | ||
| return result; | ||
| } | ||
| // Caches only the config ever used | ||
| async function cachelayerTwo( | ||
| jsonpath, | ||
| csspath, | ||
| type, | ||
| component, | ||
| utility, | ||
| jsonKey, | ||
| jsonclasskey, | ||
| key | ||
| ) { | ||
| let cssResult = null; | ||
| let jsonResult = null; | ||
| key += '_L2'; | ||
| let cachedData = null; | ||
| if (!localStorage.getItem(key)) return null; | ||
| cachedData = JSON.parse(localStorage.getItem(key)); | ||
| const entry = cachedData[jsonKey]; | ||
| if (!entry) return null; | ||
| jsonResult = entry.JSON; | ||
| cssResult = entry.CSS; | ||
| let result = { | ||
| ...(cssResult !== null && { CSS: cssResult }), | ||
| ...(jsonResult !== null && { JSON: jsonResult }) | ||
| }; | ||
| return result; | ||
| } | ||
| // Caches only the config ever used in a month. Limited to 10. | ||
| async function cachelayerOne( | ||
| jsonpath, | ||
| csspath, | ||
| type, | ||
| component, | ||
| utility, | ||
| jsonKey, | ||
| jsonclasskey, | ||
| key | ||
| ) { | ||
| let cssResult = null; | ||
| let jsonResult = null; | ||
| key += '_L1'; | ||
| let cachedData = null; | ||
| if (!localStorage.getItem(key)) return null; | ||
| cachedData = JSON.parse(localStorage.getItem(key)); | ||
| Object.keys(cachedData).forEach((element) => { | ||
| const expires_at = cachedData[element].expires; | ||
| if (expires_at && expires_at < Date.now()) { | ||
| delete cachedData[element]; | ||
| } | ||
| }); | ||
| localStorage.setItem(key, JSON.stringify(cachedData)); | ||
| const entry = cachedData[jsonKey]; | ||
| if (!entry) return null; | ||
| jsonResult = entry.JSON; | ||
| cssResult = entry.CSS; | ||
| let result = { | ||
| ...(cssResult !== null && { CSS: cssResult }), | ||
| ...(jsonResult !== null && { JSON: jsonResult }) | ||
| }; | ||
| return result; | ||
| } | ||
| async function extractFile(path) { | ||
| try { | ||
| const module = await import(/* @vite-ignore */ `${path}?raw`); | ||
| return module.default || module; | ||
| } catch (error) { | ||
| console.log('DyvixUI Sys error'); | ||
| return null; | ||
| } | ||
| } | ||
| function generateCacheKey(component, utility) { | ||
| const key = `DYVIX_${VERSION}_${component}_${utility}`; | ||
| return key; | ||
| } | ||
| async function extractCSSClass(classname, Csspath = null, cssblock = null) { | ||
| let rawCSS = null; | ||
| if (Csspath !== null) { | ||
| try { | ||
| const module = await import(/* @vite-ignore */ `${Csspath}?raw`); | ||
| rawCSS = module.default || module; | ||
| } catch (error) { | ||
| console.log('DyvixUI Sys error'); | ||
| } | ||
| } else if (cssblock !== null) { | ||
| rawCSS = cssblock; | ||
| } else { | ||
| return null; | ||
| } | ||
| const lines = rawCSS.replace(/\s+/g, ' ').trim().split('}'); | ||
| let block = ''; | ||
| const matches = lines | ||
| .filter((val) => val.trim().includes(classname)) | ||
| .map((block) => block.trim() + '}'); | ||
| block = matches.join('\n\n'); | ||
| return block; | ||
| } | ||
| function InjectCSS(csstext, Key, instance) { | ||
| if (instance === null) return false; | ||
| Key = Key + `_${instance}`; | ||
| const existing = document.getElementById(Key); | ||
| if (existing) { | ||
| if (existing.textContent === csstext) return true; | ||
| existing.textContent = csstext; | ||
| return true; | ||
| } | ||
| const style = document.createElement('style'); | ||
| style.id = Key; | ||
| style.type = 'text/css'; | ||
| style.textContent = csstext; | ||
| document.head.appendChild(style); | ||
| return true; | ||
| } | ||
| export async function ValidatAndLoadJSON( | ||
| cacheMap, | ||
| key, | ||
| callback, | ||
| utilityKey, | ||
| component, | ||
| instance = null | ||
| ) { | ||
| if (!cacheMap) return false; | ||
| const mapper = cacheMap[utilityKey]; | ||
| let type = mapper?.csspath !== null ? CACHETYPE.CSS : CACHETYPE.Default; | ||
| const res = await SJCManager( | ||
| mapper['jsonpath'], | ||
| mapper['csspath'], | ||
| type, | ||
| component, | ||
| utilityKey, | ||
| key, | ||
| 'class', | ||
| instance | ||
| ); | ||
| callback((prev) => { | ||
| if (prev[utilityKey] === res) return prev; | ||
| return { ...prev, [utilityKey]: res }; | ||
| }); | ||
| return { config: { [utilityKey]: res }, status: res !== null }; | ||
| } |
+14
-6
| { | ||
| "name": "dyvix-ui", | ||
| "version": "0.2.2", | ||
| "version": "0.2.4", | ||
| "description": "Dyvix is an open source, modern, config-driven, animated component UI library. Beautiful by default, customizable by design.", | ||
@@ -8,5 +8,8 @@ "main": "src/index.jsx", | ||
| "license": "MIT", | ||
| "workspaces": [ | ||
| "devbench" | ||
| ], | ||
| "peerDependencies": { | ||
| "react": ">=18.0.0", | ||
| "react-dom": ">=18.0.0" | ||
| "react": "^19.2.4", | ||
| "react-dom": "^19.2.4" | ||
| }, | ||
@@ -16,5 +19,5 @@ "devDependencies": { | ||
| "prettier": "^3.8.1", | ||
| "react": "^18.3.1", | ||
| "react-dom": "^18.3.1", | ||
| "vite": "^8.0.0", | ||
| "react": "^19.2.4", | ||
| "react-dom": "^19.2.4", | ||
| "vite": "8.0.8", | ||
| "vitepress": "^1.6.4" | ||
@@ -36,2 +39,4 @@ }, | ||
| "scripts": { | ||
| "dev": "npm run dev -w devbench", | ||
| "bench:install": "npm install -w devbench", | ||
| "format": "prettier --write . --ignore-path .gitignore", | ||
@@ -43,3 +48,6 @@ "prebuild": "npm run dyvix:build", | ||
| "dyvix:build": "node dev-engine/scripts/generate-constants.js" | ||
| }, | ||
| "overrides": { | ||
| "esbuild": "^0.25.0" | ||
| } | ||
| } |
+4
-5
@@ -1,6 +0,5 @@ | ||
| <h1 align="center"> | ||
| <img width="100" height="100" alt="Dyvix logo" src="https://raw.githubusercontent.com/younisdev/dyvix-ui/main/src/static/logo.png" /> | ||
| <br /> | ||
| Dyvix UI | ||
| </h1> | ||
| <div align="center"> | ||
| <img src="https://raw.githubusercontent.com/younisdev/dyvix-ui/main/src/static/logo.png" width="100" height="100" alt="Dyvix UI Logo" /> | ||
| <h1>Dyvix UI</h1> | ||
| </div> | ||
| <h4 align="center"> | ||
@@ -7,0 +6,0 @@ Dyvix is an open source, modern, config-driven, animated component UI library. Beautiful by default, customizable by design. |
@@ -164,3 +164,3 @@ .dyvix-modal-wrapper { | ||
| position: relative; | ||
| padding-bottom: 10px; | ||
| padding-bottom: 1rem; | ||
| align-items: center; | ||
@@ -170,2 +170,6 @@ min-height: 40px; | ||
| .dyvix-error-text { | ||
| white-space: nowrap; | ||
| overflow: hidden; | ||
| text-overflow: ellipsis; | ||
| font-size: 0.75rem; | ||
| position: absolute; | ||
@@ -172,0 +176,0 @@ bottom: -0.3rem; |
@@ -19,3 +19,3 @@ export function ExecuteValidator(value, validators) { | ||
| export function ExecuteRegex(value, Rgx, errorMsg = 'Invalid format') { | ||
| console.log(Rgx) | ||
| console.log(Rgx); | ||
| const Regex = new RegExp(Rgx); | ||
@@ -29,4 +29,3 @@ return { status: Regex.test(value), error: errorMsg }; | ||
| return true; | ||
| } | ||
| catch(e) { | ||
| } catch (e) { | ||
| return false; | ||
@@ -33,0 +32,0 @@ } |
@@ -0,11 +1,3 @@ | ||
| import { validType, eleData, validRules } from './modal'; | ||
| import { | ||
| validType, | ||
| vaildThemes, | ||
| validAnimations, | ||
| eleData, | ||
| validPreset, | ||
| validRules | ||
| } from './modal'; | ||
| import presetData from './dependencies/presets.json'; | ||
| import { | ||
| EvaluateFailure, | ||
@@ -16,3 +8,23 @@ GaurdStatus, | ||
| import { isValidRegex } from './dependencies/validator/validators'; | ||
| import { ValidatAndLoadJSON } from '../../utils/Smart Json Caching/SJCManager'; | ||
| const CacheMapping = { | ||
| theme: { | ||
| jsonpath: '../../components/modal/dependencies/themes.json', | ||
| csspath: '../../components/modal/dependencies/style/themes.css' | ||
| }, | ||
| animation: { | ||
| jsonpath: '../../components/animations.json', | ||
| csspath: null | ||
| }, | ||
| preset: { | ||
| jsonpath: '../../components/modal/dependencies/presets.json', | ||
| csspath: null | ||
| }, | ||
| types: { | ||
| jsonpath: '../../components/modal/dependencies/types.json', | ||
| csspath: null | ||
| } | ||
| }; | ||
| const component = 'Modal'; | ||
| const defaultElement = { | ||
@@ -39,4 +51,5 @@ type: '!/', | ||
| ]; | ||
| let config = null; | ||
| export function SerializeData( | ||
| export async function SerializeData( | ||
| title, | ||
@@ -50,5 +63,7 @@ type, | ||
| Class, | ||
| onSubmit | ||
| onSubmit, | ||
| callback, | ||
| instance | ||
| ) { | ||
| const validator = ValidateInput( | ||
| const validator = await ValidateInput( | ||
| title, | ||
@@ -62,3 +77,5 @@ type, | ||
| Class, | ||
| onSubmit | ||
| onSubmit, | ||
| callback, | ||
| instance | ||
| ); | ||
@@ -69,10 +86,6 @@ | ||
| } | ||
| elements = | ||
| preset !== '!/' | ||
| ? presetData.find( | ||
| (e) => e.preset.trim().toLowerCase() === preset.trim().toLowerCase() | ||
| )?.['fields'] || elements | ||
| : elements; | ||
| const presetData = config.find((item) => item.preset); | ||
| const finalElements = preset !== '!/' ? presetData?.preset?.fields : elements; | ||
| const normalizedElements = normalizeElements( | ||
| elements.map((ele) => ({ ...defaultElement, ...ele })) | ||
| finalElements?.map((ele) => ({ ...defaultElement, ...ele })) | ||
| ); | ||
@@ -84,6 +97,5 @@ const eleValidator = validateElements(normalizedElements); | ||
| } | ||
| return normalizedElements; | ||
| } | ||
| export function ValidateInput( | ||
| export async function ValidateInput( | ||
| title, | ||
@@ -97,6 +109,27 @@ type, | ||
| Class, | ||
| onSubmit | ||
| onSubmit, | ||
| callback, | ||
| instance | ||
| ) { | ||
| const [isAnimation, isTheme, isPreset] = await Promise.all([ | ||
| ValidatAndLoadJSON( | ||
| CacheMapping, | ||
| animation, | ||
| callback, | ||
| 'animation', | ||
| component | ||
| ), | ||
| ValidatAndLoadJSON( | ||
| CacheMapping, | ||
| theme, | ||
| callback, | ||
| 'theme', | ||
| component, | ||
| instance | ||
| ), | ||
| ValidatAndLoadJSON(CacheMapping, preset, callback, 'preset', component) | ||
| ]); | ||
| if (preset !== '!/') { | ||
| if (!validPreset.includes(preset)) { | ||
| if (!isPreset.status) { | ||
| return { | ||
@@ -109,7 +142,3 @@ status: GaurdStatus.Error, | ||
| if ( | ||
| animation !== '!/' && | ||
| !validAnimations.includes(animation) && | ||
| allowsNull(animation) | ||
| ) { | ||
| if (animation !== '!/' && !isAnimation.status && allowsNull(animation)) { | ||
| return { | ||
@@ -120,3 +149,3 @@ status: GaurdStatus.Error, | ||
| } | ||
| if (!vaildThemes.includes(theme)) { | ||
| if (!isTheme.status) { | ||
| return { | ||
@@ -127,2 +156,5 @@ status: GaurdStatus.Error, | ||
| } | ||
| config = [isTheme.config, isAnimation.config, isPreset.config]; | ||
| if (onSubmit !== undefined && typeof onSubmit !== 'function') { | ||
@@ -153,6 +185,6 @@ return { | ||
| } | ||
| function validateElements(elements) { | ||
| export function validateElements(elements) { | ||
| const MAX_ROWS = 9; | ||
| if (elements.length > MAX_ROWS) { | ||
| if (elements?.length > MAX_ROWS) { | ||
| console.warn( | ||
@@ -162,3 +194,3 @@ `[Dyvix UI] Maximum of ${MAX_ROWS} rows allowed. Extra rows will be ignored.` | ||
| elements.splice(MAX_ROWS); // trims array IN-PLACE | ||
| elements?.splice(MAX_ROWS); // trims array IN-PLACE | ||
| } | ||
@@ -240,10 +272,10 @@ for (const element of elements) { | ||
| } | ||
| // Handels Validator engine validator | ||
| // Supports regex | ||
| const rules = Array.isArray(element.validation) | ||
| ? element.validation | ||
| : [element.validation]; | ||
| const rules = Array.isArray(element.validation) | ||
| ? element.validation | ||
| : [element.validation]; | ||
| if(rules.length > element.amount) { | ||
| if (rules.length > element.amount) { | ||
| return { | ||
@@ -255,13 +287,10 @@ status: GaurdStatus.Error, | ||
| for(const rule of rules) | ||
| { | ||
| if (rule === "!/") break; | ||
| for (const rule of rules) { | ||
| if (rule === '!/') break; | ||
| if (!rule || typeof rule !== 'string') continue; | ||
| if (rule.startsWith('$R')) | ||
| { | ||
| if (rule.startsWith('$R')) { | ||
| const [pattern, customError] = rule.slice(2).split('|'); | ||
| if(!isValidRegex(pattern)) | ||
| { | ||
| if (!isValidRegex(pattern)) { | ||
| return { | ||
@@ -272,6 +301,3 @@ status: GaurdStatus.Error, | ||
| } | ||
| } | ||
| else if (!validRules.includes(rule)) | ||
| { | ||
| } else if (!validRules.includes(rule)) { | ||
| return { | ||
@@ -282,3 +308,2 @@ status: GaurdStatus.Error, | ||
| } | ||
| } | ||
@@ -299,4 +324,4 @@ } | ||
| } | ||
| function normalizeElements(elements) { | ||
| return elements.map((ele) => ({ | ||
| export function normalizeElements(elements) { | ||
| return elements?.map((ele) => ({ | ||
| ...ele, | ||
@@ -303,0 +328,0 @@ placeholder: |
| import elementsData from './dependencies/elements.json'; | ||
| import themesData from './dependencies/themes.json'; | ||
| import DynamicSelect from '../select/SelectCompiler'; | ||
| import animationsData from '../animations.json'; | ||
| import validationData from './dependencies/validator/validators.json'; | ||
| import typesData from './dependencies/types.json'; | ||
| import presetData from './dependencies/presets.json'; | ||
| import './dependencies/style/elements.css'; | ||
| import './dependencies/style/themes.css'; | ||
| import * as validatorsFunctions from './dependencies/validator/validators'; | ||
@@ -15,11 +11,18 @@ import { | ||
| } from './dependencies/validator/validators'; | ||
| import { | ||
| SJCManager, | ||
| CACHETYPE | ||
| } from '../../utils/Smart Json Caching/SJCManager'; | ||
| import React from 'react'; | ||
| import { useGSAP } from '@gsap/react'; | ||
| import gsap from 'gsap'; | ||
| import { SerializeData } from './InputValidation'; | ||
| import { | ||
| SerializeData, | ||
| normalizeElements, | ||
| validateElements | ||
| } from './InputValidation'; | ||
| import { GaurdStatus } from '../../utils/DyvixGuard'; | ||
| import Version from '../../../package.json'; | ||
| export const vaildThemes = themesData.map((e) => e.theme); | ||
| export const validType = typesData.map((e) => e.type); | ||
| export const validAnimations = animationsData.map((e) => e.animation); | ||
| export const validPreset = presetData.map((e) => e.preset); | ||
| export const validRules = validationData.map((e) => e.preset); | ||
@@ -60,15 +63,6 @@ | ||
| const [status, SetStatus] = React.useState('entering'); | ||
| const fields = SerializeData( | ||
| title, | ||
| type, | ||
| elements, | ||
| preset, | ||
| theme, | ||
| animation, | ||
| Id, | ||
| Class, | ||
| onSubmit | ||
| ); | ||
| const [configs, SetConfig] = React.useState({}); | ||
| const [fields, SetFields] = React.useState(null); | ||
| const instanceId = React.useId(); | ||
| const modalRef = React.useRef(null); | ||
| function handleInputChange(name, value) { | ||
@@ -126,25 +120,12 @@ const validation = handleValidation(); | ||
| if (fields === null) { | ||
| return null; | ||
| } | ||
| const currentType = typesData.find( | ||
| (e) => e.type.trim().toLowerCase() === type.trim().toLowerCase() | ||
| ); | ||
| const currentTheme = themesData.find( | ||
| (e) => e.theme.trim().toLowerCase() === theme.trim().toLowerCase() | ||
| ); | ||
| const currentTheme = configs['theme']; | ||
| const animationQuery = | ||
| animation === '!/' ? currentTheme['default-animation'] : animation; | ||
| const currentAnimation = animation | ||
| ? animationsData.find( | ||
| (e) => | ||
| e.animation.trim().toLowerCase() === | ||
| animationQuery.trim().toLowerCase() | ||
| ) | ||
| : null; | ||
| const currentPreset = presetData.find( | ||
| (e) => e.preset.trim().toLowerCase() === preset.trim().toLowerCase() | ||
| ); | ||
| animation === '!/' ? currentTheme?.['default-animation'] : animation; | ||
| const currentAnimation = configs['animation']; // add default animation for this new update | ||
| const currentPreset = configs['preset']; | ||
| const serilaizedClass = | ||
| Class + ` ${currentTheme.class}` + ` ${currentType.class}`; | ||
| Class + ` ${currentTheme?.class}` + ` ${currentType.class}`; | ||
| // Dynamicily calculate modal sizing and position | ||
@@ -162,5 +143,5 @@ const heightMap = { | ||
| }; | ||
| let idealSize = heightMap[fields.length] || '26rem'; | ||
| const geometryBuffer = currentTheme['radiused'] | ||
| ? (2.5 * fields.length) / 3 | ||
| let idealSize = heightMap[fields?.length] || '26rem'; | ||
| const geometryBuffer = currentTheme?.radiused | ||
| ? (2.5 * fields?.length) / 3 | ||
| : 0; | ||
@@ -171,11 +152,42 @@ idealSize = `calc(${idealSize} + ${geometryBuffer}rem)`; | ||
| const dynamicWidth = `min(${idealSize}, 95vw, 95vh)`; | ||
| const isCentered = fields.length <= 5; | ||
| const dynamicMargin = isCentered ? '15vh auto' : '1.5rem auto'; | ||
| const isCentered = fields?.length <= 5; | ||
| const dynamicMargin = isCentered ? '12vh auto' : '1.5rem auto'; | ||
| const modalStyles = { | ||
| height: dynamicHeight, | ||
| width: dynamicWidth, | ||
| margin: dynamicMargin, | ||
| transition: 'all 0.3s ease-out' | ||
| }; | ||
| if (currentPreset) { | ||
| title = title !== '!/' ? title : currentPreset['default-title']; | ||
| } | ||
| React.useEffect(() => { | ||
| async function GetFields() { | ||
| const data = await SerializeData( | ||
| title, | ||
| type, | ||
| elements, | ||
| preset, | ||
| theme, | ||
| animation, | ||
| Id, | ||
| Class, | ||
| onSubmit, | ||
| SetConfig, | ||
| instanceId | ||
| ); | ||
| SetFields(data); | ||
| } | ||
| GetFields(); | ||
| return () => { | ||
| const key = `DYVIX_${Version['version']}_Modal_theme_${instanceId}`; | ||
| const ele = document.getElementById(key); | ||
| if (ele) ele.remove(); | ||
| }; | ||
| }, [theme, preset, elements, animation, title]); | ||
| React.useEffect(() => { | ||
| fields.forEach((field) => { | ||
| fields?.forEach((field) => { | ||
| field.name.forEach((name) => { | ||
@@ -185,3 +197,3 @@ SetData((prev) => ({ ...prev, [name]: null })); | ||
| }); | ||
| }, []); | ||
| }, [fields]); | ||
@@ -231,7 +243,3 @@ // Auto-focus for the first input when modal opens | ||
| ref={modalRef} | ||
| style={{ | ||
| height: dynamicHeight, | ||
| width: dynamicWidth, | ||
| position: 'relative' | ||
| }} | ||
| style={modalStyles} | ||
| > | ||
@@ -244,4 +252,4 @@ {currentType.closable && ( | ||
| style={{ | ||
| top: currentTheme.radiused ? '2rem' : '1rem', | ||
| right: currentTheme.radiused ? '9rem' : '1rem' | ||
| top: currentTheme?.radiused ? '2rem' : '1rem', | ||
| right: currentTheme?.radiused ? '9rem' : '1rem' | ||
| }} | ||
@@ -253,3 +261,3 @@ > | ||
| <h3 id="modal-header">{title}</h3> | ||
| {fields.map((field, i) => { | ||
| {fields?.map((field, i) => { | ||
| const elementDef = | ||
@@ -256,0 +264,0 @@ elementsData.find((e) => e.element === field.type) || |
+1
-0
@@ -6,1 +6,2 @@ export { default as Modal } from './components/modal/modal'; | ||
| export { default as dyvixToast } from './components/toast/bus'; | ||
| export { default as DyvixButton } from './components/button/button'; |
86498
21.23%36
20%2837
24.98%78
-1.27%