+2
-0
@@ -14,1 +14,3 @@ # Contributors | ||
| - [@0rbiT-ai](https://github.com/0rbiT-ai) | ||
| - [@dineshg0pal](https://github.com/dineshg0pal) | ||
| - [@DevBadoory](https://github.com/DevBadoory) |
+1
-1
| { | ||
| "name": "dyvix-ui", | ||
| "version": "0.2.0", | ||
| "version": "0.2.2", | ||
| "description": "Dyvix is an open source, modern, config-driven, animated component UI library. Beautiful by default, customizable by design.", | ||
@@ -5,0 +5,0 @@ "main": "src/index.jsx", |
@@ -16,3 +16,2 @@ [ | ||
| "ease": "elastic.out(0.8, 0.6)", | ||
| "components": ["modal"], | ||
| "from": { "scale": 0, "opacity": 0 }, | ||
@@ -26,3 +25,2 @@ "to": { "scale": 1, "opacity": 1 } | ||
| "ease": "power3.out", | ||
| "components": ["modal"], | ||
| "from": { "scale": 1.15, "opacity": 0 }, | ||
@@ -36,3 +34,2 @@ "to": { "scale": 1, "opacity": 1 } | ||
| "ease": "power2.out", | ||
| "components": ["modal"], | ||
| "from": { "scaleY": -0.1, "opacity": 0, "transformOrigin": "top center" }, | ||
@@ -46,3 +43,2 @@ "to": { "scaleY": 1, "opacity": 1 } | ||
| "ease": "power4.out", | ||
| "components": ["modal"], | ||
| "from": { "x": -60, "skewX": 40, "opacity": 0 }, | ||
@@ -56,3 +52,2 @@ "to": { "x": 0, "skewX": 0, "opacity": 1 } | ||
| "ease": "elastic.out(0.6, 0.4)", | ||
| "components": ["modal"], | ||
| "from": { "scale": 0.8, "opacity": 0, "filter": "blur(50px)" }, | ||
@@ -66,3 +61,2 @@ "to": { "scale": 1, "opacity": 1, "filter": "blur(0px)" } | ||
| "ease": "power2.out", | ||
| "components": ["modal"], | ||
| "from": { "y": 80, "opacity": 0, "filter": "blur(35px)" }, | ||
@@ -76,3 +70,2 @@ "to": { "y": 0, "opacity": 1, "filter": "blur(0px)" } | ||
| "ease": "back.out(1.2)", | ||
| "components": ["modal"], | ||
| "from": { "y": -150, "opacity": 0 }, | ||
@@ -86,3 +79,2 @@ "to": { "y": 0, "opacity": 1 } | ||
| "ease": "power2.out", | ||
| "components": ["modal"], | ||
| "from": { "rotationY": 190, "opacity": 0 }, | ||
@@ -96,6 +88,13 @@ "to": { "rotationY": 0, "opacity": 1 } | ||
| "ease": "back.out(1.7)", | ||
| "components": ["modal"], | ||
| "from": { "rotationX": -10, "opacity": 0, "scale": 0.9, "y": 30 }, | ||
| "to": { "rotationX": 0, "opacity": 1, "scale": 1, "y": 0 } | ||
| }, | ||
| { | ||
| "animation": "drift", | ||
| "type": "in", | ||
| "default-duration": 0.97, | ||
| "ease": "power3.inOut", | ||
| "from": { "opacity": 0.1, "scale": 0.77, "y": 60 }, | ||
| "to": { "opacity": 1, "scale": 1, "y": 0 } | ||
| } | ||
| ] |
@@ -256,1 +256,31 @@ .dyvix-modal-lens { | ||
| } | ||
| .dyvix-modal-midnight { | ||
| background: radial-gradient(circle at center, #00024d 0%, #000000 100%); | ||
| border: 1px solid rgba(112, 168, 247, 0.3); | ||
| border-radius: 3.67rem; | ||
| box-shadow: | ||
| 0 0 40px 5px rgba(99, 102, 241, 0.2), | ||
| inset 0 0 15px rgba(112, 168, 247, 0.1); | ||
| -webkit-box-shadow: | ||
| 0 0 40px 5px rgba(99, 102, 241, 0.2), | ||
| inset 0 0 15px rgba(112, 168, 247, 0.1); | ||
| -moz-box-shadow: | ||
| 0 0 40px 5px rgba(99, 102, 241, 0.2), | ||
| inset 0 0 15px rgba(112, 168, 247, 0.1); | ||
| transition: all 0.5s cubic-bezier(0.17, 0.88, 0.3, 1.67); | ||
| backdrop-filter: blur(10px); | ||
| } | ||
| .dyvix-modal-midnight:hover { | ||
| background: radial-gradient(circle at 40% 40%, #000375 0%, #000000 100%); | ||
| border-color: rgba(112, 168, 247, 0.6); | ||
| box-shadow: | ||
| 0 0 60px 10px rgba(99, 102, 241, 0.4), | ||
| inset 0 0 20px rgba(112, 168, 247, 0.2); | ||
| -webkit-box-shadow: | ||
| 0 0 60px 10px rgba(99, 102, 241, 0.4), | ||
| inset 0 0 20px rgba(112, 168, 247, 0.2); | ||
| -moz-box-shadow: | ||
| 0 0 60px 10px rgba(99, 102, 241, 0.4), | ||
| inset 0 0 20px rgba(112, 168, 247, 0.2); | ||
| transform: translateY(-3px) scale(1.01); | ||
| } |
@@ -61,3 +61,9 @@ [ | ||
| "radiused": false | ||
| }, | ||
| { | ||
| "theme": "Midnight", | ||
| "class": "dyvix-modal-midnight", | ||
| "default-animation": "drift", | ||
| "radiused": false | ||
| } | ||
| ] |
@@ -1,2 +0,2 @@ | ||
| export default function ExecuteValidator(value, validators) { | ||
| export function ExecuteValidator(value, validators) { | ||
| for (const validator of validators) { | ||
@@ -18,2 +18,18 @@ const [funcName, optionParam] = validator.split('('); | ||
| export function ExecuteRegex(value, Rgx, errorMsg = 'Invalid format') { | ||
| console.log(Rgx) | ||
| const Regex = new RegExp(Rgx); | ||
| return { status: Regex.test(value), error: errorMsg }; | ||
| } | ||
| export function isValidRegex(pattern) { | ||
| try { | ||
| new RegExp(pattern); | ||
| return true; | ||
| } | ||
| catch(e) { | ||
| return false; | ||
| } | ||
| } | ||
| const VALIDATORS_REGISTERY = { | ||
@@ -20,0 +36,0 @@ isEmail: (value, options = {}) => ({ |
@@ -6,3 +6,4 @@ import { | ||
| eleData, | ||
| validPreset | ||
| validPreset, | ||
| validRules | ||
| } from './modal'; | ||
@@ -15,2 +16,3 @@ import presetData from './dependencies/presets.json'; | ||
| } from '../../utils/DyvixGuard'; | ||
| import { isValidRegex } from './dependencies/validator/validators'; | ||
@@ -22,2 +24,3 @@ const defaultElement = { | ||
| className: '!/', | ||
| validation: '!/', | ||
| amount: 1 | ||
@@ -144,2 +147,11 @@ }; | ||
| function validateElements(elements) { | ||
| const MAX_ROWS = 9; | ||
| if (elements.length > MAX_ROWS) { | ||
| console.warn( | ||
| `[Dyvix UI] Maximum of ${MAX_ROWS} rows allowed. Extra rows will be ignored.` | ||
| ); | ||
| elements.splice(MAX_ROWS); // trims array IN-PLACE | ||
| } | ||
| for (const element of elements) { | ||
@@ -160,3 +172,3 @@ const currentType = | ||
| status: GaurdStatus.Error, | ||
| error: `Field '${element.id}' requires an options array.` | ||
| error: `Field '${element.name}' requires an options array.` | ||
| }; | ||
@@ -172,3 +184,3 @@ } | ||
| status: GaurdStatus.Error, | ||
| error: `Amount mismatch for '${element.id}'. Expected ${element.amount} option sets.` | ||
| error: `Amount mismatch for '${element.name}'. Expected ${element.amount} option sets.` | ||
| }; | ||
@@ -222,2 +234,43 @@ } | ||
| } | ||
| // Handels Validator engine validator | ||
| // Supports regex | ||
| const rules = Array.isArray(element.validation) | ||
| ? element.validation | ||
| : [element.validation]; | ||
| if(rules.length > element.amount) { | ||
| return { | ||
| status: GaurdStatus.Error, | ||
| error: `Validation overflow: maximum of amount of ${element.amount} reached.` | ||
| }; | ||
| } | ||
| for(const rule of rules) | ||
| { | ||
| if (rule === "!/") break; | ||
| if (!rule || typeof rule !== 'string') continue; | ||
| if (rule.startsWith('$R')) | ||
| { | ||
| const [pattern, customError] = rule.slice(2).split('|'); | ||
| if(!isValidRegex(pattern)) | ||
| { | ||
| return { | ||
| status: GaurdStatus.Error, | ||
| error: `Invalid Regular Expression was provided.` | ||
| }; | ||
| } | ||
| } | ||
| else if (!validRules.includes(rule)) | ||
| { | ||
| return { | ||
| status: GaurdStatus.Error, | ||
| error: `'${rule}' is not a recognized validator.` | ||
| }; | ||
| } | ||
| } | ||
| } | ||
@@ -224,0 +277,0 @@ |
@@ -11,3 +11,6 @@ import elementsData from './dependencies/elements.json'; | ||
| import * as validatorsFunctions from './dependencies/validator/validators'; | ||
| import ExecuteValidator from './dependencies/validator/validators'; | ||
| import { | ||
| ExecuteValidator, | ||
| ExecuteRegex | ||
| } from './dependencies/validator/validators'; | ||
| import React from 'react'; | ||
@@ -22,2 +25,3 @@ import { useGSAP } from '@gsap/react'; | ||
| export const validPreset = presetData.map((e) => e.preset); | ||
| export const validRules = validationData.map((e) => e.preset); | ||
@@ -56,2 +60,3 @@ export const eleData = elementsData; | ||
| const [visibility, SetVisibility] = React.useState(true); | ||
| const [status, SetStatus] = React.useState('entering'); | ||
| const fields = SerializeData( | ||
@@ -79,3 +84,3 @@ title, | ||
| function handleModalClose() { | ||
| SetVisibility(false); | ||
| SetStatus('leaving'); | ||
| if (typeof onClose === 'function') { | ||
@@ -89,20 +94,21 @@ onClose(); | ||
| if (!field.validation) continue; | ||
| for (const [index, currentName] of field.name.entries()) { | ||
| const currentValidation = field.validation[index]; | ||
| let currentValidation = field.validation[index]; | ||
| let result = null; | ||
| if (!currentValidation) continue; | ||
| const validators = validationData.find( | ||
| (e) => | ||
| e.preset.trim().toLowerCase() === | ||
| currentValidation.trim().toLowerCase() | ||
| ); | ||
| if (currentValidation.startsWith('$R')) { | ||
| const [pattern, customError] = currentValidation.slice(2).split('|'); | ||
| result = ExecuteRegex(data[currentName], pattern, customError); | ||
| } else { | ||
| const validators = validationData.find( | ||
| (e) => | ||
| e.preset.trim().toLowerCase() === | ||
| currentValidation.trim().toLowerCase() | ||
| ); | ||
| if (!validators) continue; | ||
| if (!validators) continue; | ||
| const result = ExecuteValidator( | ||
| data[currentName], | ||
| validators.validators | ||
| ); | ||
| result = ExecuteValidator(data[currentName], validators.validators); | ||
| } | ||
| if (result) { | ||
@@ -199,9 +205,19 @@ newErrors[currentName] = result.status ? null : result.error; | ||
| gsap.set(modalRef.current, { margin: dynamicMargin }); | ||
| gsap.fromTo(modalRef.current, currentAnimation.from, { | ||
| ...currentAnimation.to, | ||
| duration: currentAnimation['default-duration'], | ||
| ease: currentAnimation.ease | ||
| }); | ||
| }, [currentAnimation]); | ||
| if (status === 'entering') { | ||
| gsap.fromTo(modalRef.current, currentAnimation.from, { | ||
| ...currentAnimation.to, | ||
| duration: currentAnimation['default-duration'], | ||
| ease: currentAnimation.ease | ||
| }); | ||
| } else { | ||
| gsap.fromTo(modalRef.current, currentAnimation.to, { | ||
| ...currentAnimation.from, | ||
| duration: currentAnimation['default-duration'], | ||
| ease: currentAnimation.ease, | ||
| onComplete: () => SetVisibility(false) | ||
| }); | ||
| } | ||
| }, [currentAnimation, status]); | ||
| return ( | ||
@@ -250,3 +266,3 @@ <> | ||
| const id = field.id[j]; | ||
| const fontSize = field.amount === 3 ? '0.5rem' : 'normal'; | ||
| const fontSize = field.amount === 3 ? '0.6rem' : 'normal'; | ||
| const fontWeight = field.amount === 3 ? '520' : '200'; | ||
@@ -287,2 +303,8 @@ // Spread aria props safely to avoid runtime errors if elementDef.aria is missing or null | ||
| } | ||
| const options = | ||
| Tag === 'select' || elementDef.tag === 'DynamicSelect' | ||
| ? Array.isArray(field.options[0]) | ||
| ? field.options[j] | ||
| : field.options | ||
| : []; | ||
@@ -307,3 +329,3 @@ const Tagprobs = { | ||
| ...(elementDef.tag === 'DynamicSelect' && { | ||
| elements: field.options[j], | ||
| elements: options, | ||
| animation: '!/', | ||
@@ -329,3 +351,3 @@ Class: 'modal-element' | ||
| </option> | ||
| {field.options[j].map((opt, index) => ( | ||
| {options.map((opt, index) => ( | ||
| <option | ||
@@ -332,0 +354,0 @@ role="option" |
@@ -56,7 +56,2 @@ .dyvix-select-wrapper { | ||
| } | ||
| .dyvix-dropdown-select li:hover { | ||
| transform: scale(1.003); | ||
| background-color: #e0f7fa; | ||
| cursor: pointer; | ||
| } | ||
| .dyvix-dropdown-select li:active { | ||
@@ -63,0 +58,0 @@ background-color: #b2ebf2; |
@@ -11,2 +11,11 @@ import './dependencies/style/styles.css'; | ||
| /** | ||
| * @param {Object} props | ||
| * @param {Array<Object>} props.elements - Array of select elements | ||
| * @param {Function} [props.onChange] - Change callback | ||
| * @param {('select'|'autocomplete')} props.type - Select type | ||
| * @param {string} [props.animation] - Animation name, defaults to fade | ||
| * @param {string} [props.Class] - Select class | ||
| * @param {string} [props.placeholder] - Select placeholder | ||
| */ | ||
| function DynamicSelect({ | ||
@@ -18,3 +27,3 @@ elements = [], | ||
| Class, | ||
| placeholder = 'Select...', | ||
| placeholder = '', | ||
| ...props | ||
@@ -28,3 +37,4 @@ }) { | ||
| elements: [], | ||
| selected: '' | ||
| selected: '', | ||
| activeIndex: -1 | ||
| }); | ||
@@ -93,2 +103,42 @@ const selectRef = React.useRef(null); | ||
| function HandleKey(e, controller) { | ||
| if (Select.is_open == false) return; | ||
| const { key } = e; | ||
| const max = Select.elements.length - 1; | ||
| const min = -1; | ||
| const index = Select.activeIndex; | ||
| if (key === 'ArrowUp') { | ||
| if (index <= min) return; | ||
| controller((prevData) => ({ | ||
| ...prevData, | ||
| activeIndex: index - 1 | ||
| })); | ||
| e.preventDefault(); | ||
| } | ||
| if (key === 'ArrowDown') { | ||
| if (index >= max) return; | ||
| controller((prevData) => ({ | ||
| ...prevData, | ||
| activeIndex: index + 1 | ||
| })); | ||
| e.preventDefault(); | ||
| } | ||
| if (key === 'Enter') { | ||
| if (index < 0 || index > max) return; | ||
| selectRef.current.value = Select.elements[index]; | ||
| controller((prevData) => ({ | ||
| ...prevData, | ||
| selected: Select.elements[index], | ||
| is_open: false, | ||
| activeIndex: -1 | ||
| })); | ||
| e.preventDefault(); | ||
| } | ||
| } | ||
| useGSAP(() => { | ||
@@ -126,2 +176,3 @@ if (!selectRef.current || !currentAnimation) return; | ||
| }} | ||
| onKeyDown={(e) => HandleKey(e, SetSelect)} | ||
| /> | ||
@@ -133,2 +184,3 @@ <SelectEngine | ||
| inputRef={selectRef} | ||
| activeIndex={Select.activeIndex} | ||
| ref={dropdownSelectRef} | ||
@@ -135,0 +187,0 @@ controller={SetSelect} |
@@ -15,2 +15,3 @@ import './dependencies/style/styles.css'; | ||
| controller, | ||
| activeIndex, | ||
| inputRef, | ||
@@ -32,3 +33,4 @@ OnChangeCallback, | ||
| ...prevData, | ||
| is_open: false | ||
| is_open: false, | ||
| activeIndex: -1 | ||
| })); | ||
@@ -77,2 +79,8 @@ | ||
| useEffect(() => { | ||
| if (activeIndex >= 0 && itemsRef.current[activeIndex]) { | ||
| itemsRef.current[activeIndex].scrollIntoView({ block: 'nearest' }); | ||
| } | ||
| }, [activeIndex]); | ||
| return ( | ||
@@ -97,2 +105,7 @@ <> | ||
| key={index} | ||
| style={ | ||
| index === activeIndex | ||
| ? { backgroundColor: '#e0f7fa', cursor: 'pointer' } | ||
| : {} | ||
| } | ||
| onMouseDown={(e) => { | ||
@@ -102,2 +115,8 @@ e.preventDefault(); | ||
| }} | ||
| onMouseEnter={() => { | ||
| controller((prevData) => ({ | ||
| ...prevData, | ||
| activeIndex: index | ||
| })); | ||
| }} | ||
| > | ||
@@ -104,0 +123,0 @@ {element} |
@@ -17,3 +17,11 @@ [ | ||
| "class": "dyvix-bottom-right" | ||
| }, | ||
| { | ||
| "position": "top-center", | ||
| "class": "dyvix-top-center" | ||
| }, | ||
| { | ||
| "position": "bottom-center", | ||
| "class": "dyvix-bottom-center" | ||
| } | ||
| ] |
@@ -12,2 +12,5 @@ .dyvix-toast-container { | ||
| display: flex; | ||
| flex-direction: column; | ||
| text-align: left; | ||
| gap: 4px; | ||
| width: 20rem; | ||
@@ -17,16 +20,56 @@ min-height: 4.5rem; | ||
| height: auto; | ||
| background-color: #ff3366; | ||
| border-radius: 2rem; | ||
| border-radius: 1.5rem; | ||
| align-items: flex-start; | ||
| padding: 1rem 1.2rem; | ||
| box-sizing: border-box; | ||
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); | ||
| } | ||
| .dyvix-toast-error { | ||
| background-color: #fcbec6; | ||
| border: 1px solid rgba(255, 51, 102, 0.3); | ||
| } | ||
| .dyvix-toast-success { | ||
| background-color: #a8fcb4; | ||
| border: 1px solid rgba(20, 206, 79, 0.3); | ||
| } | ||
| .dyvix-toast-title { | ||
| width: 100%; | ||
| font-weight: 600; | ||
| font-size: 0.9rem; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 6px; | ||
| } | ||
| .dyvix-toast-content { | ||
| width: 100%; | ||
| padding: 0 2rem; | ||
| font-size: 0.8rem; | ||
| opacity: 0.85; | ||
| padding-left: 30px; | ||
| } | ||
| .dyvix-toast-error { | ||
| background-color: #ff3366; | ||
| .dyvix-toast-icon { | ||
| border-radius: 50%; | ||
| border: 2px solid; | ||
| width: 18px; | ||
| height: 18px; | ||
| padding: 3px; | ||
| flex-shrink: 0; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| box-shadow: inset 0 0 4px rgba(1, 6, 2, 0.848); | ||
| } | ||
| .dyvix-toast-success { | ||
| background-color: green; | ||
| .toast-error { | ||
| color: #ff3366; | ||
| } | ||
| .toast-success { | ||
| color: #1df13c; | ||
| } | ||
| .toast-error .dyvix-toast-icon { | ||
| border-color: #ff3366; | ||
| box-shadow: inset 0 0 4px rgba(5, 2, 3, 0.848); | ||
| } | ||
| .toast-success .dyvix-toast-icon { | ||
| border-color: #14ce4f; | ||
| box-shadow: inset 0 0 4px rgba(2, 5, 3, 0.848); | ||
| } | ||
| /* move to positions.css ? */ | ||
@@ -49,1 +92,11 @@ .dyvix-top-right { | ||
| } | ||
| .dyvix-top-center { | ||
| top: 1rem; | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| } | ||
| .dyvix-bottom-center { | ||
| bottom: 1rem; | ||
| left: 50%; | ||
| transform: translateX(-50%); | ||
| } |
@@ -9,2 +9,3 @@ import React from 'react'; | ||
| animation, | ||
| type, | ||
| onClose, | ||
@@ -15,2 +16,3 @@ duration = 5000 | ||
| const [status, SetStatus] = React.useState('entering'); | ||
| const icons = { success: '✓', error: '✕', warning: '⚠', info: 'i' }; | ||
| const currentAnimation = animation | ||
@@ -38,2 +40,3 @@ ? animationsData.find( | ||
| duration: currentAnimation['default-duration'], | ||
| delay: Math.random() * 0.3, | ||
| ease: currentAnimation.ease, | ||
@@ -59,2 +62,6 @@ onComplete: onClose | ||
| <div className={Class} ref={toastRef}> | ||
| <span className={`dyvix-toast-title toast-${type.toLowerCase()}`}> | ||
| <span className="dyvix-toast-icon">{icons[type.toLowerCase()]}</span>{' '} | ||
| {type} | ||
| </span> | ||
| <span className="dyvix-toast-content">{message}</span> | ||
@@ -61,0 +68,0 @@ </div> |
@@ -15,5 +15,12 @@ import './dependencies/style/style.css'; | ||
| /** | ||
| * @param {Object} props | ||
| * @param {'top-left'|'top-right'|'top-center'|'bottom-left'|'bottom-right'|'bottom-center'} props.position - Toast postion | ||
| * @param {number} props.segments - The max amount of toast at a time | ||
| * @param {number} props.duration - The duration to show the toast for in mile seconds. | ||
| * @param {string} [props.animation] - Animation name, defaults to zoom | ||
| */ | ||
| function DyvixToastContainer({ | ||
| position = 'top-right', | ||
| segments, | ||
| segments = 10, | ||
| duration = 5000, | ||
@@ -45,3 +52,3 @@ animation = 'zoom' | ||
| <div className={`dyvix-toast-container ${currentPosition.class}`}> | ||
| {toasts.map((toast, i) => { | ||
| {toasts.slice(0, segments).map((toast, i) => { | ||
| const currentType = TypesData.find( | ||
@@ -62,2 +69,3 @@ (e) => e.type.trim().toLowerCase() === toast.type.trim().toLowerCase() | ||
| animation={animation} | ||
| type={currentType.type} | ||
| /> | ||
@@ -64,0 +72,0 @@ ); |
@@ -27,4 +27,10 @@ import { | ||
| } | ||
| if (segments <= 0 || segments > 10) { | ||
| return { | ||
| status: GaurdStatus.Error, | ||
| error: 'Please provide a segment between 1 and 10.' | ||
| }; | ||
| } | ||
| return { status: GaurdStatus.Success }; | ||
| } |
+4
-2
@@ -13,3 +13,4 @@ //AUTO GENERATED BY DYVIX UI ENGINE - DO NOT MANUALLY EDIT. | ||
| OCEAN: 'Ocean', | ||
| FOREST: 'Forest' | ||
| FOREST: 'Forest', | ||
| MIDNIGHT: 'Midnight' | ||
| }; | ||
@@ -30,3 +31,4 @@ export const DYVIX_MODAL_TYPE = { | ||
| FLIP: 'flip', | ||
| GLIDE: 'glide' | ||
| GLIDE: 'glide', | ||
| DRIFT: 'drift' | ||
| }; | ||
@@ -33,0 +35,0 @@ export const DYVIX_MODAL_VALIDATION_PRESET = { |
71351
12.67%2270
12.88%