Skip to content

Commit

Permalink
fix: placeholders & password autocomplete (#78)
Browse files Browse the repository at this point in the history
* Fix placeholders & password autocomplete

* Address feedback

* Update tests

* Lint

* Add input props

* Put autocomplete back

* add input props
  • Loading branch information
elenajdanova authored May 17, 2024
1 parent 4929254 commit f8e084a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 33 deletions.
6 changes: 3 additions & 3 deletions src/controls/PrimitiveArrayControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe("PrimitiveArrayControl", () => {
schema: stringArrayControlJsonSchema,
uischema: arrayControlUISchema,
})
await screen.findByPlaceholderText("Enter value")
await screen.findByPlaceholderText("value")
screen.getByRole("button", { name: "Add Assets" })
})

Expand Down Expand Up @@ -75,7 +75,7 @@ describe("PrimitiveArrayControl", () => {
data = result.data as JSONFormData<typeof stringArrayControlJsonSchema>
},
})
const newAsset = await screen.findByPlaceholderText("Enter value")
const newAsset = await screen.findByPlaceholderText("value")
await user.type(newAsset, "new")
screen.getByDisplayValue("new")
await waitFor(() => {
Expand All @@ -94,7 +94,7 @@ describe("PrimitiveArrayControl", () => {
const addButton = await screen.findByRole("button", { name: "Add Assets" })
await user.click(addButton)
await user.click(addButton)
const inputFields = await screen.findAllByPlaceholderText("Enter value")
const inputFields = await screen.findAllByPlaceholderText("value")
await user.type(inputFields[0], "my asset")
await user.type(inputFields[1], "remove me!")
await user.type(inputFields[2], "my other asset")
Expand Down
13 changes: 6 additions & 7 deletions src/controls/TextControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ test("renders default value when present", async () => {
await waitFor(() => {
expect(
screen.getByPlaceholderText(
"Enter " + defaultValueTextInputSchema.properties.foo.title,
defaultValueTextInputSchema.properties.foo.title,
{ exact: false },
),
).toHaveValue(defaultValueTextInputSchema.properties.foo.default)
Expand All @@ -60,7 +60,7 @@ test("updates jsonforms data as expected", async () => {
})

const input = screen.getByPlaceholderText(
"Enter " + textInputSchema.properties.foo.title,
textInputSchema.properties.foo.title,
{ exact: false },
)

Expand Down Expand Up @@ -88,10 +88,9 @@ test("renders a password when present", async () => {
} satisfies JSONSchema

render({ schema: passwordSchema, uischema: passwordUISchema })
await screen.findByPlaceholderText(
"Enter " + passwordSchema.properties.secret.title,
{ exact: false },
)
await screen.findByPlaceholderText(passwordSchema.properties.secret.title, {
exact: false,
})
expect(
(screen.getByLabelText("Secret") satisfies HTMLInputElement).type,
).toEqual("password")
Expand Down Expand Up @@ -122,7 +121,7 @@ test("renders error messages from rule validation", async () => {
})

const inputElement = await screen.findByPlaceholderText(
"Enter " + patternSchema.properties.name.title,
patternSchema.properties.name.title,
{ exact: false },
)

Expand Down
50 changes: 29 additions & 21 deletions src/controls/TextControl.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import type { ChangeEvent } from "react"
import { useCallback, useEffect } from "react"
import type { InputProps } from "antd"
import { Input, Form } from "antd"
import { QuestionCircleOutlined } from "@ant-design/icons"
import type { Rule } from "antd/es/form"
import type { TextAreaProps } from "antd/es/input"
import type { ControlProps } from "@jsonforms/core"

import type { TextControlOptions, TextControlType } from "../ui-schema"
import type { TextControlOptions } from "../ui-schema"
import { assertNever } from "../common/assert-never"
import { withJsonFormsControlProps } from "@jsonforms/react"
interface TextControlProps extends ControlProps {
Expand Down Expand Up @@ -40,7 +38,6 @@ export function TextControl({
const ariaLabel = label || schema.description
const options: TextControlOptions =
(uischema.options as TextControlOptions) ?? {}
const textControlType: TextControlType = options.type ?? "singleline"
const tooltip = options.tooltip
const placeholderText = options.placeholderText
const form = Form.useFormInstance()
Expand Down Expand Up @@ -73,41 +70,52 @@ export function TextControl({
: {})}
>
<TextControlInput
type={textControlType}
aria-label={ariaLabel}
disabled={!enabled}
autoComplete="off"
onChange={(e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) =>
handleChange(path, e.target.value)
}
placeholder={`Enter ${
placeholderText ?? (label.toLowerCase() || "value")
}`}
placeholder={placeholderText ?? (label.toLowerCase() || "value")}
textControlOptions={options}
/>
</Form.Item>
)
}

type TextControlInputProps =
| (InputProps & { type: "singleline" })
| (TextAreaProps & { type: "multiline" })
| (InputProps & { type: "password" })
type TextControlInputProps = {
"aria-label": string | undefined
disabled: boolean
autoComplete: string
onChange: (e: ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => void
placeholder: string
textControlOptions: TextControlOptions
}

function TextControlInput({
textControlOptions,
...rest
}: TextControlInputProps) {
if (
!(`type` in textControlOptions) ||
textControlOptions.type === undefined
) {
return <Input {...{ ...rest, ...textControlOptions }} />
}

function TextControlInput({ type, ...rest }: TextControlInputProps) {
switch (type) {
switch (textControlOptions.type) {
case "multiline":
// idk why type isn't getting narrowed properly here, but cast seems safe
return <Input.TextArea {...(rest as TextAreaProps)} />
return <Input.TextArea {...{ ...rest, ...textControlOptions }} />
case "singleline":
// idk why type isn't getting narrowed properly here, but cast seems safe
return <Input {...(rest as InputProps)} />
return <Input {...{ ...rest, ...textControlOptions }} />
case "password":
return <Input.Password {...(rest as InputProps)} />
return <Input.Password {...{ ...rest, ...textControlOptions }} />

default:
try {
assertNever(type)
assertNever(textControlOptions.type)
} catch (e) {
return <Input {...(rest as InputProps)} />
return <Input {...{ ...rest, ...textControlOptions }} />
}
}
}
Expand Down
18 changes: 16 additions & 2 deletions src/ui-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import type {
AlertProps,
CardProps,
DividerProps,
InputProps,
} from "antd"
import type { TextAreaProps } from "antd/es/input"
import type { RuleObject as AntDRule } from "antd/es/form"
import type { TitleProps } from "antd/es/typography/Title"
import type { TextProps } from "antd/es/typography/Text"
Expand Down Expand Up @@ -152,12 +154,24 @@ export type AnyOfControlOptions = OneOfControlOptions
export type TextControlType = "multiline" | "password" | "singleline"

export type TextControlOptions = {
type?: TextControlType
tooltip?: string
placeholderText?: string
required?: boolean
rules?: AntDRule[]
}
} & (
| {
type?: "singleline"
inputProps?: InputProps
}
| {
type: "multiline"
inputProps?: TextAreaProps
}
| {
type: "password"
inputProps?: InputProps
}
)

/**
* A control element. The scope property of the control determines
Expand Down

0 comments on commit f8e084a

Please sign in to comment.