Skip to content

Commit 791ca66

Browse files
devjiwonchoihuozhi
andauthored
[dev-overlay] add and apply merge class names util (#75758)
Add and apply the merge multiple class names util. Closes NDX-664 --------- Co-authored-by: Jiachi Liu <[email protected]>
1 parent 8dd3580 commit 791ca66

File tree

7 files changed

+60
-15
lines changed

7 files changed

+60
-15
lines changed

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/copy-button/index.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as React from 'react'
22
import { noop as css } from '../../helpers/noop-template'
3+
import { cx } from '../../helpers/cx'
34

45
function useCopyLegacy(content: string) {
56
type CopyState =
@@ -184,7 +185,11 @@ export function CopyButton({
184185
aria-disabled={isDisabled}
185186
disabled={isDisabled}
186187
data-nextjs-data-runtime-error-copy-button
187-
className={`${props.className || ''} nextjs-data-runtime-error-copy-button nextjs-data-runtime-error-copy-button--${copyState.state}`}
188+
className={cx(
189+
props.className,
190+
'nextjs-data-runtime-error-copy-button',
191+
`nextjs-data-runtime-error-copy-button--${copyState.state}`
192+
)}
188193
onClick={() => {
189194
if (!isDisabled) {
190195
copy()

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/dialog/header.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DialogHeader } from '../../dialog/dialog-header'
22
import { noop as css } from '../../../helpers/noop-template'
3+
import { cx } from '../../../helpers/cx'
34

45
type ErrorOverlayDialogHeaderProps = {
56
children?: React.ReactNode
@@ -12,11 +13,10 @@ export function ErrorOverlayDialogHeader({
1213
}: ErrorOverlayDialogHeaderProps) {
1314
return (
1415
<DialogHeader
15-
className={`nextjs-container-errors-header ${
16-
isTurbopack
17-
? 'nextjs-error-overlay-dialog-header-turbopack-background'
18-
: ''
19-
}`}
16+
className={cx(
17+
'nextjs-container-errors-header',
18+
isTurbopack && 'nextjs-error-overlay-dialog-header-turbopack-background'
19+
)}
2020
>
2121
{children}
2222
</DialogHeader>

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/errors/error-overlay-footer/error-feedback/error-feedback.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, useCallback } from 'react'
22
import { ThumbsUp } from '../../../../icons/thumbs/thumbs-up'
33
import { ThumbsDown } from '../../../../icons/thumbs/thumbs-down'
44
import { noop as css } from '../../../../helpers/noop-template'
5+
import { cx } from '../../../../helpers/cx'
56

67
interface ErrorFeedbackProps {
78
errorCode: string
@@ -43,7 +44,7 @@ export function ErrorFeedback({ errorCode, className }: ErrorFeedbackProps) {
4344

4445
return (
4546
<div
46-
className={`error-feedback${className ? ` ${className}` : ''}`}
47+
className={cx('error-feedback', className)}
4748
role="region"
4849
aria-label="Error feedback"
4950
>
@@ -57,15 +58,15 @@ export function ErrorFeedback({ errorCode, className }: ErrorFeedbackProps) {
5758
<button
5859
aria-label="Mark as helpful"
5960
onClick={() => handleFeedback(true)}
60-
className={`feedback-button ${voted === true ? 'voted' : ''}`}
61+
className={cx('feedback-button', voted === true && 'voted')}
6162
type="button"
6263
>
6364
<ThumbsUp aria-hidden="true" />
6465
</button>
6566
<button
6667
aria-label="Mark as not helpful"
6768
onClick={() => handleFeedback(false)}
68-
className={`feedback-button ${voted === false ? 'voted' : ''}`}
69+
className={cx('feedback-button', voted === false && 'voted')}
6970
type="button"
7071
>
7172
<ThumbsDown

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/toast/toast.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react'
2-
2+
import { cx } from '../../helpers/cx'
33
export type ToastProps = React.HTMLProps<HTMLDivElement> & {
44
children?: React.ReactNode
55
onClick?: () => void
@@ -19,7 +19,7 @@ export const Toast: React.FC<ToastProps> = function Toast({
1919
e.preventDefault()
2020
return onClick?.()
2121
}}
22-
className={`nextjs-toast${className ? ' ' + className : ''}`}
22+
className={cx('nextjs-toast', className)}
2323
>
2424
<div data-nextjs-toast-wrapper>{children}</div>
2525
</div>

packages/next/src/client/components/react-dev-overlay/_experimental/internal/components/version-staleness-info/version-staleness-info.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { VersionInfo } from '../../../../../../../server/dev/parse-version-info'
2+
import { cx } from '../../helpers/cx'
23
import { noop as css } from '../../helpers/noop-template'
34

45
export function VersionStalenessInfo({
@@ -16,11 +17,13 @@ export function VersionStalenessInfo({
1617

1718
return (
1819
<span
19-
className={`nextjs-container-build-error-version-status dialog-exclude-closing-from-outside-click ${
20-
isTurbopack ? 'turbopack-border' : ''
21-
}`}
20+
className={cx(
21+
'nextjs-container-build-error-version-status',
22+
'dialog-exclude-closing-from-outside-click',
23+
isTurbopack && 'turbopack-border'
24+
)}
2225
>
23-
<Eclipse className={`version-staleness-indicator ${indicatorClass}`} />
26+
<Eclipse className={cx('version-staleness-indicator', indicatorClass)} />
2427
<span data-nextjs-version-checker title={title}>
2528
{text}
2629
</span>{' '}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { cx } from './cx'
2+
3+
describe('cx', () => {
4+
it('should return an empty string for no arguments', () => {
5+
expect(cx()).toBe('')
6+
})
7+
8+
it('should join multiple string arguments with spaces', () => {
9+
expect(cx('foo', 'bar', 'baz')).toBe('foo bar baz')
10+
})
11+
12+
it('should filter out falsy values', () => {
13+
expect(cx('foo', null, 'bar', undefined, 'baz', false)).toBe('foo bar baz')
14+
})
15+
16+
it('should handle single string argument', () => {
17+
expect(cx('foo')).toBe('foo')
18+
})
19+
20+
it('should not add extra spaces when falsy values are between strings', () => {
21+
expect(cx('foo', null, 'bar')).toBe('foo bar')
22+
})
23+
24+
it('should handle all falsy values', () => {
25+
expect(cx(null, undefined, false)).toBe('')
26+
})
27+
})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Merge multiple args to a single string with spaces. Useful for merging class names.
3+
* @example
4+
* cx('foo', 'bar') // 'foo bar'
5+
* cx('foo', null, 'bar', undefined, 'baz', false) // 'foo bar baz'
6+
*/
7+
export function cx(...args: (string | undefined | null | false)[]): string {
8+
return args.filter(Boolean).join(' ')
9+
}

0 commit comments

Comments
 (0)