Skip to content

feat(portal-framework-ui, portal-framework-auth): add form grouping functionality#454

Merged
pcfreak30 merged 1 commit intodevelopfrom
libs/portal-plugin-dashboard
Aug 19, 2025
Merged

feat(portal-framework-ui, portal-framework-auth): add form grouping functionality#454
pcfreak30 merged 1 commit intodevelopfrom
libs/portal-plugin-dashboard

Conversation

@pcfreak30
Copy link
Copy Markdown
Member

@pcfreak30 pcfreak30 commented Aug 19, 2025

  • Introduced FormGroup component for organizing form fields
  • Added group support in FormRenderer with configurable rendering order
  • Extended form types to include group configuration (GroupOrder enum, FormGroup interface)
  • Updated register form to use field grouping for name fields
  • Exported FormGroup from form components index

Summary by CodeRabbit

  • New Features
    • Forms now support field grouping with optional titles/descriptions and a configurable display order (groups first or ungrouped first).
    • Presentational group blocks added to visually bundle related inputs.
    • Form renderer and schema-driven forms accept grouping configuration to control layout.
    • Added customizable form header content.
    • Registration form layout improved: First Name and Last Name now appear side-by-side with equal widths.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Aug 19, 2025

⚠️ No Changeset found

Latest commit: 3103dc5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 19, 2025

Walkthrough

Adds form grouping support: new types and enum, a presentational FormGroup component, grouping-aware FormRenderer and SchemaForm wiring, and updates the register form to place first/last name into a horizontal "name" group. No public signature removals.

Changes

Cohort / File(s) Summary
Form types & API
libs/portal-framework-ui/src/components/form/types.ts
Adds GroupOrder enum and FormGroupType; extends FormConfig with groups, groupOrder, header; adds group?: string to FormFieldConfig.
Renderer & schema wiring
libs/portal-framework-ui/src/components/form/FormRenderer.tsx, libs/portal-framework-ui/src/components/form/SchemaForm.tsx
FormRenderer gains groups?: FormGroupType[], partitions fields into grouped/ungrouped, renders groups via FormGroup and orders output by GroupOrder. SchemaForm passes config.groups into FormRenderer.
FormGroup component + export
libs/portal-framework-ui/src/components/form/FormGroup.tsx, libs/portal-framework-ui/src/components/form/index.ts
Adds presentational FormGroup (optional title/description, children wrapper) and re-exports it from the form index.
Register form usage
libs/portal-framework-auth/src/ui/forms/register.tsx
Adds groupOrder: GroupOrder.GROUPS_FIRST, defines a name group with className: "flex gap-4", and assigns group: "name" and itemClassName: "flex-1" to first/last name fields (layout change only).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant SchemaForm
  participant FormRenderer
  participant Grouping as "Grouping Logic"
  participant FormGroupComp as "FormGroup"
  participant FieldRenderer

  User->>SchemaForm: request render (config with fields + groups + groupOrder)
  SchemaForm->>FormRenderer: render with props { fields, groups, ... }
  FormRenderer->>Grouping: classify fields into groups / ungrouped
  alt GroupOrder = GROUPS_FIRST
    Grouping-->>FormRenderer: grouped collections
    loop each group
      FormRenderer->>FormGroupComp: render group wrapper (title/desc/class)
      loop fields in group
        FormGroupComp->>FieldRenderer: render field
      end
    end
    loop ungrouped fields
      FormRenderer->>FieldRenderer: render field
    end
  else UNGROUPED_FIRST
    loop ungrouped fields
      FormRenderer->>FieldRenderer: render field
    end
    loop each group
      FormRenderer->>FormGroupComp: render group wrapper
      loop fields in group
        FormGroupComp->>FieldRenderer: render field
      end
    end
  end
  note right of FormRenderer: Ordering controlled by GroupOrder enum
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I hop and nudge two names in line,
A tidy group, both equal-sized.
Titles whisper, fields align,
Ungrouped wait until their prize.
A rabbit's stamp — forms organized. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8a9205f and 3103dc5.

📒 Files selected for processing (6)
  • libs/portal-framework-auth/src/ui/forms/register.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/form/FormGroup.tsx (1 hunks)
  • libs/portal-framework-ui/src/components/form/FormRenderer.tsx (1 hunks)
  • libs/portal-framework-ui/src/components/form/SchemaForm.tsx (1 hunks)
  • libs/portal-framework-ui/src/components/form/index.ts (1 hunks)
  • libs/portal-framework-ui/src/components/form/types.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • libs/portal-framework-ui/src/components/form/index.ts
  • libs/portal-framework-ui/src/components/form/FormGroup.tsx
  • libs/portal-framework-ui/src/components/form/SchemaForm.tsx
  • libs/portal-framework-ui/src/components/form/FormRenderer.tsx
  • libs/portal-framework-ui/src/components/form/types.ts
  • libs/portal-framework-auth/src/ui/forms/register.tsx
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch libs/portal-plugin-dashboard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
libs/portal-framework-ui/src/components/form/types.ts (1)

