@tuwaio/docs-ui 0.5.0 → 0.5.2

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,89 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * Returns a locally embedded SVG flag node for a given locale code.
6
+ *
7
+ * Supported built-in codes:
8
+ * - English: `'en'`, `'en-US'`, `'en-GB'`
9
+ * - Ukrainian: `'uk'`, `'ua'`
10
+ * - Chinese: `'zh'`, `'zh-CN'`, `'zh-Hans'`
11
+ *
12
+ * @param code - The BCP-47 or ISO language tag (e.g. `'en'`, `'uk'`, `'zh-CN'`).
13
+ * @returns The SVG icon node or `undefined` if no built-in flag is available.
14
+ */
15
+ declare function getLocaleFlag(code: string): ReactNode;
16
+ /**
17
+ * A supported locale option for {@link LocaleSwitcher}.
18
+ */
19
+ interface LocaleOption {
20
+ /** The language tag or locale code, for example `'en'`, `'uk'`, or `'zh-CN'`. */
21
+ code: string;
22
+ /** Full native name of the language, for example `'English'`, `'Українська'`, or `'简体中文'`. */
23
+ label: string;
24
+ /** Short label displayed on the compact trigger button, for example `'EN'`, `'UK'`, or `'ZH'`. */
25
+ short?: string;
26
+ /** Optional custom flag element. If omitted, built-in vector flags are used. */
27
+ flag?: ReactNode;
28
+ }
29
+ /**
30
+ * Default list of supported locales across the TUWA documentation ecosystem.
31
+ */
32
+ declare const DEFAULT_LOCALES: readonly LocaleOption[];
33
+ /**
34
+ * Props of {@link LocaleSwitcher}.
35
+ */
36
+ interface LocaleSwitcherProps {
37
+ /** The currently active locale code (e.g. `'en'`, `'uk'`). */
38
+ locale: string;
39
+ /** Callback fired when a new locale is selected by the user. */
40
+ onSelect: (code: string) => void;
41
+ /** The list of available locales. Defaults to {@link DEFAULT_LOCALES}. */
42
+ locales?: readonly LocaleOption[] | LocaleOption[];
43
+ /** Preferred placement of the dropdown menu (`'auto'`, `'bottom'`, or `'top'`). Defaults to `'auto'`. */
44
+ placement?: 'auto' | 'bottom' | 'top';
45
+ /** Optional additional CSS classes for the root container. */
46
+ className?: string;
47
+ /** Accessible name for the button and listbox. Defaults to `'Select language'`. */
48
+ ariaLabel?: string;
49
+ /**
50
+ * Whether to render an invisible native `<select>` overlay on mobile touch devices (`pointer: coarse`).
51
+ * When `true`, tapping on iOS or Android summons the native system wheel/sheet picker.
52
+ * Defaults to `true`.
53
+ */
54
+ nativeTouch?: boolean;
55
+ }
56
+ /**
57
+ * A glassmorphic language select dropdown matching the visual footprint, theme variables, and interaction
58
+ * standards of `ThemeSwitcher`.
59
+ *
60
+ * ### Architectural Features:
61
+ * - **Zero Layout Shift:** Rigid static dimensions (`w-[5.25rem] md:w-[4.75rem]`) and fixed-width label slots
62
+ * prevent any layout reflow or navigation jumping when switching between language codes of varying character widths.
63
+ * - **Nova Motion Language:** Chevron arrow utilizes directional vertical translation (±3px), vertical scale
64
+ * compression (`scaleY(0.8)`), and opacity curves matching Nova UI Kit's `ChevronArrowWithAnim`.
65
+ * - **Flush Edge Indicator:** Replaced traditional checkboxes with a flush, zero-margin left-edge blue accent bar
66
+ * and glowing status indicator, cleanly abutting the dropdown boundary.
67
+ * - **Collision Detection & Dynamic Placement:** When configured with `'auto'` placement, dynamically calculates
68
+ * available viewport clearance below the trigger button. If rendered inside a mobile sheet, drawer, or footer
69
+ * with insufficient bottom clearance, it automatically opens upwards (`bottom-full`).
70
+ * - **Layout-Independent Latin Typeahead (WCAG 2.1.1):** Normalizes keystrokes through physical `event.code`
71
+ * to ensure keys like `E`, `U`, and `Z` reliably navigate to English, Ukrainian, and Chinese regardless of the user's
72
+ * active OS keyboard layout.
73
+ * - **Native Touch Device Support (`pointer: coarse`):** On mobile devices, an invisible native `<select>` overlay
74
+ * activates, triggering the operating system's native wheel picker (iOS Safari) or bottom sheet dialog (Android Chrome)
75
+ * with full system haptics and touch fluidness while retaining the custom visual trigger design.
76
+ * - **WCAG 2.1 & 2.2 AA/AAA Compliance:**
77
+ * - WCAG 2.5.3 (Label in Name): The accessible name of the button begins with the visible label text.
78
+ * - WCAG 3.1.2 (Language of Parts): Every option specifies its native `lang` attribute for screen readers.
79
+ * - WCAG 2.4.7 (Focus Visible): High-contrast focus rings with distinct offset colors.
80
+ * - WCAG 2.3.3 (Animation from Interactions): `motion-reduce:transition-none` disables transitions for sensitive users.
81
+ * - WCAG 4.1.2 (Name, Role, Value): WAI-ARIA `listbox`, `option`, `aria-activedescendant`, and `aria-selected`.
82
+ *
83
+ * @param props - Component properties (see {@link LocaleSwitcherProps}).
84
+ * @returns The rendered locale switcher trigger button, touch overlay, and dropdown menu.
85
+ */
86
+ declare function LocaleSwitcher({ locale, onSelect, locales, placement: placementProp, className, ariaLabel, nativeTouch, }: LocaleSwitcherProps): react.JSX.Element;
87
+
88
+ export { DEFAULT_LOCALES, LocaleSwitcher, getLocaleFlag };
89
+ export type { LocaleOption, LocaleSwitcherProps };
@@ -0,0 +1,448 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { cn } from "@tuwaio/nova-core/utils";
4
+ import { useEffect, useId, useRef, useState } from "react";
5
+ function FlagUK({ className }) {
6
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 640 480", className, "aria-hidden": "true", children: [
7
+ /* @__PURE__ */ jsx("path", { fill: "#012169", d: "M0 0h640v480H0z" }),
8
+ /* @__PURE__ */ jsx("path", { fill: "#FFF", d: "m75 0 245 180L565 0h75v60L435 240l205 180v60h-75L320 300 75 480H0v-60l205-180L0 60V0h75z" }),
9
+ /* @__PURE__ */ jsx(
10
+ "path",
11
+ {
12
+ fill: "#C8102E",
13
+ d: "m425 285 215 165v30h-35L380 300l45-15zM215 195 0 30v-30h35l225 180-45 15zm0 90L0 450v30h35l225-180-45-15zm210-90L640 30v-30h-35L380 180l45 15z"
14
+ }
15
+ ),
16
+ /* @__PURE__ */ jsx("path", { fill: "#FFF", d: "M250 0h140v480H250zM0 170h640v140H0z" }),
17
+ /* @__PURE__ */ jsx("path", { fill: "#C8102E", d: "M276 0h88v480h-88zM0 196h640v88H0z" })
18
+ ] });
19
+ }
20
+ function FlagUkraine({ className }) {
21
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 640 480", className, "aria-hidden": "true", children: [
22
+ /* @__PURE__ */ jsx("path", { fill: "#0057b7", d: "M0 0h640v240H0z" }),
23
+ /* @__PURE__ */ jsx("path", { fill: "#ffd700", d: "M0 240h640v240H0z" })
24
+ ] });
25
+ }
26
+ function FlagChina({ className }) {
27
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 640 480", className, "aria-hidden": "true", children: [
28
+ /* @__PURE__ */ jsx("rect", { width: "640", height: "480", fill: "#ee1c25" }),
29
+ /* @__PURE__ */ jsx(
30
+ "path",
31
+ {
32
+ fill: "#ffde00",
33
+ d: "M120 72l14.1 43.4H180l-36.9 26.8 14.1 43.4-37.2-26.8-37.2 26.8 14.1-43.4L60 115.4h45.9z"
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx("circle", { cx: "200", cy: "40", r: "12", fill: "#ffde00" }),
37
+ /* @__PURE__ */ jsx("circle", { cx: "240", cy: "80", r: "12", fill: "#ffde00" }),
38
+ /* @__PURE__ */ jsx("circle", { cx: "240", cy: "140", r: "12", fill: "#ffde00" }),
39
+ /* @__PURE__ */ jsx("circle", { cx: "200", cy: "180", r: "12", fill: "#ffde00" })
40
+ ] });
41
+ }
42
+ function ChevronArrowWithAnim({ isOpen, className }) {
43
+ return /* @__PURE__ */ jsxs(
44
+ "span",
45
+ {
46
+ className: cn(
47
+ "tuwadocs:relative tuwadocs:inline-flex tuwadocs:h-3.5 tuwadocs:w-3.5 tuwadocs:items-center tuwadocs:justify-center tuwadocs:overflow-hidden tuwadocs:shrink-0",
48
+ className
49
+ ),
50
+ "aria-hidden": "true",
51
+ children: [
52
+ /* @__PURE__ */ jsx(
53
+ "svg",
54
+ {
55
+ viewBox: "0 0 24 24",
56
+ fill: "none",
57
+ strokeWidth: 2,
58
+ stroke: "currentColor",
59
+ className: cn(
60
+ "tuwadocs:absolute tuwadocs:h-3.5 tuwadocs:w-3.5 tuwadocs:transition-all tuwadocs:duration-400 tuwadocs:ease-out tuwadocs:motion-reduce:transition-none tuwadocs:motion-reduce:transform-none",
61
+ isOpen ? "tuwadocs:opacity-0 -tuwadocs:translate-y-[3px] tuwadocs:scale-y-[0.8] tuwadocs:pointer-events-none" : "tuwadocs:opacity-100 tuwadocs:translate-y-0 tuwadocs:scale-y-100"
62
+ ),
63
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "m19.5 8.25-7.5 7.5-7.5-7.5" })
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsx(
67
+ "svg",
68
+ {
69
+ viewBox: "0 0 24 24",
70
+ fill: "none",
71
+ strokeWidth: 2,
72
+ stroke: "currentColor",
73
+ className: cn(
74
+ "tuwadocs:absolute tuwadocs:h-3.5 tuwadocs:w-3.5 tuwadocs:transition-all tuwadocs:duration-400 tuwadocs:ease-out tuwadocs:motion-reduce:transition-none tuwadocs:motion-reduce:transform-none",
75
+ isOpen ? "tuwadocs:opacity-100 tuwadocs:translate-y-0 tuwadocs:scale-y-100" : "tuwadocs:opacity-0 tuwadocs:translate-y-[3px] tuwadocs:scale-y-[0.8] tuwadocs:pointer-events-none"
76
+ ),
77
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "m4.5 15.75 7.5-7.5 7.5 7.5" })
78
+ }
79
+ )
80
+ ]
81
+ }
82
+ );
83
+ }
84
+ function getLocaleFlag(code) {
85
+ const normalized = code.toLowerCase();
86
+ const flagClass = "tuwadocs:h-3 tuwadocs:w-4 tuwadocs:rounded-[2px] tuwadocs:object-cover tuwadocs:shadow-xs tuwadocs:border tuwadocs:border-black/10 tuwadocs:dark:border-white/20 tuwadocs:shrink-0";
87
+ if (normalized === "en" || normalized === "en-us" || normalized === "en-gb") {
88
+ return /* @__PURE__ */ jsx(FlagUK, { className: flagClass });
89
+ }
90
+ if (normalized === "uk" || normalized === "ua") {
91
+ return /* @__PURE__ */ jsx(FlagUkraine, { className: flagClass });
92
+ }
93
+ if (normalized === "zh" || normalized === "zh-cn" || normalized === "zh-hans") {
94
+ return /* @__PURE__ */ jsx(FlagChina, { className: flagClass });
95
+ }
96
+ return void 0;
97
+ }
98
+ const DEFAULT_LOCALES = [
99
+ { code: "en", label: "English", short: "EN" },
100
+ { code: "uk", label: "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430", short: "UK" },
101
+ { code: "zh-CN", label: "\u7B80\u4F53\u4E2D\u6587", short: "ZH" }
102
+ ];
103
+ function resolveLatinChar(event) {
104
+ if (event.ctrlKey || event.metaKey || event.altKey) {
105
+ return null;
106
+ }
107
+ if (/^[a-zA-Z]$/.test(event.key)) {
108
+ return event.key.toLowerCase();
109
+ }
110
+ if (event.code && event.code.startsWith("Key")) {
111
+ return event.code.slice(3).toLowerCase();
112
+ }
113
+ return null;
114
+ }
115
+ function matchesLocaleOption(item, char) {
116
+ const code = item.code.toLowerCase();
117
+ const short = (item.short ?? "").toLowerCase();
118
+ const label = item.label.toLowerCase();
119
+ if (code.startsWith(char) || short.startsWith(char) || label.startsWith(char)) {
120
+ return true;
121
+ }
122
+ if ((code.startsWith("zh") || short === "zh") && (char === "z" || char === "c")) {
123
+ return true;
124
+ }
125
+ if ((code.startsWith("uk") || code.startsWith("ua") || short === "uk" || short === "ua") && char === "u") {
126
+ return true;
127
+ }
128
+ if ((code.startsWith("es") || short === "es") && char === "s") {
129
+ return true;
130
+ }
131
+ if ((code.startsWith("de") || short === "de") && char === "g") {
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ function LocaleSwitcher({
137
+ locale,
138
+ onSelect,
139
+ locales = DEFAULT_LOCALES,
140
+ placement: placementProp = "auto",
141
+ className,
142
+ ariaLabel = "Select language",
143
+ nativeTouch = true
144
+ }) {
145
+ const [mounted, setMounted] = useState(false);
146
+ const [isOpen, setIsOpen] = useState(false);
147
+ const [activeIndex, setActiveIndex] = useState(0);
148
+ const [resolvedPlacement, setResolvedPlacement] = useState("bottom");
149
+ const [isTouch, setIsTouch] = useState(false);
150
+ const listboxId = useId();
151
+ const containerRef = useRef(null);
152
+ const buttonRef = useRef(null);
153
+ const listRef = useRef(null);
154
+ useEffect(() => {
155
+ setMounted(true);
156
+ if (typeof window === "undefined") return;
157
+ const query = window.matchMedia("(pointer: coarse)");
158
+ const updateTouch = () => {
159
+ setIsTouch(query.matches && !window.matchMedia("(pointer: fine)").matches);
160
+ };
161
+ updateTouch();
162
+ query.addEventListener?.("change", updateTouch);
163
+ return () => {
164
+ query.removeEventListener?.("change", updateTouch);
165
+ };
166
+ }, []);
167
+ const updatePlacement = () => {
168
+ if (placementProp !== "auto") {
169
+ setResolvedPlacement(placementProp);
170
+ return;
171
+ }
172
+ if (buttonRef.current) {
173
+ const rect = buttonRef.current.getBoundingClientRect();
174
+ const spaceBelow = window.innerHeight - rect.bottom;
175
+ const spaceAbove = rect.top;
176
+ const estimatedHeight = Math.min(locales.length * 40, 260) + 16;
177
+ setResolvedPlacement(spaceBelow < estimatedHeight && spaceAbove > spaceBelow ? "top" : "bottom");
178
+ }
179
+ };
180
+ const openMenu = () => {
181
+ if (isTouch) return;
182
+ updatePlacement();
183
+ const currentIndex = locales.findIndex((item) => item.code === locale);
184
+ setActiveIndex(currentIndex >= 0 ? currentIndex : 0);
185
+ setIsOpen(true);
186
+ };
187
+ const closeMenu = () => {
188
+ setIsOpen(false);
189
+ buttonRef.current?.focus();
190
+ };
191
+ const pickLocale = (code) => {
192
+ closeMenu();
193
+ if (code !== locale) {
194
+ onSelect(code);
195
+ }
196
+ };
197
+ useEffect(() => {
198
+ if (!isOpen) return;
199
+ listRef.current?.focus();
200
+ const onPointerDown = (event) => {
201
+ if (!containerRef.current?.contains(event.target)) {
202
+ setIsOpen(false);
203
+ }
204
+ };
205
+ document.addEventListener("pointerdown", onPointerDown);
206
+ return () => {
207
+ document.removeEventListener("pointerdown", onPointerDown);
208
+ };
209
+ }, [isOpen]);
210
+ useEffect(() => {
211
+ if (!isOpen) return;
212
+ const activeEl = document.getElementById(`${listboxId}-option-${locales[activeIndex]?.code}`);
213
+ activeEl?.scrollIntoView({ block: "nearest" });
214
+ }, [activeIndex, isOpen, listboxId, locales]);
215
+ const handleKeyDown = (event) => {
216
+ const last = locales.length - 1;
217
+ if (event.key === "ArrowDown") {
218
+ event.preventDefault();
219
+ if (!isOpen) {
220
+ openMenu();
221
+ } else {
222
+ setActiveIndex((prev) => prev < last ? prev + 1 : 0);
223
+ }
224
+ return;
225
+ }
226
+ if (event.key === "ArrowUp") {
227
+ event.preventDefault();
228
+ if (!isOpen) {
229
+ openMenu();
230
+ } else {
231
+ setActiveIndex((prev) => prev > 0 ? prev - 1 : last);
232
+ }
233
+ return;
234
+ }
235
+ if (event.key === "Home" && isOpen) {
236
+ event.preventDefault();
237
+ setActiveIndex(0);
238
+ return;
239
+ }
240
+ if (event.key === "End" && isOpen) {
241
+ event.preventDefault();
242
+ setActiveIndex(last);
243
+ return;
244
+ }
245
+ if (event.key === "Enter" || event.key === " ") {
246
+ event.preventDefault();
247
+ if (!isOpen) {
248
+ openMenu();
249
+ } else {
250
+ const targetOption = locales[activeIndex];
251
+ if (targetOption) {
252
+ pickLocale(targetOption.code);
253
+ }
254
+ }
255
+ return;
256
+ }
257
+ if (event.key === "Escape") {
258
+ if (isOpen) {
259
+ event.preventDefault();
260
+ event.stopPropagation();
261
+ closeMenu();
262
+ }
263
+ return;
264
+ }
265
+ if (event.key === "Tab") {
266
+ if (isOpen) {
267
+ closeMenu();
268
+ }
269
+ return;
270
+ }
271
+ const char = resolveLatinChar(event);
272
+ if (char) {
273
+ const matchAfter = locales.findIndex((item, idx) => idx > activeIndex && matchesLocaleOption(item, char));
274
+ const matchFromStart = locales.findIndex((item) => matchesLocaleOption(item, char));
275
+ const targetIndex = matchAfter >= 0 ? matchAfter : matchFromStart;
276
+ if (targetIndex >= 0) {
277
+ event.preventDefault();
278
+ if (!isOpen) {
279
+ updatePlacement();
280
+ setActiveIndex(targetIndex);
281
+ setIsOpen(true);
282
+ } else {
283
+ setActiveIndex(targetIndex);
284
+ }
285
+ }
286
+ }
287
+ };
288
+ const defaultStyles = "tuwadocs:bg-black/5 tuwadocs:border-black/5 tuwadocs:hover:bg-black/10 tuwadocs:dark:bg-white/10 tuwadocs:dark:border-white/10 tuwadocs:dark:hover:bg-white/20";
289
+ const staticSize = "tuwadocs:h-10 tuwadocs:w-[5.25rem] tuwadocs:md:h-8 tuwadocs:md:w-[4.75rem]";
290
+ if (!mounted) {
291
+ return /* @__PURE__ */ jsx(
292
+ "div",
293
+ {
294
+ className: cn(
295
+ "tuwadocs:relative tuwadocs:flex tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:opacity-0",
296
+ staticSize,
297
+ defaultStyles,
298
+ className
299
+ )
300
+ }
301
+ );
302
+ }
303
+ const activeOption = locales.find((item) => item.code === locale) ?? locales[0];
304
+ const shortText = activeOption?.short ?? activeOption?.code?.slice(0, 2).toUpperCase() ?? "EN";
305
+ const activeFlag = activeOption?.flag ?? getLocaleFlag(activeOption?.code ?? "en");
306
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("tuwadocs:relative tuwadocs:inline-block", className), children: [
307
+ nativeTouch && /* @__PURE__ */ jsx(
308
+ "select",
309
+ {
310
+ value: locale,
311
+ onChange: (event) => pickLocale(event.target.value),
312
+ "aria-label": ariaLabel,
313
+ tabIndex: isTouch ? 0 : -1,
314
+ "aria-hidden": !isTouch,
315
+ className: "tuwadocs:absolute tuwadocs:inset-0 tuwadocs:z-10 tuwadocs:h-full tuwadocs:w-full tuwadocs:cursor-pointer tuwadocs:appearance-none tuwadocs:border-none tuwadocs:bg-transparent tuwadocs:opacity-0 tuwadocs:outline-none tuwadocs:hidden tuwadocs:pointer-coarse:block tuwadocs:pointer-fine:hidden",
316
+ children: locales.map((item) => /* @__PURE__ */ jsxs("option", { value: item.code, lang: item.code, children: [
317
+ item.label,
318
+ " (",
319
+ item.short ?? item.code.toUpperCase(),
320
+ ")"
321
+ ] }, item.code))
322
+ }
323
+ ),
324
+ /* @__PURE__ */ jsxs(
325
+ "button",
326
+ {
327
+ ref: buttonRef,
328
+ type: "button",
329
+ tabIndex: isTouch ? -1 : 0,
330
+ "aria-hidden": isTouch ? true : void 0,
331
+ onClick: () => isOpen ? closeMenu() : openMenu(),
332
+ onKeyDown: handleKeyDown,
333
+ className: cn(
334
+ "tuwadocs:group tuwadocs:relative tuwadocs:flex tuwadocs:cursor-pointer tuwadocs:items-center tuwadocs:justify-between tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:px-2 tuwadocs:transition-all tuwadocs:duration-300 tuwadocs:ease-in-out tuwadocs:backdrop-blur-sm tuwadocs:border tuwadocs:motion-reduce:transition-none",
335
+ "tuwadocs:focus-visible:ring-2 tuwadocs:focus-visible:ring-[var(--tuwa-text-accent)] tuwadocs:focus-visible:outline-none tuwadocs:focus-visible:ring-offset-1 tuwadocs:focus-visible:ring-offset-[var(--tuwa-bg-primary)]",
336
+ staticSize,
337
+ defaultStyles
338
+ ),
339
+ "aria-haspopup": "listbox",
340
+ "aria-expanded": isOpen,
341
+ "aria-controls": isOpen ? listboxId : void 0,
342
+ children: [
343
+ /* @__PURE__ */ jsx(
344
+ "span",
345
+ {
346
+ "aria-hidden": "true",
347
+ className: "tuwadocs:flex tuwadocs:items-center tuwadocs:justify-center tuwadocs:shrink-0",
348
+ children: activeFlag
349
+ }
350
+ ),
351
+ /* @__PURE__ */ jsx("span", { className: "tuwadocs:w-5 tuwadocs:text-center tuwadocs:font-mono tuwadocs:text-xs tuwadocs:font-bold tuwadocs:tracking-tight tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:shrink-0", children: shortText }),
352
+ /* @__PURE__ */ jsxs("span", { className: "tuwadocs:sr-only", children: [
353
+ ", ",
354
+ activeOption.label,
355
+ " \u2014 ",
356
+ ariaLabel
357
+ ] }),
358
+ /* @__PURE__ */ jsx(
359
+ ChevronArrowWithAnim,
360
+ {
361
+ isOpen,
362
+ className: "tuwadocs:text-[var(--tuwa-text-secondary)] tuwadocs:group-hover:text-[var(--tuwa-text-primary)]"
363
+ }
364
+ )
365
+ ]
366
+ }
367
+ ),
368
+ isOpen && /* @__PURE__ */ jsx(
369
+ "div",
370
+ {
371
+ ref: listRef,
372
+ id: listboxId,
373
+ role: "listbox",
374
+ tabIndex: -1,
375
+ "aria-label": ariaLabel,
376
+ "aria-activedescendant": `${listboxId}-option-${locales[activeIndex]?.code}`,
377
+ onKeyDown: handleKeyDown,
378
+ className: cn(
379
+ "tuwadocs:absolute tuwadocs:right-0 tuwadocs:z-50 tuwadocs:min-w-[12rem] tuwadocs:max-h-[min(20rem,60vh)] tuwadocs:overflow-y-auto tuwadocs:overflow-x-hidden tuwadocs:rounded-[var(--tuwa-rounded-corners)] tuwadocs:border tuwadocs:border-[var(--tuwa-border-primary)] tuwadocs:bg-[var(--tuwa-bg-primary)]/95 tuwadocs:p-0 tuwadocs:shadow-xl tuwadocs:backdrop-blur-md tuwadocs:outline-none",
380
+ resolvedPlacement === "top" ? "tuwadocs:bottom-full tuwadocs:mb-1.5" : "tuwadocs:top-full tuwadocs:mt-1.5"
381
+ ),
382
+ children: locales.map((item, i) => {
383
+ const isSelected = item.code === locale;
384
+ const isFocused = i === activeIndex;
385
+ const flag = item.flag ?? getLocaleFlag(item.code);
386
+ return /* @__PURE__ */ jsxs(
387
+ "button",
388
+ {
389
+ id: `${listboxId}-option-${item.code}`,
390
+ type: "button",
391
+ role: "option",
392
+ lang: item.code,
393
+ tabIndex: -1,
394
+ "aria-selected": isSelected,
395
+ onClick: () => pickLocale(item.code),
396
+ onPointerMove: () => {
397
+ if (activeIndex !== i) setActiveIndex(i);
398
+ },
399
+ className: cn(
400
+ "tuwadocs:relative tuwadocs:flex tuwadocs:w-full tuwadocs:cursor-pointer tuwadocs:items-center tuwadocs:justify-between tuwadocs:gap-2.5 tuwadocs:px-3.5 tuwadocs:py-2.5 tuwadocs:text-xs tuwadocs:transition-colors tuwadocs:outline-none",
401
+ "tuwadocs:focus-visible:ring-1 tuwadocs:focus-visible:ring-inset tuwadocs:focus-visible:ring-[var(--tuwa-text-accent)]",
402
+ isSelected ? isFocused ? "tuwadocs:bg-gradient-to-r tuwadocs:from-[var(--tuwa-text-accent)]/25 tuwadocs:via-[var(--tuwa-text-accent)]/10 tuwadocs:to-transparent tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:font-bold" : "tuwadocs:bg-gradient-to-r tuwadocs:from-[var(--tuwa-text-accent)]/15 tuwadocs:via-[var(--tuwa-text-accent)]/5 tuwadocs:to-transparent tuwadocs:text-[var(--tuwa-text-primary)] tuwadocs:font-bold" : isFocused ? "tuwadocs:bg-[var(--tuwa-bg-muted)]/70 tuwadocs:text-[var(--tuwa-text-primary)]" : "tuwadocs:text-[var(--tuwa-text-secondary)] tuwadocs:hover:bg-[var(--tuwa-bg-muted)]/50 tuwadocs:hover:text-[var(--tuwa-text-primary)]"
403
+ ),
404
+ children: [
405
+ isSelected && /* @__PURE__ */ jsx(
406
+ "span",
407
+ {
408
+ className: "tuwadocs:absolute tuwadocs:left-0 tuwadocs:top-0 tuwadocs:bottom-0 tuwadocs:w-[3px] tuwadocs:bg-[var(--tuwa-text-accent)] tuwadocs:shadow-[0_0_8px_var(--tuwa-text-accent)]",
409
+ "aria-hidden": "true"
410
+ }
411
+ ),
412
+ /* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:items-center tuwadocs:gap-2 tuwadocs:truncate", children: [
413
+ flag && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "tuwadocs:shrink-0", children: flag }),
414
+ /* @__PURE__ */ jsx("span", { className: "tuwadocs:truncate", children: item.label })
415
+ ] }),
416
+ /* @__PURE__ */ jsxs("div", { className: "tuwadocs:flex tuwadocs:items-center tuwadocs:gap-1.5 tuwadocs:shrink-0", children: [
417
+ /* @__PURE__ */ jsx(
418
+ "span",
419
+ {
420
+ className: cn(
421
+ "tuwadocs:font-mono tuwadocs:text-[10px] tuwadocs:uppercase tuwadocs:px-1.5 tuwadocs:py-0.5 tuwadocs:rounded",
422
+ isSelected ? "tuwadocs:bg-[var(--tuwa-text-accent)]/15 tuwadocs:text-[var(--tuwa-text-accent)] tuwadocs:font-bold" : "tuwadocs:text-[var(--tuwa-text-tertiary)]"
423
+ ),
424
+ children: item.short ?? item.code
425
+ }
426
+ ),
427
+ isSelected && /* @__PURE__ */ jsx(
428
+ "span",
429
+ {
430
+ className: "tuwadocs:h-1.5 tuwadocs:w-1.5 tuwadocs:rounded-full tuwadocs:bg-[var(--tuwa-text-accent)] tuwadocs:shadow-[0_0_8px_var(--tuwa-text-accent)]",
431
+ "aria-hidden": "true"
432
+ }
433
+ )
434
+ ] })
435
+ ]
436
+ },
437
+ item.code
438
+ );
439
+ })
440
+ }
441
+ )
442
+ ] });
443
+ }
444
+ export {
445
+ DEFAULT_LOCALES,
446
+ LocaleSwitcher,
447
+ getLocaleFlag
448
+ };
@@ -34,7 +34,7 @@ module.exports = __toCommonJS(Logo_exports);
34
34
  var import_jsx_runtime = require("react/jsx-runtime");
