@enderfall/ui 0.1.0 → 0.1.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.
Files changed (42) 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/Toggle.css +161 -0
  39. package/src/components/Toggle.tsx +38 -0
  40. package/src/components/UserMenu.css +273 -0
  41. package/src/index.ts +44 -0
  42. package/src/theme.css +353 -0
@@ -0,0 +1,122 @@
1
+ .ef-stat-dots {
2
+ display: grid;
3
+ grid-template-columns: var(--ef-stat-label-width, 180px) 1fr;
4
+ align-items: center;
5
+ gap: 12px;
6
+ font-size: 16px;
7
+ }
8
+
9
+
10
+ .ef-stat-dots__dots {
11
+ display: inline-flex;
12
+ gap: 4px;
13
+ }
14
+
15
+ .ef-stat-dots__input {
16
+ appearance: none;
17
+ width: 18px;
18
+ height: 18px;
19
+ border: 2px solid var(--line);
20
+ border-radius: var(--ef-stat-dots-radius, 6px);
21
+ background: var(--card);
22
+ cursor: pointer;
23
+ position: relative;
24
+ display: inline-grid;
25
+ place-items: center;
26
+ transition: border-color 0.2s ease, background 0.2s ease;
27
+ }
28
+
29
+ .ef-stat-dots__input::before {
30
+ content: "";
31
+ width: 8px;
32
+ height: 8px;
33
+ border-radius: var(--ef-stat-dots-check-radius, 2px);
34
+ background: var(--ef-toggle-check-bg, var(--ef-slider-border-color, var(--line-strong)));
35
+ transform: scale(0);
36
+ transition: transform 0.2s ease;
37
+ }
38
+
39
+ .ef-stat-dots__input:disabled {
40
+ cursor: not-allowed;
41
+ opacity: 0.6;
42
+ }
43
+
44
+ .ef-stat-dots__input:checked {
45
+ border-color: transparent;
46
+ background:
47
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
48
+ var(--ef-border-gradient) border-box;
49
+ box-shadow: none;
50
+ }
51
+
52
+ .ef-stat-dots__input:checked::before {
53
+ transform: scale(1);
54
+ }
55
+
56
+ :root[data-theme="galaxy"] .ef-stat-dots__input,
57
+ :root[data-theme="plain-dark"] .ef-stat-dots__input,
58
+ :root[data-theme="light"] .ef-stat-dots__input,
59
+ :root[data-theme="plain-light"] .ef-stat-dots__input,
60
+ [data-theme="galaxy"] .ef-stat-dots__input,
61
+ [data-theme="plain-dark"] .ef-stat-dots__input,
62
+ [data-theme="light"] .ef-stat-dots__input,
63
+ [data-theme="plain-light"] .ef-stat-dots__input,
64
+ .ef-stat-dots[data-ef-theme="galaxy"] .ef-stat-dots__input,
65
+ .ef-stat-dots[data-ef-theme="plain-dark"] .ef-stat-dots__input,
66
+ .ef-stat-dots[data-ef-theme="light"] .ef-stat-dots__input,
67
+ .ef-stat-dots[data-ef-theme="plain-light"] .ef-stat-dots__input {
68
+ background:
69
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
70
+ var(--ef-border-gradient) border-box;
71
+ border-color: transparent;
72
+ background-clip: padding-box, border-box;
73
+ background-origin: border-box;
74
+ }
75
+
76
+ :root[data-theme="galaxy"] .ef-stat-dots__input::before,
77
+ :root[data-theme="plain-dark"] .ef-stat-dots__input::before,
78
+ :root[data-theme="light"] .ef-stat-dots__input::before,
79
+ :root[data-theme="plain-light"] .ef-stat-dots__input::before,
80
+ [data-theme="galaxy"] .ef-stat-dots__input::before,
81
+ [data-theme="plain-dark"] .ef-stat-dots__input::before,
82
+ [data-theme="light"] .ef-stat-dots__input::before,
83
+ [data-theme="plain-light"] .ef-stat-dots__input::before,
84
+ .ef-stat-dots[data-ef-theme="galaxy"] .ef-stat-dots__input::before,
85
+ .ef-stat-dots[data-ef-theme="plain-dark"] .ef-stat-dots__input::before,
86
+ .ef-stat-dots[data-ef-theme="light"] .ef-stat-dots__input::before,
87
+ .ef-stat-dots[data-ef-theme="plain-light"] .ef-stat-dots__input::before {
88
+ background: var(--ef-border-gradient);
89
+ }
90
+
91
+ :root[data-theme="galaxy"] .ef-stat-dots__input:checked,
92
+ :root[data-theme="plain-dark"] .ef-stat-dots__input:checked,
93
+ :root[data-theme="light"] .ef-stat-dots__input:checked,
94
+ :root[data-theme="plain-light"] .ef-stat-dots__input:checked,
95
+ [data-theme="galaxy"] .ef-stat-dots__input:checked,
96
+ [data-theme="plain-dark"] .ef-stat-dots__input:checked,
97
+ [data-theme="light"] .ef-stat-dots__input:checked,
98
+ [data-theme="plain-light"] .ef-stat-dots__input:checked,
99
+ .ef-stat-dots[data-ef-theme="galaxy"] .ef-stat-dots__input:checked,
100
+ .ef-stat-dots[data-ef-theme="plain-dark"] .ef-stat-dots__input:checked,
101
+ .ef-stat-dots[data-ef-theme="light"] .ef-stat-dots__input:checked,
102
+ .ef-stat-dots[data-ef-theme="plain-light"] .ef-stat-dots__input:checked {
103
+ background:
104
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
105
+ var(--ef-border-gradient) border-box;
106
+ border-color: transparent;
107
+ background-clip: padding-box, border-box;
108
+ background-origin: border-box;
109
+ box-shadow: none;
110
+ }
111
+
112
+ :root[data-theme="atelier"] .ef-stat-dots__input,
113
+ [data-theme="atelier"] .ef-stat-dots__input,
114
+ .ef-stat-dots[data-ef-theme="atelier"] .ef-stat-dots__input {
115
+ border-radius: 0;
116
+ }
117
+
118
+ :root[data-theme="atelier"] .ef-stat-dots__input::before,
119
+ [data-theme="atelier"] .ef-stat-dots__input::before,
120
+ .ef-stat-dots[data-ef-theme="atelier"] .ef-stat-dots__input::before {
121
+ border-radius: 0;
122
+ }
@@ -0,0 +1,53 @@
1
+ import { useState, type HTMLAttributes } from "react";
2
+ import { Panel } from "./Panel";
3
+
4
+ type StatDotsProps = HTMLAttributes<HTMLDivElement> & {
5
+ label: string;
6
+ count?: number;
7
+ value?: number;
8
+ defaultValue?: number;
9
+ disabled?: boolean;
10
+ onChange?: (value: number) => void;
11
+ };
12
+
13
+ export const StatDots = ({
14
+ label,
15
+ count = 4,
16
+ value,
17
+ defaultValue = 0,
18
+ disabled = false,
19
+ onChange,
20
+ className,
21
+ ...rest
22
+ }: StatDotsProps) => {
23
+ const [internalValue, setInternalValue] = useState(defaultValue);
24
+ const currentValue = Math.max(0, Math.min(count, value ?? internalValue));
25
+ const handleSelect = (nextValue: number) => {
26
+ if (disabled) return;
27
+ const resolvedValue = nextValue === currentValue ? 0 : nextValue;
28
+ if (value === undefined) {
29
+ setInternalValue(resolvedValue);
30
+ }
31
+ onChange?.(resolvedValue);
32
+ };
33
+ const classes = ["ef-stat-dots", className].filter(Boolean).join(" ");
34
+ return (
35
+ <div className={classes} {...rest}>
36
+ <Panel variant="highlight" borderWidth={1} className="ef-stat-dots__label">
37
+ {label}
38
+ </Panel>
39
+ <div className="ef-stat-dots__dots" role="group" aria-label={label}>
40
+ {Array.from({ length: count }, (_, index) => (
41
+ <input
42
+ key={`${label}-${index}`}
43
+ className="ef-stat-dots__input"
44
+ type="checkbox"
45
+ checked={index < currentValue}
46
+ disabled={disabled}
47
+ onChange={() => handleSelect(index + 1)}
48
+ />
49
+ ))}
50
+ </div>
51
+ </div>
52
+ );
53
+ };
@@ -0,0 +1,161 @@
1
+ .ef-toggle {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: 12px;
5
+ cursor: pointer;
6
+ color: var(--text-strong);
7
+ font-weight: 600;
8
+ }
9
+
10
+ .ef-toggle-input {
11
+ position: absolute;
12
+ opacity: 0;
13
+ width: 1px;
14
+ height: 1px;
15
+ pointer-events: none;
16
+ }
17
+
18
+ .ef-toggle-track {
19
+ width: 46px;
20
+ height: 26px;
21
+ padding: 2px;
22
+ border-radius: var(--ef-toggle-track-radius, 999px);
23
+ border: 2px solid var(--line);
24
+ background: var(--card);
25
+ display: inline-flex;
26
+ align-items: center;
27
+ transition: border-color 0.2s ease, background 0.2s ease;
28
+ }
29
+
30
+ .ef-toggle-thumb {
31
+ width: 20px;
32
+ height: 20px;
33
+ border-radius: var(--ef-toggle-thumb-radius, 999px);
34
+ background: var(--ef-slider-surface, var(--ef-surface));
35
+ border: 2px solid var(--ef-slider-border-color, var(--line));
36
+ -webkit-mask-image: radial-gradient(circle, #fff 99%, transparent 100%);
37
+ mask-image: radial-gradient(circle, #fff 99%, transparent 100%);
38
+ transform: translateX(0);
39
+ transition: transform 0.2s ease, background 0.2s ease;
40
+ }
41
+
42
+ .ef-toggle-input:checked + .ef-toggle-track {
43
+ border-color: transparent;
44
+ background:
45
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
46
+ var(--ef-border-gradient) border-box;
47
+ }
48
+
49
+ .ef-toggle-input:checked + .ef-toggle-track .ef-toggle-thumb {
50
+ transform: translateX(18px);
51
+ background: var(--ef-border-gradient);
52
+ border-color: transparent;
53
+ background-clip: border-box;
54
+ background-origin: border-box;
55
+ }
56
+
57
+ .ef-toggle-label {
58
+ display: grid;
59
+ gap: 4px;
60
+ }
61
+
62
+ .ef-toggle-description {
63
+ font-size: 0.85rem;
64
+ color: var(--text-muted);
65
+ font-weight: 500;
66
+ }
67
+
68
+ .ef-toggle-input:disabled + .ef-toggle-track {
69
+ opacity: 0.5;
70
+ }
71
+
72
+ .ef-toggle-input:disabled ~ .ef-toggle-label {
73
+ opacity: 0.6;
74
+ cursor: not-allowed;
75
+ }
76
+
77
+ :root[data-theme="galaxy"] .ef-toggle-track,
78
+ :root[data-theme="plain-dark"] .ef-toggle-track,
79
+ :root[data-theme="light"] .ef-toggle-track,
80
+ :root[data-theme="plain-light"] .ef-toggle-track {
81
+ background:
82
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
83
+ var(--ef-border-gradient) border-box;
84
+ border-color: transparent;
85
+ }
86
+
87
+ :root[data-theme="atelier"] .ef-toggle-thumb {
88
+ border-radius: var(--ef-toggle-thumb-radius, 0px);
89
+ -webkit-mask-image: none;
90
+ mask-image: none;
91
+ }
92
+
93
+ .ef-toggle-box {
94
+ width: 22px;
95
+ height: 22px;
96
+ border-radius: var(--ef-toggle-box-radius, 6px);
97
+ border: 2px solid var(--line);
98
+ background: var(--card);
99
+ display: grid;
100
+ place-items: center;
101
+ transition: border-color 0.2s ease, background 0.2s ease;
102
+ }
103
+
104
+ .ef-toggle-check {
105
+ width: 10px;
106
+ height: 10px;
107
+ border-radius: var(--ef-toggle-check-radius, 2px);
108
+ background: var(--ef-toggle-check-bg, var(--ef-slider-border-color, var(--line-strong)));
109
+ transform: scale(0);
110
+ transition: transform 0.2s ease;
111
+ }
112
+
113
+ .ef-toggle-input:checked + .ef-toggle-box {
114
+ border-color: transparent;
115
+ background:
116
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
117
+ var(--ef-border-gradient) border-box;
118
+ }
119
+
120
+ .ef-toggle-input:checked + .ef-toggle-box .ef-toggle-check {
121
+ transform: scale(1);
122
+ }
123
+
124
+ :root[data-theme="galaxy"] .ef-toggle-box,
125
+ :root[data-theme="plain-dark"] .ef-toggle-box,
126
+ :root[data-theme="light"] .ef-toggle-box,
127
+ :root[data-theme="plain-light"] .ef-toggle-box {
128
+ background:
129
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
130
+ var(--ef-border-gradient) border-box;
131
+ border-color: transparent;
132
+ }
133
+
134
+ :root[data-theme="galaxy"] .ef-toggle-thumb,
135
+ :root[data-theme="plain-dark"] .ef-toggle-thumb,
136
+ :root[data-theme="light"] .ef-toggle-thumb,
137
+ :root[data-theme="plain-light"] .ef-toggle-thumb {
138
+ background:
139
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
140
+ var(--ef-border-gradient) border-box;
141
+ border-color: transparent;
142
+ background-clip: padding-box, border-box;
143
+ background-origin: border-box;
144
+ }
145
+
146
+ :root[data-theme="galaxy"] .ef-toggle-input:checked + .ef-toggle-track .ef-toggle-thumb,
147
+ :root[data-theme="plain-dark"] .ef-toggle-input:checked + .ef-toggle-track .ef-toggle-thumb,
148
+ :root[data-theme="light"] .ef-toggle-input:checked + .ef-toggle-track .ef-toggle-thumb,
149
+ :root[data-theme="plain-light"] .ef-toggle-input:checked + .ef-toggle-track .ef-toggle-thumb {
150
+ background: var(--ef-border-gradient);
151
+ border-color: transparent;
152
+ background-clip: border-box;
153
+ background-origin: border-box;
154
+ }
155
+
156
+ :root[data-theme="galaxy"] .ef-toggle-check,
157
+ :root[data-theme="plain-dark"] .ef-toggle-check,
158
+ :root[data-theme="light"] .ef-toggle-check,
159
+ :root[data-theme="plain-light"] .ef-toggle-check {
160
+ background: var(--ef-border-gradient);
161
+ }
@@ -0,0 +1,38 @@
1
+ import type { InputHTMLAttributes } from "react";
2
+
3
+ type ToggleProps = Omit<InputHTMLAttributes<HTMLInputElement>, "type"> & {
4
+ label?: string;
5
+ description?: string;
6
+ variant?: "switch" | "checkbox";
7
+ };
8
+
9
+ export const Toggle = ({
10
+ label,
11
+ description,
12
+ className,
13
+ variant = "switch",
14
+ ...props
15
+ }: ToggleProps) => (
16
+ <label
17
+ className={["ef-toggle", `ef-toggle--${variant}`, className]
18
+ .filter(Boolean)
19
+ .join(" ")}
20
+ >
21
+ <input type="checkbox" className="ef-toggle-input" {...props} />
22
+ {variant === "switch" ? (
23
+ <span className="ef-toggle-track" aria-hidden="true">
24
+ <span className="ef-toggle-thumb" />
25
+ </span>
26
+ ) : (
27
+ <span className="ef-toggle-box" aria-hidden="true">
28
+ <span className="ef-toggle-check" />
29
+ </span>
30
+ )}
31
+ {label ? (
32
+ <span className="ef-toggle-label">
33
+ <span className="ef-toggle-text">{label}</span>
34
+ {description ? <span className="ef-toggle-description">{description}</span> : null}
35
+ </span>
36
+ ) : null}
37
+ </label>
38
+ );
@@ -0,0 +1,273 @@
1
+ .user-section {
2
+ position: relative;
3
+ display: inline-block;
4
+ z-index: 30;
5
+ --user-border-gradient: var(--ef-border-gradient);
6
+ --user-button-height: 46px;
7
+ }
8
+
9
+ .user-button {
10
+ display: flex;
11
+ align-items: center;
12
+ gap: 10px;
13
+ border: 2px solid transparent;
14
+ padding: 8px 12px;
15
+ border-radius: var(--ef-control-radius, 12px);
16
+ min-height: 46px;
17
+ height: var(--user-button-height);
18
+ background:
19
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
20
+ var(--user-border-gradient) border-box;
21
+ color: var(--text-strong);
22
+ font-weight: 500;
23
+ cursor: pointer;
24
+ box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
25
+ }
26
+
27
+ .user-section[data-open="true"] .user-button {
28
+ border-bottom-left-radius: 0;
29
+ border-bottom-right-radius: 0;
30
+ border-bottom-color: transparent;
31
+ }
32
+
33
+ .avatar {
34
+ width: 32px;
35
+ height: 32px;
36
+ border-radius: 50%;
37
+ background: rgba(255, 255, 255, 0.12);
38
+ display: grid;
39
+ place-items: center;
40
+ overflow: hidden;
41
+ flex-shrink: 0;
42
+ }
43
+
44
+ .avatar img {
45
+ width: 100%;
46
+ height: 100%;
47
+ object-fit: cover;
48
+ }
49
+
50
+ .avatar-fallback {
51
+ font-size: 0.85rem;
52
+ font-weight: 700;
53
+ }
54
+
55
+ .user-name {
56
+ max-width: 160px;
57
+ overflow: hidden;
58
+ text-overflow: ellipsis;
59
+ white-space: nowrap;
60
+ }
61
+
62
+ .chevron {
63
+ font-size: 0.7rem;
64
+ opacity: 0.7;
65
+ display: inline-flex;
66
+ }
67
+
68
+ .chevron svg {
69
+ width: 14px;
70
+ height: 14px;
71
+ }
72
+
73
+ .chevron.open svg {
74
+ transform: rotate(180deg);
75
+ }
76
+
77
+ .dropdown {
78
+ position: absolute;
79
+ left: 0;
80
+ right: 0;
81
+ top: calc(100% - 2px);
82
+ width: 100%;
83
+ min-width: 100%;
84
+ background:
85
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
86
+ var(--user-border-gradient) border-box;
87
+ border: 2px solid transparent;
88
+ border-top: none;
89
+ border-radius: 0 0 var(--ef-control-radius, 12px) var(--ef-control-radius, 12px);
90
+ padding: 8px;
91
+ display: grid;
92
+ gap: 6px;
93
+ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4);
94
+ backdrop-filter: blur(12px);
95
+ opacity: 0;
96
+ transform: translateY(-6px);
97
+ pointer-events: none;
98
+ transition: opacity 0.12s ease, transform 0.12s ease;
99
+ z-index: 200;
100
+ }
101
+
102
+ .dropdown[data-open="true"] {
103
+ opacity: 1;
104
+ transform: translateY(0);
105
+ pointer-events: auto;
106
+ }
107
+
108
+ .dropdown-item {
109
+ border: 2px solid transparent;
110
+ padding: 10px 16px;
111
+ border-radius: var(--ef-control-radius, 12px);
112
+ background:
113
+ linear-gradient(var(--ef-surface), var(--ef-surface)) padding-box,
114
+ var(--ef-border-gradient) border-box;
115
+ color: var(--text-strong);
116
+ font-weight: 600;
117
+ font-size: 0.85rem;
118
+ text-align: left;
119
+ cursor: pointer;
120
+ width: 100%;
121
+ transition: box-shadow 0.2s ease, transform 0.2s ease;
122
+ box-shadow: 0 0 24px rgba(124, 77, 255, 0.45);
123
+ }
124
+
125
+ .dropdown-item:hover {
126
+ box-shadow: 0 0 18px rgba(124, 77, 255, 0.35);
127
+ transform: translateY(-1px);
128
+ }
129
+
130
+ .dropdown-item.theme-preview {
131
+ border: 2px solid transparent;
132
+ border-radius: var(--ef-control-radius, 12px);
133
+ background:
134
+ linear-gradient(var(--ef-button-surface), var(--ef-button-surface)) padding-box,
135
+ var(--ef-button-border) border-box;
136
+ color: var(--ef-button-text);
137
+ box-shadow: var(--shadow);
138
+ }
139
+
140
+ .dropdown-item.theme-preview:hover {
141
+ box-shadow: var(--shadow);
142
+ filter: brightness(1.04);
143
+ transform: translateY(-1px);
144
+ }
145
+
146
+ .dropdown-item.theme-preview.is-disabled,
147
+ .dropdown-item.theme-preview.is-disabled:hover {
148
+ opacity: 0.6;
149
+ box-shadow: none;
150
+ filter: none;
151
+ transform: none;
152
+ background:
153
+ linear-gradient(var(--ef-button-surface), var(--ef-button-surface)) padding-box,
154
+ var(--ef-button-border-soft) border-box;
155
+ }
156
+
157
+ .dropdown-item.is-disabled {
158
+ opacity: 0.55;
159
+ cursor: not-allowed;
160
+ box-shadow: none;
161
+ }
162
+
163
+ .dropdown-item.is-disabled:hover {
164
+ box-shadow: none;
165
+ transform: none;
166
+ }
167
+
168
+ .dropdown-empty {
169
+ padding: 10px 12px;
170
+ border-radius: var(--ef-control-radius, 12px);
171
+ border: 2px dashed rgba(255, 255, 255, 0.1);
172
+ color: var(--muted);
173
+ font-weight: 600;
174
+ font-size: 0.8rem;
175
+ text-align: center;
176
+ }
177
+
178
+ .dropdown-item-rich {
179
+ display: grid;
180
+ grid-template-columns: auto 1fr auto;
181
+ gap: 10px;
182
+ align-items: center;
183
+ padding: 10px 12px;
184
+ }
185
+
186
+ .dropdown-avatar {
187
+ width: 34px;
188
+ height: 34px;
189
+ border-radius: 10px;
190
+ background: rgba(255, 255, 255, 0.12);
191
+ display: grid;
192
+ place-items: center;
193
+ overflow: hidden;
194
+ font-size: 0.7rem;
195
+ font-weight: 700;
196
+ text-transform: uppercase;
197
+ }
198
+
199
+ .dropdown-avatar img {
200
+ width: 100%;
201
+ height: 100%;
202
+ object-fit: cover;
203
+ }
204
+
205
+ .dropdown-avatar-fallback {
206
+ line-height: 1;
207
+ }
208
+
209
+ .dropdown-item-text {
210
+ display: grid;
211
+ gap: 2px;
212
+ }
213
+
214
+ .dropdown-item-label {
215
+ font-size: 0.85rem;
216
+ font-weight: 600;
217
+ }
218
+
219
+ .dropdown-item-subtitle {
220
+ font-size: 0.72rem;
221
+ color: var(--muted);
222
+ font-weight: 500;
223
+ }
224
+
225
+ .dropdown-item-actions {
226
+ display: inline-flex;
227
+ gap: 6px;
228
+ align-items: center;
229
+ }
230
+
231
+ .dropdown-action {
232
+ width: 28px;
233
+ height: 28px;
234
+ border-radius: 8px;
235
+ border: 1px solid rgba(255, 255, 255, 0.18);
236
+ background: rgba(255, 255, 255, 0.08);
237
+ color: var(--text-strong);
238
+ display: grid;
239
+ place-items: center;
240
+ cursor: pointer;
241
+ }
242
+
243
+ .dropdown-action.ef-button {
244
+ padding: 0;
245
+ min-width: 0;
246
+ box-shadow: none;
247
+ }
248
+
249
+ .dropdown-action svg {
250
+ width: 14px;
251
+ height: 14px;
252
+ }
253
+
254
+ .dropdown-action.is-delete {
255
+ border-color: rgba(255, 120, 120, 0.45);
256
+ color: rgb(255, 164, 164);
257
+ }
258
+
259
+ .dropdown-action:hover {
260
+ filter: brightness(1.1);
261
+ }
262
+
263
+ @media (max-width: 760px) {
264
+ .user-button {
265
+ gap: 8px;
266
+ padding: 8px 10px;
267
+ }
268
+
269
+ .user-name {
270
+ display: inline;
271
+ max-width: 32vw;
272
+ }
273
+ }
package/src/index.ts ADDED
@@ -0,0 +1,44 @@
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
+
18
+ type ThemeOptions<T extends ThemeMode> = {
19
+ storageKey: string;
20
+ defaultTheme: T;
21
+ allowed: readonly T[];
22
+ dataAttribute?: string;
23
+ bodyClass?: string | null;
24
+ };
25
+
26
+ export const getStoredTheme = <T extends ThemeMode>(options: ThemeOptions<T>) => {
27
+ const stored = localStorage.getItem(options.storageKey);
28
+ if (stored && options.allowed.includes(stored as T)) {
29
+ return stored as T;
30
+ }
31
+ if (stored === "dark" && options.allowed.includes("galaxy" as T)) {
32
+ return "galaxy" as T;
33
+ }
34
+ return options.defaultTheme;
35
+ };
36
+
37
+ export const applyTheme = <T extends ThemeMode>(theme: T, options: ThemeOptions<T>) => {
38
+ const dataAttribute = options.dataAttribute ?? "data-theme";
39
+ if (options.bodyClass) {
40
+ document.body.classList.toggle(options.bodyClass, theme === (options.defaultTheme as string));
41
+ }
42
+ document.documentElement.setAttribute(dataAttribute, theme);
43
+ localStorage.setItem(options.storageKey, theme);
44
+ };