UNPKG

@brutalcomponent/react

Version:
734 lines (729 loc) 24.7 kB
'use strict'; var chunkZERAQT5C_js = require('./chunk-ZERAQT5C.js'); var chunk7YHHVG7W_js = require('./chunk-7YHHVG7W.js'); var chunk7T4KDGYW_js = require('./chunk-7T4KDGYW.js'); var React2 = require('react'); var clsx = require('clsx'); var fa = require('react-icons/fa'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var React2__default = /*#__PURE__*/_interopDefault(React2); /** * @brutalcomponent/react * (c) David Heffler (https://dvh.sh) * Licensed under MIT */ var ButtonGroup = ({ children, className, direction = "horizontal", attached = true }) => { const childrenArray = Array.isArray(children) ? children : [children]; const validChildren = childrenArray.filter( (child) => React2__default.default.isValidElement(child) ); return /* @__PURE__ */ React2__default.default.createElement( "div", { className: clsx.clsx( "inline-flex", direction === "horizontal" ? attached ? "-space-x-px" : "gap-2" : "flex-col", direction === "vertical" && (attached ? "-space-y-px" : "gap-2"), className ) }, validChildren.map((child, index) => { if (child.type === chunkZERAQT5C_js.Button) { const childCount = validChildren.length; if (!attached) { return React2__default.default.cloneElement(child, { key: child.key || index }); } const typedChild = child; return React2__default.default.cloneElement(typedChild, { key: child.key || index, className: clsx.clsx( typedChild.props.className, direction === "horizontal" && [ index === 0 && "rounded-r-none", index === childCount - 1 && "rounded-l-none", index !== 0 && index !== childCount - 1 && "rounded-none" ], direction === "vertical" && [ index === 0 && "rounded-b-none", index === childCount - 1 && "rounded-t-none", index !== 0 && index !== childCount - 1 && "rounded-none" ] ) }); } return React2__default.default.cloneElement(child, { key: child.key || index }); }) ); }; var Input = React2__default.default.forwardRef( ({ label, error, hint, leftIcon: LeftIcon, rightIcon: RightIcon, brutal = true, size = "md", variant = "default", required = false, showPasswordToggle = false, accentColor = "brutal-pink", className, disabled, type = "text", ...props }, ref) => { const [showPassword, setShowPassword] = React2.useState(false); const [isFocused, setIsFocused] = React2.useState(false); const sizeClasses = chunk7T4KDGYW_js.getSizeClasses(size); chunk7T4KDGYW_js.getAccentClasses(accentColor); const isPassword = type === "password"; const actualType = isPassword && showPassword ? "text" : type; const hasLeftIcon = LeftIcon; const hasRightIcon = RightIcon || isPassword && showPasswordToggle; return /* @__PURE__ */ React2__default.default.createElement( "div", { className: "w-full", style: { "--accent-color": accentColor.startsWith("#") ? accentColor : `var(--brutal-${accentColor.replace("brutal-", "")})` } }, label && /* @__PURE__ */ React2__default.default.createElement( "label", { className: chunk7T4KDGYW_js.cn( "block mb-2 font-black uppercase tracking-wider text-brutal-black", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm", required && "after:content-['*'] after:ml-1 after:text-accent" ) }, label ), /* @__PURE__ */ React2__default.default.createElement("div", { className: "relative" }, hasLeftIcon && /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "absolute top-1/2 -translate-y-1/2 pointer-events-none", size === "xs" ? "left-2" : size === "sm" ? "left-2.5" : "left-3" ) }, /* @__PURE__ */ React2__default.default.createElement( chunk7YHHVG7W_js.Icon, { icon: LeftIcon, size: size === "xs" ? "xs" : size === "sm" ? "sm" : "md", className: "text-brutal-gray-500" } ) ), /* @__PURE__ */ React2__default.default.createElement( "input", { ref, type: actualType, className: chunk7T4KDGYW_js.cn( // Base styling "w-full font-mono transition-all duration-200", "bg-brutal-white text-brutal-black placeholder:text-brutal-gray-500", // Size classes sizeClasses.padding, sizeClasses.text, // Icon spacing hasLeftIcon && (size === "xs" ? "pl-8" : size === "sm" ? "pl-9" : "pl-12"), hasRightIcon && (size === "xs" ? "pr-8" : size === "sm" ? "pr-9" : "pr-12"), // Brutal styling brutal && [ sizeClasses.border, "border-brutal-black", "focus:shadow-brutal focus:-translate-x-0.5 focus:-translate-y-0.5", "hover:shadow-brutal-sm hover:-translate-x-0.25 hover:-translate-y-0.25" ], !brutal && [ "border-2 border-brutal-gray-300 rounded-md", "focus:border-accent focus:ring-2 focus:ring-accent/20" ], // Variant styling variant === "ghost" && [ "bg-transparent border-transparent", "focus:bg-brutal-white focus:border-brutal-black" ], variant === "filled" && "bg-brutal-gray-100", // States error && "border-accent", disabled && [ "opacity-50 cursor-not-allowed bg-brutal-gray-100", "hover:transform-none hover:shadow-none" ], isFocused && brutal && "shadow-brutal -translate-x-0.5 -translate-y-0.5", // Focus styles "focus:outline-none", className ), disabled, required, onFocus: (e) => { setIsFocused(true); props.onFocus?.(e); }, onBlur: (e) => { setIsFocused(false); props.onBlur?.(e); }, "aria-invalid": error ? "true" : "false", "aria-describedby": error ? `${props.id || "input"}-error` : hint ? `${props.id || "input"}-hint` : void 0, ...props } ), hasRightIcon && /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "absolute top-1/2 -translate-y-1/2", size === "xs" ? "right-2" : size === "sm" ? "right-2.5" : "right-3" ) }, isPassword && showPasswordToggle ? /* @__PURE__ */ React2__default.default.createElement( "button", { type: "button", onClick: () => setShowPassword(!showPassword), className: chunk7T4KDGYW_js.cn( "p-1 transition-colors duration-200", "hover:bg-brutal-gray-100 rounded", "focus:outline-none focus:bg-brutal-gray-100" ), "aria-label": showPassword ? "Hide password" : "Show password", tabIndex: -1 }, /* @__PURE__ */ React2__default.default.createElement( chunk7YHHVG7W_js.Icon, { icon: showPassword ? fa.FaEyeSlash : fa.FaEye, size: size === "xs" ? "xs" : size === "sm" ? "sm" : "md", className: "text-brutal-gray-500 hover:text-brutal-black" } ) ) : RightIcon ? /* @__PURE__ */ React2__default.default.createElement( chunk7YHHVG7W_js.Icon, { icon: RightIcon, size: size === "xs" ? "xs" : size === "sm" ? "sm" : "md", className: "text-brutal-gray-500" } ) : null )), hint && !error && /* @__PURE__ */ React2__default.default.createElement( "p", { id: `${props.id || "input"}-hint`, className: chunk7T4KDGYW_js.cn( "mt-2 text-brutal-gray-600 font-mono", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm" ) }, hint ), error && /* @__PURE__ */ React2__default.default.createElement( "p", { id: `${props.id || "input"}-error`, className: chunk7T4KDGYW_js.cn( "mt-2 font-black uppercase tracking-wider text-accent", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm" ), role: "alert" }, error ) ); } ); Input.displayName = "Input"; var Select = React2__default.default.forwardRef( ({ label, error, hint, options, brutal = true, size = "md", variant = "default", required = false, placeholder = "Select an option", accentColor = "brutal-pink", className, disabled, ...props }, ref) => { const [isFocused, setIsFocused] = React2.useState(false); const sizeClasses = chunk7T4KDGYW_js.getSizeClasses(size); chunk7T4KDGYW_js.getAccentClasses(accentColor); return /* @__PURE__ */ React2__default.default.createElement( "div", { className: "w-full", style: { "--accent-color": accentColor.startsWith("#") ? accentColor : `var(--brutal-${accentColor.replace("brutal-", "")})` } }, label && /* @__PURE__ */ React2__default.default.createElement( "label", { className: chunk7T4KDGYW_js.cn( "block mb-2 font-black uppercase tracking-wider text-brutal-black", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm", required && "after:content-['*'] after:ml-1 after:text-accent" ) }, label ), /* @__PURE__ */ React2__default.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React2__default.default.createElement( "select", { ref, className: chunk7T4KDGYW_js.cn( // Base styling "w-full font-mono appearance-none cursor-pointer", "bg-brutal-white text-brutal-black transition-all duration-200", // Size classes sizeClasses.padding, sizeClasses.text, // Icon spacing (for dropdown arrow) size === "xs" ? "pr-8" : size === "sm" ? "pr-9" : "pr-12", // Brutal styling brutal && [ sizeClasses.border, "border-brutal-black", "focus:shadow-brutal focus:-translate-x-0.5 focus:-translate-y-0.5", "hover:shadow-brutal-sm hover:-translate-x-0.25 hover:-translate-y-0.25" ], !brutal && [ "border-2 border-brutal-gray-300 rounded-md", "focus:border-accent focus:ring-2 focus:ring-accent/20" ], // Variant styling variant === "ghost" && [ "bg-transparent border-transparent", "focus:bg-brutal-white focus:border-brutal-black" ], variant === "filled" && "bg-brutal-gray-100", // States error && "border-accent", disabled && [ "opacity-50 cursor-not-allowed bg-brutal-gray-100", "hover:transform-none hover:shadow-none" ], isFocused && brutal && "shadow-brutal -translate-x-0.5 -translate-y-0.5", // Focus styles "focus:outline-none", className ), disabled, required, onFocus: (e) => { setIsFocused(true); props.onFocus?.(e); }, onBlur: (e) => { setIsFocused(false); props.onBlur?.(e); }, "aria-invalid": error ? "true" : "false", "aria-describedby": error ? `${props.id || "select"}-error` : hint ? `${props.id || "select"}-hint` : void 0, ...props }, placeholder && /* @__PURE__ */ React2__default.default.createElement("option", { value: "", disabled: true }, placeholder), options.map((option) => /* @__PURE__ */ React2__default.default.createElement( "option", { key: option.value, value: option.value, disabled: option.disabled }, option.label )) ), /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "absolute top-1/2 -translate-y-1/2 pointer-events-none", size === "xs" ? "right-2" : size === "sm" ? "right-2.5" : "right-3" ) }, /* @__PURE__ */ React2__default.default.createElement( chunk7YHHVG7W_js.Icon, { icon: fa.FaChevronDown, size: size === "xs" ? "xs" : size === "sm" ? "sm" : "md", className: chunk7T4KDGYW_js.cn( "transition-all duration-200", isFocused ? "text-accent rotate-180" : "text-brutal-gray-500", disabled && "opacity-50" ) } ) )), hint && !error && /* @__PURE__ */ React2__default.default.createElement( "p", { id: `${props.id || "select"}-hint`, className: chunk7T4KDGYW_js.cn( "mt-2 text-brutal-gray-600 font-mono", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm" ) }, hint ), error && /* @__PURE__ */ React2__default.default.createElement( "p", { id: `${props.id || "select"}-error`, className: chunk7T4KDGYW_js.cn( "mt-2 font-black uppercase tracking-wider text-accent", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm" ), role: "alert" }, error ) ); } ); Select.displayName = "Select"; var Textarea = React2__default.default.forwardRef( ({ label, error, hint, brutal = true, size = "md", variant = "default", required = false, autoResize = false, showCharacterCount = false, maxLength, accentColor = "brutal-pink", className, disabled, rows = 4, ...props }, ref) => { const [isFocused, setIsFocused] = React2.useState(false); const [characterCount, setCharacterCount] = React2.useState(0); const textareaRef = React2.useRef(null); const sizeClasses = chunk7T4KDGYW_js.getSizeClasses(size); React2.useEffect(() => { if (autoResize && textareaRef.current) { const textarea = textareaRef.current; textarea.style.height = "auto"; textarea.style.height = `${textarea.scrollHeight}px`; } }, [props.value, autoResize]); const handleChange = (e) => { setCharacterCount(e.target.value.length); props.onChange?.(e); if (autoResize && textareaRef.current) { const textarea = textareaRef.current; textarea.style.height = "auto"; textarea.style.height = `${textarea.scrollHeight}px`; } }; const combinedRef = (node) => { if (textareaRef.current !== node) { textareaRef.current = node; } if (typeof ref === "function") { ref(node); } else if (ref) { ref.current = node; } }; const isNearLimit = maxLength && characterCount > maxLength * 0.8; const isOverLimit = maxLength && characterCount > maxLength; return /* @__PURE__ */ React2__default.default.createElement( "div", { className: "w-full", style: { "--accent-color": accentColor.startsWith("#") ? accentColor : `var(--brutal-${accentColor.replace("brutal-", "")})` } }, label && /* @__PURE__ */ React2__default.default.createElement( "label", { className: chunk7T4KDGYW_js.cn( "block mb-2 font-black uppercase tracking-wider text-brutal-black", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm", required && "after:content-['*'] after:ml-1 after:text-accent" ) }, label ), /* @__PURE__ */ React2__default.default.createElement( "textarea", { ref: combinedRef, rows: autoResize ? 1 : rows, maxLength, className: chunk7T4KDGYW_js.cn( // Base styling "w-full font-mono transition-all duration-200 resize-none", "bg-brutal-white text-brutal-black placeholder:text-brutal-gray-500", // Size classes sizeClasses.padding, sizeClasses.text, // Brutal styling brutal && [ sizeClasses.border, "border-brutal-black", "focus:shadow-brutal focus:-translate-x-0.5 focus:-translate-y-0.5", "hover:shadow-brutal-sm hover:-translate-x-0.25 hover:-translate-y-0.25" ], !brutal && [ "border-2 border-brutal-gray-300 rounded-md", "focus:border-accent focus:ring-2 focus:ring-accent/20" ], // Variant styling variant === "ghost" && [ "bg-transparent border-transparent", "focus:bg-brutal-white focus:border-brutal-black" ], variant === "filled" && "bg-brutal-gray-100", // States error && "border-accent", isOverLimit && "border-brutal-coral", disabled && [ "opacity-50 cursor-not-allowed bg-brutal-gray-100", "hover:transform-none hover:shadow-none" ], isFocused && brutal && "shadow-brutal -translate-x-0.5 -translate-y-0.5", // Resize behavior !autoResize && "resize-y", // Focus styles "focus:outline-none", className ), disabled, required, onFocus: (e) => { setIsFocused(true); props.onFocus?.(e); }, onBlur: (e) => { setIsFocused(false); props.onBlur?.(e); }, onChange: handleChange, "aria-invalid": error ? "true" : "false", "aria-describedby": error ? `${props.id || "textarea"}-error` : hint ? `${props.id || "textarea"}-hint` : void 0, ...props } ), /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex justify-between items-start mt-2" }, /* @__PURE__ */ React2__default.default.createElement("div", { className: "flex-1" }, hint && !error && /* @__PURE__ */ React2__default.default.createElement( "p", { id: `${props.id || "textarea"}-hint`, className: chunk7T4KDGYW_js.cn( "text-brutal-gray-600 font-mono", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm" ) }, hint ), error && /* @__PURE__ */ React2__default.default.createElement( "p", { id: `${props.id || "textarea"}-error`, className: chunk7T4KDGYW_js.cn( "font-black uppercase tracking-wider text-accent", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm" ), role: "alert" }, error )), showCharacterCount && maxLength && /* @__PURE__ */ React2__default.default.createElement( "p", { className: chunk7T4KDGYW_js.cn( "font-mono ml-4 flex-shrink-0", sizeClasses.text === "text-xs" ? "text-xs" : "text-sm", isOverLimit ? "text-brutal-coral font-bold" : isNearLimit ? "text-brutal-yellow font-bold" : "text-brutal-gray-500" ) }, characterCount, "/", maxLength )) ); } ); Textarea.displayName = "Textarea"; var InputSkeleton = ({ label = false, brutal = true, size = "md", className }) => { const sizeClasses = chunk7T4KDGYW_js.getSizeClasses(size); return /* @__PURE__ */ React2__default.default.createElement("div", { className: chunk7T4KDGYW_js.cn("animate-pulse", className) }, label && /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "h-4 bg-brutal-gray-300 rounded w-20 mb-2", sizeClasses.text === "text-xs" && "h-3" ) } ), /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "w-full bg-brutal-gray-200", sizeClasses.padding, brutal && sizeClasses.border && "border-brutal-gray-300", !brutal && "border-2 border-brutal-gray-300 rounded-md" ) }, /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "h-4 bg-brutal-gray-300 rounded w-1/2", sizeClasses.text === "text-xs" && "h-3" ) } ) )); }; var SelectSkeleton = (props) => /* @__PURE__ */ React2__default.default.createElement(InputSkeleton, { ...props }); var TextareaSkeleton = ({ label = false, brutal = true, size = "md", rows = 4, className }) => { const sizeClasses = chunk7T4KDGYW_js.getSizeClasses(size); return /* @__PURE__ */ React2__default.default.createElement("div", { className: chunk7T4KDGYW_js.cn("animate-pulse", className) }, label && /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "h-4 bg-brutal-gray-300 rounded w-20 mb-2", sizeClasses.text === "text-xs" && "h-3" ) } ), /* @__PURE__ */ React2__default.default.createElement( "div", { className: chunk7T4KDGYW_js.cn( "w-full bg-brutal-gray-200 space-y-2", sizeClasses.padding, brutal && sizeClasses.border && "border-brutal-gray-300", !brutal && "border-2 border-brutal-gray-300 rounded-md" ), style: { minHeight: `${rows * 1.5}rem` } }, Array.from({ length: Math.min(rows, 3) }).map((_, i) => /* @__PURE__ */ React2__default.default.createElement( "div", { key: i, className: chunk7T4KDGYW_js.cn( "h-4 bg-brutal-gray-300 rounded", i === Math.min(rows, 3) - 1 && "w-2/3" // Last line shorter ) } )) )); }; /** * @file src/components/core/Button/ButtonGroup.tsx * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Group buttons together */ /** * @file src/components/core/Button/index.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Fri Sep 12 2025 * * @description * Button components barrel export */ /** * @file src/components/core/Input/Input.tsx * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Sat Sep 13 2025 * * @description * Brutal input field with optional icons, validation, and multiple variants * @client */ /** * @file src/components/core/Input/Select.tsx * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Sat Sep 13 2025 * * @description * Brutal select dropdown with enhanced styling and functionality * @client */ /** * @file src/components/core/Input/Textarea.tsx * @author David (https://dvh.sh) * @license MIT * * @created Sat Sep 13 2025 * @updated Sat Sep 13 2025 * * @description * Brutal textarea component with auto-resize and character counting * @client */ /** * @file src/components/core/Input/InputSkeleton.tsx * @author David (https://dvh.sh) * @license MIT * * @created Sat Sep 13 2025 * @updated Sat Sep 13 2025 * * @description * Skeleton loaders for Input components */ /** * @file src/components/core/Input/index.ts * @author David (https://dvh.sh) * @license MIT * * @created Thu Sep 11 2025 * @updated Sat Sep 13 2025 * * @description * Input components barrel export */ exports.ButtonGroup = ButtonGroup; exports.Input = Input; exports.InputSkeleton = InputSkeleton; exports.Select = Select; exports.SelectSkeleton = SelectSkeleton; exports.Textarea = Textarea; exports.TextareaSkeleton = TextareaSkeleton; //# sourceMappingURL=chunk-7GZ4CFXP.js.map //# sourceMappingURL=chunk-7GZ4CFXP.js.map