@enderfall/ui 0.1.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.
Files changed (50) hide show
  1. package/dist/components/AccessGate.d.ts +16 -0
  2. package/dist/components/AccessGate.d.ts.map +1 -0
  3. package/dist/components/AccessGate.js +9 -0
  4. package/dist/components/Button.d.ts +7 -0
  5. package/dist/components/Button.d.ts.map +1 -0
  6. package/dist/components/Button.js +2 -0
  7. package/dist/components/Dropdown.d.ts +88 -0
  8. package/dist/components/Dropdown.d.ts.map +1 -0
  9. package/dist/components/Dropdown.js +179 -0
  10. package/dist/components/FloatingFooter.d.ts +10 -0
  11. package/dist/components/FloatingFooter.d.ts.map +1 -0
  12. package/dist/components/FloatingFooter.js +6 -0
  13. package/dist/components/FormField.d.ts +12 -0
  14. package/dist/components/FormField.d.ts.map +1 -0
  15. package/dist/components/FormField.js +2 -0
  16. package/dist/components/Input.d.ts +12 -0
  17. package/dist/components/Input.d.ts.map +1 -0
  18. package/dist/components/Input.js +5 -0
  19. package/dist/components/MainHeader.d.ts +15 -0
  20. package/dist/components/MainHeader.d.ts.map +1 -0
  21. package/dist/components/MainHeader.js +6 -0
  22. package/dist/components/Modal.d.ts +16 -0
  23. package/dist/components/Modal.d.ts.map +1 -0
  24. package/dist/components/Modal.js +82 -0
  25. package/dist/components/Panel.d.ts +9 -0
  26. package/dist/components/Panel.d.ts.map +1 -0
  27. package/dist/components/Panel.js +15 -0
  28. package/dist/components/PreferencesModal.d.ts +19 -0
  29. package/dist/components/PreferencesModal.d.ts.map +1 -0
  30. package/dist/components/PreferencesModal.js +17 -0
  31. package/dist/components/SideMenu.d.ts +28 -0
  32. package/dist/components/SideMenu.d.ts.map +1 -0
  33. package/dist/components/SideMenu.js +144 -0
  34. package/dist/components/Slider.d.ts +5 -0
  35. package/dist/components/Slider.d.ts.map +1 -0
  36. package/dist/components/Slider.js +20 -0
  37. package/dist/components/StackedCard.d.ts +29 -0
  38. package/dist/components/StackedCard.d.ts.map +1 -0
  39. package/dist/components/StackedCard.js +31 -0
  40. package/dist/components/StatDots.d.ts +12 -0
  41. package/dist/components/StatDots.d.ts.map +1 -0
  42. package/dist/components/StatDots.js +18 -0
  43. package/dist/components/Toggle.d.ts +9 -0
  44. package/dist/components/Toggle.d.ts.map +1 -0
  45. package/dist/components/Toggle.js +4 -0
  46. package/dist/index.d.ts +26 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +33 -0
  49. package/package.json +30 -0
  50. package/styles.css +17 -0
