Skip to content

chore: add <SettingsGroup> to auto-group settings blocks#593

Merged
SupertigerDev merged 1 commit intoNerimity:mainfrom
Aeledfyr:settings-group
Mar 2, 2026
Merged

chore: add <SettingsGroup> to auto-group settings blocks#593
SupertigerDev merged 1 commit intoNerimity:mainfrom
Aeledfyr:settings-group

Conversation

@Aeledfyr
Copy link
Copy Markdown
Contributor

@Aeledfyr Aeledfyr commented Mar 1, 2026

This adds a <SettingsGroup> component to handle styling the grouping of setting blocks automatically, and then refactors the main settings page to use the new groups. This doesn't refactor server settings / application settings / moderation; those are for future PRs.

This is also partial prep-work for making the gaps between settings blocks more consistent, but that's also for a future PR. This PR causes some minor changes to the spacing between settings blocks in some cases, but the spacing is already inconsistent, so it shouldn't be much of an issue.

Did you test your code?

Tested on Firefox on Linux and Chrome on Android. Note that this refactors the activity status modal, which only normally appears on the desktop app. I manually enabled it for testing, but I don't know how you'd properly test local changes on the desktop app.

Checklist

  • Changes are clear, concise, and easy to review
  • Code has been tested and works as intended
  • Text/content changes support internationalization (i18n)
  • Any new user-facing strings are properly localized

Summary by CodeRabbit

  • Refactor
    • Settings pages now use a unified grouping container for related options for more consistent grouping.
    • Sections like emoji picker, badges, connections, and various option groups are visually consolidated into grouped blocks.
    • Removed redundant per-item headers and rounded-edge inconsistencies; section borders and spacing simplified.
    • Overall visual spacing, alignment, and click targets improved across all settings panels; no changes to settings behavior.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Adds a new SettingsGroup export and composes many settings panels with SettingsGroup/Block; refactors SettingsBlock radius/header handling into CSS module classes; Block gains optional style and onClick props. No substantive state or behavioral changes.

Changes

Cohort / File(s) Summary
Settings pages
src/components/settings/ActivityStatus.tsx, src/components/settings/BadgeSettings.tsx, src/components/settings/CallSettings.tsx, src/components/settings/ConnectionsSettings.tsx, src/components/settings/InterfaceSettings.tsx, src/components/settings/NotificationsSettings.tsx, src/components/settings/PrivacySettings.tsx, src/components/settings/WindowSettings.tsx
Replaced FlexColumn/plain div groupings with SettingsGroup; replaced some inner wrappers with Block; removed header, borderTopRadius, borderBottomRadius usage from many SettingsBlock instances and simplified per-item wrapper logic.
UI settings-block primitives
src/components/ui/settings-block/SettingsBlock.tsx, src/components/ui/settings-block/Block.tsx, src/components/ui/settings-block/styles.module.scss
Added named export SettingsGroup; moved radius/header conditional logic to CSS classes (joinTop, joinBottom, .group); BlockProps now accept style?: JSX.CSSProperties and onClick?: () => void; class composition simplified.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I hopped through code with careful paws,

