@miozu/jera 0.0.2 → 0.3.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.
- package/CLAUDE.md +443 -0
- package/README.md +211 -1
- package/llms.txt +64 -0
- package/package.json +44 -14
- package/src/actions/index.js +375 -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 +297 -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 +326 -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/Tabs.svelte +239 -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 +262 -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/StatusBadge.svelte +122 -0
- package/src/index.js +128 -0
- package/src/tokens/colors.css +189 -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/index.js +17 -0
- package/src/utils/reactive.svelte.js +239 -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,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jera Utilities
|
|
3
|
+
*
|
|
4
|
+
* Core utilities for class composition and reactive state management.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { cn, rcn, cv, mergeClasses, when, match } from './cn.svelte.js';
|
|
8
|
+
export {
|
|
9
|
+
createReactive,
|
|
10
|
+
createDerived,
|
|
11
|
+
ThemeState,
|
|
12
|
+
createThemeContext,
|
|
13
|
+
getThemeContext,
|
|
14
|
+
createComponentState,
|
|
15
|
+
createIdGenerator,
|
|
16
|
+
generateId
|
|
17
|
+
} from './reactive.svelte.js';
|
|
@@ -0,0 +1,239 @@
|
|
|
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
|
+
import { getContext, setContext, onMount } from 'svelte';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a reactive store-like object with Svelte 5 runes
|
|
12
|
+
* Unlike stores, these work seamlessly with runes and don't need $ prefix
|
|
13
|
+
*
|
|
14
|
+
* @template T
|
|
15
|
+
* @param {T} initial
|
|
16
|
+
* @returns {{ value: T, set: (v: T) => void, update: (fn: (v: T) => T) => void }}
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const count = createReactive(0);
|
|
20
|
+
* count.value; // read
|
|
21
|
+
* count.set(5); // write
|
|
22
|
+
* count.update(n => n + 1); // update
|
|
23
|
+
*/
|
|
24
|
+
export function createReactive(initial) {
|
|
25
|
+
let value = $state(initial);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
get value() {
|
|
29
|
+
return value;
|
|
30
|
+
},
|
|
31
|
+
set value(v) {
|
|
32
|
+
value = v;
|
|
33
|
+
},
|
|
34
|
+
set(v) {
|
|
35
|
+
value = v;
|
|
36
|
+
},
|
|
37
|
+
update(fn) {
|
|
38
|
+
value = fn(value);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a derived reactive value
|
|
45
|
+
*
|
|
46
|
+
* @template T
|
|
47
|
+
* @param {() => T} fn
|
|
48
|
+
* @returns {{ readonly value: T }}
|
|
49
|
+
*/
|
|
50
|
+
export function createDerived(fn) {
|
|
51
|
+
const value = $derived.by(fn);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
get value() {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Theme Context Key
|
|
62
|
+
*/
|
|
63
|
+
const THEME_KEY = Symbol('jera-theme');
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Theme Reactive State Class
|
|
67
|
+
*
|
|
68
|
+
* Manages theme state with persistence and SSR support.
|
|
69
|
+
* Uses class-based reactive state pattern.
|
|
70
|
+
*/
|
|
71
|
+
export class ThemeState {
|
|
72
|
+
/** @type {'light' | 'dark' | 'system'} */
|
|
73
|
+
current = $state('system');
|
|
74
|
+
|
|
75
|
+
/** @type {'light' | 'dark'} */
|
|
76
|
+
resolved = $derived.by(() => this.#resolveTheme());
|
|
77
|
+
|
|
78
|
+
/** @type {boolean} */
|
|
79
|
+
#mounted = false;
|
|
80
|
+
|
|
81
|
+
/** @type {MediaQueryList | null} */
|
|
82
|
+
#mediaQuery = null;
|
|
83
|
+
|
|
84
|
+
/** @type {(() => void) | null} */
|
|
85
|
+
#mediaQueryHandler = null;
|
|
86
|
+
|
|
87
|
+
constructor(initial = 'system') {
|
|
88
|
+
this.current = initial;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#resolveTheme() {
|
|
92
|
+
if (this.current === 'system') {
|
|
93
|
+
// SSR-safe: default to light if no window
|
|
94
|
+
if (typeof window === 'undefined') return 'light';
|
|
95
|
+
return this.#mediaQuery?.matches ? 'dark' : 'light';
|
|
96
|
+
}
|
|
97
|
+
return this.current;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Initialize theme - call in onMount
|
|
102
|
+
*/
|
|
103
|
+
init() {
|
|
104
|
+
if (typeof window === 'undefined') return;
|
|
105
|
+
|
|
106
|
+
// Load from storage
|
|
107
|
+
const stored = localStorage.getItem('jera-theme');
|
|
108
|
+
if (stored && ['light', 'dark', 'system'].includes(stored)) {
|
|
109
|
+
this.current = stored;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Setup media query listener
|
|
113
|
+
this.#mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
114
|
+
this.#mediaQueryHandler = () => {
|
|
115
|
+
// Trigger re-resolution by reassigning
|
|
116
|
+
this.current = this.current;
|
|
117
|
+
};
|
|
118
|
+
this.#mediaQuery.addEventListener('change', this.#mediaQueryHandler);
|
|
119
|
+
|
|
120
|
+
this.#mounted = true;
|
|
121
|
+
this.#apply();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Cleanup - call in onDestroy to prevent memory leaks
|
|
126
|
+
*/
|
|
127
|
+
cleanup() {
|
|
128
|
+
if (this.#mediaQuery && this.#mediaQueryHandler) {
|
|
129
|
+
this.#mediaQuery.removeEventListener('change', this.#mediaQueryHandler);
|
|
130
|
+
this.#mediaQueryHandler = null;
|
|
131
|
+
}
|
|
132
|
+
this.#mounted = false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Set theme and persist
|
|
137
|
+
* @param {'light' | 'dark' | 'system'} theme
|
|
138
|
+
*/
|
|
139
|
+
set(theme) {
|
|
140
|
+
this.current = theme;
|
|
141
|
+
if (typeof window !== 'undefined') {
|
|
142
|
+
localStorage.setItem('jera-theme', theme);
|
|
143
|
+
document.cookie = `jera-theme=${theme};path=/;max-age=31536000`;
|
|
144
|
+
}
|
|
145
|
+
this.#apply();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Toggle between light and dark
|
|
150
|
+
*/
|
|
151
|
+
toggle() {
|
|
152
|
+
this.set(this.resolved === 'light' ? 'dark' : 'light');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Apply theme to document
|
|
157
|
+
*/
|
|
158
|
+
#apply() {
|
|
159
|
+
if (typeof document === 'undefined') return;
|
|
160
|
+
document.documentElement.setAttribute('data-theme', this.resolved);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create and provide theme context
|
|
166
|
+
* @param {'light' | 'dark' | 'system'} [initial]
|
|
167
|
+
* @returns {ThemeState}
|
|
168
|
+
*/
|
|
169
|
+
export function createThemeContext(initial = 'system') {
|
|
170
|
+
const theme = new ThemeState(initial);
|
|
171
|
+
setContext(THEME_KEY, theme);
|
|
172
|
+
return theme;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get theme from context
|
|
177
|
+
* @returns {ThemeState}
|
|
178
|
+
*/
|
|
179
|
+
export function getThemeContext() {
|
|
180
|
+
return getContext(THEME_KEY);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Component State Factory
|
|
185
|
+
*
|
|
186
|
+
* Creates encapsulated component state with derived values.
|
|
187
|
+
* Pattern for complex components like Select, Combobox, etc.
|
|
188
|
+
*
|
|
189
|
+
* @template T
|
|
190
|
+
* @param {T} initialState
|
|
191
|
+
* @param {(state: T) => Record<string, any>} [derivedFn]
|
|
192
|
+
*/
|
|
193
|
+
export function createComponentState(initialState, derivedFn) {
|
|
194
|
+
const state = $state(initialState);
|
|
195
|
+
|
|
196
|
+
// $derived.by tracks dependencies inside the function
|
|
197
|
+
const deriveFn = derivedFn || (() => ({}));
|
|
198
|
+
const derived = $derived.by(() => deriveFn(state));
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
get state() {
|
|
202
|
+
return state;
|
|
203
|
+
},
|
|
204
|
+
set state(v) {
|
|
205
|
+
Object.assign(state, v);
|
|
206
|
+
},
|
|
207
|
+
get derived() {
|
|
208
|
+
return derived;
|
|
209
|
+
},
|
|
210
|
+
/**
|
|
211
|
+
* Patch state partially
|
|
212
|
+
* @param {Partial<T>} patch
|
|
213
|
+
*/
|
|
214
|
+
patch(patch) {
|
|
215
|
+
Object.assign(state, patch);
|
|
216
|
+
},
|
|
217
|
+
/**
|
|
218
|
+
* Reset to initial state
|
|
219
|
+
*/
|
|
220
|
+
reset() {
|
|
221
|
+
Object.assign(state, initialState);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Create a unique ID generator for accessibility
|
|
228
|
+
* @param {string} [prefix]
|
|
229
|
+
* @returns {() => string}
|
|
230
|
+
*/
|
|
231
|
+
export function createIdGenerator(prefix = 'jera') {
|
|
232
|
+
let counter = 0;
|
|
233
|
+
return () => `${prefix}-${++counter}-${Math.random().toString(36).slice(2, 7)}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Shared ID generator instance
|
|
238
|
+
*/
|
|
239
|
+
export const generateId = createIdGenerator();
|
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();
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
<script>
|
|
2
|
-
let {
|
|
3
|
-
value = '',
|
|
4
|
-
ref = $bindable(),
|
|
5
|
-
type = 'text',
|
|
6
|
-
placeholder = '',
|
|
7
|
-
disabled = false,
|
|
8
|
-
required = false,
|
|
9
|
-
name = '',
|
|
10
|
-
id = '',
|
|
11
|
-
autocomplete = 'on',
|
|
12
|
-
autocorrect = 'off',
|
|
13
|
-
autocapitalize = 'off',
|
|
14
|
-
spellcheck = 'false',
|
|
15
|
-
maxlength = undefined,
|
|
16
|
-
minlength = undefined,
|
|
17
|
-
inputmode,
|
|
18
|
-
class: className = '',
|
|
19
|
-
unstyled = false,
|
|
20
|
-
disableBrowserFeatures = false,
|
|
21
|
-
oninput = () => {},
|
|
22
|
-
onchange = () => {},
|
|
23
|
-
onkeydown = () => {},
|
|
24
|
-
...others
|
|
25
|
-
} = $props();
|
|
26
|
-
|
|
27
|
-
const finalAutocomplete = disableBrowserFeatures ? 'new-password' : autocomplete;
|
|
28
|
-
const finalClassName = $derived(unstyled ? className : `base-input ${className}`);
|
|
29
|
-
</script>
|
|
30
|
-
|
|
31
|
-
<input
|
|
32
|
-
class={finalClassName}
|
|
33
|
-
bind:this={ref}
|
|
34
|
-
{id}
|
|
35
|
-
{name}
|
|
36
|
-
{type}
|
|
37
|
-
{value}
|
|
38
|
-
{placeholder}
|
|
39
|
-
{disabled}
|
|
40
|
-
{required}
|
|
41
|
-
{inputmode}
|
|
42
|
-
{maxlength}
|
|
43
|
-
{minlength}
|
|
44
|
-
autocomplete={finalAutocomplete}
|
|
45
|
-
autocorrect={disableBrowserFeatures ? 'off' : autocorrect}
|
|
46
|
-
autocapitalize={disableBrowserFeatures ? 'off' : autocapitalize}
|
|
47
|
-
spellcheck={disableBrowserFeatures ? 'false' : spellcheck}
|
|
48
|
-
data-form-type={disableBrowserFeatures ? 'other' : undefined}
|
|
49
|
-
data-lpignore={disableBrowserFeatures ? 'true' : undefined}
|
|
50
|
-
{oninput}
|
|
51
|
-
{onchange}
|
|
52
|
-
{onkeydown}
|
|
53
|
-
{...others}
|
|
54
|
-
/>
|
|
55
|
-
|
|
56
|
-
<style>
|
|
57
|
-
.input-wrapper {
|
|
58
|
-
@apply relative;
|
|
59
|
-
}
|
|
60
|
-
.input-label {
|
|
61
|
-
@apply font-bold inline-block;
|
|
62
|
-
}
|
|
63
|
-
</style>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './Input.svelte';
|