35
35
  var import_html_react_parser = __toESM(require("html-react-parser"), 1);
36
36
  var import_react = __toESM(require("react"), 1);
37
- const LOGO_URL = "https://cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/logo_v2.svg";
37
+ const LOGO_URL = "https://tuwa.io/brand/logo_v2.svg";
38
38
  async function RemoteLogo({ url = LOGO_URL, className, style, ...props }) {
39
39
  let rawSvg;
40
40
  try {
@@ -61,7 +61,13 @@ async function RemoteLogo({ url = LOGO_URL, className, style, ...props }) {
61
61
  delete attribs.xmlspace;
62
62
  }
63
63
  const svgClassName = [className, "remote-logo"].filter(Boolean).join(" ");
64
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...attribs, className: svgClassName, style, ...props, children });
64
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...attribs, className: svgClassName, style, ...props, children: [
65
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("linearGradient", { id: "tuwa-dark-logo-gradient", x1: "0", y1: "0", x2: "0", y2: "1", children: [
66
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "0", stopColor: "#7cf3ff" }),
67
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: "1", stopColor: "#22c3e6" })
68
+ ] }) }),
69
+ children
70
+ ] });
65
71
  }
66
72
  }
67
73
  });
@@ -1,10 +1,10 @@
1
- import React__default from 'react';
1
+ import react__default from 'react';
2
2
 