I joined the blocks and smoothed their laws,
Corners tucked and groups aligned just right,
Quietly I nibbled bugs out of sight,
Now settings sleep beneath the moonlight 🌙🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: add to auto-group settings blocks' clearly and concisely summarizes the main change, accurately reflecting the introduction of a new SettingsGroup component and its usage across settings files.
Description check ✅ Passed The PR description covers the main purpose, testing approach, and scope limitations. While i18n/localization items are marked unchecked (appropriately, as they don't apply to this change), the description addresses the key template sections including what the PR does and testing details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ui/settings-block/Block.tsx`:
- Around line 17-25: The Block component currently renders a div with
props.onClick but lacks keyboard and semantic affordances; update the render to
detect when props.onClick is present and: add role="button" and tabIndex={0},
attach an onKeyDown handler that triggers props.onClick when Enter or Space is
pressed, and add a visual affordance CSS class (e.g., styles.clickable or
similar) via the existing classNames call so focus and hover styles indicate
interactivity; ensure you use the existing symbols (Block component rendering
the div, props.onClick, classNames, conditionalClass, styles.block/
styles.joinTop/ styles.joinBottom, props.class, props.style) so the change is
localized and consistent.

In `@src/components/ui/settings-block/SettingsBlock.tsx`:
- Around line 86-100: The GroupProps type declares a style prop but it isn't
applied in SettingsGroup; either forward it to the rendered div (add
style={props.style} on the root div in SettingsGroup and ensure you're using
props.children directly rather than the strange children(() => props.children)
helper) or remove style from GroupProps if you intentionally don't want inline
styles; update GroupProps and the SettingsGroup implementation accordingly
(references: GroupProps, SettingsGroup, props.style, props.children).

In `@src/components/ui/settings-block/styles.module.scss`:
- Around line 29-39: The current grouping CSS uses .group >
.block:not(:first-child) and :not(:last-child), which considers position among
all siblings and incorrectly removes border radii when non-.block siblings
(e.g., tokenCategory divs) are present; update the rules to use adjacent sibling
combinators so only consecutive .block elements are joined (replace the
:not(:first-child) and :not(:last-child) conditions with selectors like .group >
.block + .block and .group > .block + .block { /* bottom-radius removal for
preceding block or top-radius removal for following block as appropriate */ } to
target only back-to-back .block elements and preserve radii around other
siblings).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9645755 and 07d3849.

📒 Files selected for processing (11)
  • src/components/settings/ActivityStatus.tsx
  • src/components/settings/BadgeSettings.tsx
  • src/components/settings/CallSettings.tsx
  • src/components/settings/ConnectionsSettings.tsx
  • src/components/settings/InterfaceSettings.tsx
  • src/components/settings/NotificationsSettings.tsx
  • src/components/settings/PrivacySettings.tsx
  • src/components/settings/WindowSettings.tsx
  • src/components/ui/settings-block/Block.tsx
  • src/components/ui/settings-block/SettingsBlock.tsx
  • src/components/ui/settings-block/styles.module.scss

Comment on lines +17 to +25
<div
onClick={props.onClick}
class={classNames(
styles.block,
conditionalClass(props.borderTopRadius === false, css`&& {border-top-left-radius: 0; border-top-right-radius: 0; margin-top: 0;}`),
conditionalClass(props.borderBottomRadius === false, css`&& {border-bottom-left-radius: 0; border-bottom-right-radius: 0; margin-bottom: 0;}`),
conditionalClass(props.borderBottomRadius === false && props.borderTopRadius === false, css`&& {margin-bottom: 1px;}`),
conditionalClass(props.borderTopRadius === false, styles.joinTop),
conditionalClass(props.borderBottomRadius === false, styles.joinBottom),
props.class
)}>
)}
style={props.style}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the Block.tsx file to understand the full context
cat -n src/components/ui/settings-block/Block.tsx

Repository: Nerimity/nerimity-web

Length of output: 1081


🏁 Script executed:

# Check if styles.clickable exists in the styles file
fd -e "css" -e "scss" -e "module.css" -e "module.scss" settings-block | head -20

Repository: Nerimity/nerimity-web

Length of output: 47


🏁 Script executed:

# Search for any keyboard handling patterns in the codebase
rg "onKeyDown|onKeyPress|role.*button" src/components --type tsx --type ts -A 2 | head -50

Repository: Nerimity/nerimity-web

Length of output: 92


🏁 Script executed:

# Check the styles file for Block component
cat -n src/components/ui/settings-block/styles.module.scss

Repository: Nerimity/nerimity-web

Length of output: 1675


🏁 Script executed:

# Search for keyboard handling in tsx/ts files with correct type flag
rg "onKeyDown|onKeyPress|role.*button" src/components -t ts -A 2 | head -60

Repository: Nerimity/nerimity-web

Length of output: 4130


🏁 Script executed:

# Look for other accessible button-like patterns in the codebase
rg "role=.*button" src/components -t ts -B 1 -A 3 | head -50

Repository: Nerimity/nerimity-web

Length of output: 47


🏁 Script executed:

# Check if there are any existing accessible interactive components as reference
fd -e "module.scss" settings-block

Repository: Nerimity/nerimity-web

Length of output: 47


🏁 Script executed:

# Check how Block component is used with onClick
rg "Block.*onClick|onClick.*=.*\{" src/components -t ts -B 1 -A 2 | head -40

Repository: Nerimity/nerimity-web

Length of output: 2472


🏁 Script executed:

# Check imports of Block to see usage frequency
rg "from.*settings-block.*Block|import.*Block.*from" src -t ts | head -20

Repository: Nerimity/nerimity-web

Length of output: 2614


🏁 Script executed:

# Look for other button/interactive components to see patterns
fd -type f -name "*.tsx" src/components | xargs grep -l "onClick" | head -10

Repository: Nerimity/nerimity-web

Length of output: 235


🏁 Script executed:

# Search for actual Block component usage with onClick
rg "Block.*onClick|<Block" src/components -t ts -A 3 -B 1 | head -50

Repository: Nerimity/nerimity-web

Length of output: 2985


🏁 Script executed:

# Check specific files that import Block for usage patterns
grep -n "onClick" src/components/settings/ActivityStatus.tsx src/components/settings/PrivacySettings.tsx src/components/settings/NotificationsSettings.tsx

Repository: Nerimity/nerimity-web

Length of output: 1209


Clickable Block needs keyboard semantics and interactive affordance.

onClick on a plain div is currently mouse-centric and inaccessible to keyboard users. Add keyboard activation (Enter/Space), focusability, semantic role, and visual affordance when onClick is provided.

Proposed fix
     <div
       onClick={props.onClick}
+      role={props.onClick ? "button" : undefined}
+      tabIndex={props.onClick ? 0 : undefined}
+      onKeyDown={(e) => {
+        if (!props.onClick) return;
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          props.onClick();
+        }
+      }}
       class={classNames(
         styles.block,
+        conditionalClass(props.onClick, styles.clickable),
         conditionalClass(props.borderTopRadius === false, styles.joinTop),
         conditionalClass(props.borderBottomRadius === false, styles.joinBottom),
         props.class
       )}
       style={props.style}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