@@ -0,0 +1,16 @@
1
+ type AccessGateProps = {
2
+ status: "checking" | "locked" | "allowed";
3
+ titleChecking?: string;
4
+ titleLocked?: string;
5
+ messageChecking?: string;
6
+ messageLocked?: string;
7
+ primaryLabel: string;
8
+ secondaryLabel?: string;
9
+ onPrimary: () => void;
10
+ onSecondary?: () => void;
11
+ primaryClassName?: string;
12
+ secondaryClassName?: string;
13
+ };
14
+ export declare const AccessGate: ({ status, titleChecking, titleLocked, messageChecking, messageLocked, primaryLabel, secondaryLabel, onPrimary, onSecondary, primaryClassName, secondaryClassName, }: AccessGateProps) => import("react/jsx-runtime").JSX.Element | null;
15
+ export {};
16
+ //# sourceMappingURL=AccessGate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccessGate.d.ts","sourceRoot":"","sources":["../../src/components/AccessGate.tsx"],"names":[],"mappings":"AAGA,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,qKAYxB,eAAe,mDA+BjB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "./Button";
3
+ import { Panel } from "./Panel";
4
+ export const AccessGate = ({ status, titleChecking = "Checking access...", titleLocked = "App locked", messageChecking = "Verifying your access with Enderfall Hub.", messageLocked = "Open Enderfall Hub to verify premium or admin access.", primaryLabel, secondaryLabel, onPrimary, onSecondary, primaryClassName = "primary", secondaryClassName = "ghost", }) => {
5
+ if (status === "allowed")
6
+ return null;
7
+ const isChecking = status === "checking";
8
+ return (_jsx("div", { className: "ef-access-overlay", children: _jsxs(Panel, { variant: "card", borderWidth: 2, className: "ef-access-card", children: [_jsx("div", { className: "ef-access-title", children: isChecking ? titleChecking : titleLocked }), _jsx("p", { children: isChecking ? messageChecking : messageLocked }), _jsxs("div", { className: "ef-access-actions", children: [_jsx(Button, { type: "button", className: primaryClassName, onClick: onPrimary, disabled: isChecking, children: primaryLabel }), secondaryLabel && onSecondary ? (_jsx(Button, { type: "button", className: secondaryClassName, onClick: onSecondary, disabled: isChecking, children: secondaryLabel })) : null] })] }) }));
9
+ };
@@ -0,0 +1,7 @@
1
+ import type { ButtonHTMLAttributes } from "react";
2
+ type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
3
+ variant?: "primary" | "ghost" | "locked" | "danger" | "delete" | "warning" | "info" | "success";
4
+ };
5
+ export declare const Button: ({ variant, className, ...props }: ButtonProps) => import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=Button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Button.d.ts","sourceRoot":"","sources":["../../src/components/Button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAElD,KAAK,WAAW,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,GAAG;IAC3D,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;CACjG,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,kCAA8C,WAAW,4CAK/E,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const Button = ({ variant = "primary", className, ...props }) => (_jsx("button", { ...props, className: ["ef-button", variant, className].filter(Boolean).join(" ") }));
@@ -0,0 +1,88 @@
1
+ import { type ReactNode } from "react";
2
+ export type HeaderMenuItem = {
3
+ id: string;
4
+ label: string;
5
+ content: ReactNode;
6
+ };
7
+ export type DropdownUserItem = {
8
+ id?: string;
9
+ label: string;
10
+ onClick: () => void;
11
+ disabled?: boolean;
12
+ className?: string;
13
+ title?: string;
14
+ variant?: "default" | "theme-preview";
15
+ };
16
+ export type DropdownUserListItem = {
17
+ id?: string;
18
+ label: string;
19
+ onClick: () => void;
20
+ onEdit?: () => void;
21
+ onDelete?: () => void;
22
+ disabled?: boolean;
23
+ className?: string;
24
+ title?: string;
25
+ avatarUrl?: string | null;
26
+ avatarFallback?: string;
27
+ subtitle?: string;
28
+ };
29
+ export type DropdownBookmarkOption = {
30
+ value: string;
31
+ label: string;
32
+ meta?: unknown;
33
+ className?: string;
34
+ };
35
+ export type DropdownBookmarkSection = {
36
+ label?: string;
37
+ options: DropdownBookmarkOption[];
38
+ };
39
+ type HeaderVariantProps = {
40
+ variant: "header";
41
+ menus: HeaderMenuItem[];
42
+ menuOpen: string | null;
43
+ onOpenMenu: (id: string) => void;
44
+ onCloseMenu: () => void;
45
+ };
46
+ type UserVariantProps = {
47
+ variant: "user";
48
+ name: string;
49
+ avatarUrl?: string | null;
50
+ avatarUrlFallback?: string | null;
51
+ avatarAlt?: string;
52
+ avatarFallback?: string;
53
+ items: DropdownUserItem[];
54
+ open?: boolean;
55
+ onOpenChange?: (open: boolean) => void;
56
+ };
57
+ type UserListVariantProps = {
58
+ variant: "user-list";
59
+ name: string;
60
+ avatarUrl?: string | null;
61
+ avatarUrlFallback?: string | null;
62
+ avatarAlt?: string;
63
+ avatarFallback?: string;
64
+ items: DropdownUserListItem[];
65
+ open?: boolean;
66
+ onOpenChange?: (open: boolean) => void;
67
+ emptyLabel?: string;
68
+ emptyClassName?: string;
69
+ };
70
+ type BookmarkVariantProps = {
71
+ variant: "bookmark";
72
+ label?: string;
73
+ layout?: "row" | "field";
74
+ value: string;
75
+ triggerLabel?: string;
76
+ placeholder?: string;
77
+ sections: DropdownBookmarkSection[];
78
+ onChange: (value: string, option?: DropdownBookmarkOption) => void;
79
+ renderTriggerIcon?: ReactNode;
80
+ renderItemIcon?: (option: DropdownBookmarkOption) => ReactNode;
81
+ caret?: ReactNode;
82
+ emptyLabel?: string;
83
+ emptyClassName?: string;
84
+ };
85
+ type DropdownProps = HeaderVariantProps | UserVariantProps | UserListVariantProps | BookmarkVariantProps;
86
+ export declare const Dropdown: (props: DropdownProps) => import("react/jsx-runtime").JSX.Element;
87
+ export {};
88
+ //# sourceMappingURL=Dropdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Dropdown.d.ts","sourceRoot":"","sources":["../../src/components/Dropdown.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,179 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { Button } from "./Button";
4
+ const DefaultChevron = ({ open }) => (_jsx("span", { className: `chevron ${open ? "open" : ""}`, "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 24 24", children: _jsx("path", { d: "M6 9l6 6 6-6", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) }) }));
5
+ const DefaultCaret = () => (_jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 9l6 6 6-6", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) }));
6
+ const IconEdit = () => (_jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M4 20l4.5-1 9.4-9.4a1.8 1.8 0 0 0 0-2.6l-1-1a1.8 1.8 0 0 0-2.6 0L4.9 15.4 4 20z", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M13.5 6.5l4 4", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" })] }));
7
+ const IconTrash = () => (_jsxs("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M4 7h16", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M9 7V5a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }), _jsx("path", { d: "M7 7l1 12a1 1 0 0 0 1 .9h6a1 1 0 0 0 1-.9l1-12", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" })] }));
8
+ export const Dropdown = (props) => {
9
+ const closeTimerRef = useRef(null);
10
+ const closeDelayMs = 160;
11
+ useEffect(() => {
12
+ return () => {
13
+ if (closeTimerRef.current !== null) {
14
+ window.clearTimeout(closeTimerRef.current);
15
+ }
16
+ };
17
+ }, []);
18
+ const cancelScheduledClose = () => {
19
+ if (closeTimerRef.current !== null) {
20
+ window.clearTimeout(closeTimerRef.current);
21
+ closeTimerRef.current = null;
22
+ }
23
+ };
24
+ if (props.variant === "header") {
25
+ const scheduleClose = () => {
26
+ cancelScheduledClose();
27
+ closeTimerRef.current = window.setTimeout(() => {
28
+ props.onCloseMenu();
29
+ }, closeDelayMs);
30
+ };
31
+ return (_jsx("div", { className: "ef-menu-bar", children: props.menus.map((menu) => (_jsxs("div", { className: "ef-menu-group", onMouseEnter: () => {
32
+ cancelScheduledClose();
33
+ props.onOpenMenu(menu.id);
34
+ }, onMouseLeave: scheduleClose, children: [_jsx("button", { className: "ef-menu-button", type: "button", children: menu.label }), props.menuOpen === menu.id ? (_jsx("div", { className: "ef-menu-popover", "data-open": "true", onMouseEnter: () => {
35
+ cancelScheduledClose();
36
+ props.onOpenMenu(menu.id);
37
+ }, onMouseLeave: scheduleClose, children: menu.content })) : null] }, menu.id))) }));
38
+ }
39
+ if (props.variant === "user" || props.variant === "user-list") {
40
+ const [internalOpen, setInternalOpen] = useState(false);
41
+ const [avatarState, setAvatarState] = useState("primary");
42
+ const ref = useRef(null);
43
+ const isOpen = props.open ?? internalOpen;
44
+ useEffect(() => {
45
+ setAvatarState("primary");
46
+ }, [props.avatarUrl, props.avatarUrlFallback]);
47
+ useEffect(() => {
48
+ if (!isOpen)
49
+ return;
50
+ const handlePointer = (event) => {
51
+ if (!ref.current)
52
+ return;
53
+ if (ref.current.contains(event.target))
54
+ return;
55
+ if (props.open === undefined) {
56
+ setInternalOpen(false);
57
+ }
58
+ props.onOpenChange?.(false);
59
+ };
60
+ window.addEventListener("pointerdown", handlePointer);
61
+ return () => window.removeEventListener("pointerdown", handlePointer);
62
+ }, [isOpen, props.open, props.onOpenChange]);
63
+ const setOpen = (next) => {
64
+ if (props.open === undefined) {
65
+ setInternalOpen(next);
66
+ }
67
+ props.onOpenChange?.(next);
68
+ };
69
+ const fallback = props.avatarFallback ?? props.name.slice(0, 1).toUpperCase();
70
+ const primaryAvatar = props.avatarUrl ?? null;
71
+ const fallbackAvatar = props.avatarUrlFallback ?? null;
72
+ const currentAvatar = avatarState === "primary"
73
+ ? primaryAvatar
74
+ : avatarState === "fallback"
75
+ ? fallbackAvatar
76
+ : null;
77
+ const renderUserListItems = () => {
78
+ if (props.variant !== "user-list") {
79
+ return props.items.map((item, index) => {
80
+ const key = item.id ?? `${item.label}-${index}`;
81
+ if (item.variant === "theme-preview") {
82
+ return (_jsx(Button, { type: "button", variant: "primary", className: ["theme-preview", item.className].filter(Boolean).join(" "), onClick: () => {
83
+ item.onClick();
84
+ setOpen(false);
85
+ }, disabled: item.disabled, title: item.title, children: item.label }, key));
86
+ }
87
+ return (_jsx("button", { className: ["dropdown-item", item.className].filter(Boolean).join(" "), type: "button", onClick: () => {
88
+ item.onClick();
89
+ setOpen(false);
90
+ }, disabled: item.disabled, title: item.title, children: item.label }, key));
91
+ });
92
+ }
93
+ if (!props.items.length) {
94
+ return (_jsx("div", { className: ["dropdown-empty", props.emptyClassName].filter(Boolean).join(" "), children: props.emptyLabel ?? "No items." }));
95
+ }
96
+ return props.items.map((item, index) => {
97
+ const fallback = item.avatarFallback ?? item.label.slice(0, 1).toUpperCase();
98
+ const isDisabled = !!item.disabled;
99
+ return (_jsxs("div", { className: [
100
+ "dropdown-item",
101
+ "dropdown-item-rich",
102
+ item.className,
103
+ isDisabled ? "is-disabled" : "",
104
+ ]
105
+ .filter(Boolean)
106
+ .join(" "), role: "button", tabIndex: isDisabled ? -1 : 0, "aria-disabled": isDisabled ? "true" : undefined, onClick: () => {
107
+ if (isDisabled)
108
+ return;
109
+ item.onClick();
110
+ setOpen(false);
111
+ }, onKeyDown: (event) => {
112
+ if (isDisabled)
113
+ return;
114
+ if (event.key === "Enter" || event.key === " ") {
115
+ event.preventDefault();
116
+ item.onClick();
117
+ setOpen(false);
118
+ }
119
+ }, title: item.title, children: [_jsx("span", { className: "dropdown-avatar", children: item.avatarUrl ? (_jsx("img", { src: item.avatarUrl, alt: item.label, loading: "lazy", decoding: "async", referrerPolicy: "no-referrer", crossOrigin: "anonymous" })) : (_jsx("span", { className: "dropdown-avatar-fallback", children: fallback })) }), _jsxs("span", { className: "dropdown-item-text", children: [_jsx("span", { className: "dropdown-item-label", children: item.label }), item.subtitle ? (_jsx("span", { className: "dropdown-item-subtitle", children: item.subtitle })) : null] }), item.onEdit || item.onDelete ? (_jsxs("span", { className: "dropdown-item-actions", children: [item.onEdit ? (_jsx(Button, { type: "button", variant: "ghost", className: "dropdown-action", "aria-label": `Edit ${item.label}`, onClick: (event) => {
120
+ event.stopPropagation();
121
+ item.onEdit?.();
122
+ setOpen(false);
123
+ }, children: _jsx(IconEdit, {}) })) : null, item.onDelete ? (_jsx(Button, { type: "button", variant: "delete", className: "dropdown-action is-delete", "aria-label": `Delete ${item.label}`, onClick: (event) => {
124
+ event.stopPropagation();
125
+ item.onDelete?.();
126
+ }, children: _jsx(IconTrash, {}) })) : null] })) : null] }, item.id ?? `${item.label}-${index}`));
127
+ });
128
+ };
129
+ return (_jsxs("div", { className: "user-section", ref: ref, "data-open": isOpen ? "true" : "false", children: [_jsxs("button", { className: "user-button", onClick: () => setOpen(!isOpen), type: "button", children: [_jsx("span", { className: "avatar", children: currentAvatar ? (_jsx("img", { src: currentAvatar, alt: props.avatarAlt ?? props.name, loading: "eager", decoding: "async", referrerPolicy: "no-referrer", crossOrigin: "anonymous", onError: () => {
130
+ if (avatarState === "primary" && fallbackAvatar) {
131
+ setAvatarState("fallback");
132
+ }
133
+ else {
134
+ setAvatarState("none");
135
+ }
136
+ } })) : (_jsx("span", { className: "avatar-fallback", children: fallback })) }), _jsx("span", { className: "user-name", children: props.name }), _jsx(DefaultChevron, { open: isOpen })] }), _jsx("div", { className: "dropdown", "data-open": isOpen ? "true" : "false", children: renderUserListItems() })] }));
137
+ }
138
+ const { label, layout = "row", value, placeholder = "Select a saved connection", sections, onChange, renderTriggerIcon, renderItemIcon, triggerLabel, caret, emptyLabel = "No saved connections.", emptyClassName, } = props;
139
+ const [open, setOpen] = useState(false);
140
+ const ref = useRef(null);
141
+ const options = useMemo(() => sections.flatMap((section) => section.options), [sections]);
142
+ const active = options.find((item) => item.value === value) ?? null;
143
+ useEffect(() => {
144
+ if (!open)
145
+ return;
146
+ const handlePointer = (event) => {
147
+ if (!ref.current)
148
+ return;
149
+ if (ref.current.contains(event.target))
150
+ return;
151
+ setOpen(false);
152
+ };
153
+ window.addEventListener("pointerdown", handlePointer);
154
+ return () => window.removeEventListener("pointerdown", handlePointer);
155
+ }, [open]);
156
+ const handleEllipsisTooltip = (event) => {
157
+ const target = event.currentTarget;
158
+ if (target.scrollWidth > target.clientWidth) {
159
+ target.setAttribute("title", target.textContent ?? "");
160
+ }
161
+ else {
162
+ target.removeAttribute("title");
163
+ }
164
+ };
165
+ const clearEllipsisTooltip = (event) => {
166
+ event.currentTarget.removeAttribute("title");
167
+ };
168
+ const dropdownBody = (_jsxs("div", { className: `bookmark-dropdown ${open ? "open" : ""}`, children: [_jsxs("button", { className: "bookmark-trigger", onClick: () => setOpen((prev) => !prev), type: "button", children: [renderTriggerIcon ? _jsx("span", { className: "bookmark-icon", children: renderTriggerIcon }) : null, _jsx("span", { className: "bookmark-text", children: triggerLabel ?? (active ? active.label : placeholder) }), _jsx("span", { className: "bookmark-caret", children: caret ?? _jsx(DefaultCaret, {}) })] }), open ? (_jsx("div", { className: "bookmark-menu", children: options.length === 0 ? (_jsx("div", { className: ["bookmark-empty", emptyClassName].filter(Boolean).join(" "), children: emptyLabel })) : (sections.map((section) => (_jsxs("div", { children: [section.label ? _jsx("div", { className: "bookmark-group", children: section.label }) : null, section.options.map((item) => (_jsxs("button", { className: [
169
+ "bookmark-item",
170
+ item.value === value ? "active" : "",
171
+ item.className,
172
+ ]
173
+ .filter(Boolean)
174
+ .join(" "), type: "button", onClick: () => {
175
+ onChange(item.value, item);
176
+ setOpen(false);
177
+ }, children: [renderItemIcon ? (_jsx("span", { className: "bookmark-icon", children: renderItemIcon(item) })) : null, _jsx("span", { className: "bookmark-text", onMouseEnter: handleEllipsisTooltip, onMouseLeave: clearEllipsisTooltip, children: item.label })] }, item.value)))] }, section.label ?? "options")))) })) : null] }));
178
+ return (_jsxs("div", { className: layout === "row" ? "bookmark-row" : "bookmark-field", ref: ref, children: [layout === "row" && label ? _jsx("div", { className: "bookmark-label", children: label }) : null, dropdownBody] }));
179
+ };
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from "react";
2
+ type FloatingFooterProps = {
3
+ title?: string;
4
+ subtitle?: string;
5
+ actions?: ReactNode;
6
+ className?: string;
7
+ };
8
+ export declare const FloatingFooter: ({ title, subtitle, actions, className, }: FloatingFooterProps) => import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=FloatingFooter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FloatingFooter.d.ts","sourceRoot":"","sources":["../../src/components/FloatingFooter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,KAAK,mBAAmB,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,0CAK5B,mBAAmB,4CAWrB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Panel } from "./Panel";
3
+ export const FloatingFooter = ({ title = "Quick Actions", subtitle, actions, className, }) => {
4
+ const classes = ["ef-floating-footer", className].filter(Boolean).join(" ");
5
+ return (_jsxs(Panel, { variant: "full", borderWidth: 2, className: classes, children: [_jsxs("div", { className: "ef-footer-copy", children: [_jsx("div", { className: "ef-footer-title", children: title }), subtitle ? _jsx("div", { className: "ef-footer-subtitle", children: subtitle }) : null] }), _jsx("div", { className: "ef-footer-actions", children: actions })] }));
6
+ };
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ type FormFieldProps = {
3
+ label: string;
4
+ htmlFor?: string;
5
+ helper?: string;
6
+ error?: string;
7
+ required?: boolean;
8
+ children: ReactNode;
9
+ };
10
+ export declare const FormField: ({ label, htmlFor, helper, error, required, children, }: FormFieldProps) => import("react/jsx-runtime").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=FormField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../src/components/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,KAAK,cAAc,GAAG;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,wDAOvB,cAAc,4CAUhB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const FormField = ({ label, htmlFor, helper, error, required, children, }) => (_jsxs("div", { className: ["ef-field", error ? "has-error" : ""].filter(Boolean).join(" "), children: [_jsxs("label", { className: "ef-field-label", htmlFor: htmlFor, children: [_jsx("span", { children: label }), required ? _jsx("span", { className: "ef-field-required", children: "*" }) : null] }), children, helper ? _jsx("div", { className: "ef-field-helper", children: helper }) : null, error ? _jsx("div", { className: "ef-field-error", children: error }) : null] }));
@@ -0,0 +1,12 @@
1
+ import type { InputHTMLAttributes, TextareaHTMLAttributes, SelectHTMLAttributes } from "react";
2
+ type BaseProps = {
3
+ className?: string;
4
+ };
5
+ type InputProps = InputHTMLAttributes<HTMLInputElement> & BaseProps;
6
+ type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & BaseProps;
7
+ type SelectProps = SelectHTMLAttributes<HTMLSelectElement> & BaseProps;
8
+ export declare const Input: ({ className, ...props }: InputProps) => import("react/jsx-runtime").JSX.Element;
9
+ export declare const Textarea: ({ className, ...props }: TextareaProps) => import("react/jsx-runtime").JSX.Element;
10
+ export declare const Select: ({ className, ...props }: SelectProps) => import("react/jsx-runtime").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=Input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../src/components/Input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE/F,KAAK,SAAS,GAAG;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,UAAU,GAAG,mBAAmB,CAAC,gBAAgB,CAAC,GAAG,SAAS,CAAC;AACpE,KAAK,aAAa,GAAG,sBAAsB,CAAC,mBAAmB,CAAC,GAAG,SAAS,CAAC;AAC7E,KAAK,WAAW,GAAG,oBAAoB,CAAC,iBAAiB,CAAC,GAAG,SAAS,CAAC;AAIvE,eAAO,MAAM,KAAK,GAAI,yBAAyB,UAAU,4CAExD,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,yBAAyB,aAAa,4CAE9D,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,yBAAyB,WAAW,4CAE1D,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ const cx = (...classes) => classes.filter(Boolean).join(" ");
3
+ export const Input = ({ className, ...props }) => (_jsx("input", { ...props, className: cx("ef-input", className) }));
4
+ export const Textarea = ({ className, ...props }) => (_jsx("textarea", { ...props, className: cx("ef-textarea", className) }));
5
+ export const Select = ({ className, ...props }) => (_jsx("select", { ...props, className: cx("ef-select", className) }));
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from "react";
2
+ import { type HeaderMenuItem } from "./Dropdown";
3
+ type MainHeaderProps = {
4
+ title?: string;
5
+ subtitle?: string;
6
+ logoSrc?: string;
7
+ menus: HeaderMenuItem[];
8
+ menuOpen: string | null;
9
+ onOpenMenu: (id: string) => void;
10
+ onCloseMenu: () => void;
11
+ actions?: ReactNode;
12
+ };
13
+ export declare const MainHeader: ({ title, subtitle, logoSrc, menus, menuOpen, onOpenMenu, onCloseMenu, actions, }: MainHeaderProps) => import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=MainHeader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MainHeader.d.ts","sourceRoot":"","sources":["../../src/components/MainHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAG3D,KAAK,eAAe,GAAG;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,kFASxB,eAAe,4CA0BjB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Dropdown } from "./Dropdown";
3
+ import { Panel } from "./Panel";
4
+ export const MainHeader = ({ title = "Enderfall", subtitle = "Galaxy tools for creators", logoSrc, menus, menuOpen, onOpenMenu, onCloseMenu, actions, }) => {
5
+ return (_jsxs(Panel, { variant: "header", borderWidth: 2, className: "ef-main-header", children: [_jsxs("div", { className: "ef-header-left", children: [_jsxs("div", { className: "ef-brand", children: [logoSrc ? (_jsx("img", { src: logoSrc, alt: title, className: "ef-logo" })) : (_jsx("div", { className: "ef-logo-fallback", children: "E" })), _jsxs("div", { children: [_jsx("div", { className: "ef-brand-name", children: title }), subtitle ? _jsx("div", { className: "ef-tagline", children: subtitle }) : null] })] }), _jsx(Dropdown, { variant: "header", menus: menus, menuOpen: menuOpen, onOpenMenu: onOpenMenu, onCloseMenu: onCloseMenu })] }), _jsx("div", { className: "ef-header-actions", children: actions })] }));
6
+ };
@@ -0,0 +1,16 @@
1
+ import { type ReactNode } from "react";
2
+ type ModalSize = "default" | "wide";
3
+ type ModalProps = {
4
+ isOpen: boolean;
5
+ title: string;
6
+ subtitle?: string;
7
+ size?: ModalSize;
8
+ onClose: () => void;
9
+ actions?: ReactNode;
10
+ headerActions?: ReactNode;
11
+ className?: string;
12
+ children?: ReactNode;
13
+ };
14
+ export declare const Modal: ({ isOpen, title, subtitle, size, onClose, actions, headerActions, className, children, }: ModalProps) => import("react/jsx-runtime").JSX.Element | null;
15
+ export {};
16
+ //# sourceMappingURL=Modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/components/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAmD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGxF,KAAK,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAIpC,KAAK,UAAU,GAAG;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,KAAK,GAAI,0FAUnB,UAAU,mDAgHZ,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { Panel } from "./Panel";
4
+ const MODAL_TRANSITION_MS = 240;
5
+ export const Modal = ({ isOpen, title, subtitle, size = "default", onClose, actions, headerActions, className, children, }) => {
6
+ const reduceMotion = typeof document !== "undefined" && document.documentElement.getAttribute("data-reduce-motion") === "true";
7
+ const transitionMs = reduceMotion ? 0 : MODAL_TRANSITION_MS;
8
+ const [isRendered, setIsRendered] = useState(isOpen);
9
+ const [isEntering, setIsEntering] = useState(false);
10
+ const [isExiting, setIsExiting] = useState(false);
11
+ const [enterOffset, setEnterOffset] = useState({ x: 0, y: 0 });
12
+ const exitTimerRef = useRef(null);
13
+ const enterFrameRef = useRef(null);
14
+ useEffect(() => {
15
+ return () => {
16
+ if (exitTimerRef.current !== null)
17
+ window.clearTimeout(exitTimerRef.current);
18
+ if (enterFrameRef.current !== null)
19
+ window.cancelAnimationFrame(enterFrameRef.current);
20
+ };
21
+ }, []);
22
+ useEffect(() => {
23
+ if (isOpen) {
24
+ if (exitTimerRef.current !== null) {
25
+ window.clearTimeout(exitTimerRef.current);
26
+ exitTimerRef.current = null;
27
+ }
28
+ const activeElement = document.activeElement;
29
+ const activeRect = activeElement?.getBoundingClientRect();
30
+ const sourceX = activeRect ? activeRect.left + activeRect.width / 2 : window.innerWidth / 2;
31
+ const sourceY = activeRect ? activeRect.top + activeRect.height / 2 : window.innerHeight - 56;
32
+ const centerX = window.innerWidth / 2;
33
+ const centerY = window.innerHeight / 2;
34
+ setEnterOffset({
35
+ x: (sourceX - centerX) * 0.18,
36
+ y: (sourceY - centerY) * 0.22,
37
+ });
38
+ setIsRendered(true);
39
+ setIsExiting(false);
40
+ setIsEntering(!reduceMotion);
41
+ if (reduceMotion)
42
+ return;
43
+ if (enterFrameRef.current !== null)
44
+ window.cancelAnimationFrame(enterFrameRef.current);
45
+ enterFrameRef.current = window.requestAnimationFrame(() => {
46
+ enterFrameRef.current = window.requestAnimationFrame(() => {
47
+ setIsEntering(false);
48
+ enterFrameRef.current = null;
49
+ });
50
+ });
51
+ return;
52
+ }
53
+ if (!isRendered)
54
+ return;
55
+ if (reduceMotion) {
56
+ setIsEntering(false);
57
+ setIsExiting(false);
58
+ setIsRendered(false);
59
+ return;
60
+ }
61
+ setIsEntering(false);
62
+ setIsExiting(true);
63
+ if (exitTimerRef.current !== null)
64
+ window.clearTimeout(exitTimerRef.current);
65
+ exitTimerRef.current = window.setTimeout(() => {
66
+ setIsRendered(false);
67
+ setIsExiting(false);
68
+ exitTimerRef.current = null;
69
+ }, transitionMs);
70
+ }, [isOpen, isRendered, reduceMotion, transitionMs]);
71
+ if (!isRendered)
72
+ return null;
73
+ const sizeClass = size === "wide" ? "ef-modal--wide" : "";
74
+ const modalStateClass = isExiting ? "is-exiting" : isEntering ? "is-entering" : "is-open";
75
+ const backdropClasses = ["ef-modal-backdrop", modalStateClass].filter(Boolean).join(" ");
76
+ const classes = ["ef-modal", sizeClass, modalStateClass, className].filter(Boolean).join(" ");
77
+ const modalStyle = {
78
+ "--ef-modal-enter-x": `${enterOffset.x}px`,
79
+ "--ef-modal-enter-y": `${enterOffset.y}px`,
80
+ };
81
+ return (_jsx("div", { className: backdropClasses, onClick: onClose, children: _jsxs(Panel, { variant: "card", borderWidth: 2, className: classes, style: modalStyle, onClick: (event) => event.stopPropagation(), children: [_jsxs("div", { className: "ef-modal-header", children: [_jsx("div", { className: "ef-modal-title", children: title }), _jsxs("div", { className: "ef-modal-header-actions", children: [headerActions, _jsx("button", { className: "icon-action small ef-modal-close", onClick: onClose, type: "button", "aria-label": "Close", children: "\u00D7" })] })] }), subtitle ? _jsx("div", { className: "ef-modal-subtitle", children: subtitle }) : null, children ? _jsx("div", { className: "ef-modal-body", children: children }) : null, actions ? _jsx("div", { className: "ef-modal-actions", children: actions }) : null] }) }));
82
+ };
@@ -0,0 +1,9 @@
1
+ import { type HTMLAttributes } from "react";
2
+ type PanelVariant = "card" | "highlight" | "full" | "header";
3
+ type BorderWidth = 1 | 2;
4
+ export declare const Panel: import("react").ForwardRefExoticComponent<HTMLAttributes<HTMLDivElement> & {
5
+ variant?: PanelVariant;
6
+ borderWidth?: BorderWidth;
7
+ } & import("react").RefAttributes<HTMLDivElement>>;
8
+ export {};
9
+ //# sourceMappingURL=Panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Panel.d.ts","sourceRoot":"","sources":["../../src/components/Panel.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAExD,KAAK,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,QAAQ,CAAC;AAC7D,KAAK,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC;AAOzB,eAAO,MAAM,KAAK;cAJN,YAAY;kBACR,WAAW;kDAqB1B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ export const Panel = forwardRef(({ variant = "card", borderWidth = 1, className, children, ...rest }, ref) => {
4
+ const variantClass = variant === "highlight"
5
+ ? "ef-panel--highlight"
6
+ : variant === "header"
7
+ ? "ef-panel--header"
8
+ : variant === "full"
9
+ ? "ef-panel--full"
10
+ : "ef-panel--card";
11
+ const borderClass = borderWidth === 2 ? "ef-panel--border-2" : "ef-panel--border-1";
12
+ const classes = ["ef-panel", variantClass, borderClass, className].filter(Boolean).join(" ");
13
+ return (_jsx("div", { ref: ref, className: classes, ...rest, children: children }));
14
+ });
15
+ Panel.displayName = "Panel";
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from "react";
2
+ type ThemeOption = {
3
+ value: string;
4
+ label: string;
5
+ };
6
+ type PreferencesModalProps = {
7
+ isOpen: boolean;
8
+ onClose: () => void;
9
+ themeMode: string;
10
+ onThemeChange: (value: string) => void;
11
+ themeOptions: ThemeOption[];
12
+ animationsEnabled: boolean;
13
+ onAnimationsChange: (enabled: boolean) => void;
14
+ note?: string;
15
+ children?: ReactNode;
16
+ };
17
+ export declare const PreferencesModal: ({ isOpen, onClose, themeMode, onThemeChange, themeOptions, animationsEnabled, onAnimationsChange, note, children, }: PreferencesModalProps) => import("react/jsx-runtime").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=PreferencesModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PreferencesModal.d.ts","sourceRoot":"","sources":["../../src/components/PreferencesModal.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,KAAK,WAAW,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpD,KAAK,qBAAqB,GAAG;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,qHAU9B,qBAAqB,4CAoCvB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "./Button";
3
+ import { Dropdown } from "./Dropdown";
4
+ import { Toggle } from "./Toggle";
5
+ import { Modal } from "./Modal";
6
+ export const PreferencesModal = ({ isOpen, onClose, themeMode, onThemeChange, themeOptions, animationsEnabled, onAnimationsChange, note = "Syncs across all Enderfall apps.", children, }) => {
7
+ const selectedTheme = themeOptions.find((option) => option.value === themeMode) ?? themeOptions[0];
8
+ const dropdownLabel = selectedTheme?.label ?? "Select theme";
9
+ const dropdownItems = themeOptions.map((option) => ({
10
+ id: option.value,
11
+ label: option.label,
12
+ onClick: () => onThemeChange(option.value),
13
+ className: `theme-preview theme-preview--${option.value}`,
14
+ variant: "theme-preview",
15
+ }));
16
+ return (_jsxs(Modal, { isOpen: isOpen, onClose: onClose, title: "Preferences", subtitle: "Applies across Enderfall apps.", children: [_jsxs("div", { className: "ef-modal-form", children: [_jsxs("label", { children: ["Theme", _jsx("div", { className: "ef-modal-user-dropdown", children: _jsx(Dropdown, { variant: "user", name: dropdownLabel, items: dropdownItems }) })] }), _jsx(Toggle, { checked: animationsEnabled, onChange: (event) => onAnimationsChange(event.target.checked), label: "Enable animations" }), children, note ? _jsx("div", { className: "ef-modal-note", children: note }) : null] }), _jsx("div", { className: "ef-modal-actions", children: _jsx(Button, { variant: "primary", type: "button", onClick: onClose, children: "Close" }) })] }));
17
+ };
@@ -0,0 +1,28 @@
1
+ import { type ReactNode, type MouseEvent } from "react";
2
+ type SideMenuProps = {
3
+ children: ReactNode;
4
+ resetKey?: string | number | boolean | null;
5
+ hoverDelayMs?: number;
6
+ initialDelayMs?: number;
7
+ onOpenChange?: (openId: string | null) => void;
8
+ };
9
+ export declare const SideMenu: ({ children, resetKey, hoverDelayMs, initialDelayMs, onOpenChange, }: SideMenuProps) => import("react/jsx-runtime").JSX.Element;
10
+ type SideMenuTriggerProps = {
11
+ onClick: (event: MouseEvent) => void;
12
+ disabled?: boolean;
13
+ "aria-expanded": boolean;
14
+ };
15
+ type SideMenuSubmenuProps = {
16
+ id: string;
17
+ trigger: (props: SideMenuTriggerProps) => ReactNode;
18
+ children: ReactNode;
19
+ className?: string;
20
+ panelClassName?: string;
21
+ disabled?: boolean;
22
+ enableViewportFlip?: boolean;
23
+ onOpenChange?: (open: boolean) => void;
24
+ variant?: "default" | "header";
25
+ };
26
+ export declare const SideMenuSubmenu: ({ id, trigger, children, className, panelClassName, disabled, enableViewportFlip, onOpenChange, variant, }: SideMenuSubmenuProps) => import("react/jsx-runtime").JSX.Element;
27
+ export {};
28
+ //# sourceMappingURL=SideMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SideMenu.d.ts","sourceRoot":"","sources":["../../src/components/SideMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,SAAS,EACd,KAAK,UAAU,EAChB,MAAM,OAAO,CAAC;AAcf,KAAK,aAAa,GAAG;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAChD,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,qEAMtB,aAAa,4CA8Df,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,KAAK,oBAAoB,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,SAAS,CAAC;IACpD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAChC,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,4GAU7B,oBAAoB,4CA4GtB,CAAC"}
@@ -0,0 +1,144 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { createContext, useContext, useEffect, useMemo, useRef, useState, } from "react";
3
+ const SideMenuContext = createContext(null);
4
+ export const SideMenu = ({ children, resetKey, hoverDelayMs = 220, initialDelayMs = 180, onOpenChange, }) => {
5
+ const [openId, setOpenId] = useState(null);
6
+ const hoverTimerRef = useRef(null);
7
+ const openedAtRef = useRef(Date.now());
8
+ useEffect(() => {
9
+ if (hoverTimerRef.current !== null) {
10
+ window.clearTimeout(hoverTimerRef.current);
11
+ hoverTimerRef.current = null;
12
+ }
13
+ setOpenId(null);
14
+ openedAtRef.current = Date.now();
15
+ }, [resetKey]);
16
+ useEffect(() => {
17
+ onOpenChange?.(openId);
18
+ }, [openId, onOpenChange]);
19
+ useEffect(() => {
20
+ return () => {
21
+ if (hoverTimerRef.current !== null) {
22
+ window.clearTimeout(hoverTimerRef.current);
23
+ }
24
+ };
25
+ }, []);
26
+ const scheduleOpen = (id) => {
27
+ if (hoverTimerRef.current !== null) {
28
+ window.clearTimeout(hoverTimerRef.current);
29
+ }
30
+ const elapsed = Date.now() - openedAtRef.current;
31
+ const settleDelay = elapsed < initialDelayMs ? initialDelayMs - elapsed : 0;
32
+ hoverTimerRef.current = window.setTimeout(() => {
33
+ setOpenId(id);
34
+ }, hoverDelayMs + settleDelay);
35
+ };
36
+ const clearHover = () => {
37
+ if (hoverTimerRef.current !== null) {
38
+ window.clearTimeout(hoverTimerRef.current);
39
+ hoverTimerRef.current = null;
40
+ }
41
+ };
42
+ const toggleOpen = (id) => {
43
+ setOpenId((prev) => (prev === id ? null : id));
44
+ };
45
+ const value = useMemo(() => ({
46
+ openId,
47
+ setOpenId,
48
+ scheduleOpen,
49
+ clearHover,
50
+ toggleOpen,
51
+ openedAt: openedAtRef.current,
52
+ initialDelayMs,
53
+ }), [openId, initialDelayMs]);
54
+ return _jsx(SideMenuContext.Provider, { value: value, children: children });
55
+ };
56
+ export const SideMenuSubmenu = ({ id, trigger, children, className, panelClassName, disabled, enableViewportFlip = false, onOpenChange, variant = "default", }) => {
57
+ const context = useContext(SideMenuContext);
58
+ if (!context) {
59
+ throw new Error("SideMenuSubmenu must be used within a SideMenu provider.");
60
+ }
61
+ const { openId, setOpenId, scheduleOpen, clearHover, toggleOpen, openedAt, initialDelayMs } = context;
62
+ const isOpen = openId === id;
63
+ const panelRef = useRef(null);
64
+ const containerRef = useRef(null);
65
+ const [panelStyle, setPanelStyle] = useState({});
66
+ useEffect(() => {
67
+ onOpenChange?.(isOpen);
68
+ }, [isOpen, onOpenChange]);
69
+ useEffect(() => {
70
+ if (!isOpen || !enableViewportFlip) {
71
+ setPanelStyle({});
72
+ return;
73
+ }
74
+ const updatePosition = () => {
75
+ const panel = panelRef.current;
76
+ const container = containerRef.current;
77
+ if (!panel || !container)
78
+ return;
79
+ const panelRect = panel.getBoundingClientRect();
80
+ const containerRect = container.getBoundingClientRect();
81
+ const margin = 12;
82
+ const defaultLeft = containerRect.right + 6;
83
+ const flippedLeft = containerRect.left - panelRect.width - 6;
84
+ let left = defaultLeft;
85
+ if (defaultLeft + panelRect.width > window.innerWidth - margin && flippedLeft >= margin) {
86
+ left = flippedLeft;
87
+ }
88
+ left = Math.min(Math.max(left, margin), window.innerWidth - margin - panelRect.width);
89
+ let top = containerRect.top;
90
+ top = Math.min(Math.max(top, margin), window.innerHeight - margin - panelRect.height);
91
+ setPanelStyle({
92
+ left: left - containerRect.left,
93
+ top: top - containerRect.top,
94
+ right: "auto",
95
+ });
96
+ };
97
+ const raf = requestAnimationFrame(updatePosition);
98
+ window.addEventListener("resize", updatePosition);
99
+ window.addEventListener("scroll", updatePosition, true);
100
+ return () => {
101
+ cancelAnimationFrame(raf);
102
+ window.removeEventListener("resize", updatePosition);
103
+ window.removeEventListener("scroll", updatePosition, true);
104
+ };
105
+ }, [isOpen, enableViewportFlip]);
106
+ const handlePointerEnter = () => {
107
+ if (disabled || isOpen)
108
+ return;
109
+ if (Date.now() - openedAt < initialDelayMs)
110
+ return;
111
+ setOpenId(null);
112
+ scheduleOpen(id);
113
+ };
114
+ const handlePointerLeave = () => {
115
+ clearHover();
116
+ };
117
+ const handlePointerMove = () => {
118
+ if (disabled || isOpen)
119
+ return;
120
+ if (Date.now() - openedAt < initialDelayMs)
121
+ return;
122
+ scheduleOpen(id);
123
+ };
124
+ const handleClick = (event) => {
125
+ if (disabled)
126
+ return;
127
+ event.preventDefault();
128
+ if (isOpen)
129
+ return;
130
+ setOpenId(id);
131
+ };
132
+ return (_jsxs("div", { className: [
133
+ className,
134
+ isOpen ? "is-open" : "",
135
+ variant === "header" ? "side-menu--header" : "",
136
+ ]
137
+ .filter(Boolean)
138
+ .join(" "), "data-submenu-id": id, ref: containerRef, onPointerEnter: handlePointerEnter, onPointerLeave: handlePointerLeave, onPointerMove: handlePointerMove, children: [trigger({ onClick: handleClick, disabled, "aria-expanded": isOpen }), _jsx("div", { ref: panelRef, className: [
139
+ panelClassName,
140
+ isOpen ? "is-open" : "",
141
+ ]
142
+ .filter(Boolean)
143
+ .join(" "), style: panelStyle, children: children })] }));
144
+ };
@@ -0,0 +1,5 @@
1
+ import { type InputHTMLAttributes } from "react";
2
+ type SliderProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type">;
3
+ export declare const Slider: ({ className, ...props }: SliderProps) => import("react/jsx-runtime").JSX.Element;
4
+ export {};
5
+ //# sourceMappingURL=Slider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Slider.d.ts","sourceRoot":"","sources":["../../src/components/Slider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAEtE,KAAK,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,CAAC;AAKvE,eAAO,MAAM,MAAM,GAAI,yBAAyB,WAAW,4CAyB1D,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ const getTheme = () => document.documentElement.dataset.theme || document.body.dataset.theme || "";
4
+ export const Slider = ({ className, ...props }) => {
5
+ const [theme, setTheme] = useState("");
6
+ useEffect(() => {
7
+ setTheme(getTheme());
8
+ const observer = new MutationObserver(() => setTheme(getTheme()));
9
+ observer.observe(document.documentElement, {
10
+ attributes: true,
11
+ attributeFilter: ["data-theme"],
12
+ });
13
+ observer.observe(document.body, {
14
+ attributes: true,
15
+ attributeFilter: ["data-theme"],
16
+ });
17
+ return () => observer.disconnect();
18
+ }, []);
19
+ return (_jsx("input", { type: "range", className: ["ef-slider", className].filter(Boolean).join(" "), "data-ef-theme": theme || undefined, ...props }));
20
+ };
@@ -0,0 +1,29 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ type Variant = "games" | "mods" | "servers" | "apps";
3
+ type Align = "center" | "left";
4
+ type Tone = "default" | "plain";
5
+ type Action = {
6
+ label: string;
7
+ href?: string;
8
+ onClick?: () => void;
9
+ };
10
+ type StackedCardProps = {
11
+ title?: string;
12
+ description?: string;
13
+ tags?: string[];
14
+ imageUrl?: string | null;
15
+ variant?: Variant;
16
+ action?: Action;
17
+ align?: Align;
18
+ tone?: Tone;
19
+ showImage?: boolean;
20
+ children?: ReactNode;
21
+ frameClassName?: string;
22
+ cardClassName?: string;
23
+ bodyClassName?: string;
24
+ id?: string;
25
+ style?: CSSProperties;
26
+ };
27
+ export declare const StackedCard: ({ title, description, tags, imageUrl, variant, action, align, tone, showImage, children, frameClassName, cardClassName, bodyClassName, id, style, }: StackedCardProps) => import("react/jsx-runtime").JSX.Element;
28
+ export {};
29
+ //# sourceMappingURL=StackedCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StackedCard.d.ts","sourceRoot":"","sources":["../../src/components/StackedCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEtD,KAAK,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC;AACrD,KAAK,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC/B,KAAK,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhC,KAAK,MAAM,GAAG;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,KAAK,gBAAgB,GAAG;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB,CAAC;AAoBF,eAAO,MAAM,WAAW,GAAI,qJAgBzB,gBAAgB,4CA4DlB,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const variantClass = (variant) => {
3
+ switch (variant) {
4
+ case "games":
5
+ return "ef-stacked-variant-games";
6
+ case "mods":
7
+ return "ef-stacked-variant-mods";
8
+ case "servers":
9
+ return "ef-stacked-variant-servers";
10
+ case "apps":
11
+ return "ef-stacked-variant-apps";
12
+ default:
13
+ return "";
14
+ }
15
+ };
16
+ const alignClass = (align) => (align === "left" ? "ef-stacked-body-left" : "");
17
+ const toneClass = (tone) => (tone === "plain" ? "ef-stacked-body-plain" : "ef-stacked-body");
18
+ export const StackedCard = ({ title = "", description = "", tags, imageUrl, variant, action, align = "center", tone = "default", showImage = true, children, frameClassName, cardClassName, bodyClassName, id, style, }) => {
19
+ const bodyClasses = [toneClass(tone), alignClass(align), bodyClassName]
20
+ .filter(Boolean)
21
+ .join(" ");
22
+ return (_jsx("article", { className: ["ef-stacked-frame", variantClass(variant), frameClassName].filter(Boolean).join(" "), id: id, style: style, children: _jsxs("div", { className: ["ef-stacked-card", cardClassName].filter(Boolean).join(" "), children: [showImage ? (_jsx("div", { className: ["ef-stacked-image", imageUrl ? "ef-stacked-image-photo" : ""]
23
+ .filter(Boolean)
24
+ .join(" "), style: imageUrl
25
+ ? {
26
+ backgroundImage: `linear-gradient(180deg, rgba(10, 12, 22, 0.1), rgba(10, 12, 22, 0.85)), url(${imageUrl})`,
27
+ backgroundSize: "cover",
28
+ backgroundPosition: "center",
29
+ }
30
+ : undefined })) : null, _jsxs("div", { className: bodyClasses, children: [children ? (children) : (_jsxs(_Fragment, { children: [_jsx("h3", { children: title }), _jsx("p", { children: description }), tags?.length ? (_jsx("div", { className: `ef-stacked-tags ${align === "left" ? "ef-stacked-tags-left" : ""}`, children: tags.map((tag) => (_jsx("span", { className: "ef-stacked-tag", children: tag }, tag))) })) : null] })), action ? (action.href ? (_jsx("a", { className: "ef-stacked-action", href: action.href, children: action.label })) : (_jsx("button", { className: "ef-stacked-action", type: "button", onClick: action.onClick, children: action.label }))) : null] })] }) }));
31
+ };
@@ -0,0 +1,12 @@
1
+ import { type HTMLAttributes } from "react";
2
+ type StatDotsProps = HTMLAttributes<HTMLDivElement> & {
3
+ label: string;
4
+ count?: number;
5
+ value?: number;
6
+ defaultValue?: number;
7
+ disabled?: boolean;
8
+ onChange?: (value: number) => void;
9
+ };
10
+ export declare const StatDots: ({ label, count, value, defaultValue, disabled, onChange, className, ...rest }: StatDotsProps) => import("react/jsx-runtime").JSX.Element;
11
+ export {};
12
+ //# sourceMappingURL=StatDots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatDots.d.ts","sourceRoot":"","sources":["../../src/components/StatDots.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAGtD,KAAK,aAAa,GAAG,cAAc,CAAC,cAAc,CAAC,GAAG;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,+EAStB,aAAa,4CA+Bf,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Panel } from "./Panel";
4
+ export const StatDots = ({ label, count = 4, value, defaultValue = 0, disabled = false, onChange, className, ...rest }) => {
5
+ const [internalValue, setInternalValue] = useState(defaultValue);
6
+ const currentValue = Math.max(0, Math.min(count, value ?? internalValue));
7
+ const handleSelect = (nextValue) => {
8
+ if (disabled)
9
+ return;
10
+ const resolvedValue = nextValue === currentValue ? 0 : nextValue;
11
+ if (value === undefined) {
12
+ setInternalValue(resolvedValue);
13
+ }
14
+ onChange?.(resolvedValue);
15
+ };
16
+ const classes = ["ef-stat-dots", className].filter(Boolean).join(" ");
17
+ return (_jsxs("div", { className: classes, ...rest, children: [_jsx(Panel, { variant: "highlight", borderWidth: 1, className: "ef-stat-dots__label", children: label }), _jsx("div", { className: "ef-stat-dots__dots", role: "group", "aria-label": label, children: Array.from({ length: count }, (_, index) => (_jsx("input", { className: "ef-stat-dots__input", type: "checkbox", checked: index < currentValue, disabled: disabled, onChange: () => handleSelect(index + 1) }, `${label}-${index}`))) })] }));
18
+ };
@@ -0,0 +1,9 @@
1
+ import type { InputHTMLAttributes } from "react";
2
+ type ToggleProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type"> & {
3
+ label?: string;
4
+ description?: string;
5
+ variant?: "switch" | "checkbox";
6
+ };
7
+ export declare const Toggle: ({ label, description, className, variant, ...props }: ToggleProps) => import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=Toggle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Toggle.d.ts","sourceRoot":"","sources":["../../src/components/Toggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAEjD,KAAK,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,GAAG;IACvE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;CACjC,CAAC;AAEF,eAAO,MAAM,MAAM,GAAI,sDAMpB,WAAW,4CAuBb,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export const Toggle = ({ label, description, className, variant = "switch", ...props }) => (_jsxs("label", { className: ["ef-toggle", `ef-toggle--${variant}`, className]
3
+ .filter(Boolean)
4
+ .join(" "), children: [_jsx("input", { type: "checkbox", className: "ef-toggle-input", ...props }), variant === "switch" ? (_jsx("span", { className: "ef-toggle-track", "aria-hidden": "true", children: _jsx("span", { className: "ef-toggle-thumb" }) })) : (_jsx("span", { className: "ef-toggle-box", "aria-hidden": "true", children: _jsx("span", { className: "ef-toggle-check" }) })), label ? (_jsxs("span", { className: "ef-toggle-label", children: [_jsx("span", { className: "ef-toggle-text", children: label }), description ? _jsx("span", { className: "ef-toggle-description", children: description }) : null] })) : null] }));
@@ -0,0 +1,26 @@
1
+ export type ThemeMode = string;
2
+ export { AccessGate } from "./components/AccessGate";
3
+ export { Button } from "./components/Button";
4
+ export { FormField } from "./components/FormField";
5
+ export { Input, Textarea, Select } from "./components/Input";
6
+ export { Panel } from "./components/Panel";
7
+ export { StackedCard } from "./components/StackedCard";
8
+ export { Dropdown } from "./components/Dropdown";
9
+ export { MainHeader } from "./components/MainHeader";
10
+ export { FloatingFooter } from "./components/FloatingFooter";
11
+ export { Modal } from "./components/Modal";
12
+ export { PreferencesModal } from "./components/PreferencesModal";
13
+ export { Toggle } from "./components/Toggle";
14
+ export { Slider } from "./components/Slider";
15
+ export { StatDots } from "./components/StatDots";
16
+ export { SideMenu, SideMenuSubmenu } from "./components/SideMenu";
17
+ type ThemeOptions<T extends ThemeMode> = {
18
+ storageKey: string;
19
+ defaultTheme: T;
20
+ allowed: readonly T[];
21
+ dataAttribute?: string;
22
+ bodyClass?: string | null;
23
+ };
24
+ export declare const getStoredTheme: <T extends ThemeMode>(options: ThemeOptions<T>) => T;
25
+ export declare const applyTheme: <T extends ThemeMode>(theme: T, options: ThemeOptions<T>) => void;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAElE,KAAK,YAAY,CAAC,CAAC,SAAS,SAAS,IAAI;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,CAAC,CAAC;IAChB,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,CAAC,SAAS,SAAS,EAAE,SAAS,YAAY,CAAC,CAAC,CAAC,MAS3E,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,CAAC,SAAS,SAAS,EAAE,OAAO,CAAC,EAAE,SAAS,YAAY,CAAC,CAAC,CAAC,SAOjF,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ export { AccessGate } from "./components/AccessGate";
2
+ export { Button } from "./components/Button";
3
+ export { FormField } from "./components/FormField";
4
+ export { Input, Textarea, Select } from "./components/Input";
5
+ export { Panel } from "./components/Panel";
6
+ export { StackedCard } from "./components/StackedCard";
7
+ export { Dropdown } from "./components/Dropdown";
8
+ export { MainHeader } from "./components/MainHeader";
9
+ export { FloatingFooter } from "./components/FloatingFooter";
10
+ export { Modal } from "./components/Modal";
11
+ export { PreferencesModal } from "./components/PreferencesModal";
12
+ export { Toggle } from "./components/Toggle";
13
+ export { Slider } from "./components/Slider";
14
+ export { StatDots } from "./components/StatDots";
15
+ export { SideMenu, SideMenuSubmenu } from "./components/SideMenu";
16
+ export const getStoredTheme = (options) => {
17
+ const stored = localStorage.getItem(options.storageKey);
18
+ if (stored && options.allowed.includes(stored)) {
19
+ return stored;
20
+ }
21
+ if (stored === "dark" && options.allowed.includes("galaxy")) {
22
+ return "galaxy";
23
+ }
24
+ return options.defaultTheme;
25
+ };
26
+ export const applyTheme = (theme, options) => {
27
+ const dataAttribute = options.dataAttribute ?? "data-theme";
28
+ if (options.bodyClass) {
29
+ document.body.classList.toggle(options.bodyClass, theme === options.defaultTheme);
30
+ }
31
+ document.documentElement.setAttribute(dataAttribute, theme);
32
+ localStorage.setItem(options.storageKey, theme);
33
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@enderfall/ui",
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "styles.css"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc -p tsconfig.build.json",
14
+ "dev": "tsc -p tsconfig.build.json --watch"
15
+ },
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js"
20
+ },
21
+ "./styles.css": "./styles.css"
22
+ },
23
+ "peerDependencies": {
24
+ "react": "^18.2.0"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^18.2.43",
28
+ "typescript": "^5.3.3"
29
+ }
30
+ }
package/styles.css ADDED
@@ -0,0 +1,17 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap");
2
+ @import "./src/base.css";
3
+ @import "./src/theme.css";
4
+ @import "./src/components/BookmarkDropdown.css";
5
+ @import "./src/components/AccessGate.css";
6
+ @import "./src/components/Button.css";
7
+ @import "./src/components/Input.css";
8
+ @import "./src/components/Toggle.css";
9
+ @import "./src/components/Slider.css";
10
+ @import "./src/components/StackedCard.css";
11
+ @import "./src/components/Panel.css";
12
+ @import "./src/components/Modal.css";
13
+ @import "./src/components/HeaderMenu.css";
14
+ @import "./src/components/MainHeader.css";
15
+ @import "./src/components/FloatingFooter.css";
16
+ @import "./src/components/UserMenu.css";
17
+ @import "./src/components/StatDots.css";