@underverse-ui/underverse 0.1.11 → 0.1.12

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/dist/index.cjs CHANGED
@@ -71,6 +71,8 @@ __export(index_exports, {
71
71
  Input: () => Input_default,
72
72
  InteractiveBadge: () => InteractiveBadge,
73
73
  Label: () => Label,
74
+ LanguageSwitcher: () => LanguageSwitcherHeadless,
75
+ LanguageSwitcherHeadless: () => LanguageSwitcherHeadless,
74
76
  LoadingBar: () => LoadingBar,
75
77
  LoadingDots: () => LoadingDots,
76
78
  LoadingSpinner: () => LoadingSpinner,
@@ -111,6 +113,8 @@ __export(index_exports, {
111
113
  Tabs: () => Tabs,
112
114
  TagBadge: () => TagBadge,
113
115
  Textarea: () => Textarea_default,
116
+ ThemeToggle: () => ThemeToggleHeadless,
117
+ ThemeToggleHeadless: () => ThemeToggleHeadless,
114
118
  ToastProvider: () => Toast_default,
115
119
  Tooltip: () => Tooltip,
116
120
  VARIANT_STYLES_ALERT: () => VARIANT_STYLES_ALERT,
@@ -8605,6 +8609,199 @@ function AccessDenied({
8605
8609
  ] }) });
8606
8610
  }
8607
8611
 
