Skip to content

Instantly share code, notes, and snippets.

@rickiesmooth
Created September 26, 2022 14:32
Show Gist options
  • Save rickiesmooth/1198a80d6305b3ffd9d6e0c4458a25bb to your computer and use it in GitHub Desktop.
Save rickiesmooth/1198a80d6305b3ffd9d6e0c4458a25bb to your computer and use it in GitHub Desktop.
import { addPropertyControls, ControlType, RenderTarget, withCSS } from "framer"
import { HTMLMotionProps, motion } from "framer-motion"
import * as React from "react"
import {
containerStyles,
usePadding,
useRadius,
paddingControl,
borderRadiusControl,
fontControls,
useFontControls,
} from "https://framer.com/m/framer/default-utils.js@^0.45.0"
import {
ComponentType,
CSSProperties,
useCallback,
useMemo,
useState,
} from "react"
interface Props extends Omit<HTMLMotionProps<"div">, "layout"> {
formId: string
withName: boolean
withEmail: boolean
withMessage: boolean
withTwitterHandle: boolean
nameField: any
email: any
message: any
twitterHandle: any
layout: "horizontal" | "vertical"
inputs: any
button: any
gap: number
style: CSSProperties
onSubmit?: () => void
}
const emailRegex =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const validateEmail = (email) => {
return emailRegex.test(String(email).toLowerCase())
}
/**
* FORMSPARK
*
* @framerIntrinsicWidth 550
* @framerIntrinsicHeight 290
*
* @framerSupportedLayoutWidth fixed
* @framerSupportedLayoutHeight fixed
*/
const FormSparkWithTwitterHandle: ComponentType<Props> = withCSS<Props>(
function FormSparkWithTwitterHandle({
formId,
withName,
nameField: name,
withEmail,
email,
withMessage,
message,
withTwitterHandle,
twitterHandle,
layout,
inputs,
button,
style,
gap,
onSubmit,
...props
}) {
const [nameValue, setName] = useState(name?.value)
const [emailValue, setEmail] = useState(email?.value)
const [messageValue, setMessage] = useState(message?.value)
const [twitterHandleValue, setTwitterHandle] = useState(message?.value)
const [isNameError, setNameError] = useState(false)
const [isEmailError, setEmailError] = useState(false)
const [isMessageError, setMessageError] = useState(false)
const [isTwitterHandleError, setTwitterHandleError] = useState(false)
const [isLoading, setLoading] = useState(false)
const [isSuccess, setSuccess] = useState(false)
const isCanvas = useMemo(() => {
return RenderTarget.current() === RenderTarget.canvas
}, [])
const gridTemplateRows = useMemo(() => {
const rows = []
if (withName || withMessage || withTwitterHandle) {
rows.push("max-content")
}
if (withMessage) {
rows.push("1fr")
}
return [...rows, "max-content"].join(" ")
}, [withName, withEmail, withMessage, withTwitterHandle])
const gridTemplateColumns = useMemo(() => {
const cols = []
if (
((withName && !withEmail) || (withEmail && !withName)) &&
!withMessage &&
layout === "horizontal"
) {
return "1fr max-content"
}
return "1fr"
}, [withName, withEmail, withMessage, withTwitterHandle, layout])
const { fontFamily, fontSize, fontWeight } = useFontControls(props)
const borderRadius = useRadius(props)
const paddingValue = usePadding(props)
const validateForm = useCallback(() => {
let error = false
setNameError(false)
setEmailError(false)
setMessageError(false)
setTwitterHandleError(false)
if (withName && !nameValue) {
setNameError(true)
error = true
}
if (withEmail && (!emailValue || !validateEmail(emailValue))) {
setEmailError(true)
error = true
}
if (withMessage && !messageValue) {
setMessageError(true)
error = true
}
if (withTwitterHandle && !twitterHandleValue) {
console.log("twitter error")
setTwitterHandleError(true)
error = true
}
return error
}, [
validateEmail,
withName,
withEmail,
withMessage,
withTwitterHandle,
nameValue,
emailValue,
messageValue,
twitterHandleValue,
])
const handleSubmit = useCallback(
(event: any) => {
setLoading(true)
event.preventDefault()
if (validateForm()) {
setLoading(false)
} else {
const data = new FormData(event.target)
const entries = Object.fromEntries(data.entries())
fetch(`https://submit-form.com/${formId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(entries),
})
.then(() => {
setSuccess(true)
onSubmit()
})
.catch(() => setLoading(false))
}
},
[formId, onSubmit, validateForm]
)
const handleNameChange = useCallback((event: any) => {
setNameError(false)
setName(event.target.value)
}, [])
const handleEmailChange = useCallback((event: any) => {
setEmailError(false)
setEmail(event.target.value)
}, [])
const handleMessageChange = useCallback((event: any) => {
setMessageError(false)
setMessage(event.target.value)
}, [])
const handleTwitterHandleChange = useCallback((event: any) => {
setTwitterHandleError(false)
setTwitterHandle(event.target.value)
}, [])
return (
<motion.div
style={{
...style,
...containerStyles,
flexDirection: "column",
"--framer-formspark-placeholder-color":
inputs.placeholderColor,
}}
>
{isSuccess ? (
<motion.div
style={{
height: "60px",
width: "60px",
background: button.fill,
color: button.color,
borderRadius: "50%",
display: "flex",
justifyContent: "center",
alignItems: "center",
}}
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{
duration: 0.3,
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="28"
>
<path
d="M 2 14 L 10 22 L 26 6"
fill="transparent"
strokeWidth="4"
stroke="currentColor"
strokeLinecap="round"
/>
</svg>
</motion.div>
) : (
<form
style={{
display: "grid",
gridTemplateRows,
gridTemplateColumns,
gap,
width: "100%",
height: "100%",
}}
onSubmit={handleSubmit}
method="POST"
>
{(withName || withEmail || withTwitterHandle) && (
<div
style={{
width: "100%",
display: "grid",
gridAutoFlow:
layout === "horizontal"
? "column"
: "row",
gap,
}}
>
{withName && (
<input
className="framer-formspark-input"
type="text"
name="name"
placeholder={name.placeholder}
value={
isCanvas ? name.value : nameValue
}
onChange={handleNameChange}
style={{
...defaultStyle,
padding: paddingValue,
borderRadius,
fontFamily,
fontWeight,
fontSize,
background: inputs.fill,
color: inputs.color,
boxShadow: `inset 0 0 0 1px ${
isNameError
? inputs.error
: "transparent"
}`,
}}
/>
)}
{withEmail && (
<input
className="framer-formspark-input"
type="email"
name="email"
placeholder={email.placeholder}
value={
isCanvas ? email.value : emailValue
}
onChange={handleEmailChange}
style={{
...defaultStyle,
padding: paddingValue,
borderRadius,
fontFamily,
fontWeight,
fontSize,
background: inputs.fill,
color: inputs.color,
boxShadow: `inset 0 0 0 1px ${
isEmailError
? inputs.error
: "transparent"
}`,
}}
/>
)}
</div>
)}
{withMessage && (
<textarea
className="framer-formspark-input"
placeholder={message.placeholder}
name="message"
value={isCanvas ? message.value : messageValue}
onChange={handleMessageChange}
style={{
...defaultStyle,
minHeight: 0,
padding: paddingValue,
resize: "vertical",
borderRadius,
background: inputs.fill,
fontFamily,
fontWeight,
fontSize,
color: inputs.color,
boxShadow: `inset 0 0 0 1px ${
isMessageError
? inputs.error
: "transparent"
}`,
}}
/>
)}
{withTwitterHandle && (
<input
className="framer-formspark-input"
type="text"
name="twitter-handle"
placeholder={twitterHandle.placeholder}
value={
isCanvas
? twitterHandle.value
: twitterHandleValue
}
onChange={handleTwitterHandleChange}
style={{
...defaultStyle,
minHeight: 0,
padding: paddingValue,
resize: "vertical",
borderRadius,
background: inputs.fill,
fontFamily,
fontWeight,
fontSize,
color: inputs.color,
boxShadow: `inset 0 0 0 1px ${
isTwitterHandleError
? inputs.error
: "transparent"
}`,
}}
/>
)}
<div>
<motion.input
type="submit"
value={button.label}
style={{
...defaultStyle,
borderRadius,
padding: paddingValue,
fontFamily,
fontWeight: button.fontWeight,
fontSize,
background: button.fill,
cursor: "pointer",
color: button.color,
zIndex: 1,
}}
transition={{ type: "ease", duration: 0.3 }}
whileHover={{ opacity: 0.8 }}
/>
{isLoading && (
<div
style={{
borderRadius,
position: "absolute",
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
height: "100%",
left: 0,
top: 0,
zIndex: 2,
color: button.color,
background: button.fill,
}}
>
<motion.div
style={{ height: 16, width: 16 }}
initial={{ rotate: 0 }}
animate={{ rotate: 360 }}
transition={{
duration: 2,
repeat: Infinity,
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
>
<path
d="M 8 0 C 3.582 0 0 3.582 0 8 C 0 12.419 3.582 16 8 16 C 12.418 16 16 12.419 16 8 C 15.999 3.582 12.418 0 8 0 Z M 8 14 C 4.687 14 2 11.314 2 8 C 2 4.687 4.687 2 8 2 C 11.314 2 14 4.687 14 8 C 14 11.314 11.314 14 8 14 Z"
fill="currentColor"
opacity="0.2"
/>
<path
d="M 8 0 C 12.418 0 15.999 3.582 16 8 C 16 8 16 9 15 9 C 14 9 14 8 14 8 C 14 4.687 11.314 2 8 2 C 4.687 2 2 4.687 2 8 C 2 8 2 9 1 9 C 0 9 0 8 0 8 C 0 3.582 3.582 0 8 0 Z"
fill="currentColor"
/>
</svg>
</motion.div>
</div>
)}
</div>
</form>
)}
</motion.div>
)
},
[
".framer-formspark-input::placeholder { color: var(--framer-formspark-placeholder-color) !important; }",
]
)
FormSparkWithTwitterHandle.defaultProps = {
fontSize: 16,
fontFamily: "Inter",
fontWeight: 400,
padding: 15,
paddingTop: 15,
paddingBottom: 15,
paddingLeft: 15,
paddingRight: 15,
borderRadius: 8,
topLeftRadius: 8,
topRightRadius: 8,
bottomRightRadius: 8,
bottomLeftRadius: 8,
gap: 15,
nameField: { value: undefined, placeholder: "Name" },
email: { value: undefined, placeholder: "Email" },
message: { value: undefined, placeholder: "Message" },
inputs: {
fill: "#EBEBEB",
color: "#000",
placeholderColor: "rgba(0, 0, 0, 0.5)",
error: "#EE4444",
},
layout: {
fill: "#EBEBEB",
color: "#000",
placeholderColor: "rgba(0, 0, 0, 0.5)",
error: "#EE4444",
},
button: { label: "Sign Up", fontWeight: 600, fill: "#000", color: "#FFF" },
} as any
addPropertyControls(FormSparkWithTwitterHandle, {
formId: {
title: "ID",
placeholder: "7PbPpGN3",
type: ControlType.String,
description:
"Create a [FormSpark](https://formspark.io/) account, add a new form and copy its ID. [Learn more…](https://www.framer.com/sites/integrations/formspark/)",
},
withName: {
title: "Name",
type: ControlType.Boolean,
enabledTitle: "Show",
disabledTitle: "Hide",
defaultValue: true,
},
nameField: {
title: " ",
type: ControlType.Object,
controls: {
placeholder: {
title: "Placeholder",
type: ControlType.String,
defaultValue: "Name",
},
value: {
title: "Value",
type: ControlType.String,
defaultValue: "",
},
},
hidden: (props) => !props.withName,
},
withEmail: {
title: "Email",
type: ControlType.Boolean,
enabledTitle: "Show",
disabledTitle: "Hide",
defaultValue: true,
},
email: {
title: " ",
type: ControlType.Object,
controls: {
placeholder: {
title: "Placeholder",
type: ControlType.String,
defaultValue: "Email",
},
value: {
title: "Value",
type: ControlType.String,
},
},
hidden: (props) => !props.withEmail,
},
withMessage: {
title: "Message",
type: ControlType.Boolean,
enabledTitle: "Show",
disabledTitle: "Hide",
defaultValue: true,
},
message: {
title: " ",
type: ControlType.Object,
controls: {
placeholder: {
title: "Placeholder",
type: ControlType.String,
defaultValue: "Message",
},
value: {
title: "Value",
type: ControlType.String,
},
},
hidden: (props) => !props.withMessage,
},
withTwitterHandle: {
title: "Twitter handle",
type: ControlType.Boolean,
enabledTitle: "Show",
disabledTitle: "Hide",
defaultValue: true,
},
twitterHandle: {
title: "Twitter Handle",
type: ControlType.Object,
controls: {
placeholder: {
title: "Twitter handle",
type: ControlType.String,
defaultValue: "@framer",
},
value: {
title: "Value",
type: ControlType.String,
},
},
hidden: (props) => !props.withTwitterHandle,
},
layout: {
title: "Layout",
type: ControlType.Enum,
options: ["horizontal", "vertical"],
displaySegmentedControl: true,
defaultValue: "horizontal",
},
inputs: {
title: "Inputs",
type: ControlType.Object,
controls: {
fill: {
title: "Fill",
type: ControlType.Color,
defaultValue: "#EBEBEB",
},
color: {
title: "Text",
type: ControlType.Color,
defaultValue: "#000",
},
placeholderColor: {
title: "Placeholder",
type: ControlType.Color,
defaultValue: "rgba(0, 0, 0, 0.5)",
},
error: {
title: "Error",
type: ControlType.Color,
defaultValue: "#EE4444",
},
},
},
button: {
title: "Button",
type: ControlType.Object,
controls: {
label: {
title: "Label",
type: ControlType.String,
defaultValue: "Sign Up",
},
fontWeight: {
...fontControls.fontWeight,
defaultValue: 600,
},
fill: {
title: "Fill",
type: ControlType.Color,
defaultValue: "#000",
},
color: {
title: "Text",
type: ControlType.Color,
defaultValue: "#FFF",
},
},
},
...fontControls,
fontSize: {
title: "Font Size",
type: ControlType.Number,
displayStepper: true,
defaultValue: 16,
},
...paddingControl,
...borderRadiusControl,
gap: {
title: "Gap",
type: ControlType.Number,
displayStepper: true,
min: 0,
},
onSubmit: {
type: ControlType.EventHandler,
},
})
const defaultStyle: CSSProperties = {
WebkitAppearance: "none",
display: "inline-block",
width: "100%",
lineHeight: "1.4em",
outline: "none",
border: "none",
}
export default FormSparkWithTwitterHandle
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment