Skip to content

Commit 4c28687

Browse files
committed
changelog edits
2 parents d3b27e5 + e1ad889 commit 4c28687

File tree

10 files changed

+316
-16
lines changed

10 files changed

+316
-16
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66

77
- [#7455](https://github.com/primer/react/pull/7455) [`a86c183`](https://github.com/primer/react/commit/a86c183f5ea7add7584f895cff3027a7db35675e) Thanks [@HiroAgustin](https://github.com/HiroAgustin)! - Remove ConfirmationDialog custom renders to ensure visual parity with Dialog
88

9+
- [#7438](https://github.com/primer/react/pull/7438) [`160c1c4`](https://github.com/primer/react/commit/160c1c4cf1c5111dd46b68471d49733f47f524cc) Thanks [@francinelucca](https://github.com/francinelucca)! - feat: make Spinner's delay customizable
10+
911
- [#7436](https://github.com/primer/react/pull/7436) [`9a4e46c`](https://github.com/primer/react/commit/9a4e46cf902a5c2e046ca7771211536ae0bd08ed) Thanks [@TylerJDev](https://github.com/TylerJDev)! - FilteredActionList: Adds new prop `scrollBehavior` to allow customization of scroll behavior
1012

13+
- [#7448](https://github.com/primer/react/pull/7448) [`838859d`](https://github.com/primer/react/commit/838859d8ee2afc2afca71a28114020a14a68b297) Thanks [@francinelucca](https://github.com/francinelucca)! - feat(SkeletonBox): add customizable delay
14+
1115
### Patch Changes
1216

1317
- [#7427](https://github.com/primer/react/pull/7427) [`4bb6620`](https://github.com/primer/react/commit/4bb66203f0e6cd5d717b27d591ec5df169c1ad1a) Thanks [@barmo](https://github.com/barmo)! - Change actionlist item inline description styling from flex to block to fix overflow

packages/react/src/Skeleton/SkeletonBox.docs.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@
1616
],
1717
"importPath": "@primer/react",
1818
"props": [
19+
{
20+
"name": "delay",
21+
"type": "'short' | 'long' | number",
22+
"description": "Controls whether and how long to delay rendering the SkeletonBox. Set to 'short' to delay by 300ms, 'long' to delay by 1000ms, or provide a custom number of milliseconds.",
23+
"defaultValue": "false"
24+
},
1925
{
2026
"name": "width",
2127
"type": "string",
@@ -35,4 +41,4 @@
3541
}
3642
],
3743
"subcomponents": []
38-
}
44+
}

packages/react/src/Skeleton/SkeletonBox.features.stories.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ export default {
1010
export const CustomHeight = () => <SkeletonBox height="4rem" />
1111

1212
export const CustomWidth = () => <SkeletonBox width="300px" />
13+
14+
export const WithDelay = () => <SkeletonBox delay="long" />

packages/react/src/Skeleton/SkeletonBox.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, {useEffect, useState} from 'react'
22
import {type CSSProperties, type HTMLProps} from 'react'
33
import {clsx} from 'clsx'
44
import classes from './SkeletonBox.module.css'
@@ -10,12 +10,33 @@ export type SkeletonBoxProps = {
1010
width?: CSSProperties['width']
1111
/** The className of the skeleton box */
1212
className?: string
13+
/** Controls whether and how long to delay rendering the SkeletonBox. Set to 'short' to delay by 300ms, 'long' to delay by 1000ms, or provide a custom number of milliseconds.*/
14+
delay?: 'short' | 'long' | number
1315
} & HTMLProps<HTMLElement>
1416

1517
export const SkeletonBox = React.forwardRef<HTMLElement, SkeletonBoxProps>(function SkeletonBox(
16-
{height, width, className, style, ...props},
18+
{height, width, className, style, delay, ...props},
1719
ref,
1820
) {
21+
const [isVisible, setIsVisible] = useState(!delay)
22+
23+
useEffect(() => {
24+
if (delay) {
25+
const timeoutId = setTimeout(
26+
() => {
27+
setIsVisible(true)
28+
},
29+
typeof delay === 'number' ? delay : delay === 'short' ? 300 : 1000,
30+
)
31+
32+
return () => clearTimeout(timeoutId)
33+
}
34+
}, [delay])
35+
36+
if (!isVisible) {
37+
return null
38+
}
39+
1940
return (
2041
<div
2142
ref={ref as React.RefObject<HTMLDivElement>}

packages/react/src/Skeleton/__tests__/SkeletonBox.test.tsx

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import {render} from '@testing-library/react'
2-
import {describe, expect, it} from 'vitest'
2+
import {beforeEach, afterEach, describe, expect, it, vi} from 'vitest'
33
import {SkeletonBox} from '../SkeletonBox'
44
import classes from '../SkeletonBox.module.css'
55
import {implementsClassName} from '../../utils/testing'
6+
import {act} from 'react'
67

78
describe('SkeletonBox', () => {
89
implementsClassName(SkeletonBox, classes.SkeletonBox)
@@ -12,4 +13,138 @@ describe('SkeletonBox', () => {
1213
expect(container.firstChild).toHaveStyle('height: 100px')
1314
expect(container.firstChild).toHaveStyle('width: 200px')
1415
})
16+
17+
describe('delay behavior', () => {
18+
beforeEach(() => {
19+
vi.useFakeTimers()
20+
})
21+
22+
afterEach(() => {
23+
vi.restoreAllMocks()
24+
vi.useRealTimers()
25+
})
26+
27+
it('should render immediately when no delay is provided', () => {
28+
const {container} = render(<SkeletonBox />)
29+
expect(container.querySelector('div')).toBeInTheDocument()
30+
})
31+
32+
it('should not render immediately when delay is "short"', () => {
33+
const {container} = render(<SkeletonBox delay="short" />)
34+
expect(container.querySelector('div')).not.toBeInTheDocument()
35+
})
36+
37+
it('should render after 300ms when delay is "short"', () => {
38+
const {container} = render(<SkeletonBox delay="short" />)
39+
40+
// Not visible initially
41+
expect(container.querySelector('div')).not.toBeInTheDocument()
42+
43+
// Advance timers by less than 300ms
44+
act(() => {
45+
vi.advanceTimersByTime(250)
46+
})
47+
48+
// Still not visible
49+
expect(container.querySelector('div')).not.toBeInTheDocument()
50+
51+
// Advance timers to complete the short delay (300ms)
52+
act(() => {
53+
vi.advanceTimersByTime(50)
54+
})
55+
56+
// Now it should be visible
57+
expect(container.querySelector('div')).toBeInTheDocument()
58+
})
59+
60+
it('should not render immediately when delay is "long"', () => {
61+
const {container} = render(<SkeletonBox delay="long" />)
62+
expect(container.querySelector('div')).not.toBeInTheDocument()
63+
})
64+
65+
it('should render after 1000ms when delay is "long"', () => {
66+
const {container} = render(<SkeletonBox delay="long" />)
67+
68+
// Not visible initially
69+
expect(container.querySelector('div')).not.toBeInTheDocument()
70+
71+
// Advance timers by less than 1000ms
72+
act(() => {
73+
vi.advanceTimersByTime(800)
74+
})
75+
76+
// Still not visible
77+
expect(container.querySelector('div')).not.toBeInTheDocument()
78+
79+
// Advance timers to complete the long delay (1000ms)
80+
act(() => {
81+
vi.advanceTimersByTime(200)
82+
})
83+
84+
// Now it should be visible
85+
expect(container.querySelector('div')).toBeInTheDocument()
86+
})
87+
88+
it('should cleanup timeout on unmount when delay is "short"', () => {
89+
const {unmount} = render(<SkeletonBox delay="short" />)
90+
91+
// Unmount before the delay completes
92+
unmount()
93+
94+
// Advance timers to see if there are any side effects
95+
vi.advanceTimersByTime(300)
96+
97+
// No errors should occur
98+
expect(true).toBe(true)
99+
})
100+
101+
it('should cleanup timeout on unmount when delay is "long"', () => {
102+
const {unmount} = render(<SkeletonBox delay="long" />)
103+
104+
// Unmount before the delay completes
105+
unmount()
106+
107+
// Advance timers to see if there are any side effects
108+
vi.advanceTimersByTime(1000)
109+
110+
// No errors should occur
111+
expect(true).toBe(true)
112+
})
113+
114+
it('should render after custom delay when delay is a number', () => {
115+
const {container} = render(<SkeletonBox delay={500} />)
116+
117+
// Not visible initially
118+
expect(container.querySelector('div')).not.toBeInTheDocument()
119+
120+
// Advance timers by less than the custom delay (500ms)
121+
act(() => {
122+
vi.advanceTimersByTime(400)
123+
})
124+
125+
// Still not visible
126+
expect(container.querySelector('div')).not.toBeInTheDocument()
127+
128+
// Advance timers to complete the custom delay
129+
act(() => {
130+
vi.advanceTimersByTime(100)
131+
})
132+
133+
// Now it should be visible
134+
expect(container.querySelector('div')).toBeInTheDocument()
135+
})
136+
137+
it('should cleanup timeout on unmount when delay is a number', () => {
138+
const {unmount} = render(<SkeletonBox delay={500} />)
139+
140+
// Unmount before the delay completes
141+
unmount()
142+
143+
// Advance timers to see if there are any side effects
144+
vi.advanceTimersByTime(500)
145+
146+
// No errors should occur
147+
expect(true).toBe(true)
148+
})
149+
})
15150
})

packages/react/src/Spinner/Spinner.docs.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
},
3434
{
3535
"defaultValue": "false",
36-
"description": "Whether to delay the spinner before rendering by the defined 1000ms.",
36+
"description": "Controls whether and how long to delay rendering the spinner. Set to `true` to delay by 1000ms, `'short'` to delay by 300ms, `'long'` to delay by 1000ms, or provide a custom number of milliseconds.",
3737
"name": "delay",
38-
"type": "boolean"
38+
"type": "boolean | 'short' | 'long' | number"
3939
}
4040
],
4141
"status": "alpha",

packages/react/src/Spinner/Spinner.features.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ export const SuppressScreenReaderText = () => (
2020
</Stack>
2121
)
2222

23-
export const WithDelay = () => <Spinner delay />
23+
export const WithDelay = () => <Spinner delay="long" />

0 commit comments

Comments
 (0)