onClick={props.onClick}
class={classNames(
styles.block,
conditionalClass(props.borderTopRadius === false, css`&& {border-top-left-radius: 0; border-top-right-radius: 0; margin-top: 0;}`),
conditionalClass(props.borderBottomRadius === false, css`&& {border-bottom-left-radius: 0; border-bottom-right-radius: 0; margin-bottom: 0;}`),
conditionalClass(props.borderBottomRadius === false && props.borderTopRadius === false, css`&& {margin-bottom: 1px;}`),
conditionalClass(props.borderTopRadius === false, styles.joinTop),
conditionalClass(props.borderBottomRadius === false, styles.joinBottom),
props.class
)}>
)}
style={props.style}
<div
onClick={props.onClick}
role={props.onClick ? "button" : undefined}
tabIndex={props.onClick ? 0 : undefined}
onKeyDown={(e) => {
if (!props.onClick) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
props.onClick();
}
}}
class={classNames(
styles.block,
conditionalClass(props.onClick, styles.clickable),
conditionalClass(props.borderTopRadius === false, styles.joinTop),
conditionalClass(props.borderBottomRadius === false, styles.joinBottom),
props.class
)}
style={props.style}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/settings-block/Block.tsx` around lines 17 - 25, The Block
component currently renders a div with props.onClick but lacks keyboard and
semantic affordances; update the render to detect when props.onClick is present
and: add role="button" and tabIndex={0}, attach an onKeyDown handler that
triggers props.onClick when Enter or Space is pressed, and add a visual
affordance CSS class (e.g., styles.clickable or similar) via the existing
classNames call so focus and hover styles indicate interactivity; ensure you use
the existing symbols (Block component rendering the div, props.onClick,
classNames, conditionalClass, styles.block/ styles.joinTop/ styles.joinBottom,
props.class, props.style) so the change is localized and consistent.

Comment on lines +29 to 39
.group > .block {
&:not(:first-child) {
border-top-left-radius: 0;
border-top-right-radius: 0;
margin-top: 0;
}
&:not(:last-child) {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
margin-bottom: 1px;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, find and examine the SCSS file
fd -t f "styles.module.scss" | grep -i "settings-block"

Repository: Nerimity/nerimity-web

Length of output: 116


🏁 Script executed:

# Then check the InterfaceSettings.tsx file
fd -t f "InterfaceSettings.tsx"

Repository: Nerimity/nerimity-web

Length of output: 110


🏁 Script executed:

# Read the SCSS file to understand the full context and selector logic
cat -n src/components/ui/settings-block/styles.module.scss

Repository: Nerimity/nerimity-web

Length of output: 1675


🏁 Script executed:

# Read InterfaceSettings.tsx around line 333 to see the actual sibling structure
sed -n '320,350p' src/components/settings/InterfaceSettings.tsx

Repository: Nerimity/nerimity-web

Length of output: 1067


🏁 Script executed:

# Search for SettingsGroup usage and structure to understand the DOM hierarchy
rg -A 5 "SettingsGroup" src/components/settings/InterfaceSettings.tsx | head -100

Repository: Nerimity/nerimity-web

Length of output: 981


🏁 Script executed:

# Also search for how tokenCategory and SettingsBlock are nested
rg "tokenCategory" src/components/settings/InterfaceSettings.tsx -B 2 -A 2

Repository: Nerimity/nerimity-web

Length of output: 211


🏁 Script executed:

# Read the SettingsBlock component to understand its structure
cat -n src/components/ui/settings-block/SettingsBlock.tsx

Repository: Nerimity/nerimity-web

Length of output: 3670


🏁 Script executed:

# Search for the specific section with tokenCategory to see the full structure
sed -n '320,360p' src/components/settings/InterfaceSettings.tsx

Repository: Nerimity/nerimity-web

Length of output: 1416


Use adjacent sibling combinators instead of :first-child/:last-child for block grouping.

The selectors .group > .block:not(:first-child) and :not(:last-child) evaluate position among all sibling elements, not just .block elements. The tokenCategory divs in src/components/settings/InterfaceSettings.tsx (lines 331–334) are direct siblings to blocks within .group, which causes the CSS to incorrectly strip border radii from blocks following these non-block siblings. Use adjacent sibling combinators (e.g., .block + .block) to only join consecutive .block elements.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/settings-block/styles.module.scss` around lines 29 - 39,
The current grouping CSS uses .group > .block:not(:first-child) and
:not(:last-child), which considers position among all siblings and incorrectly
removes border radii when non-.block siblings (e.g., tokenCategory divs) are
present; update the rules to use adjacent sibling combinators so only
consecutive .block elements are joined (replace the :not(:first-child) and
:not(:last-child) conditions with selectors like .group > .block + .block and
.group > .block + .block { /* bottom-radius removal for preceding block or
top-radius removal for following block as appropriate */ } to target only
back-to-back .block elements and preserve radii around other siblings).

Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
src/components/ui/settings-block/Block.tsx (1)

17-26: ⚠️ Potential issue | 🟠 Major

Add keyboard semantics for clickable Block.

onClick on a plain div is mouse-only right now. Add button semantics (role, tabIndex, Enter/Space handling) and a clickable affordance class when props.onClick is set.

Proposed fix
     <div
       onClick={props.onClick}
+      role={props.onClick ? "button" : undefined}
+      tabIndex={props.onClick ? 0 : undefined}
+      onKeyDown={(e) => {
+        if (!props.onClick) return;
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          props.onClick();
+        }
+      }}
       class={classNames(
         styles.block,
+        conditionalClass(props.onClick, styles.clickable),
         conditionalClass(props.borderTopRadius === false, styles.joinTop),
         conditionalClass(props.borderBottomRadius === false, styles.joinBottom),
         props.class
       )}
       style={props.style}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/settings-block/Block.tsx` around lines 17 - 26, The Block
component currently only attaches props.onClick to a div, so add keyboard/button
semantics and a visual affordance when clickable: when props.onClick is present,
add role="button" and tabIndex={0}, include a clickable class (e.g.,
styles.clickable) in the classNames call, and implement an onKeyDown handler on
the same element that calls props.onClick() when the user presses Enter or Space
(for Space call preventDefault to avoid scrolling); keep existing onClick,
classNames, conditionalClass, props.style and props.class usage intact so
behavior is identical for mouse but accessible via keyboard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/components/ui/settings-block/Block.tsx`:
- Around line 17-26: The Block component currently only attaches props.onClick
to a div, so add keyboard/button semantics and a visual affordance when
clickable: when props.onClick is present, add role="button" and tabIndex={0},
include a clickable class (e.g., styles.clickable) in the classNames call, and
implement an onKeyDown handler on the same element that calls props.onClick()
when the user presses Enter or Space (for Space call preventDefault to avoid
scrolling); keep existing onClick, classNames, conditionalClass, props.style and
props.class usage intact so behavior is identical for mouse but accessible via
keyboard.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 07d3849 and cbda04c.

