@enderfall/ui 0.1.0 → 0.1.4

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 (45) hide show
  1. package/assets/brand/enderfall-lockup.png +0 -0
  2. package/assets/brand/enderfall-lockup.svg +8 -0
  3. package/assets/brand/enderfall-mark.png +0 -0
  4. package/assets/brand/enderfall-mark.svg +8 -0
  5. package/dist/components/Button.d.ts +2 -1
  6. package/dist/components/Button.d.ts.map +1 -1
  7. package/dist/components/Button.js +8 -1
  8. package/dist/components/Dropdown.d.ts.map +1 -1
  9. package/dist/components/Dropdown.js +2 -2
  10. package/package.json +5 -2
  11. package/src/base.css +160 -0
  12. package/src/components/AccessGate.css +24 -0
  13. package/src/components/AccessGate.tsx +61 -0
  14. package/src/components/BookmarkDropdown.css +220 -0
  15. package/src/components/Button.css +183 -0
  16. package/src/components/Button.tsx +20 -0
  17. package/src/components/Dropdown.tsx +570 -0
  18. package/src/components/FloatingFooter.css +49 -0
  19. package/src/components/FloatingFooter.tsx +27 -0
  20. package/src/components/FormField.tsx +29 -0
  21. package/src/components/HeaderMenu.css +280 -0
  22. package/src/components/Input.css +68 -0
  23. package/src/components/Input.tsx +23 -0
  24. package/src/components/MainHeader.css +167 -0
  25. package/src/components/MainHeader.tsx +51 -0
  26. package/src/components/Modal.css +282 -0
  27. package/src/components/Modal.tsx +142 -0
  28. package/src/components/Panel.css +71 -0
  29. package/src/components/Panel.tsx +31 -0
  30. package/src/components/PreferencesModal.tsx +67 -0
  31. package/src/components/SideMenu.tsx +239 -0
  32. package/src/components/Slider.css +114 -0
  33. package/src/components/Slider.tsx +33 -0
  34. package/src/components/StackedCard.css +180 -0
  35. package/src/components/StackedCard.tsx +125 -0
  36. package/src/components/StatDots.css +122 -0
  37. package/src/components/StatDots.tsx +53 -0
  38. package/src/components/Tabs.css +108 -0
  39. package/src/components/Tabs.tsx +68 -0
  40. package/src/components/Toggle.css +161 -0
  41. package/src/components/Toggle.tsx +38 -0
  42. package/src/components/UserMenu.css +273 -0
  43. package/src/index.ts +45 -0
  44. package/src/theme.css +353 -0
  45. package/styles.css +1 -0
