mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-19 11:11:05 -05:00
add radio buttons to react lib
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "next dev -p 3001",
|
||||
"build": "next build",
|
||||
"postbuild": "next-sitemap",
|
||||
"start": "next start",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./inputs/Button";
|
||||
export * from "./inputs/Radio";
|
||||
export * from "./inputs/Submit";
|
||||
export * from "./inputs/Text";
|
||||
export * from "./inputs/Textarea";
|
||||
|
||||
@@ -5,6 +5,8 @@ import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { SVGComponent, UniversalInputProps } from "../../types";
|
||||
import ButtonComponent from "../shared/ButtonComponent";
|
||||
import { Help } from "../shared/Help";
|
||||
import { Outer } from "../shared/Outer";
|
||||
import { Wrapper } from "../shared/Wrapper";
|
||||
|
||||
interface ButtonInputUniqueProps {
|
||||
PrefixIcon?: SVGComponent;
|
||||
@@ -21,11 +23,11 @@ export function Button(props: FormbricksProps) {
|
||||
useEffectUpdateSchema(props, inputType);
|
||||
|
||||
return (
|
||||
<div className={clsx("formbricks-outer", props.outerClassName)} data-type={inputType}>
|
||||
<div className={clsx("formbricks-wrapper", props.wrapperClassName)}>
|
||||
<Outer inputType={inputType} outerClassName={props.outerClassName}>
|
||||
<Wrapper wrapperClassName={props.wrapperClassName}>
|
||||
<ButtonComponent elemId={elemId} {...props} />
|
||||
</div>
|
||||
{props.help && <Help help={props.help} elemId={elemId} />}
|
||||
</div>
|
||||
</Wrapper>
|
||||
<Help help={props.help} elemId={elemId} />
|
||||
</Outer>
|
||||
);
|
||||
}
|
||||
|
||||
85
packages/react/src/components/inputs/Radio.tsx
Normal file
85
packages/react/src/components/inputs/Radio.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import clsx from "clsx";
|
||||
import React, { useMemo } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { getElementId } from "../../lib/element";
|
||||
import { normalizeOptions } from "../../lib/options";
|
||||
import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { getValidationRules } from "../../lib/validation";
|
||||
import { NameRequired, OptionsArray, OptionsObjectArray, UniversalInputProps } from "../../types";
|
||||
import { Help } from "../shared/Help";
|
||||
import { Inner } from "../shared/Inner";
|
||||
import { Label } from "../shared/Label";
|
||||
import { Messages } from "../shared/Messages";
|
||||
import { Outer } from "../shared/Outer";
|
||||
import { Wrapper } from "../shared/Wrapper";
|
||||
|
||||
interface RadioInputUniqueProps {
|
||||
options?: OptionsArray | OptionsObjectArray;
|
||||
}
|
||||
|
||||
type FormbricksProps = RadioInputUniqueProps & UniversalInputProps & NameRequired;
|
||||
|
||||
const inputType = "radio";
|
||||
|
||||
export function Radio(props: FormbricksProps) {
|
||||
const elemId = useMemo(() => getElementId(props.id, props.name), [props.id, props.name]);
|
||||
const options = useMemo(() => normalizeOptions(props.options), [props.options]);
|
||||
useEffectUpdateSchema(props, inputType);
|
||||
|
||||
const {
|
||||
register,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const validationRules = getValidationRules(props.validation);
|
||||
|
||||
if (!options || options.length === 0) {
|
||||
return (
|
||||
<Outer inputType={inputType} outerClassName={props.outerClassName}>
|
||||
<Wrapper wrapperClassName={props.wrapperClassName}>
|
||||
<Inner innerClassName={props.innerClassName}>
|
||||
<input
|
||||
className={clsx("formbricks-input", props.inputClassName)}
|
||||
type="radio"
|
||||
id={elemId}
|
||||
{...register(props.name, {
|
||||
required: { value: "required" in validationRules, message: "This field is required" },
|
||||
})}
|
||||
/>
|
||||
<Label label={props.label} elemId={elemId} />
|
||||
</Inner>
|
||||
</Wrapper>
|
||||
<Help help={props.help} elemId={elemId} />
|
||||
<Messages {...props} />
|
||||
</Outer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Outer inputType={inputType} outerClassName={props.outerClassName}>
|
||||
<fieldset className="formbricks-fieldset" name={props.name}>
|
||||
<legend className="formbricks-legend">{props.label}</legend>
|
||||
<Help help={props.help} elemId={elemId} />
|
||||
<div className="formbricks-options">
|
||||
{options.map((option) => (
|
||||
<div className="formbricks-option">
|
||||
<Wrapper wrapperClassName={props.wrapperClassName}>
|
||||
<Inner innerClassName={props.innerClassName}>
|
||||
<input
|
||||
className={clsx("formbricks-input", props.inputClassName)}
|
||||
type="radio"
|
||||
id={`${props.name}-${option.value}`}
|
||||
value={option.value}
|
||||
disabled={option?.config?.disabled}
|
||||
{...register(props.name)}
|
||||
/>
|
||||
<Label label={option.label} elemId={`${props.name}-${option.value}`} />
|
||||
</Inner>
|
||||
</Wrapper>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
<Messages {...props} />
|
||||
</Outer>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import clsx from "clsx";
|
||||
import React, { useMemo } from "react";
|
||||
import { getElementId } from "../../lib/element";
|
||||
import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { SVGComponent, UniversalInputProps } from "../../types";
|
||||
import ButtonComponent from "../shared/ButtonComponent";
|
||||
import { Help } from "../shared/Help";
|
||||
import { Outer } from "../shared/Outer";
|
||||
import { Wrapper } from "../shared/Wrapper";
|
||||
|
||||
interface SubmitInputUniqueProps {
|
||||
PrefixIcon?: SVGComponent;
|
||||
@@ -20,11 +21,11 @@ export function Submit(props: FormbricksProps) {
|
||||
useEffectUpdateSchema(props, inputType);
|
||||
|
||||
return (
|
||||
<div className={clsx("formbricks-outer", props.outerClassName)} data-type={inputType}>
|
||||
<div className={clsx("formbricks-wrapper", props.wrapperClassName)}>
|
||||
<Outer inputType={inputType} outerClassName={props.outerClassName}>
|
||||
<Wrapper wrapperClassName={props.wrapperClassName}>
|
||||
<ButtonComponent type="submit" elemId={elemId} {...props} />
|
||||
</div>
|
||||
{props.help && <Help help={props.help} elemId={elemId} />}
|
||||
</div>
|
||||
</Wrapper>
|
||||
<Help help={props.help} elemId={elemId} />
|
||||
</Outer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,8 +6,11 @@ import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { getValidationRules, validate } from "../../lib/validation";
|
||||
import { NameRequired, UniversalInputProps } from "../../types";
|
||||
import { Help } from "../shared/Help";
|
||||
import { Inner } from "../shared/Inner";
|
||||
import { Label } from "../shared/Label";
|
||||
import { Messages } from "../shared/Messages";
|
||||
import { Outer } from "../shared/Outer";
|
||||
import { Wrapper } from "../shared/Wrapper";
|
||||
|
||||
interface TextInputUniqueProps {
|
||||
maxLength?: number;
|
||||
@@ -30,12 +33,12 @@ export function Text(props: FormbricksProps) {
|
||||
const validationRules = getValidationRules(props.validation);
|
||||
|
||||
return (
|
||||
<div className={clsx("formbricks-outer", props.outerClassName)} data-type={inputType}>
|
||||
<div className={clsx("formbricks-wrapper", props.wrapperClassName)}>
|
||||
<Label label={props.label} elemId={elemId} />
|
||||
<div className={clsx("formbricks-inner", props.innerClassName)}>
|
||||
<Outer inputType={inputType} outerClassName={props.outerClassName}>
|
||||
<Wrapper wrapperClassName={props.wrapperClassName}>
|
||||
<Label label={props.label} elemId={elemId} labelClassName={props.labelClassName} />
|
||||
<Inner innerClassName={props.innerClassName}>
|
||||
<input
|
||||
className={clsx("form-input", "formbricks-input", props.inputClassName)}
|
||||
className={clsx("formbricks-input", props.inputClassName)}
|
||||
type="text"
|
||||
id={elemId}
|
||||
placeholder={props.placeholder || ""}
|
||||
@@ -53,10 +56,10 @@ export function Text(props: FormbricksProps) {
|
||||
validate: validate(validationRules),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{props.help && <Help help={props.help} elemId={elemId} helpClassName={props.helpClassName} />}
|
||||
<Messages errors={errors} {...props} />
|
||||
</div>
|
||||
</Inner>
|
||||
</Wrapper>
|
||||
<Help help={props.help} elemId={elemId} helpClassName={props.helpClassName} />
|
||||
<Messages {...props} />
|
||||
</Outer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ import { useEffectUpdateSchema } from "../../lib/schema";
|
||||
import { getValidationRules, validate } from "../../lib/validation";
|
||||
import { NameRequired, UniversalInputProps } from "../../types";
|
||||
import { Help } from "../shared/Help";
|
||||
import { Inner } from "../shared/Inner";
|
||||
import { Label } from "../shared/Label";
|
||||
import { Messages } from "../shared/Messages";
|
||||
import { Outer } from "../shared/Outer";
|
||||
import { Wrapper } from "../shared/Wrapper";
|
||||
|
||||
interface TextareaInputUniqueProps {
|
||||
cols?: number;
|
||||
@@ -31,10 +34,10 @@ export function Textarea(props: TextareaProps) {
|
||||
const validationRules = getValidationRules(props.validation);
|
||||
|
||||
return (
|
||||
<div className="formbricks-outer" data-type={inputType}>
|
||||
<div className="formbricks-wrapper">
|
||||
<Label label={props.label} elemId={elemId} />
|
||||
<div className="formbricks-inner">
|
||||
<Outer inputType={inputType} outerClassName={props.outerClassName}>
|
||||
<Wrapper wrapperClassName={props.wrapperClassName}>
|
||||
<Label label={props.label} elemId={elemId} labelClassName={props.labelClassName} />
|
||||
<Inner innerClassName={props.innerClassName}>
|
||||
<textarea
|
||||
className="formbricks-input"
|
||||
id={elemId}
|
||||
@@ -55,10 +58,10 @@ export function Textarea(props: TextareaProps) {
|
||||
validate: validate(validationRules),
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{props.help && <Help help={props.help} elemId={elemId} />}
|
||||
<Messages errors={errors} {...props} />
|
||||
</div>
|
||||
</Inner>
|
||||
</Wrapper>
|
||||
<Help help={props.help} elemId={elemId} helpClassName={props.helpClassName} />
|
||||
<Messages {...props} />
|
||||
</Outer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@ import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
interface HelpProps {
|
||||
help: string;
|
||||
help?: string;
|
||||
elemId: string;
|
||||
helpClassName?: string;
|
||||
}
|
||||
|
||||
export function Help({ help, elemId, helpClassName }: HelpProps) {
|
||||
if (!help) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={clsx("formbricks-help", helpClassName)} id={`help-${elemId}`}>
|
||||
{help}
|
||||
|
||||
11
packages/react/src/components/shared/Inner.tsx
Normal file
11
packages/react/src/components/shared/Inner.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface InnerProps {
|
||||
innerClassName?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Inner({ innerClassName, children }: InnerProps) {
|
||||
return <div className={clsx("formbricks-inner", innerClassName)}>{children}</div>;
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
interface LabelProps {
|
||||
label?: string;
|
||||
elemId: string;
|
||||
labelClassName?: string;
|
||||
}
|
||||
|
||||
export function Label({ label, elemId }: LabelProps) {
|
||||
export function Label({ label, elemId, labelClassName }: LabelProps) {
|
||||
return (
|
||||
<>
|
||||
{typeof label !== "undefined" && (
|
||||
<label className="formbricks-label" htmlFor={elemId}>
|
||||
<label className={clsx("formbricks-label", labelClassName)} htmlFor={elemId}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
||||
@@ -1,49 +1,36 @@
|
||||
import { ErrorMessage } from "@hookform/error-message";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { FieldError, FieldErrorsImpl, Merge } from "react-hook-form";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
interface HelpProps {
|
||||
name: string;
|
||||
errors?: Partial<
|
||||
FieldErrorsImpl<{
|
||||
[x: string]: any;
|
||||
}>
|
||||
>;
|
||||
messagesClassName?: string;
|
||||
messageClassName?: string;
|
||||
}
|
||||
|
||||
export function Messages({ errors, messagesClassName, messageClassName, name }: HelpProps) {
|
||||
export function Messages({ messagesClassName, messageClassName, name }: HelpProps) {
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
return (
|
||||
<>
|
||||
{/* <ul className={clsx("formbricks-messages", messagesClassName)}>
|
||||
{console.log(messages)}
|
||||
<li
|
||||
className={clsx("formbricks-message", messageClassName)}
|
||||
id="input_1-rule_required"
|
||||
data-message-type="validation">
|
||||
FormKit Input is required.
|
||||
</li>
|
||||
</ul> */}
|
||||
<ErrorMessage
|
||||
errors={errors}
|
||||
name={name}
|
||||
render={({ messages }) =>
|
||||
messages &&
|
||||
Object.entries(messages).map(([type, message]) => (
|
||||
<ul className={clsx("formbricks-messages", messagesClassName)}>
|
||||
<li
|
||||
className={clsx("formbricks-message", messageClassName)}
|
||||
id={`${name}-${type}`}
|
||||
data-message-type={type}
|
||||
role="alert">
|
||||
{message}
|
||||
</li>
|
||||
</ul>
|
||||
))
|
||||
}
|
||||
/>
|
||||
</>
|
||||
<ErrorMessage
|
||||
errors={errors}
|
||||
name={name}
|
||||
render={({ messages }) =>
|
||||
messages &&
|
||||
Object.entries(messages).map(([type, message]) => (
|
||||
<ul className={clsx("formbricks-messages", messagesClassName)}>
|
||||
<li
|
||||
className={clsx("formbricks-message", messageClassName)}
|
||||
id={`${name}-${type}`}
|
||||
data-message-type={type}
|
||||
role="alert">
|
||||
{message}
|
||||
</li>
|
||||
</ul>
|
||||
))
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
22
packages/react/src/components/shared/Outer.tsx
Normal file
22
packages/react/src/components/shared/Outer.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
import { FieldErrorsImpl, useFormContext } from "react-hook-form";
|
||||
import { Help } from "./Help";
|
||||
import { Messages } from "./Messages";
|
||||
|
||||
interface OuterProps {
|
||||
inputType: string;
|
||||
outerClassName?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Outer({ inputType, outerClassName, children }: OuterProps) {
|
||||
const {
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
return (
|
||||
<div className={clsx("formbricks-outer", outerClassName)} data-type={inputType}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
11
packages/react/src/components/shared/Wrapper.tsx
Normal file
11
packages/react/src/components/shared/Wrapper.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
interface WrapperProps {
|
||||
wrapperClassName?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Wrapper({ wrapperClassName, children }: WrapperProps) {
|
||||
return <div className={clsx("formbricks-wrapper", wrapperClassName)}>{children}</div>;
|
||||
}
|
||||
25
packages/react/src/lib/options.ts
Normal file
25
packages/react/src/lib/options.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { OptionsArray, OptionsObjectArray } from "../types";
|
||||
|
||||
export const normalizeOptions = (options?: OptionsArray | OptionsObjectArray) => {
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
const normalizedOptions = [];
|
||||
if (Array.isArray(options)) {
|
||||
for (const option of options) {
|
||||
if (typeof option === "string") {
|
||||
normalizedOptions.push({
|
||||
label: option,
|
||||
value: option,
|
||||
});
|
||||
} else if (typeof option === "object" && !Array.isArray(option)) {
|
||||
normalizedOptions.push({
|
||||
label: option.label,
|
||||
value: option.value,
|
||||
config: option.config || {},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return normalizedOptions;
|
||||
};
|
||||
@@ -7,17 +7,21 @@
|
||||
}
|
||||
|
||||
.formbricks-label {
|
||||
@apply block text-base font-medium text-gray-700 font-sans sm:text-sm;
|
||||
@apply text-base font-medium text-gray-700 font-sans sm:text-sm;
|
||||
}
|
||||
|
||||
button.formbricks-input {
|
||||
@apply my-2 inline-flex items-center rounded-md border border-transparent bg-slate-600 px-3 py-2 text-base font-medium leading-4 text-white shadow-sm hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-2 sm:text-sm;
|
||||
}
|
||||
|
||||
input.formbricks-input {
|
||||
input[type="text"].formbricks-input {
|
||||
@apply form-input text-base block rounded-md border-gray-300 shadow-sm focus:border-slate-500 focus:ring-slate-500 sm:text-sm;
|
||||
}
|
||||
|
||||
input[type="radio"].formbricks-input {
|
||||
@apply h-4 w-4 border-gray-300 text-slate-600 focus:ring-slate-500 mr-2;
|
||||
}
|
||||
|
||||
textarea.formbricks-input {
|
||||
@apply form-textarea text-base font-sans block rounded-md border-gray-300 shadow-sm focus:border-slate-500 focus:ring-slate-500 sm:text-sm;
|
||||
}
|
||||
@@ -37,3 +41,11 @@ textarea.formbricks-input {
|
||||
.formbricks-message {
|
||||
@apply font-sans text-base sm:text-sm text-red-500;
|
||||
}
|
||||
|
||||
.formbricks-legend {
|
||||
@apply text-base font-medium text-gray-700 font-sans sm:text-sm;
|
||||
}
|
||||
|
||||
.formbricks-fieldset {
|
||||
@apply border-gray-50 rounded-lg max-w-md;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ export interface UniversalInputProps {
|
||||
name?: string;
|
||||
label?: string;
|
||||
validation?: string;
|
||||
labelClassName?: string;
|
||||
outerClassName?: string;
|
||||
wrapperClassName?: string;
|
||||
innerClassName?: string;
|
||||
@@ -18,3 +19,16 @@ export interface UniversalInputProps {
|
||||
export interface NameRequired {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type OptionsArray = string[];
|
||||
|
||||
export interface OptionsObject {
|
||||
label: string;
|
||||
value: string;
|
||||
config?: {
|
||||
validation?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export type OptionsObjectArray = OptionsObject[];
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
format: ["cjs", "esm"],
|
||||
entry: ["src/index.tsx", "src/components/*.tsx"],
|
||||
entry: ["src/index.tsx"],
|
||||
clean: true,
|
||||
splitting: true,
|
||||
dts: true,
|
||||
|
||||
Reference in New Issue
Block a user