@charcoal-ui/react 6.0.0-rc.3 → 6.0.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/_lib/useIsomorphicLayoutEffect.d.ts +6 -0
  2. package/dist/_lib/useIsomorphicLayoutEffect.d.ts.map +1 -0
  3. package/dist/components/Carousel/index.d.ts +2 -6
  4. package/dist/components/Carousel/index.d.ts.map +1 -1
  5. package/dist/components/Carousel/useCarouselScroller.d.ts.map +1 -1
  6. package/dist/components/Modal/index.d.ts.map +1 -1
  7. package/dist/components/Modal/useTransitionPresence.d.ts +19 -0
  8. package/dist/components/Modal/useTransitionPresence.d.ts.map +1 -0
  9. package/dist/index.cjs +2 -2
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.css +100 -47
  12. package/dist/index.d.ts +1 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +2 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/layered.css +100 -47
  17. package/dist/layered.css.map +1 -1
  18. package/package.json +5 -6
  19. package/src/__tests__/css-variables.test.ts +1 -1
  20. package/src/__tests__/token-v1-compat.test.ts +1 -2
  21. package/src/__tests__/token-v1-remap.test.ts +6 -6
  22. package/src/_lib/useIsomorphicLayoutEffect.ts +7 -0
  23. package/src/components/Carousel/MIGRATION.md +39 -39
  24. package/src/components/Carousel/__snapshots__/index.css.snap +39 -37
  25. package/src/components/Carousel/index.css +45 -44
  26. package/src/components/Carousel/index.story.tsx +87 -68
  27. package/src/components/Carousel/index.test.tsx +68 -47
  28. package/src/components/Carousel/index.tsx +23 -14
  29. package/src/components/Carousel/useCarouselScroller.ts +8 -12
  30. package/src/components/FieldLabel/__snapshots__/index.css.snap +1 -1
  31. package/src/components/FieldLabel/index.css +1 -1
  32. package/src/components/IconButton/__snapshots__/index.css.snap +1 -1
  33. package/src/components/IconButton/index.css +1 -1
  34. package/src/components/Modal/__snapshots__/index.css.snap +32 -1
  35. package/src/components/Modal/index.css +34 -0
  36. package/src/components/Modal/index.test.tsx +200 -0
  37. package/src/components/Modal/index.tsx +48 -78
  38. package/src/components/Modal/useTransitionPresence.ts +95 -0
  39. package/src/index.ts +0 -1
@@ -1,45 +1,52 @@
1
1
  import { Meta, StoryObj } from '@storybook/react-vite'
2
- import Carousel, { type CarouselItem } from '.'
3
-
4
- const sampleImage = (
5
- <img
6
- src="/carousel-sample.png"
7
- alt="サンプル画像"
8
- style={{
9
- width: '100%',
10
- height: '100%',
11
- objectFit: 'cover',
12
- display: 'block',
13
- }}
14
- />
15
- )
16
-
17
- const items: CarouselItem[] = Array.from({ length: 6 }, (_, i) => ({
18
- id: `item-${i + 1}`,
19
- children: sampleImage,
20
- }))
21
-
22
- // 横幅が確定したスロット(defaultScroll の初期位置計算と 0.75 ページ送りを確認するため)
23
- const numberedItems: CarouselItem[] = Array.from({ length: 20 }, (_, i) => ({
24
- id: `num-${i + 1}`,
25
- children: (
26
- <div
2
+ import type { CSSProperties } from 'react'
3
+ import Carousel from '.'
4
+
5
+ const makeSampleImages = (style: CSSProperties) =>
6
+ Array.from({ length: 6 }, (_, i) => (
7
+ <img
8
+ key={`item-${i + 1}`}
9
+ src="/carousel-sample.png"
10
+ alt="サンプル画像"
27
11
  style={{
28
- width: 200,
29
- height: 120,
30
- display: 'flex',
31
- alignItems: 'center',
32
- justifyContent: 'center',
33
- background: i % 2 === 0 ? '#cfe3ff' : '#ffe3cf',
34
- borderRadius: 8,
35
- font: 'bold 32px sans-serif',
36
- color: '#333',
12
+ objectFit: 'cover',
13
+ display: 'block',
14
+ ...style,
37
15
  }}
38
- >
39
- {i + 1}
40
- </div>
41
- ),
42
- }))
16
+ />
17
+ ))
18
+
19
+ // M 用: スライド間隔はコンポーネントが所有しないので、利用者側の margin で注入する。
20
+ // width: 100% と margin の併用はラッパーとの循環サイズ解決で間隔が潰れるため、明示サイズにする。
21
+ const spacedImages = makeSampleImages({
22
+ width: 280,
23
+ height: 210,
24
+ marginInlineEnd: 24,
25
+ })
26
+ // S 用: 1 枚全幅ページングのため間隔なし
27
+ const fullWidthImages = makeSampleImages({ width: '100%', height: '100%' })
28
+
29
+ // 横幅が確定したスロット(defaultScroll の初期位置計算と 0.75 ページ送りを確認するため)。
30
+ // スライド間隔はコンポーネントが所有しないので、利用者側の margin で注入する。
31
+ const numberedSlides = Array.from({ length: 20 }, (_, i) => (
32
+ <div
33
+ key={`num-${i + 1}`}
34
+ style={{
35
+ width: 200,
36
+ height: 120,
37
+ marginInlineEnd: 24,
38
+ display: 'flex',
39
+ alignItems: 'center',
40
+ justifyContent: 'center',
41
+ background: i % 2 === 0 ? '#cfe3ff' : '#ffe3cf',
42
+ borderRadius: 8,
43
+ font: 'bold 32px sans-serif',
44
+ color: '#333',
45
+ }}
46
+ >
47
+ {i + 1}
48
+ </div>
49
+ ))
43
50
 
