Skip to content

Commit b1c5fa4

Browse files
refactor(ui): migrate provider wizard forms from HeroUI to shadcn (#10259)
1 parent cc02c6f commit b1c5fa4

File tree

43 files changed

+920
-563
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+920
-563
lines changed

ui/components/providers/organizations/org-setup-form.tsx

Lines changed: 188 additions & 210 deletions
Large diffs are not rendered by default.

ui/components/providers/workflow/credentials-role-helper.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { IdIcon } from "@/components/icons";
44
import { Button } from "@/components/shadcn";
5-
import { SnippetChip } from "@/components/ui/entities";
5+
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
66
import { IntegrationType } from "@/types/integrations";
77

88
interface CredentialsRoleHelperProps {
@@ -95,7 +95,7 @@ export const CredentialsRoleHelper = ({
9595
<span className="text-default-500 block text-xs font-medium">
9696
External ID:
9797
</span>
98-
<SnippetChip value={externalId} icon={<IdIcon size={16} />} />
98+
<CodeSnippet value={externalId} icon={<IdIcon size={16} />} />
9999
</div>
100100
</div>
101101
</div>

ui/components/providers/workflow/forms/base-credentials-form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"use client";
22

3-
import { Divider } from "@heroui/divider";
43
import { ChevronLeftIcon, ChevronRightIcon, Loader2 } from "lucide-react";
54
import { useEffect } from "react";
65
import { Control, UseFormSetValue } from "react-hook-form";
76

87
import { Button } from "@/components/shadcn";
8+
import { Separator } from "@/components/shadcn/separator/separator";
99
import { Form } from "@/components/ui/form";
1010
import { useCredentialsForm } from "@/hooks/use-credentials-form";
1111
import { getAWSCredentialsTemplateLinks } from "@/lib";
@@ -149,7 +149,7 @@ export const BaseCredentialsForm = ({
149149

150150
<ProviderTitleDocs providerType={providerType} />
151151

152-
<Divider />
152+
<Separator />
153153

154154
{providerType === "aws" && effectiveVia === "role" && (
155155
<AWSRoleCredentialsForm

ui/components/providers/workflow/forms/connect-account-form.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import { z } from "zod";
99

1010
import { addProvider } from "@/actions/providers/providers";
1111
import { AwsMethodSelector } from "@/components/providers/organizations/aws-method-selector";
12+
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
1213
import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs";
1314
import { Button } from "@/components/shadcn";
1415
import { useToast } from "@/components/ui";
15-
import { CustomInput } from "@/components/ui/custom";
1616
import { Form } from "@/components/ui/form";
1717
import { addProviderFormSchema, ApiError, ProviderType } from "@/types";
1818

@@ -346,7 +346,7 @@ export const ConnectAccountForm = ({
346346
(providerType !== "aws" || awsMethod === "single") && (
347347
<>
348348
<ProviderTitleDocs providerType={providerType} />
349-
<CustomInput
349+
<WizardInputField
350350
control={form.control}
351351
name="providerUid"
352352
type="text"
@@ -356,7 +356,7 @@ export const ConnectAccountForm = ({
356356
variant="bordered"
357357
isRequired
358358
/>
359-
<CustomInput
359+
<WizardInputField
360360
control={form.control}
361361
name="providerAlias"
362362
type="text"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { WizardInputField } from "./wizard-input-field";
2+
export { WizardRadioCard } from "./wizard-radio-card";
3+
export { WizardTextareaField } from "./wizard-textarea-field";
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"use client";
2+
3+
import { Icon } from "@iconify/react";
4+
import { InputHTMLAttributes, useState } from "react";
5+
import { Control, FieldPath, FieldValues } from "react-hook-form";
6+
7+
import { Input } from "@/components/shadcn/input/input";
8+
import { FormControl, FormField, FormMessage } from "@/components/ui/form";
9+
import { cn } from "@/lib/utils";
10+
11+
interface WizardInputFieldProps<T extends FieldValues> {
12+
control: Control<T>;
13+
name: FieldPath<T>;
14+
label?: string;
15+
labelPlacement?: "inside" | "outside";
16+
variant?: "flat" | "bordered" | "underlined" | "faded";
17+
size?: "sm" | "md" | "lg";
18+
type?: string;
19+
placeholder?: string;
20+
password?: boolean;
21+
confirmPassword?: boolean;
22+
defaultValue?: string;
23+
isReadOnly?: boolean;
24+
isRequired?: boolean;
25+
isDisabled?: boolean;
26+
normalizeValue?: (value: string) => string;
27+
autoCapitalize?: InputHTMLAttributes<HTMLInputElement>["autoCapitalize"];
28+
autoCorrect?: InputHTMLAttributes<HTMLInputElement>["autoCorrect"];
29+
spellCheck?: boolean;
30+
requiredIndicator?: boolean;
31+
}
32+
33+
export const WizardInputField = <T extends FieldValues>({
34+
control,
35+
name,
36+
type = "text",
37+
label = name,
38+
labelPlacement = "inside",
39+
variant,
40+
size,
41+
placeholder,
42+
confirmPassword = false,
43+
password = false,
44+
defaultValue,
45+
isReadOnly = false,
46+
isRequired = true,
47+
isDisabled = false,
48+
normalizeValue,
49+
autoCapitalize,
50+
autoCorrect,
51+
spellCheck,
52+
requiredIndicator,
53+
}: WizardInputFieldProps<T>) => {
54+
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
55+
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =
56+
useState(false);
57+
void variant;
58+
void size;
59+
60+
const inputLabel = confirmPassword
61+
? "Confirm Password"
62+
: password
63+
? "Password"
64+
: label;
65+
66+
const inputPlaceholder = confirmPassword
67+
? "Confirm Password"
68+
: password
69+
? "Password"
70+
: placeholder;
71+
72+
const isMaskedInput = type === "password" || password || confirmPassword;
73+
const inputType = isMaskedInput
74+
? isPasswordVisible || isConfirmPasswordVisible
75+
? "text"
76+
: "password"
77+
: type;
78+
const inputIsRequired = password || confirmPassword ? true : isRequired;
79+
const showRequiredIndicator = requiredIndicator ?? inputIsRequired;
80+
81+
const toggleVisibility = () => {
82+
if (password || type === "password") {
83+
setIsPasswordVisible((current) => !current);
84+
return;
85+
}
86+
if (confirmPassword) {
87+
setIsConfirmPasswordVisible((current) => !current);
88+
}
89+
};
90+
91+
return (
92+
<FormField
93+
control={control}
94+
name={name}
95+
render={({ field }) => {
96+
const value = field.value ?? defaultValue ?? "";
97+
98+
return (
99+
<div className="flex flex-col gap-1.5">
100+
<label
101+
htmlFor={name}
102+
className={cn(
103+
"text-text-neutral-tertiary text-xs",
104+
labelPlacement === "outside"
105+
? "font-medium"
106+
: "font-light tracking-tight",
107+
)}
108+
>
109+
{inputLabel}
110+
{showRequiredIndicator && (
111+
<span className="text-text-error-primary">*</span>
112+
)}
113+
</label>
114+
<FormControl>
115+
<div className="relative">
116+
<Input
117+
id={name}
118+
aria-label={inputLabel}
119+
placeholder={inputPlaceholder}
120+
type={inputType}
121+
required={inputIsRequired}
122+
disabled={isDisabled}
123+
readOnly={isReadOnly}
124+
autoCapitalize={autoCapitalize}
125+
autoCorrect={autoCorrect}
126+
spellCheck={spellCheck}
127+
className={cn(isMaskedInput && "pr-10")}
128+
name={field.name}
129+
onBlur={field.onBlur}
130+
ref={field.ref}
131+
onChange={(event) => {
132+
if (!normalizeValue) {
133+
field.onChange(event);
134+
return;
135+
}
136+
const normalizedValue = normalizeValue(event.target.value);
137+
field.onChange(normalizedValue);
138+
}}
139+
value={value}
140+
/>
141+
{isMaskedInput && (
142+
<button
143+
type="button"
144+
onClick={toggleVisibility}
145+
className="text-default-400 hover:text-default-500 absolute top-1/2 right-3 -translate-y-1/2"
146+
aria-label={
147+
inputType === "password"
148+
? "Show password"
149+
: "Hide password"
150+
}
151+
>
152+
<Icon
153+
className="pointer-events-none text-xl"
154+
icon={
155+
(password && isPasswordVisible) ||
156+
(confirmPassword && isConfirmPasswordVisible) ||
157+
(type === "password" && isPasswordVisible)
158+
? "solar:eye-closed-linear"
159+
: "solar:eye-bold"
160+
}
161+
/>
162+
</button>
163+
)}
164+
</div>
165+
</FormControl>
166+
<FormMessage className="text-text-error max-w-full text-xs" />
167+
</div>
168+
);
169+
}}
170+
/>
171+
);
172+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client";
2+
3+
import { RadioGroupItem } from "@/components/shadcn/radio-group/radio-group";
4+
import { cn } from "@/lib/utils";
5+
6+
interface WizardRadioCardProps {
7+
value: string;
8+
children: React.ReactNode;
9+
isInvalid?: boolean;
10+
}
11+
12+
export const WizardRadioCard = ({
13+
value,
14+
children,
15+
isInvalid = false,
16+
}: WizardRadioCardProps) => {
17+
return (
18+
<div
19+
className={cn(
20+
"group inline-flex w-full cursor-pointer items-center justify-between gap-4 rounded-lg border-2 p-4",
21+
"border-default hover:border-button-primary",
22+
"has-[[data-state=checked]]:border-button-primary",
23+
isInvalid && "border-bg-fail",
24+
)}
25+
>
26+
<label
27+
htmlFor={value}
28+
className="flex flex-1 cursor-pointer items-center"
29+
>
30+
<span className="ml-2">{children}</span>
31+
</label>
32+
<RadioGroupItem value={value} id={value} />
33+
</div>
34+
);
35+
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"use client";
2+
3+
import { Control, FieldPath, FieldValues } from "react-hook-form";
4+
5+
import { Textarea } from "@/components/shadcn/textarea/textarea";
6+
import { FormControl, FormField, FormMessage } from "@/components/ui/form";
7+
import { cn } from "@/lib/utils";
8+
9+
interface WizardTextareaFieldProps<T extends FieldValues> {
10+
control: Control<T>;
11+
name: FieldPath<T>;
12+
label?: string;
13+
labelPlacement?: "inside" | "outside" | "outside-left";
14+
variant?: "flat" | "bordered" | "underlined" | "faded";
15+
size?: "sm" | "md" | "lg";
16+
placeholder?: string;
17+
defaultValue?: string;
18+
isRequired?: boolean;
19+
minRows?: number;
20+
maxRows?: number;
21+
fullWidth?: boolean;
22+
disableAutosize?: boolean;
23+
description?: React.ReactNode;
24+
requiredIndicator?: boolean;
25+
}
26+
27+
export const WizardTextareaField = <T extends FieldValues>({
28+
control,
29+
name,
30+
label = name,
31+
labelPlacement = "inside",
32+
variant,
33+
size,
34+
placeholder,
35+
defaultValue,
36+
isRequired = false,
37+
minRows = 3,
38+
maxRows,
39+
fullWidth = true,
40+
disableAutosize = false,
41+
description,
42+
requiredIndicator,
43+
}: WizardTextareaFieldProps<T>) => {
44+
void variant;
45+
void size;
46+
void fullWidth;
47+
void disableAutosize;
48+
const showRequiredIndicator = requiredIndicator ?? isRequired;
49+
50+
return (
51+
<FormField
52+
control={control}
53+
name={name}
54+
render={({ field }) => {
55+
const value = field.value ?? defaultValue ?? "";
56+
57+
return (
58+
<div className="flex flex-col gap-1.5">
59+
<label
60+
htmlFor={name}
61+
className={cn(
62+
"text-text-neutral-tertiary text-xs",
63+
labelPlacement === "outside"
64+
? "font-medium"
65+
: "font-light tracking-tight",
66+
)}
67+
>
68+
{label}
69+
{showRequiredIndicator && (
70+
<span className="text-text-error-primary">*</span>
71+
)}
72+
</label>
73+
<FormControl>
74+
<Textarea
75+
id={name}
76+
aria-label={label}
77+
placeholder={placeholder}
78+
required={isRequired}
79+
rows={maxRows ? Math.min(minRows, maxRows) : minRows}
80+
className={cn(description && "mb-1")}
81+
{...field}
82+
value={value}
83+
/>
84+
</FormControl>
85+
{description && (
86+
<p className="text-text-neutral-tertiary max-w-full text-xs">
87+
{description}
88+
</p>
89+
)}
90+
<FormMessage className="text-text-error max-w-full text-xs" />
91+
</div>
92+
);
93+
}}
94+
/>
95+
);
96+
};

0 commit comments

Comments
 (0)