@miozu/jera 0.3.0 → 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 +350 -59
- package/README.md +30 -22
- package/llms.txt +37 -4
- package/package.json +12 -2
- 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 +6 -6
- package/src/components/feedback/ProgressBar.svelte +8 -8
- package/src/components/feedback/Skeleton.svelte +4 -4
- package/src/components/feedback/Spinner.svelte +1 -1
- package/src/components/feedback/Toast.svelte +137 -173
- package/src/components/forms/Checkbox.svelte +10 -10
- package/src/components/forms/Dropzone.svelte +14 -14
- package/src/components/forms/FileUpload.svelte +16 -16
- package/src/components/forms/IconInput.svelte +4 -4
- package/src/components/forms/Input.svelte +14 -14
- package/src/components/forms/NumberInput.svelte +13 -13
- package/src/components/forms/PinInput.svelte +8 -8
- package/src/components/forms/Radio.svelte +8 -8
- package/src/components/forms/RangeSlider.svelte +12 -12
- package/src/components/forms/SearchInput.svelte +10 -10
- package/src/components/forms/Select.svelte +156 -158
- package/src/components/forms/Switch.svelte +4 -4
- package/src/components/forms/Textarea.svelte +9 -9
- package/src/components/navigation/Accordion.svelte +1 -1
- package/src/components/navigation/AccordionItem.svelte +6 -6
- 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 +18 -18
- 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 +18 -18
- package/src/components/overlays/Dropdown.svelte +2 -2
- package/src/components/overlays/DropdownDivider.svelte +1 -1
- package/src/components/overlays/DropdownItem.svelte +5 -5
- package/src/components/overlays/Modal.svelte +13 -13
- package/src/components/overlays/Popover.svelte +3 -3
- package/src/components/primitives/Avatar.svelte +12 -12
- package/src/components/primitives/Badge.svelte +7 -7
- package/src/components/primitives/Button.svelte +126 -174
- package/src/components/primitives/Card.svelte +15 -15
- package/src/components/primitives/Divider.svelte +3 -3
- package/src/components/primitives/LazyImage.svelte +1 -1
- package/src/components/primitives/Link.svelte +2 -2
- package/src/components/primitives/Stat.svelte +197 -0
- package/src/components/primitives/StatusBadge.svelte +24 -24
- package/src/index.js +62 -7
- package/src/tokens/colors.css +96 -128
- package/src/utils/highlighter.js +124 -0
- package/src/utils/index.js +7 -2
- package/src/utils/navigation.svelte.js +423 -0
- package/src/utils/reactive.svelte.js +126 -37
- package/src/utils/sidebar.svelte.js +211 -0
package/CLAUDE.md
CHANGED
|
@@ -28,7 +28,8 @@ jera/
|
|
|
28
28
|
│ ├── forms/ # Input, Select, Checkbox, Switch
|
|
29
29
|
│ ├── feedback/ # Toast, Skeleton, ProgressBar, Spinner
|
|
30
30
|
│ ├── overlays/ # Modal, Popover
|
|
31
|
-
│
|
|
31
|
+
│ ├── navigation/ # Tabs, Accordion, Sidebar
|
|
32
|
+
│ └── docs/ # CodeBlock, PropsTable, SplitPane, DocSection
|
|
32
33
|
├── llms.txt # AI documentation index
|
|
33
34
|
├── CLAUDE.md # This file
|
|
34
35
|
└── package.json
|
|
@@ -83,47 +84,49 @@ export const componentStyles = cv({
|
|
|
83
84
|
|
|
84
85
|
### Naming Conventions
|
|
85
86
|
- Components: PascalCase (`Button.svelte`)
|
|
86
|
-
- Utilities: camelCase (`
|
|
87
|
+
- Utilities: camelCase (`getTheme`)
|
|
87
88
|
- CSS tokens: kebab-case (`--color-primary`)
|
|
88
89
|
- Actions: camelCase (`clickOutside`)
|
|
89
90
|
|
|
90
91
|
---
|
|
91
92
|
|
|
92
|
-
## Miozu Color System
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
|
98
|
-
|
|
99
|
-
| `--
|
|
100
|
-
| `--
|
|
101
|
-
| `--
|
|
102
|
-
| `--
|
|
103
|
-
| `--
|
|
104
|
-
| `--
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
|
110
|
-
|
|
111
|
-
| `--
|
|
112
|
-
| `--
|
|
113
|
-
| `--
|
|
114
|
-
| `--
|
|
115
|
-
| `--
|
|
116
|
-
| `--
|
|
93
|
+
## Miozu Base16 Color System
|
|
94
|
+
|
|
95
|
+
Uses standard Base16 naming: `base00`-`base0F` (hex digits, NOT decimal).
|
|
96
|
+
|
|
97
|
+
### Grayscale (base00-base07)
|
|
98
|
+
| Token | Dark Theme | Light Theme | Usage |
|
|
99
|
+
|-------|------------|-------------|-------|
|
|
100
|
+
| `--color-base00` | #0f1419 | #ffffff | Primary background |
|
|
101
|
+
| `--color-base01` | #1a1f26 | #f8f9fa | Surface/card |
|
|
102
|
+
| `--color-base02` | #242a33 | #f1f3f5 | Selection/hover |
|
|
103
|
+
| `--color-base03` | #4a5568 | #adb5bd | Muted/disabled |
|
|
104
|
+
| `--color-base04` | #a0aec0 | #6c757d | Secondary text |
|
|
105
|
+
| `--color-base05` | #e2e8f0 | #212529 | Primary text |
|
|
106
|
+
| `--color-base06` | #f7fafc | #1a1d20 | High emphasis |
|
|
107
|
+
| `--color-base07` | #ffffff | #0d0f10 | Maximum contrast |
|
|
108
|
+
|
|
109
|
+
### Accents (base08-base0F)
|
|
110
|
+
| Token | Color | Usage |
|
|
111
|
+
|-------|-------|-------|
|
|
112
|
+
| `--color-base08` | Red | Error |
|
|
113
|
+
| `--color-base09` | Orange | Warning |
|
|
114
|
+
| `--color-base0A` | Yellow | Highlight |
|
|
115
|
+
| `--color-base0B` | Green | Success |
|
|
116
|
+
| `--color-base0C` | Cyan | Info |
|
|
117
|
+
| `--color-base0D` | Blue | Primary |
|
|
118
|
+
| `--color-base0E` | Purple | Accent |
|
|
119
|
+
| `--color-base0F` | Brown/Orange | Secondary accent |
|
|
117
120
|
|
|
118
121
|
### Semantic Mappings
|
|
119
122
|
```css
|
|
120
|
-
--color-bg: var(--
|
|
121
|
-
--color-surface: var(--
|
|
122
|
-
--color-text: var(--
|
|
123
|
-
--color-text-strong: var(--
|
|
124
|
-
--color-primary: var(--
|
|
125
|
-
--color-success: var(--
|
|
126
|
-
--color-error: var(--
|
|
123
|
+
--color-bg: var(--color-base00);
|
|
124
|
+
--color-surface: var(--color-base01);
|
|
125
|
+
--color-text: var(--color-base05);
|
|
126
|
+
--color-text-strong: var(--color-base07);
|
|
127
|
+
--color-primary: var(--color-base0D);
|
|
128
|
+
--color-success: var(--color-base0B);
|
|
129
|
+
--color-error: var(--color-base08);
|
|
127
130
|
```
|
|
128
131
|
|
|
129
132
|
---
|
|
@@ -241,6 +244,34 @@ Props: `content`, `position` (top/bottom/left/right), `delay` ({show, hide}), `o
|
|
|
241
244
|
|
|
242
245
|
Props: `orientation` (horizontal/vertical), `thickness`, `spacing`, `children`
|
|
243
246
|
|
|
247
|
+
### Stat
|
|
248
|
+
```svelte
|
|
249
|
+
<Stat value="42" label="Users" />
|
|
250
|
+
<Stat value="95%" label="Uptime" status="success" />
|
|
251
|
+
<Stat value="75" max={100} label="Progress" showBar />
|
|
252
|
+
<Stat value="12" unit="/15" label="Tasks" status="warning" />
|
|
253
|
+
<Stat value="8" label="Errors" href="/errors" status="error" />
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Props: `value`, `label`, `unit`, `secondary`, `status` (success/warning/error/info), `size` (sm/md/lg), `showBar`, `barValue`, `max`, `href`
|
|
257
|
+
|
|
258
|
+
### Alert
|
|
259
|
+
```svelte
|
|
260
|
+
<Alert variant="info">This is an informational message.</Alert>
|
|
261
|
+
<Alert variant="warning" title="Warning">Please review your settings.</Alert>
|
|
262
|
+
<Alert variant="error" dismissible onclose={() => showAlert = false}>
|
|
263
|
+
An error occurred.
|
|
264
|
+
</Alert>
|
|
265
|
+
<Alert variant="success">
|
|
266
|
+
{#snippet icon()}
|
|
267
|
+
<CheckIcon size={16} />
|
|
268
|
+
{/snippet}
|
|
269
|
+
Your changes have been saved.
|
|
270
|
+
</Alert>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Props: `variant` (info/success/warning/error), `title`, `dismissible`, `size` (sm/md/lg), `onclose`, `icon` (snippet), `actions` (snippet)
|
|
274
|
+
|
|
244
275
|
### Avatar
|
|
245
276
|
```svelte
|
|
246
277
|
<Avatar src="/user.jpg" alt="John Doe" />
|
|
@@ -316,6 +347,72 @@ Props: `tabs` (array), `active`, `variant` (default/underline/pills), `size` (sm
|
|
|
316
347
|
Accordion props: `expanded` (array of ids), `multiple`
|
|
317
348
|
AccordionItem props: `id`, `title`, `disabled`
|
|
318
349
|
|
|
350
|
+
### CodeBlock
|
|
351
|
+
```svelte
|
|
352
|
+
<script>
|
|
353
|
+
import { CodeBlock } from '@miozu/jera';
|
|
354
|
+
</script>
|
|
355
|
+
|
|
356
|
+
<CodeBlock
|
|
357
|
+
code={`const greeting = "Hello";`}
|
|
358
|
+
lang="javascript"
|
|
359
|
+
filename="example.js"
|
|
360
|
+
showLineNumbers
|
|
361
|
+
/>
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Props: `code`, `lang` (default: javascript), `filename`, `showLineNumbers`, `class`
|
|
365
|
+
|
|
366
|
+
**Requires:** `shiki` package for syntax highlighting.
|
|
367
|
+
|
|
368
|
+
### PropsTable
|
|
369
|
+
```svelte
|
|
370
|
+
<script>
|
|
371
|
+
import { PropsTable } from '@miozu/jera';
|
|
372
|
+
</script>
|
|
373
|
+
|
|
374
|
+
<PropsTable props={[
|
|
375
|
+
{ name: 'variant', type: 'string', default: '"default"', description: 'Visual style' },
|
|
376
|
+
{ name: 'onclick', type: 'function', required: true, description: 'Click handler' }
|
|
377
|
+
]} />
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Props: `props` (array of PropDef), `class`
|
|
381
|
+
|
|
382
|
+
PropDef: `{ name, type, default?, description, required? }`
|
|
383
|
+
|
|
384
|
+
### SplitPane
|
|
385
|
+
```svelte
|
|
386
|
+
<script>
|
|
387
|
+
import { SplitPane, CodeBlock } from '@miozu/jera';
|
|
388
|
+
</script>
|
|
389
|
+
|
|
390
|
+
<SplitPane ratio="1:1" gap="2rem" stickyRight>
|
|
391
|
+
{#snippet left()}
|
|
392
|
+
<h2>Description</h2>
|
|
393
|
+
<p>Explanation of the feature...</p>
|
|
394
|
+
{/snippet}
|
|
395
|
+
{#snippet right()}
|
|
396
|
+
<CodeBlock code={exampleCode} lang="javascript" />
|
|
397
|
+
{/snippet}
|
|
398
|
+
</SplitPane>
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Props: `left` (snippet), `right` (snippet), `ratio` (e.g., '1:1', '2:1'), `gap`, `minHeight`, `stickyRight`, `class`
|
|
402
|
+
|
|
403
|
+
### DocSection
|
|
404
|
+
```svelte
|
|
405
|
+
<script>
|
|
406
|
+
import { DocSection, CodeBlock } from '@miozu/jera';
|
|
407
|
+
</script>
|
|
408
|
+
|
|
409
|
+
<DocSection id="installation" title="Installation" description="How to install">
|
|
410
|
+
<CodeBlock code="npm install @miozu/jera" lang="bash" />
|
|
411
|
+
</DocSection>
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Props: `id`, `title`, `description`, `level` (2-6), `showAnchor`, `children`, `class`
|
|
415
|
+
|
|
319
416
|
---
|
|
320
417
|
|
|
321
418
|
## Actions Reference
|
|
@@ -358,32 +455,226 @@ AccordionItem props: `id`, `title`, `disabled`
|
|
|
358
455
|
2. Use semantic naming
|
|
359
456
|
3. Add light theme variant if applicable
|
|
360
457
|
|
|
361
|
-
### Theme Switching
|
|
458
|
+
### Theme Switching (Single Source of Truth Pattern)
|
|
459
|
+
|
|
460
|
+
jera uses a singleton pattern for global theme state. Storage key: `miozu-theme`.
|
|
461
|
+
|
|
462
|
+
#### SvelteKit Execution Order (from source code analysis)
|
|
463
|
+
|
|
464
|
+
Understanding when code runs helps choose the right pattern:
|
|
465
|
+
|
|
466
|
+
| Phase | File | Server | Client | Notes |
|
|
467
|
+
|-------|------|--------|--------|-------|
|
|
468
|
+
| 1 | +layout.server.js | ✓ | - | Server-only data |
|
|
469
|
+
| 2 | +layout.js | ✓ | ✓ | Universal load, runs on EVERY navigation |
|
|
470
|
+
| 3 | +layout.svelte `<script>` | ✓ | ✓ | Component script, runs once on mount |
|
|
471
|
+
| 4 | +layout.svelte `onMount()` | - | ✓ | Client-only, after hydration |
|
|
472
|
+
|
|
473
|
+
**Key insight from SvelteKit source (`client.js:684`):**
|
|
474
|
+
```javascript
|
|
475
|
+
data = { ...data, ...node.data }; // Parent data cascades to children
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
#### Recommended Pattern: +layout.svelte (for singletons like theme)
|
|
479
|
+
|
|
480
|
+
For global singleton state that doesn't need server context:
|
|
481
|
+
|
|
482
|
+
```svelte
|
|
483
|
+
<!-- +layout.svelte - Initialize ONCE, pass via props -->
|
|
484
|
+
<script>
|
|
485
|
+
import { getTheme } from '@miozu/jera';
|
|
486
|
+
import { onMount } from 'svelte';
|
|
487
|
+
import { Sidebar } from '$components';
|
|
488
|
+
|
|
489
|
+
// Call singleton once in root layout
|
|
490
|
+
const themeState = getTheme();
|
|
491
|
+
|
|
492
|
+
onMount(() => {
|
|
493
|
+
themeState.sync(); // Hydrate from DOM (app.html)
|
|
494
|
+
themeState.init(); // Setup media query listener
|
|
495
|
+
});
|
|
496
|
+
</script>
|
|
497
|
+
|
|
498
|
+
<!-- Pass themeState as prop to children -->
|
|
499
|
+
<Sidebar {themeState} />
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
```svelte
|
|
503
|
+
<!-- Child component - receives themeState as prop -->
|
|
504
|
+
<script>
|
|
505
|
+
// DON'T call getTheme() here - receive as prop instead
|
|
506
|
+
let { themeState } = $props();
|
|
507
|
+
|
|
508
|
+
function handleToggle() {
|
|
509
|
+
themeState.toggle();
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
let isDark = $derived(themeState.isDark);
|
|
513
|
+
</script>
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
#### Alternative Pattern: +layout.js (for data-heavy state)
|
|
517
|
+
|
|
518
|
+
For state that needs server context or async initialization:
|
|
519
|
+
|
|
362
520
|
```javascript
|
|
363
|
-
//
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
|
378
|
-
|
|
379
|
-
|
|
|
380
|
-
|
|
|
381
|
-
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
521
|
+
// +layout.js - Universal load
|
|
522
|
+
export const load = async ({ data, fetch }) => {
|
|
523
|
+
// Can fetch data, access parent(), etc.
|
|
524
|
+
const someData = await fetch('/api/data').then(r => r.json());
|
|
525
|
+
|
|
526
|
+
return {
|
|
527
|
+
...data,
|
|
528
|
+
someData
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**When to use which:**
|
|
534
|
+
|
|
535
|
+
| Use +layout.svelte | Use +layout.js |
|
|
536
|
+
|-------------------|----------------|
|
|
537
|
+
| Singletons (theme, toast) | Fetched data |
|
|
538
|
+
| Client-only initialization | Async operations |
|
|
539
|
+
| No server context needed | Needs `parent()` data |
|
|
540
|
+
| Runs once per mount | Needs invalidation support |
|
|
541
|
+
|
|
542
|
+
**ThemeState API:**
|
|
543
|
+
```javascript
|
|
544
|
+
themeState.toggle(); // Toggle dark/light
|
|
545
|
+
themeState.set('dark'); // Set to dark
|
|
546
|
+
themeState.set('light'); // Set to light
|
|
547
|
+
themeState.set('system'); // Follow system preference
|
|
548
|
+
|
|
549
|
+
// Reactive properties
|
|
550
|
+
themeState.current; // 'light' | 'dark' | 'system'
|
|
551
|
+
themeState.resolved; // 'light' | 'dark' (actual resolved value)
|
|
552
|
+
themeState.dataTheme; // 'miozu-light' | 'miozu-dark'
|
|
553
|
+
themeState.isDark; // boolean
|
|
554
|
+
themeState.isLight; // boolean
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
**Why pass as props instead of calling `getTheme()` everywhere?**
|
|
558
|
+
1. **Explicit data flow** - You see what state each component depends on
|
|
559
|
+
2. **Better testing** - Can inject mock state easily
|
|
560
|
+
3. **Single initialization** - Clear where state originates
|
|
561
|
+
4. **Efficiency** - Singleton runs once, not on every navigation
|
|
562
|
+
|
|
563
|
+
### Theme Data Attributes
|
|
564
|
+
|
|
565
|
+
| Theme | data-theme value |
|
|
566
|
+
|-------|------------------|
|
|
567
|
+
| Dark | `miozu-dark` |
|
|
568
|
+
| Light | `miozu-light` |
|
|
569
|
+
|
|
570
|
+
CSS selectors should use:
|
|
571
|
+
```css
|
|
572
|
+
[data-theme='miozu-dark'] { /* dark styles */ }
|
|
573
|
+
[data-theme='miozu-light'] { /* light styles */ }
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Migration Guide: Custom Theme → Jera Singleton
|
|
577
|
+
|
|
578
|
+
If you have a custom `theme.svelte.js` or similar, follow these steps:
|
|
579
|
+
|
|
580
|
+
#### Step 1: Update app.html
|
|
581
|
+
Replace custom theme script with unified storage key:
|
|
582
|
+
```javascript
|
|
583
|
+
// CRITICAL: Prevent FOUC - runs before any CSS
|
|
584
|
+
(function () {
|
|
585
|
+
try {
|
|
586
|
+
let pref = localStorage.getItem('miozu-theme');
|
|
587
|
+
|
|
588
|
+
// Migrate from old keys if needed
|
|
589
|
+
if (!pref || !['light', 'dark', 'system'].includes(pref)) {
|
|
590
|
+
const oldTheme = localStorage.getItem('theme'); // or your old key
|
|
591
|
+
if (oldTheme === 'miozu-dark' || oldTheme === 'dark') pref = 'dark';
|
|
592
|
+
else if (oldTheme === 'miozu-light' || oldTheme === 'light') pref = 'light';
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Resolve theme
|
|
596
|
+
let theme;
|
|
597
|
+
if (pref === 'light') theme = 'miozu-light';
|
|
598
|
+
else if (pref === 'dark') theme = 'miozu-dark';
|
|
599
|
+
else {
|
|
600
|
+
theme = window.matchMedia?.('(prefers-color-scheme: dark)').matches
|
|
601
|
+
? 'miozu-dark' : 'miozu-light';
|
|
602
|
+
pref = 'system';
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Apply and persist
|
|
606
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
607
|
+
document.documentElement.style.colorScheme = theme === 'miozu-dark' ? 'dark' : 'light';
|
|
608
|
+
localStorage.setItem('miozu-theme', pref);
|
|
609
|
+
document.cookie = 'miozu-theme=' + pref + '; path=/; max-age=31536000; SameSite=Lax';
|
|
610
|
+
|
|
611
|
+
// Clean up old keys
|
|
612
|
+
localStorage.removeItem('theme');
|
|
613
|
+
} catch (e) {
|
|
614
|
+
document.documentElement.setAttribute('data-theme', 'miozu-dark');
|
|
615
|
+
}
|
|
616
|
+
})();
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
#### Step 2: Update +layout.svelte
|
|
620
|
+
```svelte
|
|
621
|
+
<script>
|
|
622
|
+
import { getTheme } from '@miozu/jera';
|
|
623
|
+
import { onMount } from 'svelte';
|
|
624
|
+
|
|
625
|
+
const themeState = getTheme();
|
|
626
|
+
|
|
627
|
+
onMount(() => {
|
|
628
|
+
themeState.sync();
|
|
629
|
+
themeState.init();
|
|
630
|
+
});
|
|
631
|
+
</script>
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
#### Step 3: Update +layout.js
|
|
635
|
+
Remove any theme initialization - it should NOT be in +layout.js:
|
|
636
|
+
```javascript
|
|
637
|
+
// REMOVE these lines:
|
|
638
|
+
// import { ThemeReactiveState } from '$lib/reactiveStates/theme.svelte.js';
|
|
639
|
+
// const themeState = new ThemeReactiveState();
|
|
640
|
+
// return { themeState, ... };
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
#### Step 4: Update components using theme
|
|
644
|
+
```svelte
|
|
645
|
+
<script>
|
|
646
|
+
// OLD (remove):
|
|
647
|
+
// import { getThemeState } from '$lib/reactiveStates/theme.svelte.js';
|
|
648
|
+
// const themeState = getThemeState();
|
|
649
|
+
|
|
650
|
+
// NEW:
|
|
651
|
+
import { getTheme } from '@miozu/jera';
|
|
652
|
+
const themeState = getTheme();
|
|
653
|
+
|
|
654
|
+
let isDark = $derived(themeState.isDark);
|
|
655
|
+
</script>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
#### Step 5: Delete old theme file
|
|
659
|
+
```bash
|
|
660
|
+
rm src/lib/reactiveStates/theme.svelte.js
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
#### Step 6: Verify no old imports remain
|
|
664
|
+
```bash
|
|
665
|
+
grep -r "theme.svelte.js\|getThemeState\|ThemeReactiveState" src/
|
|
666
|
+
# Should return no matches
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### Apps Using Jera Theme (Proof of Concept)
|
|
670
|
+
|
|
671
|
+
| App | Status | Storage Key |
|
|
672
|
+
|-----|--------|-------------|
|
|
673
|
+
| dash.selify.ai | ✓ Migrated | `miozu-theme` |
|
|
674
|
+
| admin.selify.ai | ✓ Migrated | `miozu-theme` |
|
|
675
|
+
| miozu.com | ✓ Migrated | `miozu-theme` |
|
|
676
|
+
|
|
677
|
+
All three apps share the same theme preference via unified `miozu-theme` localStorage key.
|
|
387
678
|
|
|
388
679
|
---
|
|
389
680
|
|
package/README.md
CHANGED
|
@@ -48,18 +48,20 @@ Import tokens for consistent styling:
|
|
|
48
48
|
@import '@miozu/jera/tokens/effects';
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
### Miozu Color Palette
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
|
56
|
-
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
51
|
+
### Miozu Base16 Color Palette
|
|
52
|
+
|
|
53
|
+
Standard Base16 naming: `base00`-`base0F` (hex digits).
|
|
54
|
+
|
|
55
|
+
| Grayscale | Usage | Accents | Usage |
|
|
56
|
+
|-----------|-------|---------|-------|
|
|
57
|
+
| `base00` | Background | `base08` | Error (Red) |
|
|
58
|
+
| `base01` | Surface | `base09` | Warning (Orange) |
|
|
59
|
+
| `base02` | Selection | `base0A` | Highlight (Yellow) |
|
|
60
|
+
| `base03` | Muted | `base0B` | Success (Green) |
|
|
61
|
+
| `base04` | Secondary text | `base0C` | Info (Cyan) |
|
|
62
|
+
| `base05` | Primary text | `base0D` | Primary (Blue) |
|
|
63
|
+
| `base06` | High emphasis | `base0E` | Accent (Purple) |
|
|
64
|
+
| `base07` | Maximum contrast | `base0F` | Secondary accent |
|
|
63
65
|
|
|
64
66
|
## Components
|
|
65
67
|
|
|
@@ -172,23 +174,29 @@ button({ variant: 'secondary' }); // => "inline-flex items-center bg-surface h-1
|
|
|
172
174
|
|
|
173
175
|
## Theming
|
|
174
176
|
|
|
175
|
-
Dark theme is default.
|
|
177
|
+
Dark theme is default. Uses singleton pattern with `miozu-theme` storage key.
|
|
176
178
|
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
```javascript
|
|
180
|
+
// In root +layout.svelte
|
|
181
|
+
import { getTheme } from '@miozu/jera';
|
|
182
|
+
import { onMount } from 'svelte';
|
|
181
183
|
|
|
182
|
-
|
|
184
|
+
const theme = getTheme();
|
|
185
|
+
onMount(() => theme.init());
|
|
186
|
+
```
|
|
183
187
|
|
|
184
188
|
```javascript
|
|
185
|
-
|
|
189
|
+
// Anywhere in your app
|
|
190
|
+
import { getTheme } from '@miozu/jera';
|
|
186
191
|
|
|
187
|
-
const theme =
|
|
188
|
-
theme.
|
|
189
|
-
theme.
|
|
192
|
+
const theme = getTheme();
|
|
193
|
+
theme.toggle(); // Switch between light/dark
|
|
194
|
+
theme.set('system'); // Follow system preference
|
|
195
|
+
theme.isDark; // boolean reactive property
|
|
190
196
|
```
|
|
191
197
|
|
|
198
|
+
Data-theme values: `miozu-dark` (default) or `miozu-light`.
|
|
199
|
+
|
|
192
200
|
## AI-First Design
|
|
193
201
|
|
|
194
202
|
This library includes AI-optimized documentation:
|
package/llms.txt
CHANGED
|
@@ -56,9 +56,42 @@ const button = cv({
|
|
|
56
56
|
// Usage: button({ variant: 'secondary', size: 'sm' })
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
### Theme Usage Pattern
|
|
59
|
+
### Theme Usage Pattern (Single Source of Truth)
|
|
60
|
+
|
|
61
|
+
**SvelteKit Execution Order:**
|
|
62
|
+
1. +layout.server.js (server only)
|
|
63
|
+
2. +layout.js (server + client, runs on EVERY navigation)
|
|
64
|
+
3. +layout.svelte script (runs once on mount)
|
|
65
|
+
4. onMount() (client only, after hydration)
|
|
66
|
+
|
|
67
|
+
**Recommended: Initialize in +layout.svelte** (for singletons):
|
|
68
|
+
```svelte
|
|
69
|
+
<!-- +layout.svelte -->
|
|
70
|
+
<script>
|
|
71
|
+
import { getTheme } from '@miozu/jera';
|
|
72
|
+
import { onMount } from 'svelte';
|
|
73
|
+
|
|
74
|
+
const themeState = getTheme(); // Call ONCE
|
|
75
|
+
onMount(() => {
|
|
76
|
+
themeState.sync(); // Hydrate from DOM
|
|
77
|
+
themeState.init(); // Setup media query listener
|
|
78
|
+
});
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<!-- Pass to children as props -->
|
|
82
|
+
<Sidebar {themeState} />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Child components receive as prop:**
|
|
60
86
|
```javascript
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
let { themeState } = $props(); // DON'T call getTheme()
|
|
88
|
+
themeState.toggle(); // Switch light/dark
|
|
89
|
+
themeState.isDark; // Reactive boolean
|
|
64
90
|
```
|
|
91
|
+
|
|
92
|
+
**Why +layout.svelte over +layout.js for singletons?**
|
|
93
|
+
- Runs once per mount (not on every navigation)
|
|
94
|
+
- onMount clearly separates SSR from client code
|
|
95
|
+
- More efficient for global state that doesn't change
|
|
96
|
+
|
|
97
|
+
Storage key: `miozu-theme`. Data-theme values: `miozu-dark` | `miozu-light`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miozu/jera",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Minimal, reactive component library for Svelte 5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -26,7 +26,17 @@
|
|
|
26
26
|
"CLAUDE.md"
|
|
27
27
|
],
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"svelte": "^5.0.0"
|
|
29
|
+
"svelte": "^5.0.0",
|
|
30
|
+
"shiki": "^3.0.0",
|
|
31
|
+
"@lucide/svelte": "^0.500.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependenciesMeta": {
|
|
34
|
+
"shiki": {
|
|
35
|
+
"optional": true
|
|
36
|
+
},
|
|
37
|
+
"@lucide/svelte": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
30
40
|
},
|
|
31
41
|
"devDependencies": {
|
|
32
42
|
"svelte": "^5.41.0"
|