@charcoal-ui/react 6.0.0-rc.6 → 6.0.0-rc.7
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.
- package/dist/components/Carousel/index.d.ts +2 -0
- package/dist/components/Carousel/index.d.ts.map +1 -1
- package/dist/components/TagItem/index.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +35 -10
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layered.css +35 -10
- package/dist/layered.css.map +1 -1
- package/package.json +5 -5
- package/src/components/Carousel/MIGRATION.md +8 -8
- package/src/components/Carousel/__snapshots__/index.css.snap +4 -2
- package/src/components/Carousel/index.css +5 -2
- package/src/components/Carousel/index.story.tsx +14 -12
- package/src/components/Carousel/index.test.tsx +24 -0
- package/src/components/Carousel/index.tsx +17 -0
- package/src/components/TagItem/index.test.tsx +38 -0
- package/src/components/TagItem/index.tsx +1 -0
- package/src/components/TextArea/__snapshots__/index.css.snap +29 -8
- package/src/components/TextArea/index.css +30 -8
- package/src/components/TextArea/index.tsx +1 -1
|
@@ -16,25 +16,18 @@ const makeSampleImages = (style: CSSProperties) =>
|
|
|
16
16
|
/>
|
|
17
17
|
))
|
|
18
18
|
|
|
19
|
-
// M 用:
|
|
20
|
-
|
|
21
|
-
const spacedImages = makeSampleImages({
|
|
22
|
-
width: 280,
|
|
23
|
-
height: 210,
|
|
24
|
-
marginInlineEnd: 24,
|
|
25
|
-
})
|
|
19
|
+
// M 用: スライド間隔は gap prop で注入する(既定 args の gap: 24)。
|
|
20
|
+
const spacedImages = makeSampleImages({ width: 280, height: 210 })
|
|
26
21
|
// S 用: 1 枚全幅ページングのため間隔なし
|
|
27
22
|
const fullWidthImages = makeSampleImages({ width: '100%', height: '100%' })
|
|
28
23
|
|
|
29
24
|
// 横幅が確定したスロット(defaultScroll の初期位置計算と 0.75 ページ送りを確認するため)。
|
|
30
|
-
// スライド間隔はコンポーネントが所有しないので、利用者側の margin で注入する。
|
|
31
25
|
const numberedSlides = Array.from({ length: 20 }, (_, i) => (
|
|
32
26
|
<div
|
|
33
27
|
key={`num-${i + 1}`}
|
|
34
28
|
style={{
|
|
35
29
|
width: 200,
|
|
36
30
|
height: 120,
|
|
37
|
-
marginInlineEnd: 24,
|
|
38
31
|
display: 'flex',
|
|
39
32
|
alignItems: 'center',
|
|
40
33
|
justifyContent: 'center',
|
|
@@ -60,6 +53,7 @@ export default {
|
|
|
60
53
|
hasGradient: false,
|
|
61
54
|
fullWidth: false,
|
|
62
55
|
scrollStep: 0.75,
|
|
56
|
+
gap: 24,
|
|
63
57
|
},
|
|
64
58
|
argTypes: {
|
|
65
59
|
children: { control: false },
|
|
@@ -80,6 +74,10 @@ export default {
|
|
|
80
74
|
// Controls では数値(比率)のみ操作可能。関数形式は ScrollStepFunction story を参照。
|
|
81
75
|
control: { type: 'range', min: 0.1, max: 1.5, step: 0.05 },
|
|
82
76
|
},
|
|
77
|
+
gap: {
|
|
78
|
+
// number は px。'1rem' などの CSS 値文字列も渡せる。
|
|
79
|
+
control: { type: 'number' },
|
|
80
|
+
},
|
|
83
81
|
},
|
|
84
82
|
} satisfies Meta<typeof Carousel>
|
|
85
83
|
|
|
@@ -88,7 +86,7 @@ export const SizeM: StoryObj<typeof Carousel> = {
|
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
export const SizeS: StoryObj<typeof Carousel> = {
|
|
91
|
-
args: { size: 'S', children: fullWidthImages },
|
|
89
|
+
args: { size: 'S', children: fullWidthImages, gap: 0 },
|
|
92
90
|
}
|
|
93
91
|
|
|
94
92
|
export const WithGradient: StoryObj<typeof Carousel> = {
|
|
@@ -100,7 +98,12 @@ export const FullWidth: StoryObj<typeof Carousel> = {
|
|
|
100
98
|
}
|
|
101
99
|
|
|
102
100
|
export const NavigationButtonsOnSizeS: StoryObj<typeof Carousel> = {
|
|
103
|
-
args: {
|
|
101
|
+
args: {
|
|
102
|
+
size: 'S',
|
|
103
|
+
navigationButtons: true,
|
|
104
|
+
children: fullWidthImages,
|
|
105
|
+
gap: 0,
|
|
106
|
+
},
|
|
104
107
|
}
|
|
105
108
|
|
|
106
109
|
export const IndicatorOnSizeM: StoryObj<typeof Carousel> = {
|
|
@@ -140,7 +143,6 @@ const darkTiles = Array.from({ length: 10 }, (_, i) => (
|
|
|
140
143
|
style={{
|
|
141
144
|
width: 220,
|
|
142
145
|
height: 140,
|
|
143
|
-
marginInlineEnd: 24,
|
|
144
146
|
display: 'flex',
|
|
145
147
|
alignItems: 'center',
|
|
146
148
|
justifyContent: 'center',
|
|
@@ -92,6 +92,30 @@ describe('Carousel', () => {
|
|
|
92
92
|
})
|
|
93
93
|
})
|
|
94
94
|
|
|
95
|
+
describe('gap', () => {
|
|
96
|
+
it('sets --charcoal-carousel-gap in px when gap is a number', () => {
|
|
97
|
+
const { container } = render(<Carousel gap={24}>{slides}</Carousel>)
|
|
98
|
+
const root = container.querySelector('.charcoal-carousel') as HTMLElement
|
|
99
|
+
expect(root.style.getPropertyValue('--charcoal-carousel-gap')).toBe(
|
|
100
|
+
'24px',
|
|
101
|
+
)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('passes string gap values through as-is', () => {
|
|
105
|
+
const { container } = render(<Carousel gap="1rem">{slides}</Carousel>)
|
|
106
|
+
const root = container.querySelector('.charcoal-carousel') as HTMLElement
|
|
107
|
+
expect(root.style.getPropertyValue('--charcoal-carousel-gap')).toBe(
|
|
108
|
+
'1rem',
|
|
109
|
+
)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('does not set the custom property when gap is not specified', () => {
|
|
113
|
+
const { container } = render(<Carousel>{slides}</Carousel>)
|
|
114
|
+
const root = container.querySelector('.charcoal-carousel') as HTMLElement
|
|
115
|
+
expect(root.style.getPropertyValue('--charcoal-carousel-gap')).toBe('')
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
95
119
|
describe('Size M (default)', () => {
|
|
96
120
|
it('shows navigation buttons', () => {
|
|
97
121
|
render(<Carousel size="M">{slides}</Carousel>)
|
|
@@ -7,9 +7,11 @@ import {
|
|
|
7
7
|
memo,
|
|
8
8
|
useCallback,
|
|
9
9
|
useImperativeHandle,
|
|
10
|
+
useMemo,
|
|
10
11
|
useRef,
|
|
11
12
|
useState,
|
|
12
13
|
useSyncExternalStore,
|
|
14
|
+
type CSSProperties,
|
|
13
15
|
type ReactNode,
|
|
14
16
|
} from 'react'
|
|
15
17
|
import { mergeProps, useFocusRing, useKeyboard } from 'react-aria'
|
|
@@ -67,6 +69,8 @@ export type CarouselProps = Readonly<{
|
|
|
67
69
|
onResize?: (width: number) => void
|
|
68
70
|
onScrollStateChange?: (canScroll: boolean) => void
|
|
69
71
|
defaultScroll?: { align?: ScrollAlign; offset?: number }
|
|
72
|
+
// スライド間隔。number は px、string は CSS 値をそのまま使う。未指定は間隔なし。
|
|
73
|
+
gap?: number | string
|
|
70
74
|
// 1 直接子要素 = 1 スライド(react-sandbox 互換)。
|
|
71
75
|
children: ReactNode
|
|
72
76
|
}>
|
|
@@ -147,6 +151,7 @@ const Carousel = forwardRef<CarouselHandlerRef, CarouselProps>(function Render(
|
|
|
147
151
|
onResize,
|
|
148
152
|
onScrollStateChange,
|
|
149
153
|
defaultScroll: { align = 'left', offset = 0 } = {},
|
|
154
|
+
gap,
|
|
150
155
|
children,
|
|
151
156
|
...props
|
|
152
157
|
}: CarouselProps,
|
|
@@ -213,10 +218,22 @@ const Carousel = forwardRef<CarouselHandlerRef, CarouselProps>(function Render(
|
|
|
213
218
|
const { focusProps: rootFocusProps, isFocusVisible: rootFocusVisible } =
|
|
214
219
|
useFocusRing({ within: true })
|
|
215
220
|
|
|
221
|
+
// gap 宣言自体は index.css 側に置き、ここでは CSS 変数の値だけを注入する。
|
|
222
|
+
const gapStyle = useMemo(
|
|
223
|
+
() =>
|
|
224
|
+
({
|
|
225
|
+
...(gap != null && {
|
|
226
|
+
'--charcoal-carousel-gap': typeof gap === 'number' ? `${gap}px` : gap,
|
|
227
|
+
}),
|
|
228
|
+
}) satisfies CSSProperties,
|
|
229
|
+
[gap],
|
|
230
|
+
)
|
|
231
|
+
|
|
216
232
|
return (
|
|
217
233
|
<div
|
|
218
234
|
{...rootFocusProps}
|
|
219
235
|
className={className}
|
|
236
|
+
style={gapStyle}
|
|
220
237
|
data-size={size}
|
|
221
238
|
data-has-gradient={hasGradient}
|
|
222
239
|
data-full-width={fullWidth}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from '@testing-library/react'
|
|
2
|
+
import { forwardRef } from 'react'
|
|
3
|
+
import TagItem from '.'
|
|
4
|
+
|
|
5
|
+
describe('TagItem', () => {
|
|
6
|
+
it('preserves link props on a custom link component', () => {
|
|
7
|
+
const NextLink = forwardRef<
|
|
8
|
+
HTMLAnchorElement,
|
|
9
|
+
React.ComponentPropsWithoutRef<'a'>
|
|
10
|
+
>(function NextLink({ children, ...props }, ref) {
|
|
11
|
+
return (
|
|
12
|
+
<a ref={ref} {...props}>
|
|
13
|
+
{children}
|
|
14
|
+
</a>
|
|
15
|
+
)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const onClick = vi.fn()
|
|
19
|
+
|
|
20
|
+
render(
|
|
21
|
+
<TagItem
|
|
22
|
+
component={NextLink}
|
|
23
|
+
href="/tags/girl"
|
|
24
|
+
label="#girl"
|
|
25
|
+
target="_blank"
|
|
26
|
+
onClick={onClick}
|
|
27
|
+
/>,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const link = screen.getByRole('link')
|
|
31
|
+
|
|
32
|
+
expect(link).toHaveAttribute('href', '/tags/girl')
|
|
33
|
+
expect(link).toHaveAttribute('target', '_blank')
|
|
34
|
+
|
|
35
|
+
fireEvent.click(link)
|
|
36
|
+
expect(onClick).toHaveBeenCalledTimes(1)
|
|
37
|
+
})
|
|
38
|
+
})
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
box-shadow: 0 0 0 4px rgba(0, 150, 250, 0.32);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
.charcoal-text-area-container:has([data-no-bottom-padding='true']) {
|
|
34
|
+
height: calc(22px * (var(--charcoal-text-area-rows) + 1) + 18px);
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
.charcoal-text-area-container:not([aria-disabled='true']):hover {
|
|
34
38
|
background-color: var(--charcoal-color-container-secondary-hover-a);
|
|
35
39
|
}
|
|
@@ -47,13 +51,26 @@
|
|
|
47
51
|
box-sizing: border-box;
|
|
48
52
|
|
|
49
53
|
/* Prevent zooming for iOS Safari */
|
|
54
|
+
--charcoal-textarea-safari-prevent-zoom-scale: 0.875;
|
|
55
|
+
--charcoal-textarea-line-height: calc(
|
|
56
|
+
22px / var(--charcoal-textarea-safari-prevent-zoom-scale)
|
|
57
|
+
);
|
|
58
|
+
--charcoal-textarea-padding-y: calc(
|
|
59
|
+
8px / var(--charcoal-textarea-safari-prevent-zoom-scale)
|
|
60
|
+
);
|
|
61
|
+
--charcoal-textarea-padding-x: calc(
|
|
62
|
+
9px / var(--charcoal-textarea-safari-prevent-zoom-scale)
|
|
63
|
+
);
|
|
50
64
|
transform-origin: top left;
|
|
51
|
-
transform: scale(
|
|
52
|
-
width: calc(100% /
|
|
53
|
-
font-size: calc(14px /
|
|
54
|
-
line-height:
|
|
55
|
-
padding:
|
|
56
|
-
height: calc(
|
|
65
|
+
transform: scale(var(--charcoal-textarea-safari-prevent-zoom-scale));
|
|
66
|
+
width: calc(100% / var(--charcoal-textarea-safari-prevent-zoom-scale));
|
|
67
|
+
font-size: calc(14px / var(--charcoal-textarea-safari-prevent-zoom-scale));
|
|
68
|
+
line-height: var(--charcoal-textarea-line-height);
|
|
69
|
+
padding: var(--charcoal-textarea-padding-y) var(--charcoal-textarea-padding-x);
|
|
70
|
+
height: calc(
|
|
71
|
+
var(--charcoal-textarea-line-height) * var(--charcoal-text-area-rows) +
|
|
72
|
+
var(--charcoal-textarea-padding-y) * 2
|
|
73
|
+
);
|
|
57
74
|
|
|
58
75
|
/* Display box-shadow for iOS Safari */
|
|
59
76
|
appearance: none;
|
|
@@ -61,8 +78,12 @@
|
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
.charcoal-text-area-textarea[data-no-bottom-padding='true'] {
|
|
64
|
-
padding:
|
|
65
|
-
|
|
81
|
+
padding: var(--charcoal-textarea-padding-y)
|
|
82
|
+
var(--charcoal-textarea-padding-x) 0;
|
|
83
|
+
height: calc(
|
|
84
|
+
var(--charcoal-textarea-line-height) * var(--charcoal-text-area-rows) +
|
|
85
|
+
var(--charcoal-textarea-padding-y)
|
|
86
|
+
);
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
.charcoal-text-area-textarea::placeholder {
|
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
box-shadow: 0 0 0 4px rgba(0, 150, 250, 0.32);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
&:has([data-no-bottom-padding='true']) {
|
|
36
|
+
height: calc(22px * (var(--charcoal-text-area-rows) + 1) + 18px);
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
&:not([aria-disabled='true']):hover {
|
|
36
40
|
background-color: var(--charcoal-color-container-secondary-hover-a);
|
|
37
41
|
}
|
|
@@ -50,13 +54,27 @@
|
|
|
50
54
|
box-sizing: border-box;
|
|
51
55
|
|
|
52
56
|
/* Prevent zooming for iOS Safari */
|
|
57
|
+
--charcoal-textarea-safari-prevent-zoom-scale: 0.875;
|
|
58
|
+
--charcoal-textarea-line-height: calc(
|
|
59
|
+
22px / var(--charcoal-textarea-safari-prevent-zoom-scale)
|
|
60
|
+
);
|
|
61
|
+
--charcoal-textarea-padding-y: calc(
|
|
62
|
+
8px / var(--charcoal-textarea-safari-prevent-zoom-scale)
|
|
63
|
+
);
|
|
64
|
+
--charcoal-textarea-padding-x: calc(
|
|
65
|
+
9px / var(--charcoal-textarea-safari-prevent-zoom-scale)
|
|
66
|
+
);
|
|
67
|
+
|
|
53
68
|
transform-origin: top left;
|
|
54
|
-
transform: scale(
|
|
55
|
-
width: calc(100% /
|
|
56
|
-
font-size: calc(14px /
|
|
57
|
-
line-height:
|
|
58
|
-
padding:
|
|
59
|
-
height: calc(
|
|
69
|
+
transform: scale(var(--charcoal-textarea-safari-prevent-zoom-scale));
|
|
70
|
+
width: calc(100% / var(--charcoal-textarea-safari-prevent-zoom-scale));
|
|
71
|
+
font-size: calc(14px / var(--charcoal-textarea-safari-prevent-zoom-scale));
|
|
72
|
+
line-height: var(--charcoal-textarea-line-height);
|
|
73
|
+
padding: var(--charcoal-textarea-padding-y) var(--charcoal-textarea-padding-x);
|
|
74
|
+
height: calc(
|
|
75
|
+
var(--charcoal-textarea-line-height) * var(--charcoal-text-area-rows) +
|
|
76
|
+
var(--charcoal-textarea-padding-y) * 2
|
|
77
|
+
);
|
|
60
78
|
|
|
61
79
|
/* Display box-shadow for iOS Safari */
|
|
62
80
|
appearance: none;
|
|
@@ -64,8 +82,12 @@
|
|
|
64
82
|
background: none;
|
|
65
83
|
|
|
66
84
|
&[data-no-bottom-padding='true'] {
|
|
67
|
-
padding:
|
|
68
|
-
|
|
85
|
+
padding: var(--charcoal-textarea-padding-y)
|
|
86
|
+
var(--charcoal-textarea-padding-x) 0;
|
|
87
|
+
height: calc(
|
|
88
|
+
var(--charcoal-textarea-line-height) * var(--charcoal-text-area-rows) +
|
|
89
|
+
var(--charcoal-textarea-padding-y)
|
|
90
|
+
);
|
|
69
91
|
}
|
|
70
92
|
|
|
71
93
|
&::placeholder {
|
|
@@ -206,7 +206,7 @@ const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
|
|
206
206
|
aria-invalid={invalid === true}
|
|
207
207
|
ref={containerRef}
|
|
208
208
|
style={{
|
|
209
|
-
'--charcoal-text-area-rows':
|
|
209
|
+
'--charcoal-text-area-rows': rows,
|
|
210
210
|
}}
|
|
211
211
|
>
|
|
212
212
|
<textarea
|