Quellcode durchsuchen

Neo: loading keeps custom children mounted; loadingLabel is a string

Review on #5943: wrapping custom children in a Row only while loading gave
them a new parent each time loading started or ended, so React remounted
them and reset their state. They now sit in the same Row whenever the button
takes a `loading` prop at all, and only its opacity changes. Buttons without
`loading` render children as before. This also removes the nested ternary
SonarCloud flagged (S3358).

`loadingLabel` was typed ReactNode but only a string can name the button,
so it is now `string`, documented as the accessible name while loading
(native glass buttons still show it as their text).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Andrey Yasko vor 5 Stunden
Ursprung
Commit
918f76806e

+ 8 - 4
neo/packages/app/design/controls/neo-button/neo-button.tsx

@@ -588,9 +588,7 @@ const NeoButtonStyled = (props: NeoButtonProps & { titleHidden?: boolean }) => {
     // spinner, so the button keeps its size), and `loadingLabel`, when given,
     // becomes the accessible name instead of swapping the visible text.
     const effectiveLabel = label ?? title ?? '';
-    const loadingName = loading && typeof loadingLabel === 'string' && loadingLabel !== ''
-        ? loadingLabel
-        : undefined;
+    const loadingName = loading && loadingLabel ? loadingLabel : undefined;
     const effectiveImage = image ?? systemImage ?? resolved.defaultImage;
     const effectivePlacement = imagePlacement ?? resolved.imagePlacement;
 
@@ -887,8 +885,14 @@ const NeoButtonStyled = (props: NeoButtonProps & { titleHidden?: boolean }) => {
     // The spinner sits over the content, which stays in place invisible (see
     // `renderLabel`). A button with no content draws it inline instead.
     const spinnerOverlay = loading && (isTitle || !!effectiveImage || isCustomChildren);
+    // Custom children get a wrapper whenever the button takes `loading` at all,
+    // so toggling it only fades them: a new parent would remount them and reset
+    // their state. Buttons without `loading` render them as before.
+    const childrenContent = props.loading === undefined
+        ? children
+        : <Row className={cn('flex-row items-center', loading && 'opacity-0')}>{children}</Row>;
     const labelContent = isCustomChildren
-        ? (loading ? <Row className="flex-row items-center opacity-0">{children}</Row> : children)
+        ? childrenContent
         : renderLabel({
             label: effectiveLabel,
             image: effectiveImage,

+ 6 - 1
neo/packages/app/design/controls/neo-button/neo-button.types.ts

@@ -61,7 +61,12 @@ export type NeoButtonProps = {
 
     // Content
     label?: ReactNode
-    loadingLabel?: ReactNode
+    /**
+     * Accessible name while `loading` ("Saving…"). The drawn content stays as
+     * it is under the spinner, so the button keeps its size. Native glass
+     * buttons (Expo UI, no spinner) show it as their text instead.
+     */
+    loadingLabel?: string
     title?: ReactNode
     /** Icon name, emoji, or a ready element. */
     image?: string | ReactElement | null | false