Skip to content

Commit 0fa0927

Browse files
Extract a CheckboxInput component from onboarding flow (#16545)
* add CheckboxInput component * use CheckboxInput component * wip * Adjusting styles and adding CheckboxInputField * renaming file for consistency * change back to semibold
1 parent a0b79e3 commit 0fa0927

File tree

4 files changed

+142
-47
lines changed

4 files changed

+142
-47
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import classNames from "classnames";
8+
import { FC, ReactNode, useCallback } from "react";
9+
import { useId } from "../../hooks/useId";
10+
import { InputField } from "./InputField";
11+
import { InputFieldHint } from "./InputFieldHint";
12+
13+
type CheckboxInputFieldProps = {
14+
label: string;
15+
error?: ReactNode;
16+
className?: string;
17+
};
18+
export const CheckboxInputField: FC<CheckboxInputFieldProps> = ({ label, error, className, children }) => {
19+
return (
20+
<InputField label={label} className={className} error={error}>
21+
<div className="space-y-2 ml-2">{children}</div>
22+
</InputField>
23+
);
24+
};
25+
26+
type CheckboxInputProps = {
27+
id?: string;
28+
value: string;
29+
checked: boolean;
30+
disabled?: boolean;
31+
label: string;
32+
hint?: string;
33+
onChange: (checked: boolean) => void;
34+
};
35+
export const CheckboxInput: FC<CheckboxInputProps> = ({
36+
id,
37+
value,
38+
label,
39+
hint,
40+
checked,
41+
disabled = false,
42+
onChange,
43+
}) => {
44+
const maybeId = useId();
45+
const elementId = id || maybeId;
46+
47+
const handleChange = useCallback(
48+
(e) => {
49+
onChange(e.target.checked);
50+
},
51+
[onChange],
52+
);
53+
54+
return (
55+
<label className="flex space-x-2 justify-start items-start" htmlFor={elementId}>
56+
<input
57+
type="checkbox"
58+
className={classNames(
59+
"h-4 w-4 mt-0.5 rounded cursor-pointer border-2 dark:filter-invert",
60+
"focus:ring-2 focus:border-gray-900 ring-blue-400 dark:focus:border-gray-800",
61+
"border-gray-600 dark:border-gray-900 bg-transparent",
62+
{ "bg-gray-600 dark:bg-gray-900": checked },
63+
)}
64+
value={value}
65+
id={elementId}
66+
checked={checked}
67+
disabled={disabled}
68+
onChange={handleChange}
69+
/>
70+
<div className="flex flex-col">
71+
<span
72+
className={classNames(
73+
"text-sm font-semibold cursor-pointer",
74+
disabled ? "text-gray-400 dark:text-gray-400" : "text-gray-600 dark:text-gray-100",
75+
)}
76+
>
77+
{label}
78+
</span>
79+
80+
{hint && <InputFieldHint disabled={disabled}>{hint}</InputFieldHint>}
81+
</div>
82+
</label>
83+
);
84+
};

components/dashboard/src/components/forms/InputField.tsx

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

77
import classNames from "classnames";
88
import { FunctionComponent, memo, ReactNode } from "react";
9+
import { InputFieldHint } from "./InputFieldHint";
910

1011
type Props = {
1112
label?: ReactNode;
@@ -21,7 +22,7 @@ export const InputField: FunctionComponent<Props> = memo(({ label, id, hint, err
2122
{label && (
2223
<label
2324
className={classNames(
24-
"text-sm font-semibold dark:text-gray-400",
25+
"text-md font-semibold dark:text-gray-400",
2526
error ? "text-red-600" : "text-gray-600",
2627
)}
2728
htmlFor={id}
@@ -31,7 +32,7 @@ export const InputField: FunctionComponent<Props> = memo(({ label, id, hint, err
3132
)}
3233
{children}
3334
{error && <span className="text-red-500 text-sm">{error}</span>}
34-
{hint && <span className="text-gray-500 text-sm">{hint}</span>}
35+
{hint && <InputFieldHint>{hint}</InputFieldHint>}
3536
</div>
3637
);
3738
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import classNames from "classnames";
8+
import { FC } from "react";
9+
10+
type Props = {
11+
disabled?: boolean;
12+
};
13+
export const InputFieldHint: FC<Props> = ({ disabled = false, children }) => {
14+
return (
15+
<span
16+
className={classNames(
17+
"text-sm",
18+
disabled ? "text-gray-400 dark:text-gray-400" : "text-gray-500 dark:text-gray-400",
19+
)}
20+
>
21+
{children}
22+
</span>
23+
);
24+
};

components/dashboard/src/onboarding/StepOrgInfo.tsx

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { User } from "@gitpod/gitpod-protocol";
88
import { FC, useCallback, useMemo, useState } from "react";
9-
import { InputField } from "../components/forms/InputField";
9+
import { CheckboxInput, CheckboxInputField } from "../components/forms/CheckboxInputField";
1010
import { SelectInputField } from "../components/forms/SelectInputField";
1111
import { TextInputField } from "../components/forms/TextInputField";
1212
import { useUpdateCurrentUserMutation } from "../data/current-user/update-mutation";
@@ -187,55 +187,41 @@ export const StepOrgInfo: FC<Props> = ({ user, onComplete }) => {
187187
onBlur={websiteError.onBlur}
188188
/>
189189

190-
<InputField label="I'm exploring Gitpod..." />
191-
<div className="mt-4 ml-2 space-y-2">
190+
<CheckboxInputField label="I'm exploring Gitpod...">
192191
{explorationReasonsOptions.map((o) => (
193-
<div key={o.value} className="flex space-x-2 justify-start items-center">
194-
<input
195-
type="checkbox"
196-
className="rounded"
197-
value={o.value}
198-
id={`explore_${o.value}`}
199-
checked={explorationReasons.includes(o.value)}
200-
onChange={(e) => {
201-
if (e.target.checked) {
202-
addExplorationReason(o.value);
203-
} else {
204-
removeExplorationReason(o.value);
205-
}
206-
}}
207-
/>
208-
<label className="text-sm dark:text-gray-400 text-gray-600" htmlFor={`explore_${o.value}`}>
209-
{o.label}
210-
</label>
211-
</div>
192+
<CheckboxInput
193+
key={o.value}
194+
value={o.value}
195+
label={o.label}
196+
checked={explorationReasons.includes(o.value)}
197+
onChange={(checked) => {
198+
if (checked) {
199+
addExplorationReason(o.value);
200+
} else {
201+
removeExplorationReason(o.value);
202+
}
203+
}}
204+
/>
212205
))}
213-
</div>
206+
</CheckboxInputField>
214207

215-
<InputField label="I'm signing up for Gitpod to..." />
216-
<div className="mt-4 ml-2 space-y-2">
208+
<CheckboxInputField label="I'm signing up for Gitpod to...">
217209
{signupGoalsOptions.map((o) => (
218-
<div key={o.value} className="flex space-x-2 justify-start items-center">
219-
<input
220-
type="checkbox"
221-
className="rounded"
222-
value={o.value}
223-
id={`goals_${o.value}`}
224-
checked={signupGoals.includes(o.value)}
225-
onChange={(e) => {
226-
if (e.target.checked) {
227-
addSignupGoal(o.value);
228-
} else {
229-
removeSignupGoal(o.value);
230-
}
231-
}}
232-
/>
233-
<label className="text-sm dark:text-gray-400 text-gray-600" htmlFor={`goals_${o.value}`}>
234-
{o.label}
235-
</label>
236-
</div>
210+
<CheckboxInput
211+
key={o.value}
212+
value={o.value}
213+
label={o.label}
214+
checked={signupGoals.includes(o.value)}
215+
onChange={(checked) => {
216+
if (checked) {
217+
addSignupGoal(o.value);
218+
} else {
219+
removeSignupGoal(o.value);
220+
}
221+
}}
222+
/>
237223
))}
238-
</div>
224+
</CheckboxInputField>
239225

240226
{signupGoals.includes(SIGNUP_GOALS_OTHER) && (
241227
<TextInputField value={signupGoalsOther} placeholder="Please specify" onChange={setSignupGoalsOther} />

0 commit comments

Comments
 (0)