// Dependencies import { capitalize, cloudflareImageUrl, measurementAsMm, measurementAsUnits, distanceAsMm, validateEmail, } from '@freesewing/utils' import { collection } from '@freesewing/collection' import { measurements as measurementsTranslations } from '@freesewing/i18n' // Context import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' // Hooks import React, { useState, useCallback, useContext } from 'react' import { useDropzone } from 'react-dropzone' import { useBackend } from '@freesewing/react/hooks/useBackend' // Components import { Link as WebLink } from '@freesewing/react/components/Link' import { TrashIcon, ResetIcon, UploadIcon, HelpIcon } from '@freesewing/react/components/Icon' import { isDegreeMeasurement } from '@freesewing/config' import { Tabs, Tab } from '@freesewing/react/components/Tab' import Markdown from 'react-markdown' /* * A helper component to render the help link in formcontrol * * @param {string|function| help - The help href of onClick method * @param {React.component} Link - An optional framework-specific link component */ const HelpLink = ({ help, Link = false }) => { if (!Link) Link = WebLink if (typeof help === 'function') return ( ) if (typeof help === 'string') return ( ) return null } /** * A component for a fieldset, which wraps form elements and providers labels. * * @component * @param {object} props - All component props * @param {React.Component} [props.Link = undefined] - A framework specific Link component for client-side routing * @param {boolean} [props.box = undefined] - Set this to true to render a boxed fieldset * @param {JSX.Element} props.children - The component children * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {string} [props.forId = ''] - Id of the HTML element we are wrapping * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @returns {JSX.Element} */ export const Fieldset = ({ Link = false, box = false, children, label = false, labelBL = false, labelBR = false, labelTR = false, legend = false, forId = '', help = false, }) => (
{legend ? ( {legend} ) : null}
{label ? ( ) : null} {labelTR ? ( ) : null}
{children}
{labelBL ? ( ) : null} {labelBR ? ( ) : null}
) /** * A component to wrap content in a button * * @component * @param {object} props - All component props * @param {boolean} [props.active = false] - Set this to true to render the button as active/selected * @param {JSX.Element} props.children - The component children * @param {boolean} [props.dense = false] - Set this to render a more compact variant * @param {boolean} [props.noBg = false] - Set this to true to not use a background color in active state * @param {function} props.onClick - The button's onClick handler * @returns {JSX.Element} */ export const ButtonFrame = ({ active, children, dense, noBg, onClick }) => ( ) /** * A component to handle input of numbers * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.inputMode = 'decimal'] - The inputMode of the input * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.id = ''] - Id of the HTML element to link the fieldset labels * @param {string} [props.legend = false] - The fieldset legend * @param {number} [props.max = 225] - The maximum value * @param {number} [props.min = 0] - The minimum value * @param {number} props.original - The original value, which detects whether it was changed * @param {string} props.placeholder - The placeholder text * @param {number} [props.step = 1] - The input step * @param {function} props.update - The onChange handler * @param {function} props.valid - A function that should return whether the value is valid or not * @returns {JSX.Element} */ export const NumberInput = ({ box = false, current, help = false, inputMode = 'decimal', label = false, labelBL = false, labelBR = false, labelTR = false, id = '', legend = false, max = 225, min = 0, original, placeholder, step = 1, update, valid, }) => (
update(evt.target.value)} className={`tw:daisy-input tw:w-full tw:daisy-input-bordered ${ current === original ? 'tw:daisy-input-secondary' : valid(current) ? 'tw:daisy-input-success' : 'tw:daisy-input-error' }`} {...{ max, min, step }} />
) /** * A component to handle input of strings (single-line text) * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.id = ''] - Id of the HTML element to link the fieldset labels * @param {string} [props.legend = false] - The fieldset legend * @param {number} props.original - The original value, which detects whether it was changed * @param {string} props.placeholder - The placeholder text * @param {function} props.update - The onChange handler * @param {function} [props.valid = () => true] - A function that should return whether the value is valid or not * @returns {JSX.Element} */ export const StringInput = ({ box = false, current, help = false, label = false, labelBL = false, labelBR = false, labelTR = false, id = '', legend = false, original, placeholder, update, valid = () => true, }) => (
update(evt.target.value)} className={`tw:daisy-input tw:w-full tw:daisy-input-bordered tw:text-current ${ current === original ? 'tw:daisy-input-secondary' : valid(current) ? 'tw:daisy-input-success' : 'tw:daisy-input-error' }`} />
) /** * A component to handle input of MFA codes. Essentially a NumberInput with some default props set. * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.id = 'mfa'] - Id of the HTML element to link the fieldset labels * @param {string} [props.inputMode = 'numeric'] - The input mode of the input * @param {string} [props.legend = false] - The fieldset legend * @param {string} [props.placeholder = 'MFA Code'] - The placeholder text * @param {function} props.update - The onChange handler * @param {function} props.valid - A function that should return whether the value is valid or not * @returns {JSX.Element} */ export const MfaInput = ({ box = false, current, help = false, label = false, labelBL = false, labelBR = false, labelTR = false, id = 'mfa', inputMode = 'numeric', legend = false, placeholder = 'MFA Code', update, valid = (val) => val.length > 4, }) => ( ) /** * A component to handle input of passwords * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.id = 'password'] - Id of the HTML element to link the fieldset labels * @param {string} [props.legend = false] - The fieldset legend * @param {string} [placeholder = '¯\\_(ツ)_/¯' - The placeholder text * @param {function} props.update - The onChange handler * @param {function} [props.valid = () => true] - A function that should return whether the value is valid or not * @param {function} [props.onKeyDown = false] - An optional handler to capture keypresses (like enter) * @returns {JSX.Element} */ export const PasswordInput = ({ box = false, current, help = false, label = false, labelBL = false, labelTR = false, id = 'password', legend = false, placeholder = '¯\\_(ツ)_/¯', update, valid = () => true, onKeyDown = false, }) => { const [reveal, setReveal] = useState(false) const extraProps = onKeyDown ? { onKeyDown } : {} return (
setReveal(!reveal)} > {reveal ? 'Hide Password' : 'Reveal Password'} } > update(evt.target.value)} className={`tw:daisy-input tw:w-full tw:daisy-input-bordered ${ valid(current) ? 'input-success' : 'input-error' }`} {...extraProps} />
) } /** * A component to handle input of email addresses * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.id = ''] - Id of the HTML element to link the fieldset labels * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {number} [props.original = ''] - The original value, which detects whether it was changed * @param {string} [props.placeholder = 'Email Address'] - The placeholder text * @param {function} props.update - The onChange handler * @param {function} [props.valid = @freesewing/utils.validateEmail] - A function that should return whether the value is valid or not * @returns {JSX.Element} */ export const EmailInput = ({ box = false, current, help = false, id = 'email', label = false, labelBL = false, labelBR = false, labelTR = false, legend = false, original = '', update, placeholder = 'Email Address', valid = validateEmail, }) => (
update(evt.target.value)} className={`tw:daisy-input tw:w-full tw:daisy-input-bordered ${ current === original ? 'tw:daisy-input-secondary' : valid(current) ? 'tw:daisy-input-success' : 'tw:daisy-input-error' }`} />
) /** * A component to handle input of a design name (a select) * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string} [props.firstOption = false] - An optional first option to add to the select * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.id = 'design'] - Id of the HTML element to link the fieldset labels * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {function} props.update - The onChange handler * @returns {JSX.Element} */ export const DesignInput = ({ box = false, current, firstOption = false, help = false, id = 'design', label = false, labelBL = false, labelBR = false, labelTR = false, legend = false, update, }) => (
) /** * A component to handle input of an image * * @component * @param {object} props - All component props * @param {boolean} [props.active = false] - Set this to true to automatically upload the image * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.id = 'image'] - Id of the HTML element to link the fieldset labels * @param {string} [props.imgType = 'showcase'] - The type of image. One of 'showcase' or 'blog' * @param {string} props.imgSlug - The slug of the image, which is the foldername holding the blog or showcase post * @param {string} props.imgSubid - Set this id to upload non-main images, should be unique per post (1,2,3,...) * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {number} props.original - The original value, which allows a reset * @param {function} props.update - The onChange handler * @returns {JSX.Element} */ export const ImageInput = ({ active = false, box = false, current, help = false, id = 'image', imgSlug, imgSubid, imgType = 'showcase', label = false, labelBL = false, labelBR = false, labelTR = false, legend = false, update, original, }) => { const backend = useBackend() const { setLoadingStatus } = useContext(LoadingStatusContext) const [url, setUrl] = useState(false) const [uploadedId, setUploadedId] = useState(false) const upload = async (img, fromUrl = false) => { setLoadingStatus([true, 'uploadingImage']) const data = { type: imgType, subId: imgSubid, slug: imgSlug, } if (fromUrl) data.url = img else data.img = img const [status, body] = await backend.uploadImageAnon(data) setLoadingStatus([true, 'allDone', true, true]) if (status === 200 && body.result === 'success') { update(body.imgId) setUploadedId(body.imgId) } else setLoadingStatus([true, 'backendError', true, false]) } const onDrop = useCallback( (acceptedFiles) => { const reader = new FileReader() reader.onload = async () => { if (active) upload(reader.result) else update(reader.result) } acceptedFiles.forEach((file) => reader.readAsDataURL(file)) }, [current] ) const { getRootProps, getInputProps } = useDropzone({ onDrop }) if (current) return (
) return (

Drag and drop and image here

or

or

setUrl(evt.target.value) : (evt) => update(evt.target.value)} /> {active && ( )}
) } /** * A component to handle input of an image and upload it (active) * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.id = 'image'] - Id of the HTML element to link the fieldset labels * @param {string} [props.imgType = 'showcase'] - The type of image. One of 'showcase' or 'blog' * @param {string} props.imgSlug - The slug of the image, which is the foldername holding the blog or showcase post * @param {string} props.imgSubid - Set this id to upload non-main images, should be unique per post (1,2,3,...) * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {number} props.original - The original value, which allows a reset * @param {function} props.update - The onChange handler * @returns {JSX.Element} */ export const ActiveImageInput = (props) => /** * A component to handle input of an image and not upload it (inactive) * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.id = 'image'] - Id of the HTML element to link the fieldset labels * @param {string} [props.imgType = 'showcase'] - The type of image. One of 'showcase' or 'blog' * @param {string} props.imgSlug - The slug of the image, which is the foldername holding the blog or showcase post * @param {string} props.imgSubid - Set this id to upload non-main images, should be unique per post (1,2,3,...) * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {number} props.original - The original value, which allows a reset * @param {function} props.update - The onChange handler * @returns {JSX.Element} */ export const PassiveImageInput = (props) => /** * A component to handle input of list of items to pick from * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.id = ''] - Id of the HTML element to link the fieldset labels * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = false] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {array} props.list - An array of { val, label, desc } objects to populate the list * @param {function} props.update - The onChange handler * @returns {JSX.Element} */ export const ListInput = ({ box = false, current, help = false, id = '', label = false, labelBL = false, labelBR = false, labelTR = false, legend = false, list, update, }) => (
{list.map((item, i) => ( update(item.val)}>
{item.label}
{item.desc ? (
{item.desc}
) : null}
))}
) /** * A component to handle input of markdown content * * @component * @param {object} props - All component props * @param {boolean} [props.box = false] - Set this to true to render a boxed fieldset * @param {number} props.current - The current value, to manage the state of this input * @param {string|function} [props.help = false] - An optional URL/method to link/show help or docs * @param {string} [props.id = ''] - Id of the HTML element to link the fieldset labels * @param {string} [props.label = false] - The (top-left) label * @param {string} [props.labelBL = 'This field supports markdown'] - The bottom-left) label * @param {string} [props.labelBR = false] - The bottom-right) label * @param {string} [props.labelTR = false] - The top-right label * @param {string} [props.legend = false] - The fieldset legend * @param {function} props.update - The onChange handler * @param {string} [props.placeholder = ''] - The placeholder text * @returns {JSX.Element} */ export const MarkdownInput = ({ box = false, current, help = false, id = '', label = false, labelBL = 'This field supports markdown', labelBR = false, labelTR = false, legend = false, update, placeholder = '', }) => (