8612
+ // ../../components/ui/ThemeToggleHeadless.tsx
8613
+ var import_lucide_react24 = require("lucide-react");
8614
+ var import_react24 = require("react");
8615
+ var import_react_dom9 = require("react-dom");
8616
+ var import_jsx_runtime43 = require("react/jsx-runtime");
8617
+ function ThemeToggleHeadless({
8618
+ theme,
8619
+ onChange,
8620
+ labels,
8621
+ className
8622
+ }) {
8623
+ const [isOpen, setIsOpen] = (0, import_react24.useState)(false);
8624
+ const [mounted, setMounted] = (0, import_react24.useState)(false);
8625
+ const triggerRef = (0, import_react24.useRef)(null);
8626
+ const [dropdownPosition, setDropdownPosition] = (0, import_react24.useState)(null);
8627
+ (0, import_react24.useEffect)(() => setMounted(true), []);
8628
+ const themes = [
8629
+ { value: "light", label: labels?.light ?? "Light", icon: import_lucide_react24.Sun },
8630
+ { value: "dark", label: labels?.dark ?? "Dark", icon: import_lucide_react24.Moon },
8631
+ { value: "system", label: labels?.system ?? "System", icon: import_lucide_react24.Monitor }
8632
+ ];
8633
+ const current = mounted ? themes.find((t) => t.value === theme) || themes[2] : themes[2];
8634
+ const CurrentIcon = current.icon;
8635
+ const calculatePosition = () => {
8636
+ const rect = triggerRef.current?.getBoundingClientRect();
8637
+ if (!rect) return null;
8638
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
8639
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
8640
+ const width = 192;
8641
+ const left = rect.right + scrollLeft - width;
8642
+ const top = rect.bottom + scrollTop + 8;
8643
+ return { top, left, width };
8644
+ };
8645
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: cn("relative", className), children: [
8646
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
8647
+ Button_default,
8648
+ {
8649
+ variant: "ghost",
8650
+ size: "icon",
8651
+ ref: triggerRef,
8652
+ onClick: () => {
8653
+ const next = !isOpen;
8654
+ if (next) {
8655
+ const pos = calculatePosition();
8656
+ if (pos) setDropdownPosition(pos);
8657
+ }
8658
+ setIsOpen(next);
8659
+ },
8660
+ className: "bg-muted hover:bg-accent",
8661
+ "aria-haspopup": "menu",
8662
+ "aria-expanded": isOpen,
8663
+ "aria-label": labels?.heading ?? "Theme",
8664
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(CurrentIcon, { className: "h-5 w-5" })
8665
+ }
8666
+ ),
8667
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(import_jsx_runtime43.Fragment, { children: [
8668
+ typeof window !== "undefined" && (0, import_react_dom9.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "fixed inset-0 z-[9998]", onClick: () => setIsOpen(false) }), document.body),
8669
+ typeof window !== "undefined" && dropdownPosition && (0, import_react_dom9.createPortal)(
8670
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(
8671
+ "div",
8672
+ {
8673
+ className: "z-[9999] bg-card border border-border rounded-lg shadow-lg overflow-hidden",
8674
+ style: { position: "absolute", top: dropdownPosition.top, left: dropdownPosition.left, width: dropdownPosition.width },
8675
+ onMouseDown: (e) => e.stopPropagation(),
8676
+ role: "menu",
8677
+ children: /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)("div", { className: "p-2", children: [
8678
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "px-3 py-2 text-sm font-medium text-muted-foreground border-b border-border mb-2", children: labels?.heading ?? "Theme" }),
8679
+ themes.map((opt) => {
8680
+ const Icon = opt.icon;
8681
+ const active = theme === opt.value;
8682
+ return /* @__PURE__ */ (0, import_jsx_runtime43.jsxs)(
8683
+ Button_default,
8684
+ {
8685
+ variant: "ghost",
8686
+ size: "sm",
8687
+ onClick: () => {
8688
+ onChange(opt.value);
8689
+ setIsOpen(false);
8690
+ },
8691
+ className: cn(
8692
+ "w-full justify-start gap-3 h-auto py-2 px-3",
8693
+ active && "bg-primary/10 text-primary"
8694
+ ),
8695
+ role: "menuitemradio",
8696
+ "aria-checked": active,
8697
+ children: [
8698
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)(Icon, { className: "h-4 w-4" }),
8699
+ /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("span", { className: "flex-1 text-left", children: opt.label }),
8700
+ active && /* @__PURE__ */ (0, import_jsx_runtime43.jsx)("div", { className: "w-2 h-2 rounded-full bg-primary" })
8701
+ ]
8702
+ },
8703
+ opt.value
8704
+ );
8705
+ })
8706
+ ] })
8707
+ }
8708
+ ),
8709
+ document.body
8710
+ )
8711
+ ] })
8712
+ ] });
8713
+ }
8714
+
8715
+ // ../../components/ui/LanguageSwitcherHeadless.tsx
8716
+ var import_react25 = require("react");
8717
+ var import_react_dom10 = require("react-dom");
8718
+ var import_lucide_react25 = require("lucide-react");
8719
+ var import_jsx_runtime44 = require("react/jsx-runtime");
8720
+ function LanguageSwitcherHeadless({
8721
+ locales,
8722
+ currentLocale,
8723
+ onSwitch,
8724
+ labels,
8725
+ className
8726
+ }) {
8727
+ const [isOpen, setIsOpen] = (0, import_react25.useState)(false);
8728
+ const [dropdownPosition, setDropdownPosition] = (0, import_react25.useState)(null);
8729
+ const triggerButtonRef = (0, import_react25.useRef)(null);
8730
+ const currentLanguage = locales.find((l) => l.code === currentLocale) || locales[0];
8731
+ const calculatePosition = () => {
8732
+ const rect = triggerButtonRef.current?.getBoundingClientRect();
8733
+ if (!rect) return null;
8734
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
8735
+ const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
8736
+ const width = 192;
8737
+ const left = rect.right + scrollLeft - width;
8738
+ const top = rect.bottom + scrollTop + 8;
8739
+ return { top, left, width };
8740
+ };
8741
+ return /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: cn("relative", className), children: [
8742
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
8743
+ Button_default,
8744
+ {
8745
+ variant: "ghost",
8746
+ size: "icon",
8747
+ ref: triggerButtonRef,
8748
+ onClick: () => {
8749
+ const next = !isOpen;
8750
+ if (next) {
8751
+ const pos = calculatePosition();
8752
+ if (pos) setDropdownPosition(pos);
8753
+ }
8754
+ setIsOpen(next);
8755
+ },
8756
+ className: "bg-muted hover:bg-accent",
8757
+ "aria-haspopup": "menu",
8758
+ "aria-expanded": isOpen,
8759
+ "aria-label": labels?.heading ?? "Language",
8760
+ title: labels?.heading ?? "Language",
8761
+ children: /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(import_lucide_react25.Globe, { className: "h-5 w-5" })
8762
+ }
8763
+ ),
8764
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(import_jsx_runtime44.Fragment, { children: [
8765
+ typeof window !== "undefined" && (0, import_react_dom10.createPortal)(/* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "fixed inset-0 z-[9998]", onClick: () => setIsOpen(false) }), document.body),
8766
+ typeof window !== "undefined" && dropdownPosition && (0, import_react_dom10.createPortal)(
8767
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)(
8768
+ "div",
8769
+ {
8770
+ className: "z-[9999] bg-card border border-border rounded-lg shadow-lg overflow-hidden",
8771
+ style: { position: "absolute", top: dropdownPosition.top, left: dropdownPosition.left, width: dropdownPosition.width },
8772
+ onMouseDown: (e) => e.stopPropagation(),
8773
+ role: "menu",
8774
+ children: /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)("div", { className: "p-2", children: [
8775
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "px-3 py-2 text-sm font-medium text-muted-foreground border-b border-border mb-2", children: labels?.heading ?? "Language" }),
8776
+ locales.map((language) => /* @__PURE__ */ (0, import_jsx_runtime44.jsxs)(
8777
+ Button_default,
8778
+ {
8779
+ variant: "ghost",
8780
+ size: "sm",
8781
+ onClick: () => {
8782
+ onSwitch(language.code);
8783
+ setIsOpen(false);
8784
+ },
8785
+ className: cn("w-full justify-start gap-3 h-auto py-2 px-3", currentLocale === language.code && "bg-primary/10 text-primary"),
8786
+ role: "menuitemradio",
8787
+ "aria-checked": currentLocale === language.code,
8788
+ children: [
8789
+ language.flag && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "text-lg", children: language.flag }),
8790
+ /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("span", { className: "flex-1 text-left", children: language.name }),
8791
+ currentLocale === language.code && /* @__PURE__ */ (0, import_jsx_runtime44.jsx)("div", { className: "w-2 h-2 rounded-full bg-primary" })
8792
+ ]
8793
+ },
8794
+ language.code
8795
+ ))
8796
+ ] })
8797
+ }
8798
+ ),
8799
+ document.body
8800
+ )
8801
+ ] })
8802
+ ] });
8803
+ }
8804
+
8608
8805
  // locales/en.json
8609
8806
  var en_default = {
8610
8807
  Common: {
@@ -8779,6 +8976,8 @@ function getUnderverseMessages(locale = "en") {
8779
8976
  Input,
8780
8977
  InteractiveBadge,
8781
8978
  Label,
8979
+ LanguageSwitcher,
8980
+ LanguageSwitcherHeadless,
8782
8981
  LoadingBar,
8783
8982
  LoadingDots,
8784
8983
  LoadingSpinner,
@@ -8819,6 +9018,8 @@ function getUnderverseMessages(locale = "en") {
8819
9018
  Tabs,
8820
9019
  TagBadge,
8821
9020
  Textarea,
9021
+ ThemeToggle,
9022
+ ThemeToggleHeadless,
8822
9023
  ToastProvider,
8823
9024
  Tooltip,
8824
9025
  VARIANT_STYLES_ALERT,