feat: add new UI component for multi option inputs (#20533)
* add multi option input support * fix import * add multi-key-field-support
This commit is contained in:
1 parent
277da187d0
commit
46d1823ccc
10 files changed
+647
-12
No files matched your search
@@ -1027,4 +1027,4 @@ select:focus {
|
||||
|
||||
.prose :where(a:not([data-card])):not(:where([class~="not-prose"] *)) {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
import { RootProvider } from "fumadocs-ui/provider";
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Inter } from "next/font/google";
|
||||
|
||||
import "./global.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
const inter = Inter({
|
||||
variable: "--font-inter",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
|
||||
@@ -26,7 +21,14 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
||||
<head>
|
||||
<style>{`
|
||||
:root {
|
||||
--font-inter: ${inter.style.fontFamily.replace(/\'/g, "")};
|
||||
}
|
||||
`}</style>
|
||||
</head>
|
||||
<body className={`${inter.variable} antialiased`}>
|
||||
<RootProvider>{children}</RootProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: MultiOptionInput
|
||||
---
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form"
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render"
|
||||
import { Row } from "@/app/components/row"
|
||||
import { StatesExample } from "./multiInputField.states"
|
||||
import { PasteExample } from "./multiInputField.paste"
|
||||
import { CustomizationExample } from "./multiInputField.customization"
|
||||
|
||||
## Overview
|
||||
|
||||
The MultiOptionInput component is a flexible form input that allows users to manage a list of options with features like:
|
||||
- Adding and removing options
|
||||
- Reordering options with move up/down buttons
|
||||
- Pasting multiple options with configurable delimiters
|
||||
- Minimum options requirement
|
||||
- Customizable placeholders and labels
|
||||
- Key-value pair mode for environment variables, configuration settings, etc.
|
||||
|
||||
## States
|
||||
|
||||
MultiOptionInput in different states: default, disabled, and with minimum options.
|
||||
|
||||
<StatesExample/>
|
||||
|
||||
## Paste Support
|
||||
|
||||
Demonstrates the paste functionality with different delimiters. Users can paste comma-separated values, newline-separated values, or use custom delimiters.
|
||||
|
||||
In key-value mode, the component can also parse key-value pairs from pasted text. For example:
|
||||
- `KEY=value`
|
||||
- `NODE_ENV:production`
|
||||
|
||||
<PasteExample/>
|
||||
|
||||
## Customization
|
||||
|
||||
Examples of different customization options like placeholders, move buttons, remove buttons, and key-value pairs.
|
||||
|
||||
<CustomizationExample/>
|
||||
|
||||
## Usage
|
||||
|
||||
### Standard Options
|
||||
|
||||
```tsx
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form/inputs/MultiOptionInput";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
type FormValues = {
|
||||
options: Array<{ label: string; id: string }>;
|
||||
};
|
||||
|
||||
const MyForm = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="options"
|
||||
optionPlaceholders={["Option 1", "Option 2"]}
|
||||
defaultNumberOfOptions={2}
|
||||
pasteDelimiters={["\n", ","]} // Optional
|
||||
showMoveButtons={true} // Optional
|
||||
minOptions={1} // Optional
|
||||
addOptionLabel="Add another option" // Optional
|
||||
/>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Key-Value Pairs
|
||||
|
||||
```tsx
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form/inputs/MultiOptionInput";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
type FormValues = {
|
||||
envVars: Array<{ label: string; value: string; id: string }>;
|
||||
};
|
||||
|
||||
const EnvVarsForm = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="envVars"
|
||||
keyValueMode={true}
|
||||
keyLabel="Environment Variable"
|
||||
valueLabel="Value"
|
||||
optionPlaceholders={["NODE_ENV", "PORT"]}
|
||||
valuePlaceholders={["production", "3000"]}
|
||||
keyValueDelimiters={[":", "="]} // Optional, defaults to [":", "="]
|
||||
defaultNumberOfOptions={2}
|
||||
/>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
customPlaceholders: Array<{ label: string; id: string }>;
|
||||
noMoveButtons: Array<{ label: string; id: string }>;
|
||||
customLabel: Array<{ label: string; id: string }>;
|
||||
keyValuePairs: Array<{ label: string; value: string; id: string }>;
|
||||
};
|
||||
|
||||
export const CustomizationExample: React.FC = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<FormProvider {...methods}>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Key-Value Pairs</h3>
|
||||
<p className="text-subtle text-xs">Allows inputting keys and values</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="keyValuePairs"
|
||||
keyValueMode
|
||||
keyLabel="Environment Variable"
|
||||
valueLabel="Value"
|
||||
optionPlaceholders={["NODE_ENV", "PORT", "DATABASE_URL"]}
|
||||
valuePlaceholders={["production", "3000", "postgres://..."]}
|
||||
defaultNumberOfOptions={3}
|
||||
keyValueDelimiters={[":", "="]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Custom Placeholders</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="customPlaceholders"
|
||||
optionPlaceholders={["Enter your name", "Enter your email", "Enter your phone"]}
|
||||
defaultNumberOfOptions={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Without Move Buttons</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="noMoveButtons"
|
||||
optionPlaceholders={["Static option 1", "Static option 2"]}
|
||||
defaultNumberOfOptions={2}
|
||||
showMoveButtons={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Custom Add Button Label</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="customLabel"
|
||||
optionPlaceholders={["Social media link"]}
|
||||
defaultNumberOfOptions={1}
|
||||
addOptionLabel="Add another social media link"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
newlineOptions: Array<{ label: string; id: string }>;
|
||||
commaOptions: Array<{ label: string; id: string }>;
|
||||
customOptions: Array<{ label: string; id: string }>;
|
||||
keyValueOptions: Array<{ label: string; value: string; id: string }>;
|
||||
};
|
||||
|
||||
export const PasteExample: React.FC = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<FormProvider {...methods}>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Default Delimiters (Newline and Comma)</h3>
|
||||
<p className="text-subtle text-xs">
|
||||
Try pasting: “Option 1, Option 2” or multiple lines
|
||||
</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="newlineOptions"
|
||||
optionPlaceholders={["Paste here..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Comma Only Delimiter</h3>
|
||||
<p className="text-subtle text-xs">Try pasting: “First, Second, Third”</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="commaOptions"
|
||||
optionPlaceholders={["Paste here..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
pasteDelimiters={[","]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Key-Value Pair Paste Support</h3>
|
||||
<p className="text-subtle text-xs">
|
||||
Try pasting: “NODE_ENV=production” or “KEY1:value1, KEY2:value2”
|
||||
</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="keyValueOptions"
|
||||
keyValueMode
|
||||
optionPlaceholders={["Key..."]}
|
||||
valuePlaceholders={["Value..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
keyValueDelimiters={[":", "="]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Custom Delimiters (Semicolon and Pipe)</h3>
|
||||
<p className="text-subtle text-xs">Try pasting: “One;Two|Three”</p>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="customOptions"
|
||||
optionPlaceholders={["Paste here..."]}
|
||||
defaultNumberOfOptions={1}
|
||||
pasteDelimiters={[";", "|"]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
"use client";
|
||||
|
||||
import { RenderComponentWithSnippet } from "@/app/components/render";
|
||||
import { useForm, FormProvider } from "react-hook-form";
|
||||
|
||||
import { MultiOptionInput } from "@calcom/ui/components/form";
|
||||
|
||||
type FormValues = {
|
||||
defaultOptions: Array<{ label: string; id: string }>;
|
||||
disabledOptions: Array<{ label: string; id: string }>;
|
||||
minOptions: Array<{ label: string; id: string }>;
|
||||
};
|
||||
|
||||
export const StatesExample: React.FC = () => {
|
||||
const methods = useForm<FormValues>();
|
||||
|
||||
return (
|
||||
<RenderComponentWithSnippet>
|
||||
<FormProvider {...methods}>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Default State</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="defaultOptions"
|
||||
optionPlaceholders={["First option", "Second option", "Third option"]}
|
||||
defaultNumberOfOptions={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Disabled State</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="disabledOptions"
|
||||
optionPlaceholders={["Disabled option 1", "Disabled option 2"]}
|
||||
defaultNumberOfOptions={2}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col space-y-2">
|
||||
<h3 className="text-emphasis text-sm">Minimum Options (2)</h3>
|
||||
<MultiOptionInput<FormValues>
|
||||
fieldArrayName="minOptions"
|
||||
optionPlaceholders={["Required option 1", "Required option 2", "Optional option"]}
|
||||
defaultNumberOfOptions={3}
|
||||
minOptions={2}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FormProvider>
|
||||
</RenderComponentWithSnippet>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user