-
Notifications
You must be signed in to change notification settings - Fork 4
Improve StrongForm validation UX and text input styling #1661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Improve StrongForm validation UX and text input styling #1661
Conversation
- add interaction-aware helpers to `useStrongForm` so inputs only go invalid after user focus/blur or submit, and expose ready-to-spread props for focus/blur/csModifiers/aria - refactor create-team, add-member, and add-service-account forms to use the new helper props instead of hand-rolled wiring - teach the theme’s TextInput component about invalid state colors so `csModifiers` visually reflect errors
WalkthroughThis PR enhances form field validation and interaction tracking across multiple components. The useStrongForm hook is extended with three new methods—getFieldState, getFieldInteractionProps, and getFieldComponentProps—to enable per-field state management and validation. Three settings components (Teams.tsx, MemberAddForm.tsx, ServiceAccounts.tsx) are updated to use these new APIs for binding field-level props and displaying required indicators. Supporting CSS changes introduce invalid state styling with new color tokens for input elements. Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/cyberstorm-remix/app/settings/teams/Teams.tsx (1)
234-236: Modal may close on submit error.
strongForm.submit()can throw an error, but.then(() => setOpen(false))will still execute if the promise resolves (even whenonSubmitErroris called internally before re-throwing). Consider usingasync/awaitwith try/catch, or movesetOpen(false)intoonSubmitSuccessto ensure the modal stays open on error.- onClick={() => { - strongForm.submit().then(() => setOpen(false)); - }} + onClick={async () => { + try { + await strongForm.submit(); + setOpen(false); + } catch { + // Modal stays open on error + } + }}
🧹 Nitpick comments (3)
packages/cyberstorm-theme/src/components/TextInput/TextInput.css (1)
31-33: Wrapper invalid icon color missing focus-within state.Line 31-33 sets the icon color for invalid state, but line 27-29 still applies focus color unconditionally. When an invalid input is focused, the icon will show focus color instead of invalid color.
Consider updating to maintain invalid styling during focus:
&:focus-within .text-input__left-icon { + &:not(.text-input__wrapper--invalid) { --text-input-left-icon-color: var(--input-icon-color--focus); + } } &.text-input__wrapper--invalid .text-input__left-icon { --text-input-left-icon-color: var(--input-icon-color--invalid); }apps/cyberstorm-remix/cyberstorm/utils/StrongForm/useStrongForm.ts (2)
73-78: Consider extendingisValueEmptyfor arrays/objects.Currently handles strings,
undefined, andnull. If you later need to validate that an array has items or an object has keys, this helper would incorrectly consider[]or{}as non-empty.const isValueEmpty = (value: unknown) => { if (typeof value === "string") { return value.trim() === ""; } + if (Array.isArray(value)) { + return value.length === 0; + } return value === undefined || value === null; };
191-191: Missing dependencies in useEffect dependency array.The effect references
props.refiner,props.onRefineSuccess, andprops.onRefineError(lines 172, 174-175, 182-183) but the dependency array only includesprops.inputs. If these callbacks change between renders, the effect will use stale versions, potentially causing bugs.Add them to the dependency array or wrap them with
useRefto maintain stable references.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/cyberstorm-remix/app/settings/teams/Teams.tsx(3 hunks)apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/MemberAddForm.tsx(3 hunks)apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx(2 hunks)apps/cyberstorm-remix/cyberstorm/utils/StrongForm/useStrongForm.ts(4 hunks)packages/cyberstorm-theme/src/components/TextInput/TextInput.css(2 hunks)packages/cyberstorm-theme/src/components/componentsColors.css(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx (1)
packages/cyberstorm/src/newComponents/Modal/Modal.tsx (1)
Modal(185-345)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build
- GitHub Check: Generate visual diffs
🔇 Additional comments (9)
apps/cyberstorm-remix/app/settings/teams/team/tabs/Members/MemberAddForm.tsx (1)
98-99: LGTM!Clean integration of the new
getFieldComponentPropshelper. The spread placement after explicit props is correct, and thearia-hidden="true"on the asterisk appropriately hides the visual indicator from screen readers while therequiredattribute handles accessibility.Also applies to: 126-127, 141-142
apps/cyberstorm-remix/app/settings/teams/Teams.tsx (1)
186-187: LGTM!Consistent use of the new field props pattern. Required indicator and accessibility attributes are properly configured.
Also applies to: 213-213, 216-216, 225-226
packages/cyberstorm-theme/src/components/TextInput/TextInput.css (1)
62-79: CSS specificity: hover/focus may override invalid styling.The invalid state rules (lines 62-68) are placed before hover/focus rules (lines 70-79). Since both use equivalent specificity, hover and focus styles will override the invalid styling when users interact with an invalid input.
If the intent is for invalid styling to persist during hover/focus, consider either:
- Adding
:not(.text-input--invalid)to the hover/focus rules, or- Repeating the invalid rules after hover/focus to ensure they take precedence
.text-input:hover { + &:not(.text-input--invalid) { --text-input-background-color: var(--input-bg-color--hover); --text-input-border-color: var(--input-border-color--hover); + } } .text-input:focus-within { + &:not(.text-input--invalid) { --text-input-text-color: var(--input-text-color--focus); --text-input-background-color: var(--input-bg-color--focus); --text-input-border-color: var(--input-border-color--focus); + } }packages/cyberstorm-theme/src/components/componentsColors.css (1)
266-266: LGTM!New invalid state color tokens follow existing naming conventions and use appropriate red accent colors for visual feedback.
Also applies to: 270-270, 274-274, 279-279
apps/cyberstorm-remix/app/settings/teams/team/tabs/ServiceAccounts/ServiceAccounts.tsx (1)
157-158: LGTM!Clean refactor to use Modal subcomponents with the new field props pattern. The direct
onClick={strongForm.submit}approach avoids the modal-close-on-error issue seen in Teams.tsx. Consistent integration with the rest of the PR.Also applies to: 202-242
apps/cyberstorm-remix/cyberstorm/utils/StrongForm/useStrongForm.ts (4)
1-1: LGTM!Good additions for field interaction tracking. The
fieldInteractionsstate structure andhasAttemptedSubmitflag enable the interaction-aware validation UX described in the PR objectives.Also applies to: 60-71
93-110: Well-designed field state logic.The interaction-aware validation (
hasFinishedInteractioncombining focus+blur or submit attempt) provides good UX by not showing errors before user engagement. Clean implementation.
112-130: Nice optimization with idempotent updates.The early return when state hasn't changed (lines 120-125) prevents unnecessary re-renders. Good practice.
140-158: Clean composition of field props.
getFieldComponentPropsnicely combines state derivation with interaction handlers and ARIA attributes. The optionaldisabledparameter provides flexibility.One consideration:
csModifiersreturns a new array on each call. IfNewTextInputdoes shallow comparison oncsModifiers, this could cause re-renders. Memoizing the array or using a stable reference pattern would help if this becomes an issue.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #1661 +/- ##
==========================================
- Coverage 11.57% 11.55% -0.03%
==========================================
Files 317 317
Lines 22867 22955 +88
Branches 505 508 +3
==========================================
+ Hits 2647 2652 +5
- Misses 20220 20303 +83 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| <div className="add-member-form__field add-member-form__username"> | ||
| <label className="add-member-form__label" htmlFor="username"> | ||
| Username | ||
| Username <span aria-hidden="true">*</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on:
- adding something like
title="Required"to this element? - having a dedicated component for it to not have to repeat this over and over around the project?
| </Modal.Body> | ||
| ) : ( | ||
| <Modal.Body> | ||
| <form |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, hmm, I'm not sure of the usage of the form elements with useStrongForm 🤔 But I'll check this out next time me work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this could be achieved by attaching event listeners to inputs, but on a bigged form that's a lot of boilerplate. Maybe the getFieldComponentProps could now be used to avoid that? But then we'd need to be able to tell different field types apart (input vs textarea vs select...).
Just a thought, some completely different approach might be better.
| >({}); | ||
| const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false); | ||
|
|
||
| const isValueEmpty = (value: unknown) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: the body of useStrongForm is getting rather long, so a helper function like this could be moved elsewhere.
| const validator = props.validators?.[field]; | ||
| const value = props.inputs[field]; | ||
| const isRequired = Boolean(validator?.required); | ||
| const rawInvalid = isRequired && isValueEmpty(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As part of my own task, I tried to build a URL validator for the team profile form on top of the changes on this PR. Having duplicate checks here and in isReady gets less ideal with each validator that is added. Maybe check if that can be avoided?


useStrongFormso inputs only go invalid after user focus/blur or submit, and expose ready-to-spread props for focus/blur/csModifiers/ariacsModifiersvisually reflect errors