📒 Files selected for processing (11)
  • src/components/settings/ActivityStatus.tsx
  • src/components/settings/BadgeSettings.tsx
  • src/components/settings/CallSettings.tsx
  • src/components/settings/ConnectionsSettings.tsx
  • src/components/settings/InterfaceSettings.tsx
  • src/components/settings/NotificationsSettings.tsx
  • src/components/settings/PrivacySettings.tsx
  • src/components/settings/WindowSettings.tsx
  • src/components/ui/settings-block/Block.tsx
  • src/components/ui/settings-block/SettingsBlock.tsx
  • src/components/ui/settings-block/styles.module.scss
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/settings/NotificationsSettings.tsx
  • src/components/ui/settings-block/styles.module.scss
  • src/components/settings/ConnectionsSettings.tsx

Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
src/components/settings/ActivityStatus.tsx (1)

418-436: The negative margin is a visual workaround.

The margin-top: "-1px" on the Block containing the EmojiPicker appears to be compensating for border/gap spacing to visually connect it to the settings block above. This works but is fragile if the underlying styles change.

Consider documenting this with a comment or exploring if the SettingsGroup CSS could handle this edge case natively.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/settings/ActivityStatus.tsx` around lines 418 - 436, The
negative margin on the Block wrapping EmojiPicker is a fragile visual
workaround; update the code to remove the inline margin-top: "-1px" and either
(a) add a short explanatory comment at the Block (near the Block component
usage) if keeping it is unavoidable, or (b) better: adjust the surrounding CSS
by modifying SettingsGroup/SettingsBlock styles to remove the gap or collapse
the border so the picker sits flush (look for SettingsGroup, SettingsBlock,
Block, and EmojiPicker to implement the style change), then toggle
showEmojiPicker() as before and ensure the visual connection remains without
hardcoded negative margins.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/components/settings/ActivityStatus.tsx`:
- Around line 418-436: The negative margin on the Block wrapping EmojiPicker is
a fragile visual workaround; update the code to remove the inline margin-top:
"-1px" and either (a) add a short explanatory comment at the Block (near the
Block component usage) if keeping it is unavoidable, or (b) better: adjust the
surrounding CSS by modifying SettingsGroup/SettingsBlock styles to remove the
gap or collapse the border so the picker sits flush (look for SettingsGroup,
SettingsBlock, Block, and EmojiPicker to implement the style change), then
toggle showEmojiPicker() as before and ensure the visual connection remains
without hardcoded negative margins.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbda04c and 239c1b8.

📒 Files selected for processing (11)
  • src/components/settings/ActivityStatus.tsx
  • src/components/settings/BadgeSettings.tsx
  • src/components/settings/CallSettings.tsx
  • src/components/settings/ConnectionsSettings.tsx
  • src/components/settings/InterfaceSettings.tsx
  • src/components/settings/NotificationsSettings.tsx
  • src/components/settings/PrivacySettings.tsx
  • src/components/settings/WindowSettings.tsx
  • src/components/ui/settings-block/Block.tsx
  • src/components/ui/settings-block/SettingsBlock.tsx
  • src/components/ui/settings-block/styles.module.scss
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/components/ui/settings-block/Block.tsx
  • src/components/settings/WindowSettings.tsx
  • src/components/ui/settings-block/styles.module.scss
  • src/components/settings/PrivacySettings.tsx
  • src/components/settings/NotificationsSettings.tsx

@SupertigerDev SupertigerDev merged commit 58c21d9 into Nerimity:main Mar 2, 2026
2 checks passed
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.

2 participants