44
51
  export default {
45
52
  title: 'react/Carousel',
@@ -48,13 +55,14 @@ export default {
48
55
  layout: 'padded',
49
56
  },
50
57
  args: {
51
- items,
58
+ children: spacedImages,
52
59
  size: 'M',
53
60
  hasGradient: false,
54
61
  fullWidth: false,
55
62
  scrollStep: 0.75,
56
63
  },
57
64
  argTypes: {
65
+ children: { control: false },
58
66
  size: {
59
67
  control: { type: 'radio' },
60
68
  options: ['S', 'M'],
@@ -80,7 +88,7 @@ export const SizeM: StoryObj<typeof Carousel> = {
80
88
  }
81
89
 
82
90
  export const SizeS: StoryObj<typeof Carousel> = {
83
- args: { size: 'S' },
91
+ args: { size: 'S', children: fullWidthImages },
84
92
  }
85
93
 
86
94
  export const WithGradient: StoryObj<typeof Carousel> = {
@@ -92,7 +100,7 @@ export const FullWidth: StoryObj<typeof Carousel> = {
92
100
  }
93
101
 
94
102
  export const NavigationButtonsOnSizeS: StoryObj<typeof Carousel> = {
95
- args: { size: 'S', navigationButtons: true },
103
+ args: { size: 'S', navigationButtons: true, children: fullWidthImages },
96
104
  }
97
105
 
98
106
  export const IndicatorOnSizeM: StoryObj<typeof Carousel> = {
@@ -101,42 +109,53 @@ export const IndicatorOnSizeM: StoryObj<typeof Carousel> = {
101
109
 
102
110
  // 横幅が確定したスロット(番号付き)
103
111
  export const DefaultScrollCenter: StoryObj<typeof Carousel> = {
104
- args: { size: 'M', items: numberedItems, defaultScroll: { align: 'center' } },
112
+ args: {
113
+ size: 'M',
114
+ children: numberedSlides,
115
+ defaultScroll: { align: 'center' },
116
+ },
105
117
  }
106
118
 
107
119
  export const DefaultScrollRight: StoryObj<typeof Carousel> = {
108
- args: { size: 'M', items: numberedItems, defaultScroll: { align: 'right' } },
120
+ args: {
121
+ size: 'M',
122
+ children: numberedSlides,
123
+ defaultScroll: { align: 'right' },
124
+ },
109
125
  }
110
126
 
111
127
  // 遅延読み込み画像(マウント時に幅が未確定)でも初期位置が効くことの確認
112
128
  export const DefaultScrollCenterAsyncImages: StoryObj<typeof Carousel> = {
113
- args: { size: 'M', items, defaultScroll: { align: 'center' } },
129
+ args: {
130
+ size: 'M',
131
+ children: spacedImages,
132
+ defaultScroll: { align: 'center' },
133
+ },
114
134
  }
115
135
 
116
136
  // 白フェードが見えることの確認用(暗色コンテンツなら白フェードが明確に出る)
117
- const darkTiles: CarouselItem[] = Array.from({ length: 10 }, (_, i) => ({
118
- id: `dark-${i + 1}`,
119
- children: (
120
- <div
121
- style={{
122
- width: 220,
123
- height: 140,
124
- display: 'flex',
125
- alignItems: 'center',
126
- justifyContent: 'center',
127
- background: '#2a3b8f',
128
- color: '#fff',
129
- borderRadius: 4,
130
- font: 'bold 28px sans-serif',
131
- }}
132
- >
133
- {i + 1}
134
- </div>
135
- ),
136
- }))
137
+ const darkTiles = Array.from({ length: 10 }, (_, i) => (
138
+ <div
139
+ key={`dark-${i + 1}`}
140
+ style={{
141
+ width: 220,
142
+ height: 140,
143
+ marginInlineEnd: 24,
144
+ display: 'flex',
145
+ alignItems: 'center',
146
+ justifyContent: 'center',
147
+ background: '#2a3b8f',
148
+ color: '#fff',
149
+ borderRadius: 4,
150
+ font: 'bold 28px sans-serif',
151
+ }}
152
+ >
153
+ {i + 1}
154
+ </div>
155
+ ))
137
156
 
138
157
  export const GradientOnDarkContent: StoryObj<typeof Carousel> = {
139
- args: { size: 'M', hasGradient: true, items: darkTiles },
158
+ args: { size: 'M', hasGradient: true, children: darkTiles },
140
159
  }
141
160
 
142
161
  // scrollStep に関数を渡してフル制御する例(送り量を clientWidth - 48px に固定)。
@@ -144,7 +163,7 @@ export const GradientOnDarkContent: StoryObj<typeof Carousel> = {
144
163
  export const ScrollStepFunction: StoryObj<typeof Carousel> = {
145
164
  args: {
146
165
  size: 'M',
147
- items: numberedItems,
166
+ children: numberedSlides,
148
167
  scrollStep: ({ clientWidth }) => clientWidth - 48,
149
168
  },
150
169
  }
@@ -153,7 +172,7 @@ export const ScrollStepFunction: StoryObj<typeof Carousel> = {
153
172
  export const ScrollSnapPerItem: StoryObj<typeof Carousel> = {
154
173
  args: {
155
174
  size: 'M',
156
- items: numberedItems,
175
+ children: numberedSlides,
157
176
  scrollSnap: { type: 'mandatory', align: 'start' },
158
177
  },
159
178
  }
@@ -2,12 +2,13 @@ import { createRef } from 'react'
2
2
  import { render, screen, fireEvent, act } from '@testing-library/react'
3
3
  import { renderToString } from 'react-dom/server'
4
4
  import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'
5
- import Carousel, { type CarouselItem, type CarouselHandlerRef } from '.'
5
+ import Carousel, { type CarouselHandlerRef } from '.'
6
6
 
7
- const items: CarouselItem[] = Array.from({ length: 6 }, (_, i) => ({
8
- id: `item-${i}`,
9
- children: <div data-testid={`slide-${i}`}>Slide {i}</div>,
10
- }))
7
+ const slides = Array.from({ length: 6 }, (_, i) => (
8
+ <div key={`item-${i}`} data-testid={`slide-${i}`}>
9
+ Slide {i}
10
+ </div>
11
+ ))
11
12
 
12
13
  function mockScrollerGeometry(
13
14
  el: HTMLElement,
@@ -77,14 +78,14 @@ function fireIntersect(el: Element) {
77
78
  describe('Carousel', () => {
78
79
  describe('rendering', () => {
79
80
  it('renders all items', () => {
80
- render(<Carousel items={items} />)
81
+ render(<Carousel>{slides}</Carousel>)
81
82
  for (let i = 0; i < 6; i++) {
82
83
  expect(screen.getByTestId(`slide-${i}`)).toBeInTheDocument()
83
84
  }
84
85
  })
85
86
 
86
87
  it('has carousel ARIA attributes', () => {
87
- render(<Carousel items={items} />)
88
+ render(<Carousel>{slides}</Carousel>)
88
89
  const region = screen.getByRole('region')
89
90
  expect(region).toHaveAttribute('aria-roledescription', 'carousel')
90
91
  expect(region).toHaveAttribute('aria-label', 'Carousel')
@@ -93,7 +94,7 @@ describe('Carousel', () => {
93
94
 
94
95
  describe('Size M (default)', () => {
95
96
  it('shows navigation buttons', () => {
96
- render(<Carousel items={items} size="M" />)
97
+ render(<Carousel size="M">{slides}</Carousel>)
97
98
  expect(
98
99
  screen.getByRole('button', { name: 'Previous' }),
99
100
  ).toBeInTheDocument()
@@ -101,7 +102,7 @@ describe('Carousel', () => {
101
102
  })
102
103
 
103
104
  it('hides indicator by default', () => {
104
- const { container } = render(<Carousel items={items} size="M" />)
105
+ const { container } = render(<Carousel size="M">{slides}</Carousel>)
105
106
  const indicator = container.querySelector('.charcoal-carousel__indicator')
106
107
  expect(indicator).toHaveAttribute('data-visible', 'false')
107
108
  })
@@ -109,7 +110,7 @@ describe('Carousel', () => {
109
110
 
110
111
  describe('Size S', () => {
111
112
  it('hides navigation buttons by default', () => {
112
- const { container } = render(<Carousel items={items} size="S" />)
113
+ const { container } = render(<Carousel size="S">{slides}</Carousel>)
113
114
  const nav = container.querySelector('.charcoal-carousel__navigation')
114
115
  expect(nav).toHaveAttribute('data-visible', 'false')
115
116
  })
@@ -117,13 +118,13 @@ describe('Carousel', () => {
117
118
 
118
119
  describe('navigation button state', () => {
119
120
  it('disables prev button at scroll start', () => {
120
- render(<Carousel items={items} />)
121
+ render(<Carousel>{slides}</Carousel>)
121
122
  const prev = screen.getByRole('button', { name: 'Previous' })
122
123
  expect(prev).toBeDisabled()
123
124
  })
124
125
 
125
126
  it('enables next button when scrollable content exists', () => {
126
- render(<Carousel items={items} />)
127
+ render(<Carousel>{slides}</Carousel>)
127
128
  const scroller = getScroller()
128
129
  mockScrollerGeometry(scroller, { scrollLeft: 0 })
129
130
  act(() => {
@@ -136,7 +137,7 @@ describe('Carousel', () => {
136
137
 
137
138
  describe('scrollByStep (0.75x viewport)', () => {
138
139
  it('calls scrollBy with 0.75x viewport width on next button click', () => {
139
- render(<Carousel items={items} />)
140
+ render(<Carousel>{slides}</Carousel>)
140
141
  const scroller = getScroller()
141
142
  mockScrollerGeometry(scroller, { scrollLeft: 100 })
142
143
  act(() => {
@@ -156,7 +157,7 @@ describe('Carousel', () => {
156
157
  })
157
158
 
158
159
  it('calls scrollBy with negative 0.75x on prev button click', () => {
159
- render(<Carousel items={items} />)
160
+ render(<Carousel>{slides}</Carousel>)
160
161
  const scroller = getScroller()
161
162
  mockScrollerGeometry(scroller, { scrollLeft: 400 })
162
163
  act(() => {
@@ -176,7 +177,7 @@ describe('Carousel', () => {
176
177
  })
177
178
 
178
179
  it('respects custom scrollStep ratio (number)', () => {
179
- render(<Carousel items={items} scrollStep={0.5} />)
180
+ render(<Carousel scrollStep={0.5}>{slides}</Carousel>)
180
181
  const scroller = getScroller()
181
182
  mockScrollerGeometry(scroller, { scrollLeft: 100 })
182
183
  act(() => {
@@ -199,7 +200,7 @@ describe('Carousel', () => {
199
200
  const scrollStep = vi.fn(
200
201
  ({ clientWidth }: { clientWidth: number }) => clientWidth - 48,
201
202
  )
202
- render(<Carousel items={items} scrollStep={scrollStep} />)
203
+ render(<Carousel scrollStep={scrollStep}>{slides}</Carousel>)
203
204
  const scroller = getScroller()
204
205
  mockScrollerGeometry(scroller, { scrollLeft: 100 })
205
206
  act(() => {
@@ -228,7 +229,7 @@ describe('Carousel', () => {
228
229
 
229
230
  describe('keyboard navigation', () => {
230
231
  it('scrolls next by 0.75x viewport on ArrowRight', () => {
231
- render(<Carousel items={items} />)
232
+ render(<Carousel>{slides}</Carousel>)
232
233
  const scroller = getScroller()
233
234
  mockScrollerGeometry(scroller, { scrollLeft: 100 })
234
235
  act(() => {
@@ -247,7 +248,7 @@ describe('Carousel', () => {
247
248
  })
248
249
 
249
250
  it('scrolls prev by negative 0.75x viewport on ArrowLeft', () => {
250
- render(<Carousel items={items} />)
251
+ render(<Carousel>{slides}</Carousel>)
251
252
  const scroller = getScroller()
252
253
  mockScrollerGeometry(scroller, { scrollLeft: 400 })
253
254
  act(() => {
@@ -266,7 +267,7 @@ describe('Carousel', () => {
266
267
  })
267
268
 
268
269
  it('ignores unrelated keys', () => {
269
- render(<Carousel items={items} />)
270
+ render(<Carousel>{slides}</Carousel>)
270
271
  const scroller = getScroller()
271
272
  mockScrollerGeometry(scroller, { scrollLeft: 100 })
272
273
 
@@ -284,7 +285,7 @@ describe('Carousel', () => {
284
285
  // jsdom には scrollIntoView が無いのでモックを定義する。
285
286
  const scrollIntoView = vi.fn()
286
287
  Element.prototype.scrollIntoView = scrollIntoView
287
- const { container } = render(<Carousel items={items} size="S" />)
288
+ const { container } = render(<Carousel size="S">{slides}</Carousel>)
288
289
 
289
290
  const dots = container.querySelectorAll(
290
291
  '.charcoal-carousel__indicator__item',
@@ -311,7 +312,9 @@ describe('Carousel', () => {
311
312
  it('item が中央に入ると activeIndex が更新され indicator に反映される', () => {
312
313
  const restore = installIOMock()
313
314
  const { container } = render(
314
- <Carousel items={items} size="M" indicator />,
315
+ <Carousel size="M" indicator>
316
+ {slides}
317
+ </Carousel>,
315
318
  )
316
319
  const itemEls = container.querySelectorAll('.charcoal-carousel__item')
317
320
  act(() => {
@@ -327,7 +330,7 @@ describe('Carousel', () => {
327
330
 
328
331
  describe('scroll-state data attributes (mask)', () => {
329
332
  it('reflects canPrev/canNext on the root element', () => {
330
- const { container } = render(<Carousel items={items} hasGradient />)
333
+ const { container } = render(<Carousel hasGradient>{slides}</Carousel>)
331
334
  const root = container.querySelector('.charcoal-carousel')
332
335
  const scroller = getScroller()
333
336
  mockScrollerGeometry(scroller, { scrollLeft: 100 })
@@ -339,7 +342,7 @@ describe('Carousel', () => {
339
342
  })
340
343
 
341
344
  it('marks data-can-prev false at the start edge', () => {
342
- const { container } = render(<Carousel items={items} hasGradient />)
345
+ const { container } = render(<Carousel hasGradient>{slides}</Carousel>)
343
346
  const root = container.querySelector('.charcoal-carousel')
344
347
  const scroller = getScroller()
345
348
  mockScrollerGeometry(scroller, { scrollLeft: 0 })
@@ -378,26 +381,25 @@ describe('Carousel', () => {
378
381
  })
379
382
 
380
383
  it('defaults to the left edge (0)', () => {
381
- render(<Carousel items={items} />)
384
+ render(<Carousel>{slides}</Carousel>)
382
385
  expect(lastSetLeft).toBe(0)
383
386
  })
384
387
 
385
388
  it('centers the content when align=center (maxScroll / 2)', () => {
386
- render(<Carousel items={items} defaultScroll={{ align: 'center' }} />)
389
+ render(<Carousel defaultScroll={{ align: 'center' }}>{slides}</Carousel>)
387
390
  expect(lastSetLeft).toBe(800)
388
391
  })
389
392
 
390
393
  it('aligns to the right edge when align=right (maxScroll)', () => {
391
- render(<Carousel items={items} defaultScroll={{ align: 'right' }} />)
394
+ render(<Carousel defaultScroll={{ align: 'right' }}>{slides}</Carousel>)
392
395
  expect(lastSetLeft).toBe(1600)
393
396
  })
394
397
 
395
398
  it('applies offset from the base position', () => {
396
399
  render(
397
- <Carousel
398
- items={items}
399
- defaultScroll={{ align: 'left', offset: 100 }}
400
- />,
400
+ <Carousel defaultScroll={{ align: 'left', offset: 100 }}>
401
+ {slides}
402
+ </Carousel>,
401
403
  )
402
404
  expect(lastSetLeft).toBe(100)
403
405
  })
@@ -444,7 +446,7 @@ describe('Carousel', () => {
444
446
  })
445
447
 
446
448
  it('re-applies the initial position when content width settles later', () => {
447
- render(<Carousel items={items} defaultScroll={{ align: 'right' }} />)
449
+ render(<Carousel defaultScroll={{ align: 'right' }}>{slides}</Carousel>)
448
450
  // at mount the content is not laid out yet (maxScroll 0) -> right -> 0
449
451
  expect(lastSetLeft).toBe(0)
450
452
 
@@ -460,7 +462,7 @@ describe('Carousel', () => {
460
462
  })
461
463
 
462
464
  it('stops re-applying once the user interacts', () => {
463
- render(<Carousel items={items} defaultScroll={{ align: 'right' }} />)
465
+ render(<Carousel defaultScroll={{ align: 'right' }}>{slides}</Carousel>)
464
466
 
465
467
  // user interacts (pointerdown on the scroller) before the content settles
466
468
  fireEvent.pointerDown(getScroller())
@@ -476,16 +478,31 @@ describe('Carousel', () => {
476
478
  })
477
479
  })
478
480
 
481
+ describe('children (sandbox-compatible API)', () => {
482
+ it('renders each direct child as one slide', () => {
483
+ const { container } = render(<Carousel>{slides}</Carousel>)
484
+ expect(
485
+ container.querySelectorAll('.charcoal-carousel__item'),
486
+ ).toHaveLength(6)
487
+ })
488
+ })
489
+
479
490
  describe('props override defaults', () => {
480
491
  it('shows indicator on Size M when prop is true', () => {
481
492
  const { container } = render(
482
- <Carousel items={items} size="M" indicator />,
493
+ <Carousel size="M" indicator>
494
+ {slides}
495
+ </Carousel>,
483
496
  )
484
497
  expect(container.querySelector("[data-indicator='true']")).toBeTruthy()
485
498
  })
486
499
 
487
500
  it('shows navigation on Size S when prop is true', () => {
488
- render(<Carousel items={items} size="S" navigationButtons />)
501
+ render(
502
+ <Carousel size="S" navigationButtons>
503
+ {slides}
504
+ </Carousel>,
505
+ )
489
506
  const nav = document.querySelector('.charcoal-carousel__navigation')
490
507
  expect(nav).toHaveAttribute('data-visible', 'true')
491
508
  })
@@ -493,12 +510,12 @@ describe('Carousel', () => {
493
510
 
494
511
  describe('variant data attributes', () => {
495
512
  it('sets gradient data attribute', () => {
496
- const { container } = render(<Carousel items={items} hasGradient />)
513
+ const { container } = render(<Carousel hasGradient>{slides}</Carousel>)
497
514
  expect(container.querySelector("[data-has-gradient='true']")).toBeTruthy()
498
515
  })
499
516
 
500
517
  it('sets full-width data attribute', () => {
501
- const { container } = render(<Carousel items={items} fullWidth />)
518
+ const { container } = render(<Carousel fullWidth>{slides}</Carousel>)
502
519
  expect(container.querySelector("[data-full-width='true']")).toBeTruthy()
503
520
  })
504
521
  })
@@ -507,30 +524,32 @@ describe('Carousel', () => {
507
524
  const getRegion = () => screen.getByRole('region')
508
525
 
509
526
  it('defaults to none / center on Size M (sandbox 同等の 0.75)', () => {
510
- render(<Carousel items={items} size="M" />)
527
+ render(<Carousel size="M">{slides}</Carousel>)
511
528
  expect(getRegion()).toHaveAttribute('data-scroll-snap-type', 'none')
512
529
  expect(getRegion()).toHaveAttribute('data-scroll-snap-align', 'center')
513
530
  })
514
531
 
515
532
  it('defaults to mandatory on Size S', () => {
516
- render(<Carousel items={items} size="S" />)
533
+ render(<Carousel size="S">{slides}</Carousel>)
517
534
  expect(getRegion()).toHaveAttribute('data-scroll-snap-type', 'mandatory')
518
535
  })
519
536
 
520
537
  it('overrides type per prop (item-ごと mandatory)', () => {
521
538
  render(
522
- <Carousel items={items} size="M" scrollSnap={{ type: 'mandatory' }} />,
539
+ <Carousel size="M" scrollSnap={{ type: 'mandatory' }}>
540
+ {slides}
541
+ </Carousel>,
523
542
  )
524
543
  expect(getRegion()).toHaveAttribute('data-scroll-snap-type', 'mandatory')
525
544
  })
526
545
 
527
546
  it('overrides align per prop', () => {
528
- render(<Carousel items={items} scrollSnap={{ align: 'start' }} />)
547
+ render(<Carousel scrollSnap={{ align: 'start' }}>{slides}</Carousel>)
529
548
  expect(getRegion()).toHaveAttribute('data-scroll-snap-align', 'start')
530
549
  })
531
550
 
532
551
  it('supports disabling snap (none)', () => {
533
- render(<Carousel items={items} scrollSnap={{ type: 'none' }} />)
552
+ render(<Carousel scrollSnap={{ type: 'none' }}>{slides}</Carousel>)
534
553
  expect(getRegion()).toHaveAttribute('data-scroll-snap-type', 'none')
535
554
  })
536
555
  })
@@ -538,7 +557,7 @@ describe('Carousel', () => {
538
557
  describe('react-sandbox compat (callbacks / ref)', () => {
539
558
  it('calls onScroll with scrollLeft on scroll', () => {
540
559
  const onScroll = vi.fn()
541
- render(<Carousel items={items} onScroll={onScroll} />)
560
+ render(<Carousel onScroll={onScroll}>{slides}</Carousel>)
542
561
  const scroller = getScroller()
543
562
  mockScrollerGeometry(scroller, { scrollLeft: 240 })
544
563
  act(() => {
@@ -550,7 +569,7 @@ describe('Carousel', () => {
550
569
  it('calls onScrollStateChange when scrollability changes', () => {
551
570
  const onScrollStateChange = vi.fn()
552
571
  render(
553
- <Carousel items={items} onScrollStateChange={onScrollStateChange} />,
572
+ <Carousel onScrollStateChange={onScrollStateChange}>{slides}</Carousel>,
554
573
  )
555
574
  const scroller = getScroller()
556
575
  mockScrollerGeometry(scroller, { scrollLeft: 100 })
@@ -577,7 +596,9 @@ describe('Carousel', () => {
577
596
  ]
578
597
  const ref = createRef<CarouselHandlerRef>()
579
598
  render(
580
- <Carousel ref={ref} items={items} defaultScroll={{ align: 'right' }} />,
599
+ <Carousel ref={ref} defaultScroll={{ align: 'right' }}>
600
+ {slides}
601
+ </Carousel>,
581
602
  )
582
603
  lastSetLeft = undefined
583
604
  act(() => {
@@ -603,7 +624,7 @@ describe('Carousel', () => {
603
624
  .spyOn(HTMLElement.prototype, 'clientWidth', 'get')
604
625
  .mockReturnValue(640)
605
626
  const onResize = vi.fn()
606
- render(<Carousel items={items} onResize={onResize} />)
627
+ render(<Carousel onResize={onResize}>{slides}</Carousel>)
607
628
  act(() => {
608
629
  roCallbacks.forEach((cb) => cb([]))
609
630
  })
@@ -641,13 +662,13 @@ describe('Carousel', () => {
641
662
  // getServerSnapshot を使うため、サーバー描画でブラウザ API に触れない。
642
663
  expect(() =>
643
664
  renderToString(
644
- <Carousel items={items} defaultScroll={{ align: 'center' }} />,
665
+ <Carousel defaultScroll={{ align: 'center' }}>{slides}</Carousel>,
645
666
  ),
646
667
  ).not.toThrow()
647
668
  })
648
669
 
649
670
  it('uses the server snapshot (canPrev/canNext = false) and renders items + indicator', () => {
650
- const html = renderToString(<Carousel items={items} size="M" />)
671
+ const html = renderToString(<Carousel size="M">{slides}</Carousel>)
651
672
 
652
673
  // サーバースナップショットでは canPrev/canNext は false。
653
674
  expect(html).toContain('data-can-prev="false"')