214-223: Fix return type: use ReactNode instead of React.ReactNode

Same namespace issue as header.

Apply this diff:

 export type StepFormFooterRenderer<
   TRequest extends BaseRecord = any,
   TResponse extends BaseRecord = any,
 > = (
   stepMethods: StepFormMethods,
   formMethods: any,
   closeDialog: () => void,
   currentDialog?: DialogConfig<TRequest, TResponse>,
-) => React.ReactNode;
+) => ReactNode;
🧹 Nitpick comments (7)
libs/portal-framework-ui/src/components/form/FormGroup.tsx (2)

2-2: Add a11y hooks (role/ARIA), expose id, and make inner spacing overridable

Current markup doesn’t expose an id or any a11y semantics, and the hardcoded "space-y-4" can clash with horizontal group layouts (e.g., className "flex gap-4"). Expose id, wire ARIA to title/description, and allow overriding the inner wrapper spacing.

Apply this diff:

-import React from "react";
+import React, { useId } from "react";

 interface FormGroupProps {
   children: React.ReactNode;
   className?: string;
   description?: string;
+  /**
+   * DOM id for the group container (useful for anchors/a11y)
+   */
+  id?: string;
+  /**
+   * Override classes for the inner content wrapper
+   */
+  contentClassName?: string;
   title?: string;
 }

 export const FormGroup = ({
   children,
   className,
   description,
+  contentClassName,
+  id,
   title,
 }: FormGroupProps) => {
+  const headingId = useId();
+  const descriptionId = useId();
   return (
-    <div className={cn(className)}>
+    <div
+      className={cn(className)}
+      id={id}
+      role="group"
+      aria-labelledby={title ? headingId : undefined}
+      aria-describedby={description ? descriptionId : undefined}
+    >
       {title || description ? (
         <>
-          {title && <h3 className="text-lg font-medium">{title}</h3>}
+          {title && (
+            <h3 id={headingId} className="text-lg font-medium">
+              {title}
+            </h3>
+          )}
           {description && (
-            <p className="text-sm text-muted-foreground">{description}</p>
+            <p id={descriptionId} className="text-sm text-muted-foreground">
+              {description}
+            </p>
           )}
-          <div className="space-y-4">{children}</div>
+          <div className={cn("space-y-4", contentClassName)}>{children}</div>
         </>
       ) : (
         children
       )}
     </div>
   );
 };

Note: If you want consistent DOM structure regardless of header presence, consider always wrapping children in the inner div (can be done in a follow-up).

Also applies to: 4-9, 17-30


25-26: Header-to-content spacing is likely tight

There’s no spacing between the header (title/description) and the children wrapper. Consider adding a small margin (e.g., mt-2 on the content wrapper) or using a header wrapper with space-y-1. Covered by the contentClassName addition above.

libs/portal-framework-ui/src/components/form/index.ts (1)

266-266: Re-export looks good; avoid name collision between component and type “FormGroup”

You’re now exporting both the component FormGroup (value) and the interface FormGroup (type) via export * from "./types". While TS keeps type/value namespaces separate, import { FormGroup } becomes ambiguous to consumers.

To improve DX without breaking changes, add an alias in types so consumers can explicitly import the config type:

// libs/portal-framework-ui/src/components/form/types.ts
 export interface FormGroup {
   className?: string;
   description?: string;
   id: string;
   title?: string;
 }
+
+/**
+ * Alias to avoid confusion with the FormGroup component export.
+ */
+export type FormGroupConfig = FormGroup;

Consumers can then do:

  • import { FormGroup } from "@lumeweb/portal-framework-ui" for the component
  • import type { FormGroupConfig } from "@lumeweb/portal-framework-ui" for the type
libs/portal-framework-ui/src/components/form/types.ts (1)

163-168: Type name “FormGroup” conflicts with component export; provide alias for clarity

Interface name is fine, but it collides (nominally) with the component export, confusing imports.

Non-breaking alias (recommended):

 export interface FormGroup {
   className?: string;
   description?: string;
   id: string;
   title?: string;
 }
+
+/**
+ * Alias to improve DX and avoid confusion with the FormGroup component.
+ */
+export type FormGroupConfig = FormGroup;

Optionally, update groups?: FormGroup[]; to groups?: FormGroupConfig[]; for self-documentation.

libs/portal-framework-auth/src/ui/forms/register.tsx (1)

70-76: Group configuration is coherent; watch for spacing interaction with FormGroup defaults

