@akinon/projectzero 2.0.0-beta.19 → 2.0.0-beta.20
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 +9 -7
- package/app-template/CHANGELOG.md +251 -204
- package/app-template/akinon.json +1 -1
- package/app-template/package.json +28 -28
- package/app-template/public/amex.svg +12 -0
- package/app-template/public/apple-pay.svg +16 -0
- package/app-template/public/assets/images/product-placeholder-1.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-2.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-3.jpg +0 -0
- package/app-template/public/assets/images/product-placeholder-4.jpg +0 -0
- package/app-template/public/google-pay.svg +16 -0
- package/app-template/public/locales/en/account.json +6 -3
- package/app-template/public/locales/en/auth.json +6 -7
- package/app-template/public/locales/en/basket.json +6 -6
- package/app-template/public/locales/en/blog.json +7 -0
- package/app-template/public/locales/en/category.json +3 -1
- package/app-template/public/locales/en/checkout.json +5 -4
- package/app-template/public/locales/en/common.json +11 -2
- package/app-template/public/locales/en/forgot_password.json +6 -7
- package/app-template/public/locales/en/product.json +4 -3
- package/app-template/public/locales/tr/account.json +6 -3
- package/app-template/public/locales/tr/auth.json +16 -17
- package/app-template/public/locales/tr/basket.json +4 -4
- package/app-template/public/locales/tr/blog.json +7 -0
- package/app-template/public/locales/tr/category.json +3 -1
- package/app-template/public/locales/tr/checkout.json +39 -38
- package/app-template/public/locales/tr/common.json +10 -1
- package/app-template/public/locales/tr/forgot_password.json +12 -13
- package/app-template/public/locales/tr/product.json +1 -0
- package/app-template/public/logo.svg +3 -27
- package/app-template/public/mastercard.svg +14 -0
- package/app-template/public/promotion-banner.jpg +0 -0
- package/app-template/public/shop-pay.svg +12 -0
- package/app-template/public/visa.svg +12 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/blog/[slug]/page.tsx +118 -0
- package/app-template/src/app/[commerce]/[locale]/[currency]/pages/[slug]/page.tsx +15 -0
- package/app-template/src/app/api/theme-settings/route.ts +12 -0
- package/app-template/src/assets/fonts/pz-icon.css +211 -49
- package/app-template/src/assets/fonts/pz-icon.eot +0 -0
- package/app-template/src/assets/fonts/pz-icon.html +486 -0
- package/app-template/src/assets/fonts/pz-icon.scss +373 -49
- package/app-template/src/assets/fonts/pz-icon.svg +215 -53
- package/app-template/src/assets/fonts/pz-icon.ttf +0 -0
- package/app-template/src/assets/fonts/pz-icon.woff +0 -0
- package/app-template/src/assets/fonts/pz-icon.woff2 +0 -0
- package/app-template/src/assets/globals.scss +4 -0
- package/app-template/src/assets/icons/arrow-right.svg +3 -0
- package/app-template/src/assets/icons/cart.svg +4 -12
- package/app-template/src/assets/icons/check.svg +2 -18
- package/app-template/src/assets/icons/chevron-down.svg +2 -7
- package/app-template/src/assets/icons/delete.svg +3 -0
- package/app-template/src/assets/icons/facebook.svg +2 -8
- package/app-template/src/assets/icons/fav-off.svg +5 -0
- package/app-template/src/assets/icons/fav-on.svg +5 -0
- package/app-template/src/assets/icons/filter-and-sort.svg +3 -0
- package/app-template/src/assets/icons/heart.svg +3 -0
- package/app-template/src/assets/icons/instagram.svg +2 -13
- package/app-template/src/assets/icons/materials.svg +3 -0
- package/app-template/src/assets/icons/person.svg +4 -0
- package/app-template/src/assets/icons/pinterest.svg +5 -11
- package/app-template/src/assets/icons/ruler.svg +3 -0
- package/app-template/src/assets/icons/search.svg +8 -11
- package/app-template/src/assets/icons/share.svg +2 -9
- package/app-template/src/assets/icons/snapchat.svg +3 -0
- package/app-template/src/assets/icons/tiktok.svg +3 -0
- package/app-template/src/assets/icons/tumblr.svg +6 -0
- package/app-template/src/assets/icons/twitter.svg +2 -10
- package/app-template/src/assets/icons/vimeo.svg +3 -0
- package/app-template/src/assets/icons/youtube.svg +3 -0
- package/app-template/src/assets/icons/zoom.svg +8 -0
- package/app-template/src/components/accordion.tsx +33 -11
- package/app-template/src/components/action-tooltip.tsx +160 -0
- package/app-template/src/components/currency-select.tsx +149 -4
- package/app-template/src/components/icon.tsx +5 -6
- package/app-template/src/components/index.ts +4 -1
- package/app-template/src/components/language-select.tsx +88 -2
- package/app-template/src/components/pagination.tsx +132 -20
- package/app-template/src/components/quantity-input.tsx +63 -0
- package/app-template/src/components/quantity-selector.tsx +203 -0
- package/app-template/src/components/route-handler.tsx +50 -0
- package/app-template/src/components/select.tsx +89 -69
- package/app-template/src/components/types/index.ts +26 -0
- package/app-template/src/components/widget-content.tsx +323 -0
- package/app-template/src/data/server/theme.ts +70 -0
- package/app-template/src/hooks/use-fav-button.tsx +5 -2
- package/app-template/src/hooks/use-product-cart.ts +11 -8
- package/app-template/src/hooks/use-theme-settings.ts +42 -0
- package/app-template/src/lib/fonts.ts +149 -0
- package/app-template/src/settings.js +2 -2
- package/app-template/src/types/hookform-resolvers-yup.d.ts +28 -0
- package/app-template/src/types/widget.ts +169 -0
- package/app-template/src/utils/formatDate.ts +48 -0
- package/app-template/src/utils/styles.ts +71 -0
- package/app-template/src/views/account/contact-form.tsx +147 -130
- package/app-template/src/views/basket/basket-item.tsx +691 -107
- package/app-template/src/views/basket/basket-summary-context.tsx +560 -0
- package/app-template/src/views/basket/designer-context.tsx +617 -0
- package/app-template/src/views/basket/index.ts +2 -0
- package/app-template/src/views/basket/summary.tsx +496 -75
- package/app-template/src/views/breadcrumb/breadcrumb-client.tsx +190 -0
- package/app-template/src/views/breadcrumb/breadcrumb-registrar.tsx +286 -0
- package/app-template/src/views/breadcrumb/constants.ts +15 -0
- package/app-template/src/views/breadcrumb/index.tsx +127 -0
- package/app-template/src/views/breadcrumb.tsx +13 -38
- package/app-template/src/views/category/category-banner.tsx +4 -23
- package/app-template/src/views/category/category-header.tsx +289 -66
- package/app-template/src/views/category/category-info.tsx +173 -24
- package/app-template/src/views/category/filters/filter-item.tsx +138 -42
- package/app-template/src/views/category/filters/index.tsx +208 -48
- package/app-template/src/views/category/layout.tsx +7 -4
- package/app-template/src/views/category/native-widget-context.tsx +257 -0
- package/app-template/src/views/category/product-list-registrar.tsx +665 -0
- package/app-template/src/views/checkout/auth.tsx +64 -40
- package/app-template/src/views/checkout/checkout-address-registrar.tsx +254 -0
- package/app-template/src/views/checkout/checkout-buttons-registrar.tsx +183 -0
- package/app-template/src/views/checkout/checkout-delivery-method-registrar.tsx +259 -0
- package/app-template/src/views/checkout/checkout-payment-options-registrar.tsx +253 -0
- package/app-template/src/views/checkout/checkout-summary-registrar.tsx +183 -0
- package/app-template/src/views/checkout/constants.ts +5 -0
- package/app-template/src/views/checkout/index.tsx +5 -0
- package/app-template/src/views/checkout/layout/header.tsx +9 -5
- package/app-template/src/views/checkout/steps/payment/index.tsx +5 -2
- package/app-template/src/views/checkout/steps/payment/options/credit-card/index.tsx +72 -1
- package/app-template/src/views/checkout/steps/payment/options/masterpass-rest.tsx +15 -0
- package/app-template/src/views/checkout/steps/payment/options/saved-card.tsx +18 -0
- package/app-template/src/views/checkout/steps/payment/payment-option-buttons.tsx +171 -40
- package/app-template/src/views/checkout/steps/shipping/address-box.tsx +74 -12
- package/app-template/src/views/checkout/steps/shipping/addresses.tsx +128 -45
- package/app-template/src/views/checkout/steps/shipping/shipping-options.tsx +232 -27
- package/app-template/src/views/checkout/summary.tsx +303 -29
- package/app-template/src/views/footer/footer-app-banner-context.tsx +326 -0
- package/app-template/src/views/footer/footer-bottom-context.tsx +215 -0
- package/app-template/src/views/footer/footer-bottom-wrapper.tsx +74 -0
- package/app-template/src/views/footer/footer-layout-constants.ts +35 -0
- package/app-template/src/views/footer/footer-layout-registrar.tsx +342 -0
- package/app-template/src/views/footer/footer-layout-switcher.tsx +110 -0
- package/app-template/src/views/footer/footer-menu-context.tsx +211 -0
- package/app-template/src/views/footer/footer-native-widgets.tsx +60 -0
- package/app-template/src/views/footer/footer-social-context.tsx +254 -0
- package/app-template/src/views/footer/footer-subscription-context.tsx +210 -0
- package/app-template/src/views/footer/footer-utils.ts +43 -0
- package/app-template/src/views/footer/footer-value-props-context.tsx +326 -0
- package/app-template/src/views/footer/logo-settings.ts +183 -0
- package/app-template/src/views/footer/native-widget-config.ts +262 -0
- package/app-template/src/views/footer/subscription-settings.ts +122 -0
- package/app-template/src/views/footer/use-footer-logo.ts +162 -0
- package/app-template/src/views/footer.tsx +415 -13
- package/app-template/src/views/guest-login/index.tsx +62 -58
- package/app-template/src/views/header/action-menu.tsx +277 -45
- package/app-template/src/views/header/band.tsx +6 -21
- package/app-template/src/views/header/designer-context.tsx +261 -0
- package/app-template/src/views/header/header-announcement-registrar.tsx +267 -0
- package/app-template/src/views/header/header-client-wrapper.tsx +496 -0
- package/app-template/src/views/header/header-content.tsx +1026 -0
- package/app-template/src/views/header/header-currency-registrar.tsx +348 -0
- package/app-template/src/views/header/header-icons-context.tsx +262 -0
- package/app-template/src/views/header/header-language-registrar.tsx +348 -0
- package/app-template/src/views/header/header-layout-context.tsx +143 -0
- package/app-template/src/views/header/header-layout-registrar.tsx +658 -0
- package/app-template/src/views/header/header-logo-context.tsx +228 -0
- package/app-template/src/views/header/header-logo.tsx +118 -0
- package/app-template/src/views/header/header-mini-basket-context.tsx +524 -0
- package/app-template/src/views/header/header-search-registrar.tsx +511 -0
- package/app-template/src/views/header/header-text-slider-registrar.tsx +382 -0
- package/app-template/src/views/header/index.tsx +109 -47
- package/app-template/src/views/header/inline-search.tsx +262 -0
- package/app-template/src/views/header/mini-basket.tsx +819 -44
- package/app-template/src/views/header/mobile-hamburger-button.tsx +5 -8
- package/app-template/src/views/header/mobile-menu.tsx +12 -0
- package/app-template/src/views/header/navbar-menu-context.tsx +219 -0
- package/app-template/src/views/header/navbar.tsx +178 -111
- package/app-template/src/views/header/search/index.tsx +71 -32
- package/app-template/src/views/header/search/results.tsx +127 -65
- package/app-template/src/views/header/search/search-input.tsx +61 -0
- package/app-template/src/views/header/server-settings-parser.ts +1105 -0
- package/app-template/src/views/header/use-header-icons.ts +241 -0
- package/app-template/src/views/header/use-header-logo.ts +213 -0
- package/app-template/src/views/header/use-navbar-menu.ts +179 -0
- package/app-template/src/views/login/index.tsx +54 -46
- package/app-template/src/views/product/accordion-section.tsx +61 -0
- package/app-template/src/views/product/accordion-wrapper.tsx +135 -43
- package/app-template/src/views/product/custom-button-group.tsx +69 -0
- package/app-template/src/views/product/favorites-button-section.tsx +69 -0
- package/app-template/src/views/product/find-in-store-section.tsx +60 -0
- package/app-template/src/views/product/index.ts +1 -0
- package/app-template/src/views/product/layout.tsx +6 -5
- package/app-template/src/views/product/misc-buttons.tsx +339 -25
- package/app-template/src/views/product/price-wrapper.tsx +3 -29
- package/app-template/src/views/product/product-actions.tsx +137 -8
- package/app-template/src/views/product/product-info-section.tsx +140 -0
- package/app-template/src/views/product/product-info.tsx +69 -31
- package/app-template/src/views/product/product-share.tsx +13 -8
- package/app-template/src/views/product/product-variants.tsx +2 -2
- package/app-template/src/views/product/quantity-section.tsx +73 -0
- package/app-template/src/views/product/sale-tag.tsx +10 -0
- package/app-template/src/views/product/share-section.tsx +357 -0
- package/app-template/src/views/product/slider.tsx +117 -79
- package/app-template/src/views/product/variant.tsx +69 -41
- package/app-template/src/views/product/variants-section.tsx +126 -0
- package/app-template/src/views/product-detail/constants.ts +272 -0
- package/app-template/src/views/product-detail/index.ts +10 -0
- package/app-template/src/views/product-detail/product-detail-registrar.tsx +616 -0
- package/app-template/src/views/product-item/index.tsx +119 -46
- package/app-template/src/views/register/index.tsx +14 -25
- package/app-template/src/views/share/index.tsx +9 -6
- package/app-template/src/views/widgets/home-hero-slider-content.tsx +41 -39
- package/app-template/src/widgets/flatpages/about-us/index.tsx +78 -0
- package/app-template/src/widgets/flatpages/blog-list/index.tsx +129 -0
- package/app-template/src/widgets/footer-app-banner.tsx +444 -0
- package/app-template/src/widgets/footer-bottom.tsx +127 -0
- package/app-template/src/widgets/footer-menu-compact.tsx +238 -0
- package/app-template/src/widgets/footer-menu-two.tsx +298 -0
- package/app-template/src/widgets/footer-social-client.tsx +251 -0
- package/app-template/src/widgets/footer-social.tsx +47 -16
- package/app-template/src/widgets/footer-subscription/footer-subscription-form.tsx +17 -14
- package/app-template/src/widgets/footer-subscription/index.tsx +183 -17
- package/app-template/src/widgets/footer-value-props.tsx +201 -0
- package/app-template/src/widgets/index.ts +7 -0
- package/app-template/src/widgets/schemas/about-us.json +46 -0
- package/app-template/src/widgets/schemas/blog-list.json +37 -0
- package/app-template/src/widgets/schemas/blog.json +29 -0
- package/app-template/tailwind.config.js +18 -2
- package/package.json +1 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Fragment, CSSProperties } from 'react';
|
|
4
|
+
import { Icon, Link } from '@theme/components';
|
|
5
|
+
import { ROUTES } from '@theme/routes';
|
|
6
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
7
|
+
import { BreadcrumbResultType } from '@akinon/next/types';
|
|
8
|
+
import { capitalize } from '@akinon/next/utils';
|
|
9
|
+
import BreadcrumbRegistrar, { useBreadcrumb } from './breadcrumb-registrar';
|
|
10
|
+
import {
|
|
11
|
+
BREADCRUMB_SECTION_ID,
|
|
12
|
+
BREADCRUMB_PLACEHOLDER_ID,
|
|
13
|
+
BreadcrumbProperties
|
|
14
|
+
} from './constants';
|
|
15
|
+
|
|
16
|
+
export interface BreadcrumbClientProps {
|
|
17
|
+
breadcrumbList?: BreadcrumbResultType[];
|
|
18
|
+
initialSettings?: {
|
|
19
|
+
sectionStyles?: Record<string, unknown>;
|
|
20
|
+
properties?: BreadcrumbProperties;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Breadcrumb Client Component
|
|
26
|
+
*
|
|
27
|
+
* Displays a breadcrumb navigation with customizable styles via Theme Editor.
|
|
28
|
+
* Wraps content with BreadcrumbRegistrar for native widget support.
|
|
29
|
+
*/
|
|
30
|
+
export default function BreadcrumbClient(props: BreadcrumbClientProps) {
|
|
31
|
+
return (
|
|
32
|
+
<BreadcrumbRegistrar initialSettings={props.initialSettings}>
|
|
33
|
+
<BreadcrumbContent breadcrumbList={props.breadcrumbList} />
|
|
34
|
+
</BreadcrumbRegistrar>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function BreadcrumbContent(props: { breadcrumbList?: BreadcrumbResultType[] }) {
|
|
39
|
+
const { t, locale } = useLocalization();
|
|
40
|
+
const { breadcrumbList = [] } = props;
|
|
41
|
+
const { sectionStyles, properties, isBreadcrumbSectionSelected, isDesigner } =
|
|
42
|
+
useBreadcrumb();
|
|
43
|
+
|
|
44
|
+
// Handle click in designer mode - select section instead of navigating
|
|
45
|
+
const handleDesignerClick = (e: React.MouseEvent) => {
|
|
46
|
+
if (!isDesigner) return;
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
window.parent?.postMessage(
|
|
50
|
+
{
|
|
51
|
+
type: 'SELECT_SECTION',
|
|
52
|
+
data: {
|
|
53
|
+
placeholderId: BREADCRUMB_PLACEHOLDER_ID,
|
|
54
|
+
sectionId: BREADCRUMB_SECTION_ID
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
'*'
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const list = [
|
|
62
|
+
{ url: ROUTES.HOME, text: t('common.breadcrumb.homepage') },
|
|
63
|
+
...breadcrumbList.map((breadcrumb) => ({
|
|
64
|
+
url: breadcrumb.url,
|
|
65
|
+
text: breadcrumb.label
|
|
66
|
+
}))
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
// Helper to format style value with unit if needed
|
|
70
|
+
const formatStyleValue = (
|
|
71
|
+
value: unknown,
|
|
72
|
+
unit = 'px'
|
|
73
|
+
): string | undefined => {
|
|
74
|
+
if (value === undefined || value === null || value === '') return undefined;
|
|
75
|
+
// If already has unit (string like '17px'), return as is
|
|
76
|
+
if (typeof value === 'string' && value.includes(unit)) return value;
|
|
77
|
+
// If number or numeric string, add unit
|
|
78
|
+
return `${value}${unit}`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// Build CSS styles from section styles
|
|
82
|
+
const containerStyles: CSSProperties = {
|
|
83
|
+
fontSize: formatStyleValue(sectionStyles['font-size']),
|
|
84
|
+
fontWeight: sectionStyles['font-weight'] as CSSProperties['fontWeight'],
|
|
85
|
+
color: sectionStyles['color'] as string,
|
|
86
|
+
gap: formatStyleValue(sectionStyles['gap']),
|
|
87
|
+
paddingTop: formatStyleValue(sectionStyles['padding-top']),
|
|
88
|
+
paddingRight: formatStyleValue(sectionStyles['padding-right']),
|
|
89
|
+
paddingBottom: formatStyleValue(sectionStyles['padding-bottom']),
|
|
90
|
+
paddingLeft: formatStyleValue(sectionStyles['padding-left']),
|
|
91
|
+
backgroundColor: sectionStyles['background-color'] as string,
|
|
92
|
+
borderRadius: formatStyleValue(sectionStyles['border-radius'])
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Link styles
|
|
96
|
+
const linkStyles: CSSProperties = {
|
|
97
|
+
color: sectionStyles['link-color'] as string,
|
|
98
|
+
textDecoration: sectionStyles['link-text-decoration'] as string
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Separator styles
|
|
102
|
+
const separatorStyles: CSSProperties = {
|
|
103
|
+
color: sectionStyles['separator-color'] as string
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Separator icon from properties
|
|
107
|
+
const separatorIcon = (properties.separatorIcon as string) || 'chevron-end';
|
|
108
|
+
|
|
109
|
+
// Extract numeric value from separator-icon-size (may come as '10px' or 10)
|
|
110
|
+
const separatorIconSizeRaw = sectionStyles['separator-icon-size'];
|
|
111
|
+
const separatorIconSize =
|
|
112
|
+
typeof separatorIconSizeRaw === 'string'
|
|
113
|
+
? parseInt(separatorIconSizeRaw.replace(/[^0-9]/g, ''), 10) || 8
|
|
114
|
+
: Number(separatorIconSizeRaw) || 8;
|
|
115
|
+
|
|
116
|
+
// Home icon settings
|
|
117
|
+
const showHomeIcon = properties.showHomeIcon ?? false;
|
|
118
|
+
|
|
119
|
+
// Render separator based on type
|
|
120
|
+
const renderSeparator = () => {
|
|
121
|
+
const separatorStyle: CSSProperties = {
|
|
122
|
+
...separatorStyles,
|
|
123
|
+
fontSize: `${separatorIconSize}px`,
|
|
124
|
+
lineHeight: 1,
|
|
125
|
+
display: 'inline-flex',
|
|
126
|
+
alignItems: 'center',
|
|
127
|
+
justifyContent: 'center'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// Text-based separators
|
|
131
|
+
if (separatorIcon === 'slash') {
|
|
132
|
+
return <span style={separatorStyle}>/</span>;
|
|
133
|
+
}
|
|
134
|
+
if (separatorIcon === 'dot') {
|
|
135
|
+
return <span style={separatorStyle}>•</span>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Icon-based separators
|
|
139
|
+
return (
|
|
140
|
+
<span
|
|
141
|
+
style={{
|
|
142
|
+
display: 'var(--theme-breadcrumb-icon-display, inline-flex)',
|
|
143
|
+
...separatorStyles
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<Icon name={separatorIcon} size={separatorIconSize} />
|
|
147
|
+
</span>
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div
|
|
153
|
+
data-section-id={BREADCRUMB_SECTION_ID}
|
|
154
|
+
className="flex items-center leading-4"
|
|
155
|
+
onClick={handleDesignerClick}
|
|
156
|
+
style={{
|
|
157
|
+
fontSize: containerStyles.fontSize || '12px',
|
|
158
|
+
gap: containerStyles.gap || '12px',
|
|
159
|
+
fontWeight: containerStyles.fontWeight,
|
|
160
|
+
color: containerStyles.color,
|
|
161
|
+
paddingTop: containerStyles.paddingTop,
|
|
162
|
+
paddingRight: containerStyles.paddingRight,
|
|
163
|
+
paddingBottom: containerStyles.paddingBottom,
|
|
164
|
+
paddingLeft: containerStyles.paddingLeft,
|
|
165
|
+
backgroundColor: containerStyles.backgroundColor,
|
|
166
|
+
borderRadius: containerStyles.borderRadius,
|
|
167
|
+
outline: isBreadcrumbSectionSelected ? '2px solid #3b82f6' : undefined,
|
|
168
|
+
outlineOffset: isBreadcrumbSectionSelected ? '-2px' : undefined,
|
|
169
|
+
cursor: isDesigner ? 'pointer' : undefined
|
|
170
|
+
}}
|
|
171
|
+
>
|
|
172
|
+
{list.map((item, index) => (
|
|
173
|
+
<Fragment key={index}>
|
|
174
|
+
{index === 0 && showHomeIcon ? (
|
|
175
|
+
<Link href={item.url} style={linkStyles}>
|
|
176
|
+
<Icon name="home" size={14} />
|
|
177
|
+
</Link>
|
|
178
|
+
) : (
|
|
179
|
+
<Link href={item.url} style={linkStyles}>
|
|
180
|
+
{capitalize(item.text.toLocaleLowerCase(locale))}
|
|
181
|
+
</Link>
|
|
182
|
+
)}
|
|
183
|
+
{index !== list.length - 1 && renderSeparator()}
|
|
184
|
+
</Fragment>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export { BreadcrumbRegistrar, useBreadcrumb };
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Breadcrumb Section Registrar
|
|
5
|
+
*
|
|
6
|
+
* This component registers the "Breadcrumb" section as a native widget.
|
|
7
|
+
* The breadcrumb section allows customization of styles and appearance
|
|
8
|
+
* via the Theme Editor on list and product detail pages.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
createContext,
|
|
13
|
+
useContext,
|
|
14
|
+
useEffect,
|
|
15
|
+
useRef,
|
|
16
|
+
useState,
|
|
17
|
+
PropsWithChildren
|
|
18
|
+
} from 'react';
|
|
19
|
+
import {
|
|
20
|
+
registerPlaceholder,
|
|
21
|
+
unregisterPlaceholder
|
|
22
|
+
} from '@akinon/next/components/theme-editor/placeholder-registry';
|
|
23
|
+
import {
|
|
24
|
+
BREADCRUMB_PLACEHOLDER_ID,
|
|
25
|
+
BREADCRUMB_SECTION_ID,
|
|
26
|
+
BreadcrumbProperties
|
|
27
|
+
} from './constants';
|
|
28
|
+
|
|
29
|
+
// Re-export constants for external use
|
|
30
|
+
export {
|
|
31
|
+
BREADCRUMB_PLACEHOLDER_ID,
|
|
32
|
+
BREADCRUMB_SECTION_ID,
|
|
33
|
+
BREADCRUMB_WIDGET_SLUG
|
|
34
|
+
} from './constants';
|
|
35
|
+
export type { BreadcrumbProperties } from './constants';
|
|
36
|
+
|
|
37
|
+
// Global flag to track if registration has been done
|
|
38
|
+
declare global {
|
|
39
|
+
interface Window {
|
|
40
|
+
__breadcrumbRegistered?: boolean;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if running inside designer iframe
|
|
46
|
+
*/
|
|
47
|
+
function isInDesignerMode(): boolean {
|
|
48
|
+
if (typeof window === 'undefined') return false;
|
|
49
|
+
return window.self !== window.top;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ThemeSection {
|
|
53
|
+
id: string;
|
|
54
|
+
visible?: boolean;
|
|
55
|
+
properties?: Record<string, unknown>;
|
|
56
|
+
styles?: Record<string, unknown>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ThemePlaceholder {
|
|
60
|
+
slug: string;
|
|
61
|
+
sections: ThemeSection[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface BreadcrumbContextValue {
|
|
65
|
+
isDesigner: boolean;
|
|
66
|
+
isBreadcrumbSectionSelected: boolean;
|
|
67
|
+
properties: BreadcrumbProperties;
|
|
68
|
+
sectionStyles: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const BreadcrumbContext = createContext<BreadcrumbContextValue>({
|
|
72
|
+
isDesigner: false,
|
|
73
|
+
isBreadcrumbSectionSelected: false,
|
|
74
|
+
properties: {
|
|
75
|
+
separatorIcon: 'chevron-end',
|
|
76
|
+
showHomeIcon: false
|
|
77
|
+
},
|
|
78
|
+
sectionStyles: {}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export const useBreadcrumb = () => useContext(BreadcrumbContext);
|
|
82
|
+
|
|
83
|
+
interface BreadcrumbRegistrarProps extends PropsWithChildren {
|
|
84
|
+
/**
|
|
85
|
+
* Initial settings from server-side parsing (to avoid flash)
|
|
86
|
+
*/
|
|
87
|
+
initialSettings?: {
|
|
88
|
+
sectionStyles?: Record<string, unknown>;
|
|
89
|
+
properties?: BreadcrumbProperties;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* BreadcrumbRegistrar
|
|
95
|
+
*
|
|
96
|
+
* Registers the Breadcrumb native section with Theme Editor.
|
|
97
|
+
* This section allows customizing breadcrumb appearance including
|
|
98
|
+
* font size, color, separator icon, spacing, etc.
|
|
99
|
+
*/
|
|
100
|
+
export default function BreadcrumbRegistrar({
|
|
101
|
+
children,
|
|
102
|
+
initialSettings = {}
|
|
103
|
+
}: BreadcrumbRegistrarProps) {
|
|
104
|
+
const isDesignerRef = useRef(false);
|
|
105
|
+
const [sectionProperties, setSectionProperties] =
|
|
106
|
+
useState<BreadcrumbProperties>(
|
|
107
|
+
initialSettings.properties || {
|
|
108
|
+
separatorIcon: 'chevron-end',
|
|
109
|
+
showHomeIcon: false
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
const [sectionStyles, setSectionStyles] = useState<Record<string, unknown>>(
|
|
113
|
+
initialSettings.sectionStyles || {}
|
|
114
|
+
);
|
|
115
|
+
const [isBreadcrumbSelected, setIsBreadcrumbSelected] = useState(false);
|
|
116
|
+
const hasRegisteredNativeWidget = useRef(false);
|
|
117
|
+
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
isDesignerRef.current = isInDesignerMode();
|
|
120
|
+
}, []);
|
|
121
|
+
|
|
122
|
+
const isDesigner = isDesignerRef.current;
|
|
123
|
+
|
|
124
|
+
// Register native widget - only once on mount
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
const isInIframe =
|
|
127
|
+
typeof window !== 'undefined' && window.self !== window.top;
|
|
128
|
+
if (!isInIframe || !window.parent) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Register placeholder for save functionality
|
|
133
|
+
registerPlaceholder(BREADCRUMB_PLACEHOLDER_ID);
|
|
134
|
+
|
|
135
|
+
// Skip if already registered
|
|
136
|
+
if (
|
|
137
|
+
typeof window !== 'undefined' &&
|
|
138
|
+
window.__breadcrumbRegistered &&
|
|
139
|
+
hasRegisteredNativeWidget.current
|
|
140
|
+
) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Send native widget registration to Theme Editor
|
|
145
|
+
const nativeWidgetConfig = {
|
|
146
|
+
placeholderId: BREADCRUMB_PLACEHOLDER_ID,
|
|
147
|
+
autoAdd: true,
|
|
148
|
+
section: {
|
|
149
|
+
id: BREADCRUMB_SECTION_ID,
|
|
150
|
+
type: 'native',
|
|
151
|
+
label: 'Breadcrumb',
|
|
152
|
+
blocks: [],
|
|
153
|
+
properties: initialSettings.properties || {
|
|
154
|
+
separatorIcon: 'chevron-end',
|
|
155
|
+
showHomeIcon: false
|
|
156
|
+
},
|
|
157
|
+
styles: initialSettings.sectionStyles || {}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
window.parent.postMessage(
|
|
162
|
+
{
|
|
163
|
+
type: 'REGISTER_NATIVE_WIDGETS',
|
|
164
|
+
data: { widgets: [nativeWidgetConfig] }
|
|
165
|
+
},
|
|
166
|
+
'*'
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Mark as registered
|
|
170
|
+
window.__breadcrumbRegistered = true;
|
|
171
|
+
hasRegisteredNativeWidget.current = true;
|
|
172
|
+
|
|
173
|
+
// Cleanup: unregister placeholder on unmount
|
|
174
|
+
return () => {
|
|
175
|
+
unregisterPlaceholder(BREADCRUMB_PLACEHOLDER_ID);
|
|
176
|
+
};
|
|
177
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
178
|
+
}, []);
|
|
179
|
+
|
|
180
|
+
// Listen for theme updates and selection changes from Theme Editor
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (typeof window === 'undefined') return;
|
|
183
|
+
|
|
184
|
+
const handleMessage = (event: MessageEvent) => {
|
|
185
|
+
const { type, data } = event.data || {};
|
|
186
|
+
|
|
187
|
+
// Handle theme updates - styles come as { 'font-size': { desktop: 14 } }
|
|
188
|
+
if (
|
|
189
|
+
(type === 'UPDATE_THEME' || type === 'LOAD_THEME') &&
|
|
190
|
+
data?.theme?.placeholders
|
|
191
|
+
) {
|
|
192
|
+
const placeholder = data.theme.placeholders?.find(
|
|
193
|
+
(p: ThemePlaceholder) => p.slug === BREADCRUMB_PLACEHOLDER_ID
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const breadcrumbSection = placeholder?.sections?.find(
|
|
197
|
+
(s: ThemeSection) => s.id === BREADCRUMB_SECTION_ID
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
if (breadcrumbSection) {
|
|
201
|
+
// Update properties
|
|
202
|
+
if (breadcrumbSection.properties) {
|
|
203
|
+
setSectionProperties((prev) => ({
|
|
204
|
+
...prev,
|
|
205
|
+
...extractPropertiesValues(breadcrumbSection.properties)
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Update styles - extract from responsive format
|
|
210
|
+
if (breadcrumbSection.styles) {
|
|
211
|
+
const extractedStyles = extractStylesValues(
|
|
212
|
+
breadcrumbSection.styles as Record<string, unknown>
|
|
213
|
+
);
|
|
214
|
+
setSectionStyles(extractedStyles);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Handle section selection
|
|
220
|
+
if (type === 'SELECT_SECTION') {
|
|
221
|
+
setIsBreadcrumbSelected(
|
|
222
|
+
data?.placeholderId === BREADCRUMB_PLACEHOLDER_ID &&
|
|
223
|
+
data?.sectionId === BREADCRUMB_SECTION_ID
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
window.addEventListener('message', handleMessage);
|
|
229
|
+
return () => window.removeEventListener('message', handleMessage);
|
|
230
|
+
}, []);
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<BreadcrumbContext.Provider
|
|
234
|
+
value={{
|
|
235
|
+
isDesigner,
|
|
236
|
+
isBreadcrumbSectionSelected: isBreadcrumbSelected,
|
|
237
|
+
properties: sectionProperties,
|
|
238
|
+
sectionStyles
|
|
239
|
+
}}
|
|
240
|
+
>
|
|
241
|
+
{children}
|
|
242
|
+
</BreadcrumbContext.Provider>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Extract property values handling responsive format
|
|
248
|
+
*/
|
|
249
|
+
function extractPropertiesValues(
|
|
250
|
+
properties: Record<string, unknown>
|
|
251
|
+
): BreadcrumbProperties {
|
|
252
|
+
const result: Record<string, unknown> = {};
|
|
253
|
+
|
|
254
|
+
Object.entries(properties).forEach(([key, value]) => {
|
|
255
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
256
|
+
// Responsive format: { desktop: value, mobile: value }
|
|
257
|
+
const obj = value as Record<string, unknown>;
|
|
258
|
+
result[key] = obj.desktop ?? obj.mobile ?? Object.values(obj)[0];
|
|
259
|
+
} else {
|
|
260
|
+
result[key] = value;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return result as BreadcrumbProperties;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Extract style values handling responsive format
|
|
269
|
+
*/
|
|
270
|
+
function extractStylesValues(
|
|
271
|
+
styles: Record<string, unknown>
|
|
272
|
+
): Record<string, unknown> {
|
|
273
|
+
const result: Record<string, unknown> = {};
|
|
274
|
+
|
|
275
|
+
Object.entries(styles).forEach(([key, value]) => {
|
|
276
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
277
|
+
// Responsive format: { desktop: value, mobile: value }
|
|
278
|
+
const obj = value as Record<string, unknown>;
|
|
279
|
+
result[key] = obj.desktop ?? obj.mobile ?? Object.values(obj)[0];
|
|
280
|
+
} else {
|
|
281
|
+
result[key] = value;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return result;
|
|
286
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breadcrumb Constants
|
|
3
|
+
*
|
|
4
|
+
* Shared constants for breadcrumb native widget.
|
|
5
|
+
* This file can be imported by both server and client components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const BREADCRUMB_PLACEHOLDER_ID = 'breadcrumb';
|
|
9
|
+
export const BREADCRUMB_SECTION_ID = 'breadcrumb-section';
|
|
10
|
+
export const BREADCRUMB_WIDGET_SLUG = 'breadcrumb-styles';
|
|
11
|
+
|
|
12
|
+
export interface BreadcrumbProperties {
|
|
13
|
+
separatorIcon?: string;
|
|
14
|
+
showHomeIcon?: boolean;
|
|
15
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
|
|
3
|
+
import { getWidgetData } from '@akinon/next/data/server/widget';
|
|
4
|
+
import { BreadcrumbResultType } from '@akinon/next/types';
|
|
5
|
+
import BreadcrumbClient from './breadcrumb-client';
|
|
6
|
+
import {
|
|
7
|
+
BREADCRUMB_SECTION_ID,
|
|
8
|
+
BREADCRUMB_WIDGET_SLUG,
|
|
9
|
+
BreadcrumbProperties
|
|
10
|
+
} from './constants';
|
|
11
|
+
|
|
12
|
+
export interface BreadcrumbProps {
|
|
13
|
+
breadcrumbList?: BreadcrumbResultType[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BreadcrumbSettings {
|
|
17
|
+
sectionStyles?: Record<string, unknown>;
|
|
18
|
+
properties?: BreadcrumbProperties;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract responsive value from theme editor format
|
|
23
|
+
* { desktop: value, mobile: value } -> value
|
|
24
|
+
*/
|
|
25
|
+
function extractResponsiveValue(value: unknown): unknown {
|
|
26
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
27
|
+
const obj = value as Record<string, unknown>;
|
|
28
|
+
return obj.desktop ?? obj.mobile ?? Object.values(obj)[0];
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse breadcrumb settings from widget data (server-side)
|
|
35
|
+
*/
|
|
36
|
+
function parseServerBreadcrumbSettings(
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
widgetData: any
|
|
39
|
+
): BreadcrumbSettings {
|
|
40
|
+
const result: BreadcrumbSettings = {};
|
|
41
|
+
|
|
42
|
+
if (!widgetData?.attributes) {
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const attrs = widgetData.attributes;
|
|
47
|
+
|
|
48
|
+
// Parse section-level styles and properties
|
|
49
|
+
const sectionData = attrs[BREADCRUMB_SECTION_ID];
|
|
50
|
+
if (sectionData) {
|
|
51
|
+
try {
|
|
52
|
+
const data =
|
|
53
|
+
typeof sectionData === 'string'
|
|
54
|
+
? JSON.parse(sectionData)
|
|
55
|
+
: typeof sectionData === 'object' && 'value' in sectionData
|
|
56
|
+
? JSON.parse((sectionData as { value: string }).value)
|
|
57
|
+
: null;
|
|
58
|
+
|
|
59
|
+
if (data) {
|
|
60
|
+
// Parse properties
|
|
61
|
+
if (data.properties) {
|
|
62
|
+
const properties: BreadcrumbProperties = {};
|
|
63
|
+
|
|
64
|
+
if (data.properties.separatorIcon !== undefined) {
|
|
65
|
+
properties.separatorIcon = extractResponsiveValue(
|
|
66
|
+
data.properties.separatorIcon
|
|
67
|
+
) as string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (data.properties.showHomeIcon !== undefined) {
|
|
71
|
+
const showHomeIcon = extractResponsiveValue(
|
|
72
|
+
data.properties.showHomeIcon
|
|
73
|
+
);
|
|
74
|
+
properties.showHomeIcon =
|
|
75
|
+
showHomeIcon === 'true' || Boolean(showHomeIcon);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
result.properties = properties;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Parse styles
|
|
82
|
+
if (data.styles) {
|
|
83
|
+
const styles: Record<string, unknown> = {};
|
|
84
|
+
|
|
85
|
+
Object.entries(data.styles).forEach(([key, value]) => {
|
|
86
|
+
styles[key] = extractResponsiveValue(value);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
result.sectionStyles = styles;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// Parsing failed, return empty result
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Breadcrumb Server Component
|
|
102
|
+
*
|
|
103
|
+
* Fetches widget settings server-side and passes to client component
|
|
104
|
+
* to avoid flash of unstyled content on page load.
|
|
105
|
+
*/
|
|
106
|
+
export default async function Breadcrumb({ breadcrumbList }: BreadcrumbProps) {
|
|
107
|
+
// Fetch breadcrumb widget settings server-side
|
|
108
|
+
const widgetData = await getWidgetData({
|
|
109
|
+
slug: BREADCRUMB_WIDGET_SLUG
|
|
110
|
+
}).catch(() => null);
|
|
111
|
+
|
|
112
|
+
const initialSettings = parseServerBreadcrumbSettings(widgetData);
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<BreadcrumbClient
|
|
116
|
+
breadcrumbList={breadcrumbList}
|
|
117
|
+
initialSettings={initialSettings}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Re-export client components for direct use
|
|
123
|
+
export { default as BreadcrumbClient } from './breadcrumb-client';
|
|
124
|
+
export {
|
|
125
|
+
default as BreadcrumbRegistrar,
|
|
126
|
+
useBreadcrumb
|
|
127
|
+
} from './breadcrumb-registrar';
|
|
@@ -1,38 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export default function Breadcrumb(props: BreadcrumbProps) {
|
|
15
|
-
const { t, locale } = useLocalization();
|
|
16
|
-
const { breadcrumbList = [] } = props;
|
|
17
|
-
|
|
18
|
-
const list = [
|
|
19
|
-
{ url: ROUTES.HOME, text: t('common.breadcrumb.homepage') },
|
|
20
|
-
...breadcrumbList.map((breadcrumb) => ({
|
|
21
|
-
url: breadcrumb.url,
|
|
22
|
-
text: breadcrumb.label
|
|
23
|
-
}))
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div className="flex items-center gap-3 text-xs leading-4 text-gray-950">
|
|
28
|
-
{list.map((item, index) => (
|
|
29
|
-
<Fragment key={index}>
|
|
30
|
-
<Link href={item.url}>
|
|
31
|
-
{capitalize(item.text.toLocaleLowerCase(locale))}
|
|
32
|
-
</Link>
|
|
33
|
-
{index !== list.length - 1 && <Icon name="chevron-end" size={8} />}
|
|
34
|
-
</Fragment>
|
|
35
|
-
))}
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Breadcrumb Component
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the Breadcrumb component from the breadcrumb folder.
|
|
5
|
+
* This file is kept for backwards compatibility with existing imports.
|
|
6
|
+
*/
|
|
7
|
+
export { default } from './breadcrumb/index';
|
|
8
|
+
export type { BreadcrumbProps } from './breadcrumb/index';
|
|
9
|
+
export {
|
|
10
|
+
BreadcrumbRegistrar,
|
|
11
|
+
useBreadcrumb,
|
|
12
|
+
BreadcrumbClient
|
|
13
|
+
} from './breadcrumb/index';
|