@akinon/pz-theme 2.0.154-rc.6 → 2.0.154
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 +7 -46
- package/package.json +4 -4
- package/src/__tests__/account-slot-coverage.test.ts +1 -0
- package/src/__tests__/custom-css.test.ts +12 -0
- package/src/__tests__/route-slug.test.ts +7 -0
- package/src/blocks/__tests__/group-order-items.test.ts +87 -0
- package/src/blocks/__tests__/order-detail-return.test.tsx +208 -0
- package/src/blocks/item-template.tsx +19 -25
- package/src/blocks/order-detail-block.tsx +273 -82
- package/src/blocks/order-list-block.tsx +2 -25
- package/src/blocks/product-block-utils.tsx +49 -13
- package/src/chrome/__tests__/page-context-builders.test.ts +1 -14
- package/src/chrome/page-context.ts +0 -4
- package/src/chrome/placeholder-slugs.ts +4 -0
- package/src/chrome/route-slug.ts +7 -3
- package/src/custom/README.md +0 -20
- package/src/utils/custom-css.ts +35 -2
- package/src/__tests__/row-scope.test.tsx +0 -118
- package/src/blocks/__tests__/order-line-actions.test.tsx +0 -54
- package/src/blocks/__tests__/order-list-rows.test.tsx +0 -112
- package/src/blocks/order-line-actions.ts +0 -45
- package/src/blocks/row-scope.tsx +0 -35
|
@@ -29,6 +29,10 @@ export const PLACEHOLDER_SLUGS = {
|
|
|
29
29
|
accountDashboard: 'account-dashboard-body',
|
|
30
30
|
accountOrders: 'account-orders-body',
|
|
31
31
|
accountOrderDetail: 'account-order-detail-body',
|
|
32
|
+
// The easy-return / cancellation screen under one order. It used to resolve
|
|
33
|
+
// to the detail slug and stay code-owned; its own slug lets a brand compose
|
|
34
|
+
// it without the detail composition swallowing it.
|
|
35
|
+
accountOrderCancellation: 'account-order-cancellation-body',
|
|
32
36
|
accountAddresses: 'account-address-body',
|
|
33
37
|
accountProfile: 'account-profile-body',
|
|
34
38
|
accountChangePassword: 'account-change-password-body',
|
package/src/chrome/route-slug.ts
CHANGED
|
@@ -19,9 +19,12 @@ export type RouteSlugRule = [RegExp, string];
|
|
|
19
19
|
* resolves to the order detail body and not to the order list.
|
|
20
20
|
*/
|
|
21
21
|
const DEFAULT_RULES: RouteSlugRule[] = [
|
|
22
|
-
// Account — deepest first. The
|
|
23
|
-
//
|
|
24
|
-
|
|
22
|
+
// Account — deepest first. The cancellation screen under an order has its
|
|
23
|
+
// own body; the detail rule still matches any other deeper order screen.
|
|
24
|
+
[
|
|
25
|
+
/^\/(account|users)\/orders\/[^/]+\/cancellation(\/|$)/,
|
|
26
|
+
PLACEHOLDER_SLUGS.accountOrderCancellation
|
|
27
|
+
],
|
|
25
28
|
[
|
|
26
29
|
/^\/(account|users)\/orders\/[^/]+/,
|
|
27
30
|
PLACEHOLDER_SLUGS.accountOrderDetail
|
|
@@ -130,6 +133,7 @@ const PAGE_MOUNTED_SLUGS = new Set<string>([
|
|
|
130
133
|
PLACEHOLDER_SLUGS.accountDashboard,
|
|
131
134
|
PLACEHOLDER_SLUGS.accountOrders,
|
|
132
135
|
PLACEHOLDER_SLUGS.accountOrderDetail,
|
|
136
|
+
PLACEHOLDER_SLUGS.accountOrderCancellation,
|
|
133
137
|
PLACEHOLDER_SLUGS.accountAddresses,
|
|
134
138
|
PLACEHOLDER_SLUGS.accountProfile,
|
|
135
139
|
PLACEHOLDER_SLUGS.accountChangePassword,
|
package/src/custom/README.md
CHANGED
|
@@ -303,26 +303,6 @@ attached". The editor's **Use preview** toggle turns it off while the editor
|
|
|
303
303
|
stays open, so the canvas behaves like the live site: whatever you held open
|
|
304
304
|
closes and links navigate again.
|
|
305
305
|
|
|
306
|
-
## Reading the row a block is placed in
|
|
307
|
-
|
|
308
|
-
A block dropped into a list's row layout (the child a designer marked as the
|
|
309
|
-
row template of an order list, a product grid, an address list, …) renders once
|
|
310
|
-
per row. The product lists open the product scope `useThemeProduct()` reads;
|
|
311
|
-
every list also opens a row scope that carries the row itself:
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
|
-
import { useThemeRowScope } from '@akinon/pz-theme/src/blocks/row-scope';
|
|
315
|
-
|
|
316
|
-
const scope = useThemeRowScope();
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
Inside a row it returns `{ listType, row, index }`, for example
|
|
320
|
-
`listType: 'order-list'` with the order's `number` and `items` on `row`; anywhere
|
|
321
|
-
else it returns `null`. `row` is the same record the row's bindings resolve
|
|
322
|
-
against, so whatever a text block can bind to (`item.number`), code can read.
|
|
323
|
-
Import it from that deep path for the same reason as `useThemePageContext`: the
|
|
324
|
-
package root is not importable from a client module.
|
|
325
|
-
|
|
326
306
|
## Reporting what happened
|
|
327
307
|
|
|
328
308
|
A custom unit can report a moment — a strip was seen, a lookbook was opened, a
|
package/src/utils/custom-css.ts
CHANGED
|
@@ -77,9 +77,42 @@ const splitTopLevelRules = (css: string): string[] => {
|
|
|
77
77
|
return rules.map(rule => rule.trim()).filter(Boolean);
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Splits a selector list on its top-level commas only. A comma inside
|
|
82
|
+
* `:has()`, `:is()`, `:not()`, `:where()`, an attribute selector or a string
|
|
83
|
+
* belongs to that one selector — splitting there scoped the second half of
|
|
84
|
+
* `:has(a, b)` as if it were a selector of its own, so it never matched.
|
|
85
|
+
*/
|
|
86
|
+
const splitSelectorList = (selectors: string): string[] => {
|
|
87
|
+
const parts: string[] = [];
|
|
88
|
+
let depth = 0;
|
|
89
|
+
let quote: string | null = null;
|
|
90
|
+
let start = 0;
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < selectors.length; i++) {
|
|
93
|
+
const char = selectors[i];
|
|
94
|
+
|
|
95
|
+
if (quote) {
|
|
96
|
+
if (char === '\\') i += 1;
|
|
97
|
+
else if (char === quote) quote = null;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (char === '"' || char === "'") quote = char;
|
|
102
|
+
else if (char === '(' || char === '[') depth += 1;
|
|
103
|
+
else if (char === ')' || char === ']') depth = Math.max(0, depth - 1);
|
|
104
|
+
else if (char === ',' && depth === 0) {
|
|
105
|
+
parts.push(selectors.slice(start, i));
|
|
106
|
+
start = i + 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
parts.push(selectors.slice(start));
|
|
110
|
+
|
|
111
|
+
return parts;
|
|
112
|
+
};
|
|
113
|
+
|
|
80
114
|
const scopeSelectorList = (selectors: string, scope: string): string =>
|
|
81
|
-
selectors
|
|
82
|
-
.split(',')
|
|
115
|
+
splitSelectorList(selectors)
|
|
83
116
|
.map(selector => selector.trim())
|
|
84
117
|
.filter(Boolean)
|
|
85
118
|
.map(selector =>
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { ItemTemplateList } from '../blocks/item-template';
|
|
4
|
-
import { useThemeProduct } from '../blocks/product-block-utils';
|
|
5
|
-
import { useThemeRowScope } from '../blocks/row-scope';
|
|
6
|
-
import blockRendererRegistry from '../blocks/block-renderer-registry';
|
|
7
|
-
import { resetCustomCatalog } from '../custom/catalog';
|
|
8
|
-
import { resetCustomCollection } from '../custom/collect';
|
|
9
|
-
import { isCustomThemeType, registerCustomBlocks } from '../custom';
|
|
10
|
-
import type { Block } from '../theme-block';
|
|
11
|
-
|
|
12
|
-
const READER = 'custom:acme:row-reader';
|
|
13
|
-
|
|
14
|
-
function RowReader() {
|
|
15
|
-
const scope = useThemeRowScope();
|
|
16
|
-
const product = useThemeProduct();
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<span
|
|
20
|
-
data-testid="reader"
|
|
21
|
-
data-list={scope?.listType ?? ''}
|
|
22
|
-
data-index={scope?.index ?? ''}
|
|
23
|
-
data-number={String(scope?.row.number ?? '')}
|
|
24
|
-
data-product={String(product?.pk ?? '')}
|
|
25
|
-
/>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const template = (): Block =>
|
|
30
|
-
({
|
|
31
|
-
id: 'tpl',
|
|
32
|
-
type: 'group',
|
|
33
|
-
label: 'Row',
|
|
34
|
-
order: 0,
|
|
35
|
-
hidden: false,
|
|
36
|
-
value: '',
|
|
37
|
-
properties: { role: { desktop: 'item-template' } },
|
|
38
|
-
styles: {},
|
|
39
|
-
blocks: [
|
|
40
|
-
{
|
|
41
|
-
id: 'tpl-reader',
|
|
42
|
-
type: READER,
|
|
43
|
-
label: 'Reader',
|
|
44
|
-
order: 0,
|
|
45
|
-
hidden: false,
|
|
46
|
-
value: '',
|
|
47
|
-
properties: {},
|
|
48
|
-
styles: {},
|
|
49
|
-
blocks: []
|
|
50
|
-
}
|
|
51
|
-
]
|
|
52
|
-
} as Block);
|
|
53
|
-
|
|
54
|
-
const list = (type: string): Block =>
|
|
55
|
-
({
|
|
56
|
-
id: 'list',
|
|
57
|
-
type,
|
|
58
|
-
label: 'List',
|
|
59
|
-
order: 0,
|
|
60
|
-
hidden: false,
|
|
61
|
-
value: '',
|
|
62
|
-
properties: {},
|
|
63
|
-
styles: {},
|
|
64
|
-
blocks: [template()]
|
|
65
|
-
} as Block);
|
|
66
|
-
|
|
67
|
-
const renderRows = (type: string, items: Record<string, unknown>[]) =>
|
|
68
|
-
render(
|
|
69
|
-
<ItemTemplateList
|
|
70
|
-
block={list(type)}
|
|
71
|
-
items={items}
|
|
72
|
-
placeholderId="p1"
|
|
73
|
-
sectionId="s1"
|
|
74
|
-
/>
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
beforeAll(() => {
|
|
78
|
-
registerCustomBlocks('acme', { 'row-reader': RowReader });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
afterAll(() => {
|
|
82
|
-
const map = (
|
|
83
|
-
blockRendererRegistry as unknown as { renderers: Map<string, unknown> }
|
|
84
|
-
).renderers;
|
|
85
|
-
Array.from(map.keys())
|
|
86
|
-
.filter(isCustomThemeType)
|
|
87
|
-
.forEach((key) => map.delete(key));
|
|
88
|
-
resetCustomCatalog();
|
|
89
|
-
resetCustomCollection();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('the row scope', () => {
|
|
93
|
-
it('hands every repeated row to the blocks inside it, on any list', () => {
|
|
94
|
-
renderRows('order-list', [{ number: 'ORD-1' }, { number: 'ORD-2' }]);
|
|
95
|
-
|
|
96
|
-
const readers = screen.getAllByTestId('reader');
|
|
97
|
-
expect(readers).toHaveLength(2);
|
|
98
|
-
expect(readers[0]).toHaveAttribute('data-list', 'order-list');
|
|
99
|
-
expect(readers[0]).toHaveAttribute('data-index', '0');
|
|
100
|
-
expect(readers[0]).toHaveAttribute('data-number', 'ORD-1');
|
|
101
|
-
expect(readers[1]).toHaveAttribute('data-number', 'ORD-2');
|
|
102
|
-
expect(readers[0]).toHaveAttribute('data-product', '');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('keeps the product scope on product lists next to the row scope', () => {
|
|
106
|
-
renderRows('product-grid', [{ pk: 7, name: 'Sneaker' }]);
|
|
107
|
-
|
|
108
|
-
const reader = screen.getByTestId('reader');
|
|
109
|
-
expect(reader).toHaveAttribute('data-list', 'product-grid');
|
|
110
|
-
expect(reader).toHaveAttribute('data-product', '7');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('is closed outside a repeated row', () => {
|
|
114
|
-
render(<RowReader />);
|
|
115
|
-
|
|
116
|
-
expect(screen.getByTestId('reader')).toHaveAttribute('data-list', '');
|
|
117
|
-
});
|
|
118
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
registerOrderLineActions,
|
|
4
|
-
renderOrderLineActions
|
|
5
|
-
} from '../order-line-actions';
|
|
6
|
-
|
|
7
|
-
const context = {
|
|
8
|
-
order: { id: 7, number: '20920397829', created_date: '2026-10-01' },
|
|
9
|
-
line: {
|
|
10
|
-
key: '1',
|
|
11
|
-
name: 'Product',
|
|
12
|
-
image: '',
|
|
13
|
-
href: '',
|
|
14
|
-
price: '100',
|
|
15
|
-
total: 100,
|
|
16
|
-
quantity: 1,
|
|
17
|
-
item: { id: 1, status: { value: '550', label: 'Delivered' } }
|
|
18
|
-
},
|
|
19
|
-
isDesigner: false
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
describe('order line actions registry', () => {
|
|
23
|
-
it('renders nothing until a brand registers', () => {
|
|
24
|
-
expect(renderOrderLineActions(context)).toBeNull();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('hands the line and order to the registered renderer', () => {
|
|
28
|
-
const renderer = jest.fn(() => <span>action</span>);
|
|
29
|
-
const unregister = registerOrderLineActions(renderer);
|
|
30
|
-
|
|
31
|
-
expect(renderOrderLineActions(context)).toEqual(<span>action</span>);
|
|
32
|
-
expect(renderer).toHaveBeenCalledWith(context);
|
|
33
|
-
|
|
34
|
-
unregister();
|
|
35
|
-
expect(renderOrderLineActions(context)).toBeNull();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('swallows a throwing renderer so a brand bug cannot break the line', () => {
|
|
39
|
-
const unregister = registerOrderLineActions(() => {
|
|
40
|
-
throw new Error('boom');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
expect(renderOrderLineActions(context)).toBeNull();
|
|
44
|
-
unregister();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('only forgets the renderer it was asked to forget', () => {
|
|
48
|
-
const first = registerOrderLineActions(() => 'first');
|
|
49
|
-
registerOrderLineActions(() => 'second');
|
|
50
|
-
|
|
51
|
-
first();
|
|
52
|
-
expect(renderOrderLineActions(context)).toBe('second');
|
|
53
|
-
});
|
|
54
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render } from '@testing-library/react';
|
|
3
|
-
import OrderListBlock from '../order-list-block';
|
|
4
|
-
|
|
5
|
-
const captured: { items: Record<string, unknown>[] } = { items: [] };
|
|
6
|
-
|
|
7
|
-
jest.mock('../item-template', () => ({
|
|
8
|
-
__esModule: true,
|
|
9
|
-
hasItemTemplate: () => true,
|
|
10
|
-
ItemTemplateList: ({ items }: { items: Record<string, unknown>[] }) => {
|
|
11
|
-
captured.items = items;
|
|
12
|
-
return <div data-testid="item-template-list" />;
|
|
13
|
-
}
|
|
14
|
-
}));
|
|
15
|
-
|
|
16
|
-
jest.mock('next-auth/react', () => ({
|
|
17
|
-
useSession: () => ({ data: null, status: 'authenticated' })
|
|
18
|
-
}));
|
|
19
|
-
|
|
20
|
-
jest.mock('@akinon/next/hooks', () => ({
|
|
21
|
-
__esModule: true,
|
|
22
|
-
useLocalization: () => ({ locale: 'tr', currency: 'try' })
|
|
23
|
-
}));
|
|
24
|
-
|
|
25
|
-
jest.mock('@akinon/next/redux/hooks', () => ({
|
|
26
|
-
__esModule: true,
|
|
27
|
-
useAppSelector: () => undefined,
|
|
28
|
-
useAppDispatch: () => jest.fn()
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
|
-
jest.mock('@akinon/next/data/client/account', () => ({
|
|
32
|
-
__esModule: true,
|
|
33
|
-
useGetOrdersQuery: () => ({
|
|
34
|
-
data: {
|
|
35
|
-
count: 1,
|
|
36
|
-
results: [
|
|
37
|
-
{
|
|
38
|
-
id: 42,
|
|
39
|
-
number: 'ORD-42',
|
|
40
|
-
amount: '100.00',
|
|
41
|
-
created_date: '2026-09-01T10:00:00Z',
|
|
42
|
-
status: { value: '550', label: 'Delivered' },
|
|
43
|
-
orderitem_set: [
|
|
44
|
-
{
|
|
45
|
-
id: 7,
|
|
46
|
-
status: { value: 550, label: 'Delivered' },
|
|
47
|
-
product: {
|
|
48
|
-
sku: 'SKU-1',
|
|
49
|
-
base_code: 'BASE-1',
|
|
50
|
-
name: 'Sneaker',
|
|
51
|
-
image: '/s.jpg'
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
status: { value: '400', label: 'Approved' },
|
|
56
|
-
product: { name: 'Boot' }
|
|
57
|
-
}
|
|
58
|
-
]
|
|
59
|
-
}
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
isLoading: false
|
|
63
|
-
})
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
describe('order list rows', () => {
|
|
67
|
-
it('publishes the order items with their product codes and status code', () => {
|
|
68
|
-
render(
|
|
69
|
-
<OrderListBlock
|
|
70
|
-
block={{
|
|
71
|
-
id: 'order-list-1',
|
|
72
|
-
type: 'order-list',
|
|
73
|
-
properties: {},
|
|
74
|
-
styles: {},
|
|
75
|
-
blocks: []
|
|
76
|
-
}}
|
|
77
|
-
placeholderId="p1"
|
|
78
|
-
sectionId="s1"
|
|
79
|
-
/>
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
expect(captured.items).toHaveLength(1);
|
|
83
|
-
expect(captured.items[0]).toMatchObject({
|
|
84
|
-
id: 42,
|
|
85
|
-
number: 'ORD-42',
|
|
86
|
-
createdDate: '2026-09-01T10:00:00Z'
|
|
87
|
-
});
|
|
88
|
-
expect(captured.items[0].items).toEqual([
|
|
89
|
-
{
|
|
90
|
-
id: 7,
|
|
91
|
-
name: 'Sneaker',
|
|
92
|
-
image: '/s.jpg',
|
|
93
|
-
status: 'Delivered',
|
|
94
|
-
statusValue: '550',
|
|
95
|
-
product: {
|
|
96
|
-
sku: 'SKU-1',
|
|
97
|
-
base_code: 'BASE-1',
|
|
98
|
-
name: 'Sneaker',
|
|
99
|
-
image: '/s.jpg'
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: 1,
|
|
104
|
-
name: 'Boot',
|
|
105
|
-
image: '',
|
|
106
|
-
status: 'Approved',
|
|
107
|
-
statusValue: '400',
|
|
108
|
-
product: { sku: '', base_code: '', name: 'Boot', image: '' }
|
|
109
|
-
}
|
|
110
|
-
]);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import type { GroupedOrderLine, OrderLineLike } from './product-block-utils';
|
|
3
|
-
|
|
4
|
-
export interface OrderLineActionsOrder {
|
|
5
|
-
id?: number;
|
|
6
|
-
number?: string;
|
|
7
|
-
created_date?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type OrderLineActionsItem = OrderLineLike & {
|
|
11
|
-
status?: { value?: string; label?: string };
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export interface OrderLineActionsContext {
|
|
16
|
-
order: OrderLineActionsOrder;
|
|
17
|
-
line: GroupedOrderLine<OrderLineActionsItem>;
|
|
18
|
-
isDesigner: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type OrderLineActionsRenderer = (
|
|
22
|
-
context: OrderLineActionsContext
|
|
23
|
-
) => ReactNode;
|
|
24
|
-
|
|
25
|
-
let renderer: OrderLineActionsRenderer | null = null;
|
|
26
|
-
|
|
27
|
-
export const registerOrderLineActions = (
|
|
28
|
-
fn: OrderLineActionsRenderer
|
|
29
|
-
): (() => void) => {
|
|
30
|
-
renderer = fn;
|
|
31
|
-
return () => {
|
|
32
|
-
if (renderer === fn) renderer = null;
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const renderOrderLineActions = (
|
|
37
|
-
context: OrderLineActionsContext
|
|
38
|
-
): ReactNode => {
|
|
39
|
-
if (!renderer) return null;
|
|
40
|
-
try {
|
|
41
|
-
return renderer(context) ?? null;
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
};
|
package/src/blocks/row-scope.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
|
|
5
|
-
export interface ThemeRowScopeValue {
|
|
6
|
-
listType: string;
|
|
7
|
-
row: Record<string, unknown>;
|
|
8
|
-
index: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const ThemeRowScopeContext = React.createContext<ThemeRowScopeValue | null>(
|
|
12
|
-
null
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
export const ThemeRowScopeProvider = ({
|
|
16
|
-
listType,
|
|
17
|
-
row,
|
|
18
|
-
index,
|
|
19
|
-
children
|
|
20
|
-
}: ThemeRowScopeValue & { children: React.ReactNode }) => {
|
|
21
|
-
const value = React.useMemo(
|
|
22
|
-
() => ({ listType, row, index }),
|
|
23
|
-
[listType, row, index]
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<ThemeRowScopeContext.Provider value={value}>
|
|
28
|
-
{children}
|
|
29
|
-
</ThemeRowScopeContext.Provider>
|
|
30
|
-
);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/** Non-null while a repeated row of any list renders; null everywhere else. */
|
|
34
|
-
export const useThemeRowScope = (): ThemeRowScopeValue | null =>
|
|
35
|
-
React.useContext(ThemeRowScopeContext);
|