className: "flex gap-4" achieves the intended horizontal layout and GroupOrder.GROUPS_FIRST ensures the group renders before ungrouped fields.

However, the FormGroup component currently wraps children in a div with space-y-4 when a header/description is present, which may introduce unwanted vertical spacing in horizontal layouts. The companion refactor I suggested in FormGroup.tsx adds a contentClassName prop to override that. If you don’t plan on adding a header/description, you’re fine; if you will, consider overriding the inner spacing.

Would you like me to open a follow-up patch wiring contentClassName: "contents" here once the FormGroup prop is available?

libs/portal-framework-ui/src/components/form/FormRenderer.tsx (2)

36-57: Minor: use imported useMemo consistently (instead of React.useMemo).

You already import useMemo at the top. Use it consistently for readability and to align with the rest of the file.

Apply this diff:

-  const { groupedFields, ungroupedFields } = React.useMemo(() => {
+  const { groupedFields, ungroupedFields } = useMemo(() => {
     const grouped: Record<string, FormFieldConfig<TRequest>[]> = {};
     const ungrouped: FormFieldConfig<TRequest>[] = [];

Optional: consider warning when a field specifies group that is not declared in groups (it silently falls back to ungrouped). That can help catch configuration typos.


66-86: Rendering only non-empty groups is good; consider consistent spacing behavior.

Skipping empty groups avoids unnecessary DOM. Note: your FormGroup component adds spacing wrapper only when title/description exist; groups without headers will render children without that spacing. If uniform spacing is desired across all groups, centralize spacing inside FormGroup regardless of header presence.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between de8a559 and 8a9205f.

📒 Files selected for processing (6)
  • libs/portal-framework-auth/src/ui/forms/register.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/form/FormGroup.tsx (1 hunks)
  • libs/portal-framework-ui/src/components/form/FormRenderer.tsx (1 hunks)
  • libs/portal-framework-ui/src/components/form/SchemaForm.tsx (1 hunks)
  • libs/portal-framework-ui/src/components/form/index.ts (1 hunks)
  • libs/portal-framework-ui/src/components/form/types.ts (5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
libs/portal-framework-ui/src/components/form/FormGroup.tsx (1)
libs/portal-framework-ui/src/components/form/types.ts (1)
  • FormGroup (163-168)
libs/portal-framework-ui/src/components/form/SchemaForm.tsx (1)
libs/portal-framework-ui/src/components/form/FormRenderer.tsx (1)
  • FormRenderer (29-110)
libs/portal-framework-ui/src/components/form/FormRenderer.tsx (4)
libs/portal-framework-ui/src/components/form/types.ts (2)
  • FormFieldConfig (110-159)
  • FormGroup (163-168)
libs/portal-framework-ui/src/components/form/FormGroup.tsx (1)
  • FormGroup (11-32)
libs/portal-framework-ui/src/components/form/context.tsx (1)
  • useFormContext (35-43)
libs/portal-framework-ui/src/components/form/adapters.tsx (1)
  • adapters (44-111)
libs/portal-framework-ui/src/components/form/types.ts (1)
libs/portal-framework-ui/src/components/form/FormGroup.tsx (1)
  • FormGroup (11-32)
🔇 Additional comments (8)
libs/portal-framework-ui/src/components/form/types.ts (3)

21-24: Enum addition LGTM

The GroupOrder enum is clear and future-proofed for layout control.


120-123: Field grouping hook LGTM

Adding the group?: string field is consistent with the new grouping feature.


104-104: No concerns

The submitLabel line change is benign.

libs/portal-framework-ui/src/components/form/SchemaForm.tsx (1)

157-157: Passing groups into FormRenderer is correct

Propagating cConfig.groups enables grouping without altering submit/validation paths. Looks good.

libs/portal-framework-auth/src/ui/forms/register.tsx (1)

15-19: Grouping first/last name fields is implemented correctly

Using group: "name" with itemClassName: "flex-1" and per-field className: "space-y-2" matches the new grouping model and preserves equal widths.

Also applies to: 24-28

libs/portal-framework-ui/src/components/form/FormRenderer.tsx (3)

64-65: LGTM: sensible default for rendering order.

Falling back to GroupOrder.UNGROUPED_FIRST keeps backward compatibility when groupOrder isn’t specified in config.


87-94: LGTM: ungrouped fields rendering is straightforward and preserves field order.


95-109: LGTM: clear control-flow for groupOrder.

The conditional keeps intent obvious and avoids mixing the sequences.

…unctionality

- Introduced FormGroup component for organizing form fields
- Added group support in FormRenderer with configurable rendering order
- Extended form types to include group configuration (GroupOrder enum, FormGroup interface)
- Updated register form to use field grouping for name fields
- Exported FormGroup from form components index
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant