@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
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
# @miozu/jera - AI Context File
|
|
2
|
+
|
|
3
|
+
**Package:** @miozu/jera
|
|
4
|
+
**Purpose:** Minimal, reactive component library for Svelte 5
|
|
5
|
+
**Author:** Nicholas Glazer <glazer.nicholas@gmail.com>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Project Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
jera/
|
|
13
|
+
├── src/
|
|
14
|
+
│ ├── index.js # Main exports
|
|
15
|
+
│ ├── tokens/ # Design tokens (CSS custom properties)
|
|
16
|
+
│ │ ├── index.css # Bundle all tokens
|
|
17
|
+
│ │ ├── colors.css # Miozu Base16 palette
|
|
18
|
+
│ │ ├── spacing.css # 4px-based scale
|
|
19
|
+
│ │ ├── typography.css # Font system
|
|
20
|
+
│ │ └── effects.css # Shadows, radius, transitions
|
|
21
|
+
│ ├── utils/
|
|
22
|
+
│ │ ├── cn.svelte.js # cn(), cv() class utilities
|
|
23
|
+
│ │ └── reactive.svelte.js # ThemeState, reactive helpers
|
|
24
|
+
│ ├── actions/
|
|
25
|
+
│ │ └── index.js # Svelte actions
|
|
26
|
+
│ └── components/
|
|
27
|
+
│ ├── primitives/ # Button, Badge, Divider, Avatar
|
|
28
|
+
│ ├── forms/ # Input, Select, Checkbox, Switch
|
|
29
|
+
│ ├── feedback/ # Toast, Skeleton, ProgressBar, Spinner
|
|
30
|
+
│ ├── overlays/ # Modal, Popover
|
|
31
|
+
│ ├── navigation/ # Tabs, Accordion, Sidebar
|
|
32
|
+
│ └── docs/ # CodeBlock, PropsTable, SplitPane, DocSection
|
|
33
|
+
├── llms.txt # AI documentation index
|
|
34
|
+
├── CLAUDE.md # This file
|
|
35
|
+
└── package.json
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Coding Standards
|
|
41
|
+
|
|
42
|
+
### Svelte 5 Patterns (REQUIRED)
|
|
43
|
+
- Use `$props()` for component props (single call only)
|
|
44
|
+
- Use `$state()` for reactive local state
|
|
45
|
+
- Use `$derived()` for computed values
|
|
46
|
+
- Use `$effect()` sparingly, only for side effects
|
|
47
|
+
- Use `$bindable()` for two-way binding props
|
|
48
|
+
|
|
49
|
+
### Component Template
|
|
50
|
+
```svelte
|
|
51
|
+
<script>
|
|
52
|
+
let {
|
|
53
|
+
variant = 'default',
|
|
54
|
+
size = 'md',
|
|
55
|
+
disabled = false,
|
|
56
|
+
class: className = '',
|
|
57
|
+
...rest
|
|
58
|
+
} = $props();
|
|
59
|
+
|
|
60
|
+
const computedClass = $derived(/* class logic */);
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<element class={computedClass} {disabled} {...rest}>
|
|
64
|
+
{@render children?.()}
|
|
65
|
+
</element>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Class Variants (cv) Pattern
|
|
69
|
+
```javascript
|
|
70
|
+
export const componentStyles = cv({
|
|
71
|
+
base: 'base-classes here',
|
|
72
|
+
variants: {
|
|
73
|
+
variantName: {
|
|
74
|
+
option1: 'classes-for-option1',
|
|
75
|
+
option2: 'classes-for-option2'
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
compounds: [
|
|
79
|
+
{ condition: { variant: 'x', size: 'y' }, class: 'compound-classes' }
|
|
80
|
+
],
|
|
81
|
+
defaults: { variantName: 'option1' }
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Naming Conventions
|
|
86
|
+
- Components: PascalCase (`Button.svelte`)
|
|
87
|
+
- Utilities: camelCase (`getTheme`)
|
|
88
|
+
- CSS tokens: kebab-case (`--color-primary`)
|
|
89
|
+
- Actions: camelCase (`clickOutside`)
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
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 |
|
|
120
|
+
|
|
121
|
+
### Semantic Mappings
|
|
122
|
+
```css
|
|
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);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Component API Reference
|
|
135
|
+
|
|
136
|
+
### Button
|
|
137
|
+
```svelte
|
|
138
|
+
<Button
|
|
139
|
+
variant="primary|secondary|ghost|outline|danger|success"
|
|
140
|
+
size="xs|sm|md|lg|xl"
|
|
141
|
+
disabled={boolean}
|
|
142
|
+
loading={boolean}
|
|
143
|
+
fullWidth={boolean}
|
|
144
|
+
href={string} // Renders as <a> if provided
|
|
145
|
+
onclick={function}
|
|
146
|
+
>
|
|
147
|
+
Content
|
|
148
|
+
</Button>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Input
|
|
152
|
+
```svelte
|
|
153
|
+
<Input
|
|
154
|
+
bind:value={string}
|
|
155
|
+
type="text|email|password|number|..."
|
|
156
|
+
placeholder={string}
|
|
157
|
+
disabled={boolean}
|
|
158
|
+
required={boolean}
|
|
159
|
+
oninput={function}
|
|
160
|
+
onchange={function}
|
|
161
|
+
/>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Select
|
|
165
|
+
```svelte
|
|
166
|
+
<Select
|
|
167
|
+
options={[{ value, label }]}
|
|
168
|
+
bind:value={any}
|
|
169
|
+
placeholder={string}
|
|
170
|
+
labelKey="label"
|
|
171
|
+
valueKey="value"
|
|
172
|
+
disabled={boolean}
|
|
173
|
+
onchange={function}
|
|
174
|
+
/>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Badge
|
|
178
|
+
```svelte
|
|
179
|
+
<Badge
|
|
180
|
+
variant="default|primary|secondary|success|warning|error"
|
|
181
|
+
size="sm|md|lg"
|
|
182
|
+
>
|
|
183
|
+
Text
|
|
184
|
+
</Badge>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Toast
|
|
188
|
+
```svelte
|
|
189
|
+
<!-- In root layout -->
|
|
190
|
+
<script>
|
|
191
|
+
import { Toast, createToastContext } from '@miozu/jera';
|
|
192
|
+
const toast = createToastContext();
|
|
193
|
+
</script>
|
|
194
|
+
<Toast />
|
|
195
|
+
|
|
196
|
+
<!-- Usage anywhere -->
|
|
197
|
+
<script>
|
|
198
|
+
import { getToastContext } from '@miozu/jera';
|
|
199
|
+
const toast = getToastContext();
|
|
200
|
+
toast.success('Message');
|
|
201
|
+
toast.error('Error message');
|
|
202
|
+
</script>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Modal
|
|
206
|
+
```svelte
|
|
207
|
+
<script>
|
|
208
|
+
import { Modal, Button } from '@miozu/jera';
|
|
209
|
+
let showModal = $state(false);
|
|
210
|
+
</script>
|
|
211
|
+
|
|
212
|
+
<Button onclick={() => showModal = true}>Open Modal</Button>
|
|
213
|
+
|
|
214
|
+
<Modal bind:open={showModal} title="Confirm Action" variant="danger">
|
|
215
|
+
<p>Are you sure you want to proceed?</p>
|
|
216
|
+
{#snippet footer()}
|
|
217
|
+
<Button variant="ghost" onclick={() => showModal = false}>Cancel</Button>
|
|
218
|
+
<Button variant="danger" onclick={handleConfirm}>Confirm</Button>
|
|
219
|
+
{/snippet}
|
|
220
|
+
</Modal>
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Props: `open`, `title`, `size` (sm/md/lg/xl), `variant` (default/danger/warning/success/info), `closeOnBackdrop`, `closeOnEscape`, `showClose`, `children`, `footer`, `icon`, `onclose`
|
|
224
|
+
|
|
225
|
+
### Popover
|
|
226
|
+
```svelte
|
|
227
|
+
<script>
|
|
228
|
+
import { Popover, Button } from '@miozu/jera';
|
|
229
|
+
</script>
|
|
230
|
+
|
|
231
|
+
<Popover content="Helpful tooltip text" position="top">
|
|
232
|
+
<Button>Hover me</Button>
|
|
233
|
+
</Popover>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Props: `content`, `position` (top/bottom/left/right), `delay` ({show, hide}), `offset`
|
|
237
|
+
|
|
238
|
+
### Divider
|
|
239
|
+
```svelte
|
|
240
|
+
<Divider />
|
|
241
|
+
<Divider orientation="vertical" />
|
|
242
|
+
<Divider>or continue with</Divider>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Props: `orientation` (horizontal/vertical), `thickness`, `spacing`, `children`
|
|
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
|
+
|
|
275
|
+
### Avatar
|
|
276
|
+
```svelte
|
|
277
|
+
<Avatar src="/user.jpg" alt="John Doe" />
|
|
278
|
+
<Avatar name="John Doe" />
|
|
279
|
+
<Avatar src="/user.jpg" status="online" size="lg" />
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Props: `src`, `alt`, `name`, `size` (xs/sm/md/lg/xl/2xl), `status` (online/offline/busy/away)
|
|
283
|
+
|
|
284
|
+
### Skeleton
|
|
285
|
+
```svelte
|
|
286
|
+
<Skeleton width="80%" />
|
|
287
|
+
<Skeleton variant="circle" size="48px" />
|
|
288
|
+
<Skeleton variant="rect" width="100%" height="200px" />
|
|
289
|
+
<Skeleton lines={3} />
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Props: `variant` (text/heading/circle/rect), `width`, `height`, `size`, `lines`, `animate`
|
|
293
|
+
|
|
294
|
+
### ProgressBar
|
|
295
|
+
```svelte
|
|
296
|
+
<ProgressBar value={65} />
|
|
297
|
+
<ProgressBar value={80} showLabel variant="success" />
|
|
298
|
+
<ProgressBar indeterminate />
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Props: `value`, `max`, `size` (sm/md/lg), `variant` (primary/success/warning/error/info), `showLabel`, `label`, `indeterminate`
|
|
302
|
+
|
|
303
|
+
### Spinner
|
|
304
|
+
```svelte
|
|
305
|
+
<Spinner />
|
|
306
|
+
<Spinner size="lg" color="var(--color-base11)" />
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Props: `size` (xs/sm/md/lg/xl), `color`, `label`
|
|
310
|
+
|
|
311
|
+
### Tabs
|
|
312
|
+
```svelte
|
|
313
|
+
<script>
|
|
314
|
+
import { Tabs } from '@miozu/jera';
|
|
315
|
+
let activeTab = $state('tab1');
|
|
316
|
+
</script>
|
|
317
|
+
|
|
318
|
+
<Tabs
|
|
319
|
+
tabs={[
|
|
320
|
+
{ id: 'tab1', label: 'Overview' },
|
|
321
|
+
{ id: 'tab2', label: 'Settings', badge: 3 },
|
|
322
|
+
{ id: 'tab3', label: 'Analytics', disabled: true }
|
|
323
|
+
]}
|
|
324
|
+
bind:active={activeTab}
|
|
325
|
+
variant="underline"
|
|
326
|
+
/>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Props: `tabs` (array), `active`, `variant` (default/underline/pills), `size` (sm/md/lg), `fullWidth`, `onchange`
|
|
330
|
+
|
|
331
|
+
### Accordion
|
|
332
|
+
```svelte
|
|
333
|
+
<script>
|
|
334
|
+
import { Accordion, AccordionItem } from '@miozu/jera';
|
|
335
|
+
</script>
|
|
336
|
+
|
|
337
|
+
<Accordion multiple>
|
|
338
|
+
<AccordionItem id="section1" title="Section 1">
|
|
339
|
+
Content for section 1
|
|
340
|
+
</AccordionItem>
|
|
341
|
+
<AccordionItem id="section2" title="Section 2">
|
|
342
|
+
Content for section 2
|
|
343
|
+
</AccordionItem>
|
|
344
|
+
</Accordion>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Accordion props: `expanded` (array of ids), `multiple`
|
|
348
|
+
AccordionItem props: `id`, `title`, `disabled`
|
|
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
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Actions Reference
|
|
419
|
+
|
|
420
|
+
### clickOutside
|
|
421
|
+
```svelte
|
|
422
|
+
<div use:clickOutside={() => isOpen = false}>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### focusTrap
|
|
426
|
+
```svelte
|
|
427
|
+
<dialog use:focusTrap={{ enabled: isOpen }}>
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### portal
|
|
431
|
+
```svelte
|
|
432
|
+
<div use:portal={'body'}>
|
|
433
|
+
Renders at body level
|
|
434
|
+
</div>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### escapeKey
|
|
438
|
+
```svelte
|
|
439
|
+
<div use:escapeKey={() => close()}>
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## Common Tasks
|
|
445
|
+
|
|
446
|
+
### Add New Component
|
|
447
|
+
1. Create file in appropriate folder (`primitives/`, `forms/`, `feedback/`)
|
|
448
|
+
2. Use single `$props()` call
|
|
449
|
+
3. Export styles with `cv()` if variants needed
|
|
450
|
+
4. Add to `src/index.js` exports
|
|
451
|
+
5. Document in this file
|
|
452
|
+
|
|
453
|
+
### Add New Token
|
|
454
|
+
1. Add to appropriate token file in `src/tokens/`
|
|
455
|
+
2. Use semantic naming
|
|
456
|
+
3. Add light theme variant if applicable
|
|
457
|
+
|
|
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
|
+
|
|
520
|
+
```javascript
|
|
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.
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
## Integration with dash.selify.ai
|
|
682
|
+
|
|
683
|
+
jera components work out-of-the-box with dash.selify.ai. The semantic tokens are already configured in both systems.
|
|
684
|
+
|
|
685
|
+
### Required: Import jera tokens
|
|
686
|
+
```css
|
|
687
|
+
/* In your app.css or layout */
|
|
688
|
+
@import '@miozu/jera/tokens/colors.css';
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### Semantic Token Mapping
|
|
692
|
+
| jera Token | dash.selify.ai Equivalent |
|
|
693
|
+
|------------|---------------------------|
|
|
694
|
+
| `--color-bg` | `--color-base00` |
|
|
695
|
+
| `--color-surface` | `--color-base01` |
|
|
696
|
+
| `--color-surface-alt` | `--color-base02` |
|
|
697
|
+
| `--color-text` | `--color-base05` |
|
|
698
|
+
| `--color-text-strong` | `--color-base07` |
|
|
699
|
+
| `--color-text-muted` | `--color-base04` |
|
|
700
|
+
| `--color-primary` | `--color-base0D` |
|
|
701
|
+
| `--color-success` | `--color-base0B` |
|
|
702
|
+
| `--color-warning` | `--color-base0A` |
|
|
703
|
+
| `--color-error` | `--color-base08` |
|
|
704
|
+
| `--color-info` | `--color-base0C` |
|
|
705
|
+
|
|
706
|
+
### Using jera Components in dash.selify.ai
|
|
707
|
+
```svelte
|
|
708
|
+
<script>
|
|
709
|
+
import { Button, Modal, Input } from '@miozu/jera';
|
|
710
|
+
|
|
711
|
+
let showModal = $state(false);
|
|
712
|
+
</script>
|
|
713
|
+
|
|
714
|
+
<!-- Works with existing dash.selify.ai theme system -->
|
|
715
|
+
<Button variant="primary" onclick={() => showModal = true}>
|
|
716
|
+
Open Modal
|
|
717
|
+
</Button>
|
|
718
|
+
|
|
719
|
+
<Modal bind:open={showModal} title="Example">
|
|
720
|
+
<Input placeholder="Type here..." />
|
|
721
|
+
</Modal>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
---
|
|
725
|
+
|
|
726
|
+
## Rules for AI Assistants
|
|
727
|
+
|
|
728
|
+
1. **Always use Svelte 5 runes** - No legacy `$:`, `export let`, stores
|
|
729
|
+
2. **Single $props() call** - Destructure all props in one call
|
|
730
|
+
3. **Use cv() for variants** - Don't hardcode conditional classes
|
|
731
|
+
4. **Semantic colors** - Use `--color-*` tokens, not raw `--base*`
|
|
732
|
+
5. **Accessibility first** - Include ARIA attributes, keyboard support
|
|
733
|
+
6. **No TypeScript** - Pure JavaScript with JSDoc for documentation
|
|
734
|
+
7. **Mobile-first** - Design for mobile, enhance for desktop
|