@akinon/pz-theme 2.0.61-rc.0 → 2.0.62-beta.0
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/CHANGELOG.md +15 -31
- package/package.json +4 -4
- package/src/blocks/__tests__/slider-block.test.tsx +118 -4
- package/src/blocks/slider-block.tsx +110 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,36 +1,20 @@
|
|
|
1
1
|
# @akinon/pz-theme
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Updated dependencies [
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- Updated dependencies [57d7eb305]
|
|
19
|
-
- Updated dependencies [d99a6a7d5]
|
|
20
|
-
- Updated dependencies [9db81a714]
|
|
21
|
-
- Updated dependencies [591e345e1]
|
|
22
|
-
- Updated dependencies [4de5303c5]
|
|
23
|
-
- Updated dependencies [95b139dc1]
|
|
24
|
-
- Updated dependencies [1d00f2d06]
|
|
25
|
-
- Updated dependencies [4ac7b2a1e]
|
|
26
|
-
- Updated dependencies [e9598c71]
|
|
27
|
-
- Updated dependencies [4998a9631]
|
|
28
|
-
- Updated dependencies [804d2bd6]
|
|
29
|
-
- Updated dependencies [3909d3224]
|
|
30
|
-
- Updated dependencies [6a3d8a63]
|
|
31
|
-
- Updated dependencies [e18836b20]
|
|
32
|
-
- @akinon/next@2.0.61-rc.0
|
|
33
|
-
- @akinon/theme-contract@2.0.61-rc.0
|
|
3
|
+
## 2.0.62-beta.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [cbbbfd75]
|
|
8
|
+
- @akinon/next@2.0.62-beta.0
|
|
9
|
+
- @akinon/theme-contract@2.0.62-beta.0
|
|
10
|
+
|
|
11
|
+
## 2.0.61
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 760ea8b: ZERO-4799: Read slider arrow button geometry from its own block styles
|
|
16
|
+
- @akinon/next@2.0.61
|
|
17
|
+
- @akinon/theme-contract@2.0.61
|
|
34
18
|
|
|
35
19
|
## 2.0.60
|
|
36
20
|
|
package/package.json
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-theme",
|
|
3
3
|
"description": "Theme package for Project Zero Next — ThemePlaceholder system and theme editor infrastructure",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.62-beta.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "jest"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@akinon/next": "2.0.
|
|
11
|
+
"@akinon/next": "2.0.62-beta.0",
|
|
12
12
|
"react": "^18.0.0 || ^19.0.0",
|
|
13
13
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@akinon/theme-contract": "2.0.
|
|
16
|
+
"@akinon/theme-contract": "2.0.62-beta.0",
|
|
17
17
|
"clsx": "2.1.1",
|
|
18
18
|
"lottie-web": "^5.13.0",
|
|
19
19
|
"tailwind-merge": "^2.5.4",
|
|
20
20
|
"zod": "4.4.1"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
-
"@akinon/next": "2.0.
|
|
23
|
+
"@akinon/next": "2.0.62-beta.0",
|
|
24
24
|
"@testing-library/jest-dom": "5.16.4",
|
|
25
25
|
"@testing-library/react": "16.3.2",
|
|
26
26
|
"@testing-library/user-event": "14.1.1",
|
|
@@ -22,10 +22,7 @@ jest.mock('../../theme-block', () => ({
|
|
|
22
22
|
|
|
23
23
|
import SliderBlock from '../slider-block';
|
|
24
24
|
|
|
25
|
-
const child = (
|
|
26
|
-
id: string,
|
|
27
|
-
over: Record<string, unknown> = {}
|
|
28
|
-
): any => ({
|
|
25
|
+
const child = (id: string, over: Record<string, unknown> = {}): any => ({
|
|
29
26
|
id,
|
|
30
27
|
type: 'text',
|
|
31
28
|
label: id,
|
|
@@ -146,3 +143,120 @@ describe('SliderBlock — child filtering, ordering and empty state', () => {
|
|
|
146
143
|
expect(markers(container)).toEqual(['a', 'b', 'c']);
|
|
147
144
|
});
|
|
148
145
|
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The arrow button's geometry used to be hardcoded, so the only way to reach it
|
|
149
|
+
* was to edit this component. These cover the contract the panel now relies on:
|
|
150
|
+
* the legacy circle when nothing is authored, and the block's own styles when
|
|
151
|
+
* something is.
|
|
152
|
+
*/
|
|
153
|
+
describe('SliderBlock — arrow button geometry', () => {
|
|
154
|
+
const arrow = (label: string, styles: Record<string, unknown>) =>
|
|
155
|
+
child(label === 'Left Arrow Icon' ? 'left' : 'right', {
|
|
156
|
+
type: 'icon',
|
|
157
|
+
label,
|
|
158
|
+
styles
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const renderArrows = (
|
|
162
|
+
styles: Record<string, unknown>,
|
|
163
|
+
over: Record<string, unknown> = {}
|
|
164
|
+
) =>
|
|
165
|
+
render(
|
|
166
|
+
<SliderBlock
|
|
167
|
+
{...props({
|
|
168
|
+
isDesigner: true,
|
|
169
|
+
block: {
|
|
170
|
+
...props().block,
|
|
171
|
+
blocks: [
|
|
172
|
+
arrow('Left Arrow Icon', styles),
|
|
173
|
+
child('s1'),
|
|
174
|
+
arrow('Right Arrow Icon', styles)
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
...over
|
|
178
|
+
})}
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const previous = (container: HTMLElement) =>
|
|
183
|
+
container.querySelector('button[aria-label="Previous"]') as HTMLElement;
|
|
184
|
+
|
|
185
|
+
it('keeps the 40px circle when no geometry is authored', () => {
|
|
186
|
+
const { container } = renderArrows({});
|
|
187
|
+
const style = previous(container).style;
|
|
188
|
+
|
|
189
|
+
expect(style.width).toBe('40px');
|
|
190
|
+
expect(style.height).toBe('40px');
|
|
191
|
+
expect(style.borderRadius).toBe('50%');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('takes width, height and radius from the arrow block', () => {
|
|
195
|
+
const { container } = renderArrows({
|
|
196
|
+
width: '32px',
|
|
197
|
+
height: '64px',
|
|
198
|
+
'border-radius': '8px'
|
|
199
|
+
});
|
|
200
|
+
const style = previous(container).style;
|
|
201
|
+
|
|
202
|
+
expect(style.width).toBe('32px');
|
|
203
|
+
expect(style.height).toBe('64px');
|
|
204
|
+
expect(style.borderRadius).toBe('8px');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('rounds one side only, which is what a half-round tab needs', () => {
|
|
208
|
+
const { container } = renderArrows({
|
|
209
|
+
width: '32px',
|
|
210
|
+
height: '64px',
|
|
211
|
+
'border-top-left-radius': '32px',
|
|
212
|
+
'border-bottom-left-radius': '32px'
|
|
213
|
+
});
|
|
214
|
+
const style = previous(container).style;
|
|
215
|
+
|
|
216
|
+
expect(style.borderTopLeftRadius).toBe('32px');
|
|
217
|
+
expect(style.borderBottomLeftRadius).toBe('32px');
|
|
218
|
+
// The legacy 50% must not stand in here, or the flat side would be round.
|
|
219
|
+
expect(style.borderTopRightRadius).toBe('');
|
|
220
|
+
expect(style.borderBottomRightRadius).toBe('');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('writes the shorthand before a corner, so the corner overrides it', () => {
|
|
224
|
+
const { container } = renderArrows({
|
|
225
|
+
'border-radius': '20px',
|
|
226
|
+
'border-top-left-radius': '0px'
|
|
227
|
+
});
|
|
228
|
+
const button = previous(container);
|
|
229
|
+
|
|
230
|
+
expect(button.style.borderRadius).toBe('20px');
|
|
231
|
+
expect(button.style.borderTopLeftRadius).toBe('0px');
|
|
232
|
+
|
|
233
|
+
// Order is the whole mechanism: the shorthand has to be declared first or
|
|
234
|
+
// it would reset the corner. jsdom does not expand the shorthand into
|
|
235
|
+
// longhands, so the declaration order is what there is to assert.
|
|
236
|
+
const declarations = button.getAttribute('style') ?? '';
|
|
237
|
+
expect(declarations.indexOf('border-radius')).toBeLessThan(
|
|
238
|
+
declarations.indexOf('border-top-left-radius')
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('appends px to the bare numbers the numeric controls store', () => {
|
|
243
|
+
const { container } = renderArrows({ width: 48, height: 48 });
|
|
244
|
+
const style = previous(container).style;
|
|
245
|
+
|
|
246
|
+
expect(style.width).toBe('48px');
|
|
247
|
+
expect(style.height).toBe('48px');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('reads the breakpoint being previewed, not always desktop', () => {
|
|
251
|
+
const styles = {
|
|
252
|
+
width: { desktop: '40px', mobile: '28px' },
|
|
253
|
+
height: { desktop: '40px', mobile: '28px' }
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
expect(previous(renderArrows(styles).container).style.width).toBe('40px');
|
|
257
|
+
expect(
|
|
258
|
+
previous(renderArrows(styles, { currentBreakpoint: 'mobile' }).container)
|
|
259
|
+
.style.width
|
|
260
|
+
).toBe('28px');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
@@ -118,8 +118,9 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
118
118
|
}
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
const currentSlidesToSlide =
|
|
122
|
-
|
|
121
|
+
const currentSlidesToSlide = isNarrowBreakpoint(currentBreakpoint)
|
|
122
|
+
? mobileSlides
|
|
123
|
+
: desktopSlides;
|
|
123
124
|
|
|
124
125
|
const showArrows = getResponsiveValue(
|
|
125
126
|
getConfigEntry('show-arrows'),
|
|
@@ -263,32 +264,60 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
263
264
|
const rightArrowIconUrl = getIconUrl(rightArrowIconBlock);
|
|
264
265
|
|
|
265
266
|
const leftArrowIconSize = String(
|
|
266
|
-
getResponsiveValue(
|
|
267
|
+
getResponsiveValue(
|
|
268
|
+
leftArrowIconBlock?.styles?.size,
|
|
269
|
+
currentBreakpoint,
|
|
270
|
+
'24'
|
|
271
|
+
)
|
|
267
272
|
);
|
|
268
273
|
const rightArrowIconSize = String(
|
|
269
|
-
getResponsiveValue(
|
|
274
|
+
getResponsiveValue(
|
|
275
|
+
rightArrowIconBlock?.styles?.size,
|
|
276
|
+
currentBreakpoint,
|
|
277
|
+
'24'
|
|
278
|
+
)
|
|
270
279
|
);
|
|
271
280
|
|
|
272
281
|
const leftArrowIconColor = resolveThemeCssVariables(
|
|
273
282
|
String(
|
|
274
|
-
getResponsiveValue(
|
|
283
|
+
getResponsiveValue(
|
|
284
|
+
leftArrowIconBlock?.styles?.['color'],
|
|
285
|
+
currentBreakpoint,
|
|
286
|
+
'#ffffff'
|
|
287
|
+
)
|
|
275
288
|
),
|
|
276
289
|
themeSettings
|
|
277
290
|
);
|
|
278
291
|
const rightArrowIconColor = resolveThemeCssVariables(
|
|
279
292
|
String(
|
|
280
|
-
getResponsiveValue(
|
|
293
|
+
getResponsiveValue(
|
|
294
|
+
rightArrowIconBlock?.styles?.['color'],
|
|
295
|
+
currentBreakpoint,
|
|
296
|
+
'#ffffff'
|
|
297
|
+
)
|
|
281
298
|
),
|
|
282
299
|
themeSettings
|
|
283
300
|
);
|
|
284
301
|
|
|
285
302
|
const leftArrowIconOpacity =
|
|
286
303
|
parseFloat(
|
|
287
|
-
String(
|
|
304
|
+
String(
|
|
305
|
+
getResponsiveValue(
|
|
306
|
+
leftArrowIconBlock?.styles?.['opacity'],
|
|
307
|
+
currentBreakpoint,
|
|
308
|
+
'1'
|
|
309
|
+
)
|
|
310
|
+
)
|
|
288
311
|
) || 1;
|
|
289
312
|
const rightArrowIconOpacity =
|
|
290
313
|
parseFloat(
|
|
291
|
-
String(
|
|
314
|
+
String(
|
|
315
|
+
getResponsiveValue(
|
|
316
|
+
rightArrowIconBlock?.styles?.['opacity'],
|
|
317
|
+
currentBreakpoint,
|
|
318
|
+
'1'
|
|
319
|
+
)
|
|
320
|
+
)
|
|
292
321
|
) || 1;
|
|
293
322
|
|
|
294
323
|
// Process SVGs with color
|
|
@@ -328,6 +357,7 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
328
357
|
getResponsiveValue(
|
|
329
358
|
leftArrowIconBlock?.styles?.['background'] ??
|
|
330
359
|
leftArrowIconBlock?.styles?.['backgroundColor'],
|
|
360
|
+
currentBreakpoint,
|
|
331
361
|
'rgba(0, 0, 0, 0.5)'
|
|
332
362
|
)
|
|
333
363
|
),
|
|
@@ -339,12 +369,80 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
339
369
|
getResponsiveValue(
|
|
340
370
|
rightArrowIconBlock?.styles?.['background'] ??
|
|
341
371
|
rightArrowIconBlock?.styles?.['backgroundColor'],
|
|
372
|
+
currentBreakpoint,
|
|
342
373
|
'rgba(0, 0, 0, 0.5)'
|
|
343
374
|
)
|
|
344
375
|
),
|
|
345
376
|
themeSettings
|
|
346
377
|
);
|
|
347
378
|
|
|
379
|
+
// The arrow button's geometry used to live here and nowhere else — a 40px
|
|
380
|
+
// circle — so the panel could set the button's background and nothing else: a
|
|
381
|
+
// half-round tab, a square, a wider pill were all unreachable, and a Corner
|
|
382
|
+
// Radius typed in the editor was stored and then ignored. Geometry now comes
|
|
383
|
+
// from the arrow icon block's own styles, with the old circle as the default
|
|
384
|
+
// so a slider that authored none of it renders exactly as before.
|
|
385
|
+
//
|
|
386
|
+
// Only geometry is read. `color` and `opacity` stay bound to the icon image
|
|
387
|
+
// below; reading them here as well would apply each of them twice.
|
|
388
|
+
const arrowLength = (value: unknown): string | undefined => {
|
|
389
|
+
const text = String(value ?? '').trim();
|
|
390
|
+
if (!text) return undefined;
|
|
391
|
+
// A bare number is what the editor's numeric controls store; its free-text
|
|
392
|
+
// corner fields carry their own unit (`32px`, `50%`).
|
|
393
|
+
return /^-?\d*\.?\d+$/.test(text) ? `${text}px` : text;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const arrowGeometry = (iconBlock?: {
|
|
397
|
+
styles?: Record<string, unknown>;
|
|
398
|
+
}): React.CSSProperties => {
|
|
399
|
+
const styles = iconBlock?.styles ?? {};
|
|
400
|
+
const read = (key: string) => {
|
|
401
|
+
// Published JSON may carry either spelling of a two-word key.
|
|
402
|
+
const camelKey = key.replace(/-([a-z])/g, (_, letter: string) =>
|
|
403
|
+
letter.toUpperCase()
|
|
404
|
+
);
|
|
405
|
+
return arrowLength(
|
|
406
|
+
getResponsiveValue(
|
|
407
|
+
styles[key] ?? styles[camelKey],
|
|
408
|
+
currentBreakpoint,
|
|
409
|
+
''
|
|
410
|
+
)
|
|
411
|
+
);
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const corners: Record<string, string> = {};
|
|
415
|
+
(
|
|
416
|
+
[
|
|
417
|
+
['border-top-left-radius', 'borderTopLeftRadius'],
|
|
418
|
+
['border-top-right-radius', 'borderTopRightRadius'],
|
|
419
|
+
['border-bottom-right-radius', 'borderBottomRightRadius'],
|
|
420
|
+
['border-bottom-left-radius', 'borderBottomLeftRadius']
|
|
421
|
+
] as const
|
|
422
|
+
).forEach(([styleKey, cssProperty]) => {
|
|
423
|
+
const value = read(styleKey);
|
|
424
|
+
if (value) corners[cssProperty] = value;
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const geometry: React.CSSProperties = {
|
|
428
|
+
width: read('width') ?? '40px',
|
|
429
|
+
height: read('height') ?? '40px'
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// The shorthand is written before the corners so an authored corner
|
|
433
|
+
// overrides just that corner, the way the cascade does. The 50% default
|
|
434
|
+
// only stands in when no radius was authored at all — otherwise rounding
|
|
435
|
+
// one corner would leave the other three on the legacy circle.
|
|
436
|
+
const radius = read('border-radius');
|
|
437
|
+
if (radius) geometry.borderRadius = radius;
|
|
438
|
+
else if (Object.keys(corners).length === 0) geometry.borderRadius = '50%';
|
|
439
|
+
|
|
440
|
+
return { ...geometry, ...corners };
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
const leftArrowButtonStyle = arrowGeometry(leftArrowIconBlock);
|
|
444
|
+
const rightArrowButtonStyle = arrowGeometry(rightArrowIconBlock);
|
|
445
|
+
|
|
348
446
|
const handleLeftArrowClick = (e: React.MouseEvent) => {
|
|
349
447
|
e.preventDefault();
|
|
350
448
|
e.stopPropagation();
|
|
@@ -377,9 +475,7 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
377
475
|
style={{
|
|
378
476
|
backgroundColor: leftArrowBackground,
|
|
379
477
|
border: 'none',
|
|
380
|
-
|
|
381
|
-
width: '40px',
|
|
382
|
-
height: '40px',
|
|
478
|
+
...leftArrowButtonStyle,
|
|
383
479
|
display: 'flex',
|
|
384
480
|
alignItems: 'center',
|
|
385
481
|
justifyContent: 'center',
|
|
@@ -419,9 +515,7 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
419
515
|
style={{
|
|
420
516
|
backgroundColor: rightArrowBackground,
|
|
421
517
|
border: 'none',
|
|
422
|
-
|
|
423
|
-
width: '40px',
|
|
424
|
-
height: '40px',
|
|
518
|
+
...rightArrowButtonStyle,
|
|
425
519
|
display: 'flex',
|
|
426
520
|
alignItems: 'center',
|
|
427
521
|
justifyContent: 'center',
|
|
@@ -544,9 +638,7 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
544
638
|
style={{
|
|
545
639
|
backgroundColor: leftArrowBackground,
|
|
546
640
|
border: 'none',
|
|
547
|
-
|
|
548
|
-
width: '40px',
|
|
549
|
-
height: '40px',
|
|
641
|
+
...leftArrowButtonStyle,
|
|
550
642
|
display: 'flex',
|
|
551
643
|
alignItems: 'center',
|
|
552
644
|
justifyContent: 'center',
|
|
@@ -577,9 +669,7 @@ const SliderBlock: React.FC<BlockRendererProps> = ({
|
|
|
577
669
|
style={{
|
|
578
670
|
backgroundColor: rightArrowBackground,
|
|
579
671
|
border: 'none',
|
|
580
|
-
|
|
581
|
-
width: '40px',
|
|
582
|
-
height: '40px',
|
|
672
|
+
...rightArrowButtonStyle,
|
|
583
673
|
display: 'flex',
|
|
584
674
|
alignItems: 'center',
|
|
585
675
|
justifyContent: 'center',
|