@miozu/jera 0.0.2 → 0.4.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.
- package/CLAUDE.md +734 -0
- package/README.md +219 -1
- package/llms.txt +97 -0
- package/package.json +54 -14
- package/src/actions/index.js +375 -0
- package/src/components/docs/CodeBlock.svelte +203 -0
- package/src/components/docs/DocSection.svelte +120 -0
- package/src/components/docs/PropsTable.svelte +136 -0
- package/src/components/docs/SplitPane.svelte +98 -0
- package/src/components/docs/index.js +14 -0
- package/src/components/feedback/Alert.svelte +234 -0
- package/src/components/feedback/EmptyState.svelte +179 -0
- package/src/components/feedback/ProgressBar.svelte +116 -0
- package/src/components/feedback/Skeleton.svelte +107 -0
- package/src/components/feedback/Spinner.svelte +77 -0
- package/src/components/feedback/Toast.svelte +261 -0
- package/src/components/forms/Checkbox.svelte +147 -0
- package/src/components/forms/Dropzone.svelte +248 -0
- package/src/components/forms/FileUpload.svelte +266 -0
- package/src/components/forms/IconInput.svelte +184 -0
- package/src/components/forms/Input.svelte +121 -0
- package/src/components/forms/NumberInput.svelte +225 -0
- package/src/components/forms/PinInput.svelte +169 -0
- package/src/components/forms/Radio.svelte +143 -0
- package/src/components/forms/RadioGroup.svelte +62 -0
- package/src/components/forms/RangeSlider.svelte +212 -0
- package/src/components/forms/SearchInput.svelte +175 -0
- package/src/components/forms/Select.svelte +324 -0
- package/src/components/forms/Switch.svelte +159 -0
- package/src/components/forms/Textarea.svelte +122 -0
- package/src/components/navigation/Accordion.svelte +65 -0
- package/src/components/navigation/AccordionItem.svelte +146 -0
- package/src/components/navigation/NavigationContainer.svelte +344 -0
- package/src/components/navigation/Sidebar.svelte +334 -0
- package/src/components/navigation/SidebarAccountGroup.svelte +495 -0
- package/src/components/navigation/SidebarAccountItem.svelte +492 -0
- package/src/components/navigation/SidebarGroup.svelte +230 -0
- package/src/components/navigation/SidebarGroupSwitcher.svelte +262 -0
- package/src/components/navigation/SidebarItem.svelte +210 -0
- package/src/components/navigation/SidebarNavigationItem.svelte +470 -0
- package/src/components/navigation/SidebarPopover.svelte +145 -0
- package/src/components/navigation/SidebarSearch.svelte +236 -0
- package/src/components/navigation/SidebarSection.svelte +158 -0
- package/src/components/navigation/SidebarToggle.svelte +86 -0
- package/src/components/navigation/Tabs.svelte +239 -0
- package/src/components/navigation/WorkspaceMenu.svelte +416 -0
- package/src/components/navigation/blocks/NavigationAccountGroup.svelte +396 -0
- package/src/components/navigation/blocks/NavigationCustomBlock.svelte +74 -0
- package/src/components/navigation/blocks/NavigationGroupSwitcher.svelte +277 -0
- package/src/components/navigation/blocks/NavigationSearch.svelte +300 -0
- package/src/components/navigation/blocks/NavigationSection.svelte +230 -0
- package/src/components/navigation/index.js +22 -0
- package/src/components/overlays/ConfirmDialog.svelte +272 -0
- package/src/components/overlays/Dropdown.svelte +153 -0
- package/src/components/overlays/DropdownDivider.svelte +23 -0
- package/src/components/overlays/DropdownItem.svelte +97 -0
- package/src/components/overlays/Modal.svelte +232 -0
- package/src/components/overlays/Popover.svelte +206 -0
- package/src/components/primitives/Avatar.svelte +132 -0
- package/src/components/primitives/Badge.svelte +118 -0
- package/src/components/primitives/Button.svelte +214 -0
- package/src/components/primitives/Card.svelte +104 -0
- package/src/components/primitives/Divider.svelte +105 -0
- package/src/components/primitives/LazyImage.svelte +104 -0
- package/src/components/primitives/Link.svelte +122 -0
- package/src/components/primitives/Stat.svelte +197 -0
- package/src/components/primitives/StatusBadge.svelte +122 -0
- package/src/index.js +183 -0
- package/src/tokens/colors.css +157 -0
- package/src/tokens/effects.css +128 -0
- package/src/tokens/index.css +81 -0
- package/src/tokens/spacing.css +49 -0
- package/src/tokens/typography.css +79 -0
- package/src/utils/cn.svelte.js +175 -0
- package/src/utils/highlighter.js +124 -0
- package/src/utils/index.js +22 -0
- package/src/utils/navigation.svelte.js +423 -0
- package/src/utils/reactive.svelte.js +328 -0
- package/src/utils/sidebar.svelte.js +211 -0
- package/jera.js +0 -135
- package/www/components/jera/Input/Input.svelte +0 -63
- package/www/components/jera/Input/index.js +0 -1
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive State Utilities for Svelte 5
|
|
3
|
+
*
|
|
4
|
+
* Advanced patterns for creating shareable, reactive state.
|
|
5
|
+
* Uses Svelte 5 runes ($state, $derived, $effect) internally.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a reactive store-like object with Svelte 5 runes
|
|
10
|
+
* Unlike stores, these work seamlessly with runes and don't need $ prefix
|
|
11
|
+
*
|
|
12
|
+
* @template T
|
|
13
|
+
* @param {T} initial
|
|
14
|
+
* @returns {{ value: T, set: (v: T) => void, update: (fn: (v: T) => T) => void }}
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const count = createReactive(0);
|
|
18
|
+
* count.value; // read
|
|
19
|
+
* count.set(5); // write
|
|
20
|
+
* count.update(n => n + 1); // update
|
|
21
|
+
*/
|
|
22
|
+
export function createReactive(initial) {
|
|
23
|
+
let value = $state(initial);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
get value() {
|
|
27
|
+
return value;
|
|
28
|
+
},
|
|
29
|
+
set value(v) {
|
|
30
|
+
value = v;
|
|
31
|
+
},
|
|
32
|
+
set(v) {
|
|
33
|
+
value = v;
|
|
34
|
+
},
|
|
35
|
+
update(fn) {
|
|
36
|
+
value = fn(value);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Create a derived reactive value
|
|
43
|
+
*
|
|
44
|
+
* @template T
|
|
45
|
+
* @param {() => T} fn
|
|
46
|
+
* @returns {{ readonly value: T }}
|
|
47
|
+
*/
|
|
48
|
+
export function createDerived(fn) {
|
|
49
|
+
const value = $derived.by(fn);
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
get value() {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Theme Reactive State Class (Singleton Pattern)
|
|
60
|
+
*
|
|
61
|
+
* Manages theme state with persistence and SSR support.
|
|
62
|
+
* Uses singleton pattern for global app-level state.
|
|
63
|
+
*
|
|
64
|
+
* Storage key: 'miozu-theme'
|
|
65
|
+
* Data-theme values: 'miozu-dark' | 'miozu-light'
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* import { getTheme } from '@miozu/jera';
|
|
69
|
+
* const theme = getTheme();
|
|
70
|
+
* theme.init(); // Call once in root layout onMount
|
|
71
|
+
* theme.toggle(); // Toggle dark/light
|
|
72
|
+
* theme.set('system'); // Use system preference
|
|
73
|
+
*/
|
|
74
|
+
export class ThemeState {
|
|
75
|
+
/** @type {'light' | 'dark' | 'system'} */
|
|
76
|
+
current = $state('system');
|
|
77
|
+
|
|
78
|
+
/** @type {'light' | 'dark'} */
|
|
79
|
+
resolved = $derived.by(() => this.#resolveTheme());
|
|
80
|
+
|
|
81
|
+
/** @type {'miozu-light' | 'miozu-dark'} */
|
|
82
|
+
dataTheme = $derived.by(() => this.resolved === 'dark' ? 'miozu-dark' : 'miozu-light');
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Alias for dataTheme - backwards compatibility
|
|
86
|
+
* @type {'miozu-light' | 'miozu-dark'}
|
|
87
|
+
*/
|
|
88
|
+
currentTheme = $derived.by(() => this.dataTheme);
|
|
89
|
+
|
|
90
|
+
/** @type {boolean} */
|
|
91
|
+
isDark = $derived.by(() => this.resolved === 'dark');
|
|
92
|
+
|
|
93
|
+
/** @type {boolean} */
|
|
94
|
+
isLight = $derived.by(() => this.resolved === 'light');
|
|
95
|
+
|
|
96
|
+
/** @type {boolean} */
|
|
97
|
+
#initialized = false;
|
|
98
|
+
|
|
99
|
+
/** @type {MediaQueryList | null} */
|
|
100
|
+
#mediaQuery = null;
|
|
101
|
+
|
|
102
|
+
/** @type {(() => void) | null} */
|
|
103
|
+
#mediaQueryHandler = null;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Parse theme from cookie string (for SSR)
|
|
107
|
+
* @param {string | null} cookieString - Raw cookie header
|
|
108
|
+
* @returns {'miozu-light' | 'miozu-dark'} - Theme value for data-theme attribute
|
|
109
|
+
*/
|
|
110
|
+
static getThemeFromCookieString(cookieString) {
|
|
111
|
+
if (!cookieString) return 'miozu-dark';
|
|
112
|
+
|
|
113
|
+
const match = cookieString.match(/miozu-theme=([^;]+)/);
|
|
114
|
+
const preference = match ? match[1] : null;
|
|
115
|
+
|
|
116
|
+
if (preference === 'light') return 'miozu-light';
|
|
117
|
+
if (preference === 'dark') return 'miozu-dark';
|
|
118
|
+
// 'system' or invalid - default to dark
|
|
119
|
+
return 'miozu-dark';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
constructor(initial = 'system') {
|
|
123
|
+
this.current = initial;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#resolveTheme() {
|
|
127
|
+
if (this.current === 'system') {
|
|
128
|
+
// SSR-safe: default to dark if no window (miozu default)
|
|
129
|
+
if (typeof window === 'undefined') return 'dark';
|
|
130
|
+
return this.#mediaQuery?.matches ? 'dark' : 'light';
|
|
131
|
+
}
|
|
132
|
+
return this.current;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Initialize theme - call once in root layout onMount
|
|
137
|
+
* Loads from storage and sets up system preference listener
|
|
138
|
+
*/
|
|
139
|
+
init() {
|
|
140
|
+
if (typeof window === 'undefined') return;
|
|
141
|
+
if (this.#initialized) return; // Prevent double init
|
|
142
|
+
|
|
143
|
+
// Load from storage
|
|
144
|
+
const stored = localStorage.getItem('miozu-theme');
|
|
145
|
+
if (stored && ['light', 'dark', 'system'].includes(stored)) {
|
|
146
|
+
this.current = stored;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Setup media query listener for system preference
|
|
150
|
+
this.#mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
151
|
+
this.#mediaQueryHandler = () => {
|
|
152
|
+
// Force re-resolution when system preference changes
|
|
153
|
+
if (this.current === 'system') {
|
|
154
|
+
this.#apply();
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
this.#mediaQuery.addEventListener('change', this.#mediaQueryHandler);
|
|
158
|
+
|
|
159
|
+
this.#initialized = true;
|
|
160
|
+
this.#apply();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Cleanup media query listener
|
|
165
|
+
* Call in onDestroy if needed (usually not necessary for singleton)
|
|
166
|
+
*/
|
|
167
|
+
cleanup() {
|
|
168
|
+
if (this.#mediaQuery && this.#mediaQueryHandler) {
|
|
169
|
+
this.#mediaQuery.removeEventListener('change', this.#mediaQueryHandler);
|
|
170
|
+
this.#mediaQueryHandler = null;
|
|
171
|
+
}
|
|
172
|
+
this.#initialized = false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Sync state with current DOM attribute (for hydration)
|
|
177
|
+
* Call after SSR to ensure state matches what app.html set
|
|
178
|
+
*/
|
|
179
|
+
sync() {
|
|
180
|
+
if (typeof document === 'undefined') return;
|
|
181
|
+
|
|
182
|
+
const domTheme = document.documentElement.getAttribute('data-theme');
|
|
183
|
+
if (domTheme === 'miozu-dark') {
|
|
184
|
+
this.current = 'dark';
|
|
185
|
+
} else if (domTheme === 'miozu-light') {
|
|
186
|
+
this.current = 'light';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Set theme preference and persist
|
|
192
|
+
* @param {'light' | 'dark' | 'system'} theme
|
|
193
|
+
*/
|
|
194
|
+
set(theme) {
|
|
195
|
+
if (!['light', 'dark', 'system'].includes(theme)) {
|
|
196
|
+
console.warn(`Invalid theme: ${theme}`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.current = theme;
|
|
200
|
+
this.#persist();
|
|
201
|
+
this.#apply();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Toggle between light and dark (skips system)
|
|
206
|
+
*/
|
|
207
|
+
toggle() {
|
|
208
|
+
this.set(this.resolved === 'light' ? 'dark' : 'light');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Persist to localStorage and cookie
|
|
213
|
+
*/
|
|
214
|
+
#persist() {
|
|
215
|
+
if (typeof window === 'undefined') return;
|
|
216
|
+
localStorage.setItem('miozu-theme', this.current);
|
|
217
|
+
document.cookie = `miozu-theme=${this.current};path=/;max-age=31536000;SameSite=Lax`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Apply theme to document
|
|
222
|
+
*/
|
|
223
|
+
#apply() {
|
|
224
|
+
if (typeof document === 'undefined') return;
|
|
225
|
+
document.documentElement.setAttribute('data-theme', this.dataTheme);
|
|
226
|
+
document.documentElement.style.colorScheme = this.resolved;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ============================================
|
|
231
|
+
// SINGLETON INSTANCE
|
|
232
|
+
// ============================================
|
|
233
|
+
|
|
234
|
+
/** @type {ThemeState | null} */
|
|
235
|
+
let themeInstance = null;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get the global theme singleton
|
|
239
|
+
* Creates instance on first call (lazy initialization)
|
|
240
|
+
*
|
|
241
|
+
* @param {'light' | 'dark' | 'system'} [initial] - Initial theme (only used on first call)
|
|
242
|
+
* @returns {ThemeState}
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* // In root +layout.svelte
|
|
246
|
+
* import { getTheme } from '@miozu/jera';
|
|
247
|
+
* const theme = getTheme();
|
|
248
|
+
* onMount(() => theme.init());
|
|
249
|
+
*
|
|
250
|
+
* // Anywhere else
|
|
251
|
+
* import { getTheme } from '@miozu/jera';
|
|
252
|
+
* const theme = getTheme();
|
|
253
|
+
* theme.toggle();
|
|
254
|
+
*/
|
|
255
|
+
export function getTheme(initial = 'system') {
|
|
256
|
+
if (!themeInstance) {
|
|
257
|
+
themeInstance = new ThemeState(initial);
|
|
258
|
+
}
|
|
259
|
+
return themeInstance;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Reset theme singleton (for testing)
|
|
264
|
+
*/
|
|
265
|
+
export function resetTheme() {
|
|
266
|
+
if (themeInstance) {
|
|
267
|
+
themeInstance.cleanup();
|
|
268
|
+
themeInstance = null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Component State Factory
|
|
274
|
+
*
|
|
275
|
+
* Creates encapsulated component state with derived values.
|
|
276
|
+
* Pattern for complex components like Select, Combobox, etc.
|
|
277
|
+
*
|
|
278
|
+
* @template T
|
|
279
|
+
* @param {T} initialState
|
|
280
|
+
* @param {(state: T) => Record<string, any>} [derivedFn]
|
|
281
|
+
*/
|
|
282
|
+
export function createComponentState(initialState, derivedFn) {
|
|
283
|
+
const state = $state(initialState);
|
|
284
|
+
|
|
285
|
+
// $derived.by tracks dependencies inside the function
|
|
286
|
+
const deriveFn = derivedFn || (() => ({}));
|
|
287
|
+
const derived = $derived.by(() => deriveFn(state));
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
get state() {
|
|
291
|
+
return state;
|
|
292
|
+
},
|
|
293
|
+
set state(v) {
|
|
294
|
+
Object.assign(state, v);
|
|
295
|
+
},
|
|
296
|
+
get derived() {
|
|
297
|
+
return derived;
|
|
298
|
+
},
|
|
299
|
+
/**
|
|
300
|
+
* Patch state partially
|
|
301
|
+
* @param {Partial<T>} patch
|
|
302
|
+
*/
|
|
303
|
+
patch(patch) {
|
|
304
|
+
Object.assign(state, patch);
|
|
305
|
+
},
|
|
306
|
+
/**
|
|
307
|
+
* Reset to initial state
|
|
308
|
+
*/
|
|
309
|
+
reset() {
|
|
310
|
+
Object.assign(state, initialState);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Create a unique ID generator for accessibility
|
|
317
|
+
* @param {string} [prefix]
|
|
318
|
+
* @returns {() => string}
|
|
319
|
+
*/
|
|
320
|
+
export function createIdGenerator(prefix = 'jera') {
|
|
321
|
+
let counter = 0;
|
|
322
|
+
return () => `${prefix}-${++counter}-${Math.random().toString(36).slice(2, 7)}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Shared ID generator instance
|
|
327
|
+
*/
|
|
328
|
+
export const generateId = createIdGenerator();
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar State Management
|
|
3
|
+
*
|
|
4
|
+
* Provides reactive state management for sidebar components.
|
|
5
|
+
* Uses Svelte 5 runes for fine-grained reactivity.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const sidebar = createSidebarState({ persistKey: 'my-sidebar' });
|
|
9
|
+
*
|
|
10
|
+
* // In component
|
|
11
|
+
* sidebar.toggle();
|
|
12
|
+
* console.log(sidebar.collapsed); // true/false
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Create a reactive sidebar state manager
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} options
|
|
19
|
+
* @param {boolean} [options.collapsed=false] - Initial collapsed state
|
|
20
|
+
* @param {string|null} [options.persistKey=null] - localStorage key for persistence
|
|
21
|
+
* @returns {Object} Sidebar state object with reactive properties and methods
|
|
22
|
+
*/
|
|
23
|
+
export function createSidebarState(options = {}) {
|
|
24
|
+
const { collapsed: initial = false, persistKey = null } = options;
|
|
25
|
+
|
|
26
|
+
// Core state
|
|
27
|
+
let _collapsed = $state(initial);
|
|
28
|
+
let _expandedGroups = $state(new Set());
|
|
29
|
+
let _hoverPopover = $state({
|
|
30
|
+
item: null,
|
|
31
|
+
position: { top: 0, left: 0 }
|
|
32
|
+
});
|
|
33
|
+
let _hoverTimeout = null;
|
|
34
|
+
|
|
35
|
+
// Load from localStorage on init (client-side only)
|
|
36
|
+
if (persistKey && typeof localStorage !== 'undefined') {
|
|
37
|
+
try {
|
|
38
|
+
const saved = localStorage.getItem(persistKey);
|
|
39
|
+
if (saved !== null) {
|
|
40
|
+
_collapsed = saved === 'true';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const savedGroups = localStorage.getItem(`${persistKey}-groups`);
|
|
44
|
+
if (savedGroups) {
|
|
45
|
+
_expandedGroups = new Set(JSON.parse(savedGroups));
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// Ignore localStorage errors
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Persist to localStorage when state changes
|
|
53
|
+
function persist() {
|
|
54
|
+
if (persistKey && typeof localStorage !== 'undefined') {
|
|
55
|
+
try {
|
|
56
|
+
localStorage.setItem(persistKey, String(_collapsed));
|
|
57
|
+
localStorage.setItem(`${persistKey}-groups`, JSON.stringify([..._expandedGroups]));
|
|
58
|
+
} catch (e) {
|
|
59
|
+
// Ignore localStorage errors
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
// Collapsed state
|
|
66
|
+
get collapsed() {
|
|
67
|
+
return _collapsed;
|
|
68
|
+
},
|
|
69
|
+
set collapsed(value) {
|
|
70
|
+
_collapsed = value;
|
|
71
|
+
persist();
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Toggle collapsed state
|
|
76
|
+
*/
|
|
77
|
+
toggle() {
|
|
78
|
+
_collapsed = !_collapsed;
|
|
79
|
+
persist();
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Expand the sidebar
|
|
84
|
+
*/
|
|
85
|
+
expand() {
|
|
86
|
+
_collapsed = false;
|
|
87
|
+
persist();
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Collapse the sidebar
|
|
92
|
+
*/
|
|
93
|
+
collapse() {
|
|
94
|
+
_collapsed = true;
|
|
95
|
+
persist();
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Group expansion
|
|
99
|
+
get expandedGroups() {
|
|
100
|
+
return _expandedGroups;
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if a group is expanded
|
|
105
|
+
* @param {string} groupId
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
108
|
+
isGroupExpanded(groupId) {
|
|
109
|
+
return _expandedGroups.has(groupId);
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Toggle group expansion
|
|
114
|
+
* @param {string} groupId
|
|
115
|
+
*/
|
|
116
|
+
toggleGroup(groupId) {
|
|
117
|
+
if (_expandedGroups.has(groupId)) {
|
|
118
|
+
_expandedGroups = new Set([..._expandedGroups].filter(id => id !== groupId));
|
|
119
|
+
} else {
|
|
120
|
+
_expandedGroups = new Set([..._expandedGroups, groupId]);
|
|
121
|
+
}
|
|
122
|
+
persist();
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Expand a group
|
|
127
|
+
* @param {string} groupId
|
|
128
|
+
*/
|
|
129
|
+
expandGroup(groupId) {
|
|
130
|
+
if (!_expandedGroups.has(groupId)) {
|
|
131
|
+
_expandedGroups = new Set([..._expandedGroups, groupId]);
|
|
132
|
+
persist();
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Collapse a group
|
|
138
|
+
* @param {string} groupId
|
|
139
|
+
*/
|
|
140
|
+
collapseGroup(groupId) {
|
|
141
|
+
if (_expandedGroups.has(groupId)) {
|
|
142
|
+
_expandedGroups = new Set([..._expandedGroups].filter(id => id !== groupId));
|
|
143
|
+
persist();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Hover popover state
|
|
148
|
+
get hoverPopover() {
|
|
149
|
+
return _hoverPopover;
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Show hover popover for an item
|
|
154
|
+
* @param {string} itemId
|
|
155
|
+
* @param {{top: number, left: number}} position
|
|
156
|
+
*/
|
|
157
|
+
showPopover(itemId, position) {
|
|
158
|
+
if (!_collapsed) return;
|
|
159
|
+
|
|
160
|
+
if (_hoverTimeout) {
|
|
161
|
+
clearTimeout(_hoverTimeout);
|
|
162
|
+
_hoverTimeout = null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_hoverPopover = {
|
|
166
|
+
item: itemId,
|
|
167
|
+
position
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Hide hover popover with delay
|
|
173
|
+
* @param {number} [delay=150]
|
|
174
|
+
*/
|
|
175
|
+
hidePopover(delay = 150) {
|
|
176
|
+
if (_hoverTimeout) {
|
|
177
|
+
clearTimeout(_hoverTimeout);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_hoverTimeout = setTimeout(() => {
|
|
181
|
+
_hoverPopover = { item: null, position: { top: 0, left: 0 } };
|
|
182
|
+
}, delay);
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Cancel pending popover hide
|
|
187
|
+
*/
|
|
188
|
+
keepPopoverOpen() {
|
|
189
|
+
if (_hoverTimeout) {
|
|
190
|
+
clearTimeout(_hoverTimeout);
|
|
191
|
+
_hoverTimeout = null;
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Immediately hide popover
|
|
197
|
+
*/
|
|
198
|
+
hidePopoverImmediate() {
|
|
199
|
+
if (_hoverTimeout) {
|
|
200
|
+
clearTimeout(_hoverTimeout);
|
|
201
|
+
_hoverTimeout = null;
|
|
202
|
+
}
|
|
203
|
+
_hoverPopover = { item: null, position: { top: 0, left: 0 } };
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Sidebar context key for Svelte context API
|
|
210
|
+
*/
|
|
211
|
+
export const SIDEBAR_CONTEXT_KEY = 'sidebar';
|
package/jera.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import path from 'node:path';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
|
|
11
|
-
// Find the components directory relative to the package
|
|
12
|
-
function findComponentsDir() {
|
|
13
|
-
// Try to locate the components directory
|
|
14
|
-
const possiblePaths = [
|
|
15
|
-
// When running from the installed package
|
|
16
|
-
path.join(__dirname, 'src', 'components'),
|
|
17
|
-
// When running from the project root
|
|
18
|
-
path.join(__dirname, 'components'),
|
|
19
|
-
// When running from a cloned repo
|
|
20
|
-
path.join(__dirname, '..', 'src', 'components')
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
for (const dir of possiblePaths) {
|
|
24
|
-
if (fs.existsSync(dir)) {
|
|
25
|
-
return dir;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// If components directory not found, return null
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const COMPONENTS_DIR = findComponentsDir();
|
|
34
|
-
|
|
35
|
-
// List available components in the components directory
|
|
36
|
-
function listAvailableComponents() {
|
|
37
|
-
if (!COMPONENTS_DIR) {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
return fs.readdirSync(COMPONENTS_DIR)
|
|
43
|
-
.filter(file => file.endsWith('.svelte'))
|
|
44
|
-
.map(file => file.replace('.svelte', ''));
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('Error reading components directory:', error);
|
|
47
|
-
return [];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const program = new Command();
|
|
52
|
-
|
|
53
|
-
program
|
|
54
|
-
.name('jera')
|
|
55
|
-
.description('Install Jera components to your project')
|
|
56
|
-
.version('0.0.1');
|
|
57
|
-
|
|
58
|
-
program
|
|
59
|
-
.command('list')
|
|
60
|
-
.description('List all available components')
|
|
61
|
-
.action(() => {
|
|
62
|
-
const components = listAvailableComponents();
|
|
63
|
-
|
|
64
|
-
if (components.length === 0) {
|
|
65
|
-
console.log('No components found.');
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
console.log('Available components:');
|
|
70
|
-
components.forEach(comp => console.log(`- ${comp}`));
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
program
|
|
74
|
-
.command('add')
|
|
75
|
-
.description('Add a component to your project')
|
|
76
|
-
.argument('<component>', 'Component to add (e.g. Input)')
|
|
77
|
-
.option('-d, --dir <directory>', 'Target directory', 'src/lib/components')
|
|
78
|
-
.action((component, options) => {
|
|
79
|
-
console.log(`Adding ${component} to ${options.dir}...`);
|
|
80
|
-
|
|
81
|
-
const componentFile = component.endsWith('.svelte')
|
|
82
|
-
? component
|
|
83
|
-
: `${component}.svelte`;
|
|
84
|
-
|
|
85
|
-
// Check if components directory exists
|
|
86
|
-
if (!COMPONENTS_DIR) {
|
|
87
|
-
console.error('Error: Components directory not found.');
|
|
88
|
-
console.error('This could happen if you\'re running the CLI in development mode');
|
|
89
|
-
console.error('or if the package structure has changed.');
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Check if component exists
|
|
94
|
-
const sourceComponentPath = path.join(COMPONENTS_DIR, componentFile);
|
|
95
|
-
if (!fs.existsSync(sourceComponentPath)) {
|
|
96
|
-
console.error(`Error: Component "${component}" not found.`);
|
|
97
|
-
console.error('Available components:');
|
|
98
|
-
listAvailableComponents().forEach(comp => console.error(`- ${comp}`));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Create target directory if it doesn't exist
|
|
103
|
-
const targetDir = path.resolve(options.dir);
|
|
104
|
-
if (!fs.existsSync(targetDir)) {
|
|
105
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Copy the component file
|
|
109
|
-
const componentContent = fs.readFileSync(sourceComponentPath, 'utf-8');
|
|
110
|
-
fs.writeFileSync(path.join(targetDir, componentFile), componentContent);
|
|
111
|
-
|
|
112
|
-
console.log(`Component ${component} added to ${options.dir}`);
|
|
113
|
-
console.log('\nUsage:');
|
|
114
|
-
console.log('\nNOTE: This component uses the Miozu theme.');
|
|
115
|
-
console.log('Make sure you have installed @miozu/js-theme and configured it in your Tailwind config.');
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Add init command to set up the repository structure
|
|
119
|
-
program
|
|
120
|
-
.command('init')
|
|
121
|
-
.description('Initialize a new project with Jera components')
|
|
122
|
-
.action(() => {
|
|
123
|
-
console.log('Initializing Jera components...');
|
|
124
|
-
|
|
125
|
-
const componentsDir = path.resolve('src/lib/components');
|
|
126
|
-
|
|
127
|
-
if (!fs.existsSync(componentsDir)) {
|
|
128
|
-
fs.mkdirSync(componentsDir, { recursive: true });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
console.log('Project initialized! You can now add components with:');
|
|
132
|
-
console.log(' jera add <component>');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
program.parse();
|