Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
3824cd7
web: add TanStack Form dependency
dgdavid Mar 18, 2026
d711ab6
refactor(web): start connection form reimplementation
dgdavid Mar 18, 2026
66d5f8e
refactor(web): bring back method and gateway fields
dgdavid Mar 20, 2026
891869b
feat(web): add independent IPv4 and IPv6 method and gateway fields
dgdavid Mar 20, 2026
cd9e900
Bring back IP Addresses field as a textarea
dgdavid Mar 22, 2026
d650b04
Add (optional) suffix to gateway fields
dgdavid Mar 22, 2026
1b439bc
feat(web): add FieldLabel with styled suffix
dgdavid Mar 22, 2026
c36a34c
feat(web): add DNS servers field with checkbox opt-in pattern
dgdavid Mar 22, 2026
5a3ffb8
feat(web): add `hidden` prop to LabelText
dgdavid Mar 22, 2026
5e956b1
web: bring back DNS search domains field
dgdavid Mar 22, 2026
e7b2b84
doc(web): extend form conventions document
dgdavid Mar 22, 2026
a4e52c8
revert(web): use aria-label instead of LabelText hidden
dgdavid Mar 23, 2026
75bb307
fix(web): replace Checkbox body prop with NestedContent for DNS fields
dgdavid Mar 23, 2026
4e1ad67
doc(web): udpate form/conventions.md
dgdavid Mar 23, 2026
990b88b
refactor(web): reimplement network connection form using TanStack For…
dgdavid Mar 23, 2026
0137820
Merge branch 'master' into enhance-network-connection-form
dgdavid Mar 23, 2026
e5146d3
feat(web): add ChoiceField form component
dgdavid Mar 24, 2026
388e100
doc(web): add choice selector pattern to form conventions
dgdavid Mar 24, 2026
95d7ad2
feat(web): add IpSettings component
dgdavid Mar 24, 2026
fdf2464
refactor(web): integrate IpSettings into ConnectionForm
dgdavid Mar 24, 2026
37f063f
refactor(web): simplify IpSettings to a single mode selector
dgdavid Mar 25, 2026
a757999
refactor(web): apply progressive disclosure to IpSettings (approach 1)
dgdavid Mar 25, 2026
d59a56d
refactor(web): extend IpSettings mode selector with a Mixed option (a…
dgdavid Mar 25, 2026
401c5b4
refactor(web): add BindingModeSelector and DeviceSelector
dgdavid Mar 26, 2026
a7029eb
refactor(web): simplify IP settings and device binding options
dgdavid Mar 26, 2026
303d077
refactor(web): rename Advanced DHCP/Automatic to Advanced in IpSettings
dgdavid Mar 26, 2026
0f84025
refactor(web): adopt TanStack Form composition pattern in network sub…
dgdavid Mar 27, 2026
2abb1e9
refactor(web): delete HiddenLabel
dgdavid Mar 27, 2026
446ff5d
refactor(web): drop FlexItem wrappers without props in ConnectionForm
dgdavid Mar 27, 2026
8fd992d
refactor(web): move Device field before Name in ConnectionForm
dgdavid Mar 27, 2026
aae93c5
refactor(web): rename hooks/form*.tsx to .ts
dgdavid Mar 27, 2026
e65c596
web: adjustments from self-review
dgdavid Mar 29, 2026
a5007c9
refactor(web): network connection form UX and composition improvement…
dgdavid Mar 30, 2026
ae3f3c6
Merge branch 'master' into enhance-network-connection-form
dgdavid Mar 30, 2026
bd3bd6b
feat(web): add ArrayField form component for list values
dgdavid Mar 30, 2026
02f643f
feat(web): wire ArrayField for addresses, nameservers, and search dom…
dgdavid Mar 30, 2026
10b1ab5
feat(web): add client-side validation to ConnectionForm
dgdavid Mar 30, 2026
c144f5e
doc(web): add explanation about usage of aria-activedescendant
dgdavid Mar 31, 2026
3d4388d
refactor(web): rename useCustomDns/useCustomDnsSearch form fields
dgdavid Mar 31, 2026
16dc2f0
refactor(web): network connection form UX (part 3) (#3349)
dgdavid Mar 31, 2026
3bd2cb1
fix(web): Connection.method4/method6 no longer default to AUTO
dgdavid Mar 31, 2026
f2e2984
feat(web): add edit mode to ConnectionForm
dgdavid Mar 31, 2026
2bba2bb
fix(web): use config over system when pre-populating the connection form
dgdavid Mar 31, 2026
ea585ae
feat(web): do not show name field when editing a connection
dgdavid Mar 31, 2026
aef727b
feat(web): show empty state when the connection to edit is not found
dgdavid Mar 31, 2026
957ce1d
fix(web): fix broken tests after Connection.method4/method6 default r…
dgdavid Mar 31, 2026
438403c
fix(web): better "go back" link
dgdavid Apr 1, 2026
138f44b
feat(web): add useConnectionName hook
dgdavid Apr 1, 2026
57f9dc7
feat(web): auto-generate connection name from type and binding
dgdavid Apr 1, 2026
0fb92e9
doc(web): improve fixme wording
dgdavid Apr 1, 2026
478d120
refactor(web): network connection form UX (part 4) (#3351)
dgdavid Apr 1, 2026
b21cefa
Merge branch 'enhance-network-connection-form' into network-tanstack-…
dgdavid Apr 1, 2026
5f353c1
fix(web): better format for autogenerated connections name
dgdavid Apr 1, 2026
d81a81b
web: fix test left behind in previous commit
dgdavid Apr 1, 2026
d7801c4
feat(web): preserve device selection when switching binding modes
dgdavid Apr 1, 2026
c5f398b
fix(web): pre-select first device when changing binding
dgdavid Apr 1, 2026
21448ad
Merge branch 'network-tanstack-form-part5' into network-tanstack-form…
dgdavid Apr 1, 2026
bb14c02
refactor(web): remove IpSettingsForm and its dependencies
dgdavid Apr 3, 2026
24961a2
fix(web): catch invalid addresses when IP mode is advanced
dgdavid Apr 6, 2026
36dd2d5
refactor(web): replace useConnectionName hook with a pure function
dgdavid Apr 6, 2026
67e731f
refactor(web): use form-level listeners for connection name auto-sync
dgdavid Apr 6, 2026
cefcad3
feat(web): add TextField and CheckboxField form components
dgdavid Apr 6, 2026
ac02514
fix(web): please linters
dgdavid Apr 6, 2026
2273c67
refactor(web): rename ChoiceField to DropdownField
dgdavid Apr 7, 2026
b1dfdd8
docs(web): update form conventions to reflect current ConnectionForm …
dgdavid Apr 7, 2026
e5973cc
refactor(web): network connection form UX (part 5) (#3352)
dgdavid Apr 7, 2026
22a43d3
refactor(web): network connection form UX (part 6) (#3353)
dgdavid Apr 7, 2026
5db2690
Merge branch 'enhance-network-connection-form' into network-tanstack-…
dgdavid Apr 7, 2026
6901576
doc(web): update form/conventions document
dgdavid Apr 7, 2026
414f138
feat(web): add SubmitButton and CancelButton form components
dgdavid Apr 7, 2026
ea5a23d
doc(web): reformat document
dgdavid Apr 7, 2026
2c5d5c4
feat(web): add Interpolate component
dgdavid Apr 7, 2026
d26cf67
refactor(web): use core/Interploate at ArrayField
dgdavid Apr 7, 2026
3a7806d
fix(web): drop no really needed props and typecasting
dgdavid Apr 7, 2026
65a7dd0
web: add missing comments for translators
dgdavid Apr 7, 2026
30ed72d
refactor(web): improve Interpolate API naming and printf support
dgdavid Apr 8, 2026
22db5b5
Do not crash if there is no user network config
teclator Apr 8, 2026
0ab0ffa
refactor(web): network connection form UX (part 7) (#3363)
dgdavid Apr 8, 2026
8bf160d
Merge branch 'enhance-network-connection-form' into network-tanstack-…
dgdavid Apr 8, 2026
64ca6ad
feat(web): add mergeArrays option to extendCollection
dgdavid Apr 10, 2026
7d405d7
feat(web): infer IP mode from addresses when editing connections
dgdavid Apr 10, 2026
a436413
fix(web): change test method naming.
dgdavid Apr 10, 2026
dcb6d42
refactor(web): simplify auto-generated connection name
dgdavid Apr 10, 2026
6d0075e
Changes based on review feedback
teclator Apr 13, 2026
6ce4b8b
feat(web): auto-add default CIDR prefix in connection form
dgdavid Apr 13, 2026
ef1d99f
feat(web): add automatic CIDR prefix to IP addresses
dgdavid Apr 13, 2026
8100507
refactor(web): use classful prefix logic in ConnectionForm
dgdavid Apr 13, 2026
5b0c43a
refactor(web): normalize IP prefixes when loading connections
dgdavid Apr 13, 2026
44b2ca1
web: rename util to more accurated name
dgdavid Apr 13, 2026
a0dcc32
feat(web): add helper text to network form fields
dgdavid Apr 14, 2026
841ed4e
refactor(web): improve IP configuration modes in connection form
dgdavid Apr 14, 2026
618ef4c
refactor(web): use ADDRESS_REQUIRED_MODES constant for mode checks
dgdavid Apr 14, 2026
1914440
fix(web): fix broken tests
dgdavid Apr 14, 2026
79838eb
fix(web): deduplicate addresses when editing connections
dgdavid Apr 14, 2026
bc43c87
web: Adjust wording in the network connection form
ancorgs Apr 14, 2026
0d6e328
Fixed unit test
teclator Apr 14, 2026
cbc0c0a
refactor(web): network connection form UX (bonus part) (#3367)
dgdavid Apr 14, 2026
f0d3710
doc(web): add changes entry
dgdavid Apr 14, 2026
b8d4fa0
Merge branch 'master' into enhance-network-connection-form
dgdavid Apr 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"@patternfly/patternfly": "^6.4.0",
"@patternfly/react-core": "^6.4.1",
"@patternfly/react-table": "^6.4.1",
"@tanstack/react-form": "^1.28.4",
"@tanstack/react-query": "^5.90.21",
"axios": "^1.13.6",
"fast-sort": "^3.4.1",
Expand Down
6 changes: 6 additions & 0 deletions web/package/agama-web-ui.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Tue Apr 14 15:40:28 UTC 2026 - David Diaz <dgonzalez@suse.com>

- Redesign network connection form with improved user experience
(related to gh#agama-project/agama#3386 and bsc#1259067).

-------------------------------------------------------------------
Mon Apr 13 15:25:11 UTC 2026 - José Iván López González <jlopez@suse.com>

Expand Down
190 changes: 190 additions & 0 deletions web/src/components/core/Interpolate.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright (c) [2026] SUSE LLC
*
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, contact SUSE LLC.
*
* To contact SUSE LLC about this file by physical or electronic mail, you may
* find current contact information at www.suse.com.
*/

import React from "react";
import { plainRender } from "~/test-utils";
import Interpolate from "./Interpolate";

describe("Interpolate", () => {
describe("with a printf placeholder", () => {
it("renders surrounding text for %s", () => {
const { container } = plainRender(
<Interpolate sentence="Go to %s page">{() => <strong>settings</strong>}</Interpolate>,
);
expect(container.textContent).toBe("Go to settings page");
});

it("renders surrounding text for %d", () => {
const { container } = plainRender(
<Interpolate sentence="There are %d issues">{() => <strong>3</strong>}</Interpolate>,
);
expect(container.textContent).toBe("There are 3 issues");
});

it("calls children with an empty string", () => {
const received: string[] = [];
plainRender(
<Interpolate sentence="Go to %s page">
{(text) => {
received.push(text);
return <strong>{text}</strong>;
}}
</Interpolate>,
);
expect(received).toEqual([""]);
});

it("works when the placeholder is at the start", () => {
const { container } = plainRender(
<Interpolate sentence="%s is at the start">{() => <strong>This</strong>}</Interpolate>,
);
expect(container.textContent).toBe("This is at the start");
});

it("works when the placeholder is at the end", () => {
const { container } = plainRender(
<Interpolate sentence="At the end: %s">{() => <strong>here</strong>}</Interpolate>,
);
expect(container.textContent).toBe("At the end: here");
});

it("works when the whole sentence is the placeholder", () => {
const { container } = plainRender(
<Interpolate sentence="%s">{() => <strong>everything</strong>}</Interpolate>,
);
expect(container.textContent).toBe("everything");
});

it("throws when multiple placeholders are present", () => {
expect(() =>
plainRender(
<Interpolate sentence="First %s and second %d">{() => <strong>X</strong>}</Interpolate>,
),
).toThrow("Interpolate: only one printf placeholder is supported.");
});
});

describe("with a [marker] placeholder", () => {
it("renders the surrounding text", () => {
const { container } = plainRender(
<Interpolate sentence="Go to [settings] page">
{(text) => <strong>{text}</strong>}
</Interpolate>,
);
expect(container.textContent).toBe("Go to settings page");
});

it("passes the extracted text to children", () => {
const { container } = plainRender(
<Interpolate sentence="Go to [settings] page">
{(text) => <strong>{text}</strong>}
</Interpolate>,
);
expect(container.querySelector("strong")).toHaveTextContent("settings");
});

it("works when the placeholder is at the start", () => {
const { container } = plainRender(
<Interpolate sentence="[Start] of the sentence">
{(text) => <strong>{text}</strong>}
</Interpolate>,
);
expect(container.textContent).toBe("Start of the sentence");
expect(container.querySelector("strong")).toHaveTextContent("Start");
});

it("works when the placeholder is at the end", () => {
const { container } = plainRender(
<Interpolate sentence="End of the [sentence]">
{(text) => <strong>{text}</strong>}
</Interpolate>,
);
expect(container.textContent).toBe("End of the sentence");
expect(container.querySelector("strong")).toHaveTextContent("sentence");
});

it("works when the whole sentence is the placeholder", () => {
const { container } = plainRender(
<Interpolate sentence="[only]">{(text) => <strong>{text}</strong>}</Interpolate>,
);
expect(container.textContent).toBe("only");
expect(container.querySelector("strong")).toHaveTextContent("only");
});

it("passes an empty string to children when the brackets are empty", () => {
const { container } = plainRender(
<Interpolate sentence="Click [] to continue">
{(text) => <strong aria-label="injected">{text}</strong>}
</Interpolate>,
);
expect(container.querySelector("strong")).toBeEmptyDOMElement();
});

it("throws when multiple placeholders are present", () => {
expect(() =>
plainRender(
<Interpolate sentence="[first] and [second]">
{(text) => <strong>{text}</strong>}
</Interpolate>,
),
).toThrow("Interpolate: exactly one [marker] placeholder is supported.");
});

it("throws when a bracket is unmatched", () => {
expect(() =>
plainRender(
<Interpolate sentence="[unclosed">{(text) => <strong>{text}</strong>}</Interpolate>,
),
).toThrow("Interpolate: exactly one [marker] placeholder is supported.");
});
});

describe("without a placeholder", () => {
it("renders the sentence as plain text", () => {
const { container } = plainRender(
<Interpolate sentence="No placeholder here">
{(text) => <strong>{text}</strong>}
</Interpolate>,
);
expect(container.textContent).toBe("No placeholder here");
});

it("does not render any injected content", () => {
const { container } = plainRender(
<Interpolate sentence="No placeholder here">
{(text) => <strong>{text}</strong>}
</Interpolate>,
);
expect(container.querySelector("strong")).toBeNull();
});
});

describe("when children returns null", () => {
it("renders the surrounding text without the injected node", () => {
const { container } = plainRender(
<Interpolate sentence="Before [marker] after">{() => null}</Interpolate>,
);
expect(container.textContent).toBe("Before after");
expect(container.querySelector("strong")).toBeNull();
});
});
});
Loading
Loading