Skip to content

Commit dd0d5bb

Browse files
authored
Merge pull request #1236 from firebase/@invertase/bb-7
2 parents ee1569b + 7f19724 commit dd0d5bb

File tree

8 files changed

+110
-26
lines changed

8 files changed

+110
-26
lines changed

packages/angular/src/lib/auth/forms/sign-in-auth-form.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ describe("<fui-sign-in-auth-form />", () => {
7474
});
7575

7676
expect(screen.getByLabelText("Email Address")).toBeInTheDocument();
77-
expect(screen.getByLabelText("Password")).toBeInTheDocument();
77+
expect(screen.getByLabelText("Password", { selector: "input" })).toBeInTheDocument();
7878
expect(screen.getByRole("button", { name: "Sign In" })).toBeInTheDocument();
7979
expect(screen.getByText("By continuing, you agree to our")).toBeInTheDocument();
8080
expect(screen.getByRole("button", { name: "Forgot Password" })).toBeInTheDocument();

packages/angular/src/lib/auth/forms/sign-in-auth-form.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ import {
6161
type="password"
6262
>
6363
@if (forgotPassword) {
64-
<button fui-form-action (click)="forgotPassword.emit()">
64+
<button ngProjectAs="input-action" fui-form-action (click)="forgotPassword.emit()">
6565
{{ forgotPasswordLabel() }}
6666
</button>
6767
}

packages/angular/src/lib/components/form.spec.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,15 @@
1616

1717
import { render, screen } from "@testing-library/angular";
1818
import { Component, signal } from "@angular/core";
19-
20-
import { FormMetadataComponent, FormActionComponent, FormSubmitComponent, FormErrorMessageComponent } from "./form";
19+
import { injectForm, TanStackAppField } from "@tanstack/angular-form";
20+
21+
import {
22+
FormMetadataComponent,
23+
FormActionComponent,
24+
FormSubmitComponent,
25+
FormErrorMessageComponent,
26+
FormInputComponent,
27+
} from "./form";
2128
import { ButtonComponent } from "./button";
2229

2330
@Component({
@@ -164,6 +171,35 @@ describe("Form Components", () => {
164171
});
165172
});
166173

174+
describe("<fui-form-input>", () => {
175+
@Component({
176+
template: `
177+
<fui-form-input name="test" tanstack-app-field [tanstackField]="form" label="Test Label">
178+
<button ngProjectAs="input-action" fui-form-action data-testid="test-action">Action</button>
179+
</fui-form-input>
180+
`,
181+
standalone: true,
182+
imports: [FormInputComponent, TanStackAppField, FormActionComponent],
183+
})
184+
class TestFormInputHostComponent {
185+
form = injectForm({
186+
defaultValues: {
187+
test: "",
188+
},
189+
});
190+
}
191+
192+
it("renders action content when provided", async () => {
193+
await render(TestFormInputHostComponent, {
194+
imports: [TestFormInputHostComponent],
195+
});
196+
197+
const actionButton = screen.getByTestId("test-action");
198+
expect(actionButton).toBeTruthy();
199+
expect(actionButton).toHaveTextContent("Action");
200+
});
201+
});
202+
167203
describe("<fui-form-error-message>", () => {
168204
it("renders error message when onSubmit error exists", async () => {
169205
await render(TestFormErrorMessageHostComponent);

packages/angular/src/lib/components/form.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,22 @@ export class FormMetadataComponent {
3030
imports: [FormMetadataComponent],
3131
template: `
3232
<label [for]="field.api.name">
33-
<span>{{ label() }}</span>
34-
<input
35-
[attr.aria-invalid]="field.api.state.meta.isTouched && field.api.state.meta.errors.length > 0"
36-
[type]="type()"
37-
[id]="field.api.name"
38-
[name]="field.api.name"
39-
[value]="field.api.state.value"
40-
(blur)="field.api.handleBlur()"
41-
(input)="field.api.handleChange($any($event).target.value)"
42-
/>
33+
<div data-input-label>
34+
<div>{{ label() }}</div>
35+
<div><ng-content select="input-action" /></div>
36+
</div>
37+
<div data-input-group>
38+
<ng-content select="input-before" />
39+
<input
40+
[attr.aria-invalid]="field.api.state.meta.isTouched && field.api.state.meta.errors.length > 0"
41+
[id]="field.api.name"
42+
[name]="field.api.name"
43+
[value]="field.api.state.value"
44+
(blur)="field.api.handleBlur()"
45+
(input)="field.api.handleChange($any($event).target.value)"
46+
[type]="type()"
47+
/>
48+
</div>
4349
<ng-content></ng-content>
4450
<fui-form-metadata [field]="field.api"></fui-form-metadata>
4551
</label>

packages/react/src/auth/forms/sign-in-auth-form.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,17 @@ export function SignInAuthForm({ onSignIn, onForgotPasswordClick, onSignUpClick
9595
<fieldset>
9696
<form.AppField name="password">
9797
{(field) => (
98-
<field.Input label={getTranslation(ui, "labels", "password")} type="password">
99-
{onForgotPasswordClick ? (
100-
<form.Action onClick={onForgotPasswordClick}>
101-
{getTranslation(ui, "labels", "forgotPassword")}
102-
</form.Action>
103-
) : null}
104-
</field.Input>
98+
<field.Input
99+
label={getTranslation(ui, "labels", "password")}
100+
type="password"
101+
action={
102+
onForgotPasswordClick ? (
103+
<form.Action onClick={onForgotPasswordClick}>
104+
{getTranslation(ui, "labels", "forgotPassword")}
105+
</form.Action>
106+
) : null
107+
}
108+
/>
105109
)}
106110
</form.AppField>
107111
</fieldset>

packages/react/src/components/form.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,36 @@ describe("form export", () => {
9090
expect(screen.getByTestId("test-child")).toBeInTheDocument();
9191
});
9292

93+
it("should render the Input action prop when provided", () => {
94+
const { result } = renderHook(() => {
95+
return form.useAppForm({
96+
defaultValues: { foo: "bar" },
97+
});
98+
});
99+
100+
const hook = result.current;
101+
102+
render(
103+
<hook.AppForm>
104+
<hook.AppField name="foo">
105+
{(field) => (
106+
<field.Input
107+
label="Foo"
108+
action={
109+
<button type="button" data-testid="test-action">
110+
Action
111+
</button>
112+
}
113+
/>
114+
)}
115+
</hook.AppField>
116+
</hook.AppForm>
117+
);
118+
119+
expect(screen.getByTestId("test-action")).toBeInTheDocument();
120+
expect(screen.getByTestId("test-action")).toHaveTextContent("Action");
121+
});
122+
93123
it("should render the Input metadata when available", async () => {
94124
const { result } = renderHook(() => {
95125
return form.useAppForm({

packages/react/src/components/form.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,17 @@ function Input({
2323
children,
2424
before,
2525
label,
26+
action,
2627
...props
27-
}: PropsWithChildren<ComponentProps<"input"> & { label: string; before?: ReactNode }>) {
28+
}: PropsWithChildren<ComponentProps<"input"> & { label: string; before?: ReactNode; action?: ReactNode }>) {
2829
const field = useFieldContext<string>();
2930

3031
return (
3132
<label htmlFor={field.name}>
32-
<span>{label}</span>
33+
<div data-input-label>
34+
<div>{label}</div>
35+
{action ? <div>{action}</div> : null}
36+
</div>
3337
<div data-input-group>
3438
{before}
3539
<input

packages/styles/src/base.css

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,20 @@
113113
@apply flex flex-col gap-2 text-text;
114114
}
115115

116-
:where(.fui-form fieldset label span) {
117-
@apply inline-flex gap-3 text-sm font-medium;
116+
:where(.fui-form fieldset label div[data-input-label]) {
117+
@apply flex gap-3 text-sm font-medium;
118+
}
119+
120+
:where(.fui-form fieldset label div[data-input-label] > div:first-child) {
121+
@apply grow;
118122
}
119123

120124
:where(.fui-form .fui-form__action) {
121125
@apply px-1 hover:underline text-xs text-text-muted;
122126
}
123127

124128
:where(.fui-form fieldset label input) {
125-
@apply w-full border-1 border-input rounded px-2 py-2 text-sm focus:outline-2 focus:outline-primary shadow-xs bg-transparent;
129+
@apply w-full border border-input rounded px-2 py-2 text-sm focus:outline-2 focus:outline-primary shadow-xs bg-transparent;
126130
}
127131

128132
:where(.fui-form fieldset label input[aria-invalid="true"]) {

0 commit comments

Comments
 (0)