@akinon/pz-theme 2.0.38-rc.0 → 2.0.39-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.
@@ -0,0 +1,172 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { useLocalization } from '@akinon/next/hooks';
5
+ import { getResponsiveValue, partAttrs } from '../utils';
6
+ import { BlockRendererProps } from './block-renderer-registry';
7
+ import { readToggle } from './product-block-utils';
8
+
9
+ const clampNumber = (
10
+ value: unknown,
11
+ fallback: number,
12
+ min: number,
13
+ max: number
14
+ ) => {
15
+ const numeric = Math.round(Number(value));
16
+ if (!Number.isFinite(numeric)) return fallback;
17
+ return Math.min(max, Math.max(min, numeric));
18
+ };
19
+
20
+ /**
21
+ * Functional chrome block: locale switcher rendered as inline codes
22
+ * (TR | EN) or a select. Locales come from the storefront settings; the
23
+ * editor owns placement, wording, separator and styling.
24
+ * Properties: mode, separator, showSeparator, uppercase, labelSource, gap,
25
+ * ariaLabel, emptyText.
26
+ * Parts: option-list, option (selected/hover), separator, dropdown.
27
+ */
28
+ const LanguageSelectorBlock = ({
29
+ block,
30
+ currentBreakpoint = 'desktop',
31
+ isDesigner = false
32
+ }: BlockRendererProps) => {
33
+ const { locale, locales, setLocale } = useLocalization();
34
+
35
+ const mode = String(
36
+ getResponsiveValue(block.properties?.mode, currentBreakpoint, 'inline')
37
+ );
38
+ const separator = String(
39
+ getResponsiveValue(block.properties?.separator, currentBreakpoint, '|')
40
+ );
41
+ const uppercase =
42
+ String(
43
+ getResponsiveValue(block.properties?.uppercase, currentBreakpoint, 'true')
44
+ ) !== 'false';
45
+ const labelSource = String(
46
+ getResponsiveValue(
47
+ block.properties?.labelSource,
48
+ currentBreakpoint,
49
+ 'label'
50
+ )
51
+ );
52
+ const ariaLabel = String(
53
+ getResponsiveValue(
54
+ block.properties?.ariaLabel,
55
+ currentBreakpoint,
56
+ 'Language'
57
+ )
58
+ );
59
+ // Empty on purpose: the editor placeholder has always been built from the
60
+ // separator, so an unset override must keep doing exactly that.
61
+ const emptyText = String(
62
+ getResponsiveValue(block.properties?.emptyText, currentBreakpoint, '')
63
+ );
64
+ const gap = clampNumber(
65
+ getResponsiveValue(block.properties?.gap, currentBreakpoint, 6),
66
+ 6,
67
+ 0,
68
+ 32
69
+ );
70
+ const showSeparator = readToggle(
71
+ block.properties?.showSeparator,
72
+ currentBreakpoint,
73
+ true
74
+ );
75
+
76
+ const items = Array.isArray(locales) ? locales : [];
77
+
78
+ const format = (value: string) =>
79
+ uppercase ? String(value).toUpperCase() : String(value);
80
+
81
+ const readLabel = (item: { label?: string; value?: string }): string =>
82
+ labelSource === 'code'
83
+ ? String(item.value ?? '')
84
+ : String(item.label || item.value || '');
85
+
86
+ const change = (value: string) => {
87
+ if (isDesigner || value === locale) return;
88
+ setLocale?.(value);
89
+ };
90
+
91
+ if (items.length === 0) {
92
+ return isDesigner ? (
93
+ <span
94
+ data-language-selector=""
95
+ {...partAttrs('option-list', { empty: true })}
96
+ >
97
+ {emptyText || `TR ${separator} EN`}
98
+ </span>
99
+ ) : null;
100
+ }
101
+
102
+ if (mode === 'select') {
103
+ return (
104
+ <select
105
+ data-language-selector=""
106
+ {...partAttrs('dropdown')}
107
+ value={locale}
108
+ onChange={(event) => change(event.target.value)}
109
+ aria-label={ariaLabel}
110
+ style={{
111
+ background: 'transparent',
112
+ border: 'none',
113
+ color: 'inherit',
114
+ cursor: 'pointer',
115
+ outline: 'none'
116
+ }}
117
+ >
118
+ {items.map((item) => (
119
+ <option key={item.value} value={item.value}>
120
+ {format(readLabel(item))}
121
+ </option>
122
+ ))}
123
+ </select>
124
+ );
125
+ }
126
+
127
+ return (
128
+ <span
129
+ data-language-selector=""
130
+ {...partAttrs('option-list')}
131
+ style={{
132
+ display: 'inline-flex',
133
+ alignItems: 'center',
134
+ gap: `${gap}px`
135
+ }}
136
+ >
137
+ {items.map((item, index) => {
138
+ const active = item.value === locale;
139
+
140
+ return (
141
+ <React.Fragment key={item.value}>
142
+ {index > 0 && showSeparator && (
143
+ <span aria-hidden="true" {...partAttrs('separator')}>
144
+ {separator}
145
+ </span>
146
+ )}
147
+ <button
148
+ type="button"
149
+ onClick={() => change(item.value)}
150
+ aria-current={active}
151
+ data-language-option={active ? 'selected' : 'default'}
152
+ {...partAttrs('option', { selected: active })}
153
+ style={{
154
+ background: 'none',
155
+ border: 'none',
156
+ padding: 0,
157
+ cursor: 'pointer',
158
+ color: 'inherit',
159
+ opacity: active ? 1 : 0.6,
160
+ fontWeight: active ? 600 : 400
161
+ }}
162
+ >
163
+ {format(readLabel(item))}
164
+ </button>
165
+ </React.Fragment>
166
+ );
167
+ })}
168
+ </span>
169
+ );
170
+ };
171
+
172
+ export default LanguageSelectorBlock;