@@ -0,0 +1,280 @@
1
+ .ef-menu-bar {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 16px;
5
+ padding: 0;
6
+ border: 0;
7
+ background: transparent;
8
+ box-shadow: none;
9
+ width: fit-content;
10
+ }
11
+
12
+ .ef-menu-group {
13
+ position: relative;
14
+ }
15
+
16
+ .ef-menu-button {
17
+ padding: 8px 18px;
18
+ border-radius: 8px;
19
+ background: rgba(15, 18, 28, 0.7);
20
+ box-shadow: none;
21
+ border: 2px solid transparent;
22
+ font-weight: 600;
23
+ color: rgba(238, 241, 246, 0.75);
24
+ text-transform: uppercase;
25
+ letter-spacing: 0.08em;
26
+ transition: color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
27
+ cursor: pointer;
28
+ }
29
+
30
+ :root[data-theme="light"] .ef-menu-button,
31
+ :root[data-theme="plain-light"] .ef-menu-button,
32
+ :root[data-theme="galaxy"] .ef-menu-button,
33
+ :root[data-theme="plain-dark"] .ef-menu-button,
34
+ :root[data-theme="system"] .ef-menu-button {
35
+ background: rgba(15, 18, 28, 0.7);
36
+ border-radius: 8px;
37
+ border: 2px solid transparent;
38
+ padding: 8px 18px;
39
+ font-weight: 600;
40
+ text-transform: uppercase;
41
+ letter-spacing: 0.08em;
42
+ box-shadow: none;
43
+ color: rgba(238, 241, 246, 0.75);
44
+ }
45
+
46
+ .ef-menu-button:hover {
47
+ color: #eef1f6;
48
+ box-shadow: 0 0 18px rgba(124, 77, 255, 0.35);
49
+ transform: translateY(-1px);
50
+ }
51
+
52
+ :root[data-theme="light"] .ef-menu-button:hover,
53
+ :root[data-theme="plain-light"] .ef-menu-button:hover,
54
+ :root[data-theme="galaxy"] .ef-menu-button:hover,
55
+ :root[data-theme="plain-dark"] .ef-menu-button:hover,
56
+ :root[data-theme="system"] .ef-menu-button:hover {
57
+ color: #eef1f6;
58
+ box-shadow: 0 0 18px rgba(124, 77, 255, 0.35);
59
+ transform: translateY(-1px);
60
+ }
61
+
62
+ .ef-menu-button:focus-visible {
63
+ color: #eef1f6;
64
+ box-shadow: 0 0 20px rgba(124, 77, 255, 0.45);
65
+ transform: translateY(-1px);
66
+ }
67
+
68
+ .ef-menu-button[data-open="true"],
69
+ .ef-menu-group[data-open="true"] > .ef-menu-button {
70
+ background:
71
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
72
+ var(--ef-border-gradient) border-box;
73
+ color: var(--text-strong);
74
+ box-shadow: 0 0 24px rgba(124, 77, 255, 0.6);
75
+ }
76
+
77
+ .ef-menu-popover {
78
+ position: absolute;
79
+ top: calc(100% + 6px);
80
+ left: 0;
81
+ min-width: 180px;
82
+ background:
83
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
84
+ var(--ef-border-gradient-soft) border-box;
85
+ border-radius: var(--ef-control-radius, 12px);
86
+ border: 2px solid transparent;
87
+ box-shadow: var(--shadow);
88
+ backdrop-filter: blur(12px);
89
+ padding: 6px;
90
+ z-index: 30;
91
+ opacity: 0;
92
+ transform: translateY(-4px);
93
+ transition: opacity 0.14s ease, transform 0.14s ease;
94
+ pointer-events: none;
95
+ }
96
+
97
+ .ef-menu-group:hover .ef-menu-popover,
98
+ .ef-menu-popover:hover,
99
+ .ef-menu-popover[data-open="true"] {
100
+ opacity: 1;
101
+ transform: translateY(0);
102
+ pointer-events: auto;
103
+ }
104
+
105
+ .ef-menu-divider {
106
+ height: 1px;
107
+ background: var(--menu-line);
108
+ margin: 6px 4px;
109
+ }
110
+
111
+ .ef-menu-item {
112
+ width: 100%;
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 10px;
116
+ text-align: left;
117
+ padding: 10px 14px;
118
+ border-radius: 8px;
119
+ background:
120
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
121
+ var(--ef-border-gradient) border-box;
122
+ font-weight: 600;
123
+ font-size: 0.85rem;
124
+ text-transform: uppercase;
125
+ letter-spacing: 0.06em;
126
+ margin-bottom: 6px;
127
+ border: 2px solid transparent;
128
+ color: var(--text-strong);
129
+ box-shadow: 0 0 16px rgba(124, 77, 255, 0.25);
130
+ cursor: pointer;
131
+ }
132
+
133
+ .ef-menu-icon {
134
+ width: 16px;
135
+ height: 16px;
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ color: var(--menu-muted);
140
+ }
141
+
142
+ .ef-menu-icon svg {
143
+ width: 16px;
144
+ height: 16px;
145
+ }
146
+
147
+ :root[data-theme="light"] .ef-menu-item,
148
+ :root[data-theme="plain-light"] .ef-menu-item,
149
+ :root[data-theme="galaxy"] .ef-menu-item,
150
+ :root[data-theme="plain-dark"] .ef-menu-item,
151
+ :root[data-theme="system"] .ef-menu-item {
152
+ background:
153
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
154
+ var(--ef-border-gradient) border-box;
155
+ border-radius: 8px;
156
+ border: 2px solid transparent;
157
+ padding: 10px 14px;
158
+ font-weight: 600;
159
+ text-transform: uppercase;
160
+ letter-spacing: 0.06em;
161
+ box-shadow: 0 0 16px rgba(124, 77, 255, 0.25);
162
+ color: var(--text-strong);
163
+ }
164
+
165
+ .ef-menu-item:last-child {
166
+ margin-bottom: 0;
167
+ }
168
+
169
+ .side-menu--header > .ef-menu-item {
170
+ margin-bottom: 0;
171
+ }
172
+
173
+ .ef-menu-item:hover {
174
+ box-shadow: 0 0 18px rgba(124, 77, 255, 0.35);
175
+ transform: translateY(-1px);
176
+ }
177
+
178
+ .ef-menu-group-title {
179
+ padding: 6px 10px 4px;
180
+ font-size: 0.7rem;
181
+ font-weight: 700;
182
+ color: var(--menu-muted);
183
+ text-transform: uppercase;
184
+ letter-spacing: 0.08em;
185
+ }
186
+
187
+ .ef-menu-item.has-submenu {
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: space-between;
191
+ position: relative;
192
+ }
193
+
194
+ .ef-menu-sub-caret {
195
+ color: var(--menu-muted);
196
+ display: inline-flex;
197
+ align-items: center;
198
+ }
199
+
200
+ .ef-menu-sub-caret svg {
201
+ width: 14px;
202
+ height: 14px;
203
+ transform: rotate(-90deg);
204
+ }
205
+
206
+ .ef-menu-item.has-submenu > span:first-child {
207
+ flex: 1;
208
+ }
209
+
210
+ .ef-menu-item-trigger {
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: space-between;
214
+ gap: 10px;
215
+ width: 100%;
216
+ background: transparent;
217
+ border: 1px solid transparent;
218
+ padding: 8px 10px;
219
+ color: inherit;
220
+ font: inherit;
221
+ cursor: pointer;
222
+ }
223
+
224
+
225
+ .ef-menu-sub {
226
+ position: absolute;
227
+ top: 0;
228
+ left: calc(100% + 6px);
229
+ min-width: 180px;
230
+ background:
231
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
232
+ var(--ef-border-gradient-soft) border-box;
233
+ border-radius: var(--ef-control-radius, 12px);
234
+ border: 2px solid transparent;
235
+ box-shadow: var(--shadow);
236
+ backdrop-filter: blur(12px);
237
+ padding: 6px;
238
+ z-index: 40;
239
+ opacity: 0;
240
+ pointer-events: none;
241
+ transform: translateY(-4px);
242
+ transition: opacity 0.12s ease, transform 0.12s ease;
243
+ transition-delay: 0.12s;
244
+ }
245
+
246
+ .ef-menu-sub.ef-menu-sub--header {
247
+ margin-top: -6px;
248
+ }
249
+
250
+ .ef-menu-sub.ef-menu-sub--header .theme-preview {
251
+ margin-bottom: 6px;
252
+ }
253
+
254
+ .ef-menu-sub.ef-menu-sub--header .theme-preview:last-child {
255
+ margin-bottom: 0;
256
+ }
257
+
258
+ .ef-menu-sub::before {
259
+ content: "";
260
+ position: absolute;
261
+ left: -8px;
262
+ top: 0;
263
+ width: 8px;
264
+ height: 100%;
265
+ }
266
+
267
+ .ef-menu-item.has-submenu:hover .ef-menu-sub {
268
+ opacity: 1;
269
+ pointer-events: auto;
270
+ transform: translateY(0);
271
+ transition-delay: 0s;
272
+ }
273
+
274
+ .ef-menu-sub.is-open {
275
+ opacity: 1;
276
+ pointer-events: auto;
277
+ transform: translateY(0);
278
+ transition-delay: 0s;
279
+ }
280
+
@@ -0,0 +1,68 @@
1
+ .ef-field {
2
+ display: grid;
3
+ gap: 6px;
4
+ }
5
+
6
+ .ef-field-label {
7
+ display: flex;
8
+ align-items: center;
9
+ gap: 6px;
10
+ font-size: 0.85rem;
11
+ color: var(--ef-field-label);
12
+ font-weight: 500;
13
+ }
14
+
15
+ .ef-field-required {
16
+ color: var(--ef-field-required);
17
+ font-weight: 600;
18
+ }
19
+
20
+ .ef-field-helper {
21
+ font-size: 0.8rem;
22
+ color: var(--ef-field-helper);
23
+ }
24
+
25
+ .ef-field-error {
26
+ font-size: 0.8rem;
27
+ color: var(--ef-field-error);
28
+ }
29
+
30
+ .ef-input,
31
+ .ef-textarea,
32
+ .ef-select {
33
+ padding: 10px 18px;
34
+ border-radius: var(--ef-control-radius, 8px);
35
+ border: 2px solid transparent;
36
+ background:
37
+ linear-gradient(var(--ef-input-surface), var(--ef-input-surface)) padding-box,
38
+ var(--ef-input-border) border-box;
39
+ color: var(--ef-input-text);
40
+ font-size: 0.95rem;
41
+ font-family: inherit;
42
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
43
+ }
44
+
45
+ .ef-textarea {
46
+ resize: vertical;
47
+ }
48
+
49
+ .ef-input::placeholder,
50
+ .ef-textarea::placeholder {
51
+ color: var(--ef-input-placeholder);
52
+ }
53
+
54
+ .ef-input:focus,
55
+ .ef-textarea:focus,
56
+ .ef-select:focus {
57
+ outline: none;
58
+ outline: 2px solid var(--ef-input-focus);
59
+ outline-offset: 2px;
60
+ box-shadow: var(--ef-input-shadow);
61
+ }
62
+
63
+ .ef-field.has-error .ef-input,
64
+ .ef-field.has-error .ef-textarea,
65
+ .ef-field.has-error .ef-select {
66
+ border-color: var(--ef-field-error);
67
+ box-shadow: none;
68
+ }
@@ -0,0 +1,23 @@
1
+ import type { InputHTMLAttributes, TextareaHTMLAttributes, SelectHTMLAttributes } from "react";
2
+
3
+ type BaseProps = {
4
+ className?: string;
5
+ };
6
+
7
+ type InputProps = InputHTMLAttributes<HTMLInputElement> & BaseProps;
8
+ type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & BaseProps;
9
+ type SelectProps = SelectHTMLAttributes<HTMLSelectElement> & BaseProps;
10
+
11
+ const cx = (...classes: Array<string | undefined>) => classes.filter(Boolean).join(" ");
12
+
13
+ export const Input = ({ className, ...props }: InputProps) => (
14
+ <input {...props} className={cx("ef-input", className)} />
15
+ );
16
+
17
+ export const Textarea = ({ className, ...props }: TextareaProps) => (
18
+ <textarea {...props} className={cx("ef-textarea", className)} />
19
+ );
20
+
21
+ export const Select = ({ className, ...props }: SelectProps) => (
22
+ <select {...props} className={cx("ef-select", className)} />
23
+ );
@@ -0,0 +1,167 @@
1
+ .ef-main-header {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ gap: 20px;
6
+ margin: 0 0 28px;
7
+ backdrop-filter: blur(12px);
8
+ font-family: var(--font-main, "Inter", "Segoe UI", Arial, sans-serif);
9
+ position: relative;
10
+ z-index: 20;
11
+ }
12
+
13
+ .ef-header-left {
14
+ display: flex;
15
+ flex-direction: row;
16
+ gap: 12px;
17
+ flex: 1;
18
+ min-width: 0;
19
+ }
20
+
21
+ .ef-header-actions {
22
+ display: flex;
23
+ align-items: center;
24
+ gap: 12px;
25
+ }
26
+
27
+ .ef-brand {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 14px;
31
+ }
32
+
33
+ .ef-logo {
34
+ width: 46px;
35
+ height: 46px;
36
+ }
37
+
38
+ .ef-logo-fallback {
39
+ width: 46px;
40
+ height: 46px;
41
+ border-radius: 14px;
42
+ display: grid;
43
+ place-items: center;
44
+ background: var(--card);
45
+ border: 1px solid var(--card-border);
46
+ color: var(--text-strong);
47
+ font-weight: 700;
48
+ }
49
+
50
+ .ef-brand-name {
51
+ font-weight: 700;
52
+ font-size: 1rem;
53
+ letter-spacing: 0.04em;
54
+ color: var(--text-strong);
55
+ }
56
+
57
+ .ef-tagline {
58
+ color: var(--text-muted);
59
+ font-size: 0.8rem;
60
+ }
61
+
62
+ .actions {
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 10px;
66
+ }
67
+
68
+ .icon-action {
69
+ position: relative;
70
+ border: 2px solid transparent;
71
+ background:
72
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
73
+ var(--ef-border-gradient) border-box;
74
+ color: var(--text-strong);
75
+ border-radius: 10px;
76
+ width: 42px;
77
+ height: 42px;
78
+ display: grid;
79
+ place-items: center;
80
+ cursor: pointer;
81
+ box-shadow: var(--shadow, 0 24px 60px rgba(0, 0, 0, 0.35));
82
+ }
83
+
84
+ :root[data-theme="atelier"] .icon-action {
85
+ border-radius: 0;
86
+ }
87
+
88
+ .icon-action.small {
89
+ width: 36px;
90
+ height: 36px;
91
+ box-shadow: none;
92
+ }
93
+
94
+ .icon-action:disabled {
95
+ opacity: 0.5;
96
+ cursor: not-allowed;
97
+ }
98
+
99
+ .icon-action.spin svg {
100
+ animation: spin 0.9s linear infinite;
101
+ }
102
+
103
+ .icon-badge {
104
+ position: absolute;
105
+ top: -6px;
106
+ right: -6px;
107
+ min-width: 18px;
108
+ height: 18px;
109
+ padding: 0 4px;
110
+ border-radius: 999px;
111
+ background: var(--accent-gold);
112
+ color: #090b12;
113
+ font-size: 0.65rem;
114
+ font-weight: 700;
115
+ display: grid;
116
+ place-items: center;
117
+ }
118
+
119
+ @media (max-width: 760px) {
120
+ .ef-main-header {
121
+ width: calc(100% + 28px);
122
+ max-width: none;
123
+ margin: 0;
124
+ margin-left: -14px;
125
+ margin-right: -14px;
126
+ padding: 4px 12px;
127
+ border-radius: 0 0 18px 18px;
128
+ z-index: 40;
129
+ gap: 10px;
130
+ }
131
+
132
+ .ef-main-header .ef-menu-bar {
133
+ display: none;
134
+ }
135
+
136
+ .ef-header-left {
137
+ width: auto;
138
+ flex-wrap: nowrap;
139
+ align-items: center;
140
+ gap: 10px;
141
+ min-width: 0;
142
+ }
143
+
144
+ .ef-header-actions {
145
+ width: auto;
146
+ justify-content: flex-end;
147
+ flex-wrap: nowrap;
148
+ gap: 8px;
149
+ flex-shrink: 0;
150
+ align-items: center;
151
+ }
152
+
153
+ .ef-brand {
154
+ min-width: 0;
155
+ gap: 10px;
156
+ }
157
+
158
+ .ef-brand > div {
159
+ display: none;
160
+ }
161
+ }
162
+
163
+ @keyframes spin {
164
+ to {
165
+ transform: rotate(360deg);
166
+ }
167
+ }
@@ -0,0 +1,51 @@
1
+ import type { ReactNode } from "react";
2
+ import { Dropdown, type HeaderMenuItem } from "./Dropdown";
3
+ import { Panel } from "./Panel";
4
+
5
+ type MainHeaderProps = {
6
+ title?: string;
7
+ subtitle?: string;
8
+ logoSrc?: string;
9
+ menus: HeaderMenuItem[];
10
+ menuOpen: string | null;
11
+ onOpenMenu: (id: string) => void;
12
+ onCloseMenu: () => void;
13
+ actions?: ReactNode;
14
+ };
15
+
16
+ export const MainHeader = ({
17
+ title = "Enderfall",
18
+ subtitle = "Galaxy tools for creators",
19
+ logoSrc,
20
+ menus,
21
+ menuOpen,
22
+ onOpenMenu,
23
+ onCloseMenu,
24
+ actions,
25
+ }: MainHeaderProps) => {
26
+ return (
27
+ <Panel variant="header" borderWidth={2} className="ef-main-header">
28
+ <div className="ef-header-left">
29
+ <div className="ef-brand">
30
+ {logoSrc ? (
31
+ <img src={logoSrc} alt={title} className="ef-logo" />
32
+ ) : (
33
+ <div className="ef-logo-fallback">E</div>
34
+ )}
35
+ <div>
36
+ <div className="ef-brand-name">{title}</div>
37
+ {subtitle ? <div className="ef-tagline">{subtitle}</div> : null}
38
+ </div>
39
+ </div>
40
+ <Dropdown
41
+ variant="header"
42
+ menus={menus}
43
+ menuOpen={menuOpen}
44
+ onOpenMenu={onOpenMenu}
45
+ onCloseMenu={onCloseMenu}
46
+ />
47
+ </div>
48
+ <div className="ef-header-actions">{actions}</div>
49
+ </Panel>
50
+ );
51
+ };