3
3
  /**
4
4
  * Props of {@link RemoteLogo}: the SVG attributes are applied to the root `svg` element.
5
5
  */
6
- interface RemoteLogoProps extends React__default.SVGProps<SVGSVGElement> {
7
- /** URL of the SVG (default: the TUWA logo on jsDelivr, `cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/logo_v2.svg`) */
6
+ interface RemoteLogoProps extends react__default.SVGProps<SVGSVGElement> {
7
+ /** URL of the SVG (default: the TUWA logo, `https://tuwa.io/brand/logo_v2.svg`) */
8
8
  url?: string;
9
9
  }
10
10
  /**
@@ -19,7 +19,7 @@ interface RemoteLogoProps extends React__default.SVGProps<SVGSVGElement> {
19
19
  * @param props - See {@link RemoteLogoProps}.
20
20
  * @returns The logo as an `svg` element.
21
21
  */
22
- declare function RemoteLogo({ url, className, style, ...props }: RemoteLogoProps): Promise<string | React__default.JSX.Element>;
22
+ declare function RemoteLogo({ url, className, style, ...props }: RemoteLogoProps): Promise<string | react__default.JSX.Element>;
23
23
 
24
24
  export { RemoteLogo };
25
25
  export type { RemoteLogoProps };
@@ -1,10 +1,10 @@
1
- import React__default from 'react';
1
+ import react__default from 'react';
2
2
 
3
3
  /**
4
4
  * Props of {@link RemoteLogo}: the SVG attributes are applied to the root `svg` element.
5
5
  */
6
- interface RemoteLogoProps extends React__default.SVGProps<SVGSVGElement> {
7
- /** URL of the SVG (default: the TUWA logo on jsDelivr, `cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/logo_v2.svg`) */
6
+ interface RemoteLogoProps extends react__default.SVGProps<SVGSVGElement> {
7
+ /** URL of the SVG (default: the TUWA logo, `https://tuwa.io/brand/logo_v2.svg`) */
8
8
  url?: string;
9
9
  }
10
10
  /**
@@ -19,7 +19,7 @@ interface RemoteLogoProps extends React__default.SVGProps<SVGSVGElement> {
19
19
  * @param props - See {@link RemoteLogoProps}.
20
20
  * @returns The logo as an `svg` element.
21
21
  */
22
- declare function RemoteLogo({ url, className, style, ...props }: RemoteLogoProps): Promise<string | React__default.JSX.Element>;
22
+ declare function RemoteLogo({ url, className, style, ...props }: RemoteLogoProps): Promise<string | react__default.JSX.Element>;
23
23
 
24
24
  export { RemoteLogo };
25
25
  export type { RemoteLogoProps };
@@ -1,7 +1,7 @@
1
- import { jsx } from "react/jsx-runtime";
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import parse, { domToReact, Element } from "html-react-parser";
3
3
  import React from "react";
4
- const LOGO_URL = "https://cdn.jsdelivr.net/gh/TuwaIO/workflows@main/preview/logo_v2.svg";
4
+ const LOGO_URL = "https://tuwa.io/brand/logo_v2.svg";
5
5
  async function RemoteLogo({ url = LOGO_URL, className, style, ...props }) {
6
6
  let rawSvg;
7
7
  try {
@@ -28,7 +28,13 @@ async function RemoteLogo({ url = LOGO_URL, className, style, ...props }) {
28
28
  delete attribs.xmlspace;
29
29
  }
30
30
  const svgClassName = [className, "remote-logo"].filter(Boolean).join(" ");
31
- return /* @__PURE__ */ jsx("svg", { ...attribs, className: svgClassName, style, ...props, children });
31
+ return /* @__PURE__ */ jsxs("svg", { ...attribs, className: svgClassName, style, ...props, children: [
32
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: "tuwa-dark-logo-gradient", x1: "0", y1: "0", x2: "0", y2: "1", children: [
33
+ /* @__PURE__ */ jsx("stop", { offset: "0", stopColor: "#7cf3ff" }),
34
+ /* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "#22c3e6" })
35
+ ] }) }),
36
+ children
37
+ ] });
32
38
  }
33
39
  }
34
40
  });
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  /**
@@ -30,7 +30,7 @@ interface NavProps {
30
30
  * @param props - See {@link NavProps}.
31
31
  * @returns The navbar.
32
32
  */
33
- declare function Navbar({ links, logo }: NavProps): React.JSX.Element;
33
+ declare function Navbar({ links, logo }: NavProps): react.JSX.Element;
34
34
 
35
35
  export { Navbar };
36
36
  export type { NavProps, SocialLink };
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  /**
@@ -30,7 +30,7 @@ interface NavProps {
30
30
  * @param props - See {@link NavProps}.
31
31
  * @returns The navbar.
32
32
  */
33
- declare function Navbar({ links, logo }: NavProps): React.JSX.Element;
33
+ declare function Navbar({ links, logo }: NavProps): react.JSX.Element;
34
34
 
35
35
  export { Navbar };
36
36
  export type { NavProps, SocialLink };