@miozu/jera 0.8.3 → 0.8.4

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/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@miozu/jera",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Zero-dependency, AI-first component library for Svelte 5",
5
5
  "type": "module",
6
+ "scripts": {
7
+ "prepublishOnly": "echo 'Publishing @miozu/jera...' && test -f src/index.js"
8
+ },
6
9
  "svelte": "./src/index.js",
7
10
  "exports": {
8
11
  ".": {
@@ -33,7 +36,7 @@
33
36
  }
34
37
  },
35
38
  "devDependencies": {
36
- "svelte": "^5.41.0"
39
+ "svelte": "^5.49.2"
37
40
  },
38
41
  "keywords": [
39
42
  "svelte",
@@ -55,6 +58,5 @@
55
58
  "bugs": {
56
59
  "url": "https://github.com/miozu-com/jera/issues"
57
60
  },
58
- "dependencies": {},
59
- "scripts": {}
60
- }
61
+ "dependencies": {}
62
+ }
@@ -1,21 +1,18 @@
1
1
  <!--
2
2
  @component Accordion
3
3
 
4
- Collapsible content sections.
4
+ Group container for collapsible sections. Controls shared expand/collapse state.
5
5
 
6
- @example Single item
6
+ @example Single-open (default)
7
7
  <Accordion>
8
- <AccordionItem title="Section 1">
9
- Content for section 1
10
- </AccordionItem>
11
- <AccordionItem title="Section 2">
12
- Content for section 2
13
- </AccordionItem>
8
+ <AccordionItem title="Section 1">Content</AccordionItem>
9
+ <AccordionItem title="Section 2">Content</AccordionItem>
14
10
  </Accordion>
15
11
 
16
- @example Controlled
17
- <Accordion bind:expanded={openSections} multiple>
18
- ...
12
+ @example Multiple open
13
+ <Accordion bind:expanded={openIds} multiple>
14
+ <AccordionItem id="a" title="First">Content</AccordionItem>
15
+ <AccordionItem id="b" title="Second" badge="3">Content</AccordionItem>
19
16
  </Accordion>
20
17
  -->
21
18
  <script>
@@ -30,36 +27,30 @@
30
27
 
31
28
  function toggle(id) {
32
29
  if (multiple) {
33
- if (expanded.includes(id)) {
34
- expanded = expanded.filter(i => i !== id);
35
- } else {
36
- expanded = [...expanded, id];
37
- }
30
+ expanded = expanded.includes(id)
31
+ ? expanded.filter(i => i !== id)
32
+ : [...expanded, id];
38
33
  } else {
39
34
  expanded = expanded.includes(id) ? [] : [id];
40
35
  }
41
36
  }
42
37
 
43
- function isExpanded(id) {
44
- return expanded.includes(id);
45
- }
46
-
47
38
  setContext('accordion', {
48
39
  toggle,
49
40
  isExpanded: (id) => expanded.includes(id)
50
41
  });
51
42
  </script>
52
43
 
53
- <div class="accordion {className}">
44
+ <div class="jera-accordion {className}" role="region">
54
45
  {@render children?.()}
55
46
  </div>
56
47
 
57
48
  <style>
58
- .accordion {
49
+ .jera-accordion {
59
50
  display: flex;
60
51
  flex-direction: column;
61
- border: 1px solid var(--color-base03);
62
52
  border-radius: 0.5rem;
63
53
  overflow: hidden;
54
+ background: color-mix(in srgb, var(--color-base01) 60%, transparent);
64
55
  }
65
56
  </style>
@@ -1,63 +1,90 @@
1
1
  <!--
2
2
  @component AccordionItem
3
3
 
4
- Individual accordion section. Must be used inside Accordion.
4
+ Collapsible content section. Works in two modes:
5
5
 
6
- @example
7
- <AccordionItem id="section-1" title="Click to expand">
8
- Hidden content here
6
+ **Group** — inside an <Accordion>, state is managed by the parent context.
7
+ **Solo** standalone, manages its own state via bind:expanded.
8
+
9
+ @example Group (inside Accordion)
10
+ <Accordion>
11
+ <AccordionItem title="Section 1">Content</AccordionItem>
12
+ <AccordionItem title="Section 2" badge="5">Content</AccordionItem>
13
+ </Accordion>
14
+
15
+ @example Solo (standalone)
16
+ <AccordionItem title="Settings" bind:expanded={open}>
17
+ Content here
18
+ </AccordionItem>
19
+
20
+ @example With leading/trailing snippets
21
+ <AccordionItem title="Infrastructure">
22
+ {#snippet leading()}<Activity size={14} />{/snippet}
23
+ {#snippet trailing()}<Badge variant="success">OK</Badge>{/snippet}
24
+ Content here
9
25
  </AccordionItem>
10
26
  -->
11
27
  <script>
12
28
  import { getContext } from 'svelte';
13
29
  import { slide } from 'svelte/transition';
30
+ import { cubicOut } from 'svelte/easing';
14
31
 
15
32
  let {
16
33
  id,
17
34
  title = '',
35
+ expanded: expandedProp = $bindable(false),
18
36
  disabled = false,
37
+ badge = null,
38
+ leading,
39
+ trailing,
19
40
  children,
20
41
  class: className = ''
21
42
  } = $props();
22
43
 
23
44
  const accordion = getContext('accordion');
45
+ const itemId = id || `acc-${Math.random().toString(36).slice(2, 9)}`;
46
+ const inGroup = !!accordion;
24
47
 
25
- // Generate id if not provided
26
- const itemId = id || `accordion-${Math.random().toString(36).slice(2, 9)}`;
27
-
28
- const isOpen = $derived(accordion?.isExpanded(itemId) ?? false);
48
+ const isOpen = $derived(
49
+ inGroup ? (accordion.isExpanded(itemId) ?? false) : expandedProp
50
+ );
29
51
 
30
52
  function handleClick() {
31
- if (!disabled && accordion) {
53
+ if (disabled) return;
54
+ if (inGroup) {
32
55
  accordion.toggle(itemId);
33
- }
34
- }
35
-
36
- function handleKeydown(e) {
37
- if (e.key === 'Enter' || e.key === ' ') {
38
- e.preventDefault();
39
- handleClick();
56
+ } else {
57
+ expandedProp = !expandedProp;
40
58
  }
41
59
  }
42
60
  </script>
43
61
 
44
- <div class="accordion-item {className}" class:accordion-item-disabled={disabled}>
62
+ <div
63
+ class="accordion-item {className}"
64
+ class:accordion-item-solo={!inGroup}
65
+ class:accordion-item-disabled={disabled}
66
+ >
45
67
  <button
46
68
  type="button"
47
69
  class="accordion-trigger"
48
70
  class:accordion-trigger-open={isOpen}
49
71
  aria-expanded={isOpen}
50
72
  aria-controls="content-{itemId}"
51
- aria-disabled={disabled}
73
+ aria-disabled={disabled || undefined}
74
+ {disabled}
52
75
  onclick={handleClick}
53
- onkeydown={handleKeydown}
54
76
  >
77
+ {#if leading}{@render leading()}{/if}
55
78
  <span class="accordion-title">{title}</span>
79
+ {#if badge != null}
80
+ <span class="accordion-badge">{badge}</span>
81
+ {/if}
82
+ {#if trailing}{@render trailing()}{/if}
56
83
  <svg
57
- class="accordion-icon"
58
- class:accordion-icon-open={isOpen}
59
- width="16"
60
- height="16"
84
+ class="accordion-chevron"
85
+ class:accordion-chevron-open={isOpen}
86
+ width="14"
87
+ height="14"
61
88
  viewBox="0 0 24 24"
62
89
  fill="none"
63
90
  stroke="currentColor"
@@ -73,7 +100,9 @@
73
100
  <div
74
101
  id="content-{itemId}"
75
102
  class="accordion-content"
76
- transition:slide={{ duration: 200 }}
103
+ role="region"
104
+ aria-labelledby="trigger-{itemId}"
105
+ transition:slide={{ duration: 200, easing: cubicOut }}
77
106
  >
78
107
  <div class="accordion-body">
79
108
  {@render children?.()}
@@ -83,62 +112,110 @@
83
112
  </div>
84
113
 
85
114
  <style>
115
+ /* Item container */
86
116
  .accordion-item {
87
- border-bottom: 1px solid var(--color-base03);
117
+ border-bottom: 1px solid var(--color-base02);
88
118
  }
89
119
 
90
120
  .accordion-item:last-child {
91
121
  border-bottom: none;
92
122
  }
93
123
 
124
+ .accordion-item-solo {
125
+ border-radius: 0.5rem;
126
+ overflow: hidden;
127
+ background: color-mix(in srgb, var(--color-base01) 60%, transparent);
128
+ border-bottom: none;
129
+ }
130
+
94
131
  .accordion-item-disabled {
95
132
  opacity: 0.5;
133
+ pointer-events: none;
96
134
  }
97
135
 
136
+ /* Trigger / header */
98
137
  .accordion-trigger {
99
138
  display: flex;
100
139
  align-items: center;
101
- justify-content: space-between;
140
+ gap: 0.5rem;
102
141
  width: 100%;
103
- padding: 1rem;
104
- background: transparent;
142
+ height: 2.75rem;
143
+ padding: 0 1rem;
144
+ background: color-mix(in srgb, var(--color-base00) 50%, transparent);
105
145
  border: none;
106
- font-size: 0.9375rem;
107
- font-weight: 500;
108
- color: var(--color-base07);
146
+ font-family: inherit;
147
+ font-size: 0.75rem;
148
+ font-weight: 600;
149
+ color: var(--color-base06);
109
150
  cursor: pointer;
110
151
  text-align: left;
111
152
  transition: background 0.15s ease;
112
153
  }
113
154
 
114
- .accordion-trigger:hover:not([aria-disabled="true"]) {
115
- background: var(--color-base01);
155
+ .accordion-trigger:hover:not(:disabled) {
156
+ background: color-mix(in srgb, var(--color-base00) 80%, transparent);
116
157
  }
117
158
 
118
159
  .accordion-trigger-open {
119
- background: var(--color-base01);
160
+ background: color-mix(in srgb, var(--color-base00) 90%, transparent);
120
161
  }
121
162
 
163
+ .accordion-trigger:focus-visible {
164
+ outline: 2px solid var(--color-base0D);
165
+ outline-offset: -2px;
166
+ }
167
+
168
+ /* Title */
122
169
  .accordion-title {
123
170
  flex: 1;
171
+ overflow: hidden;
172
+ text-overflow: ellipsis;
173
+ white-space: nowrap;
174
+ }
175
+
176
+ /* Badge */
177
+ .accordion-badge {
178
+ flex-shrink: 0;
179
+ padding: 0.0625rem 0.375rem;
180
+ font-size: 0.6875rem;
181
+ font-weight: 600;
182
+ color: var(--color-base04);
183
+ background: color-mix(in srgb, var(--color-base04) 10%, transparent);
184
+ border-radius: 9999px;
124
185
  }
125
186
 
126
- .accordion-icon {
187
+ /* Chevron */
188
+ .accordion-chevron {
127
189
  flex-shrink: 0;
128
190
  color: var(--color-base05);
129
191
  transition: transform 0.2s ease;
130
192
  }
131
193
 
132
- .accordion-icon-open {
194
+ .accordion-trigger:hover:not(:disabled) .accordion-chevron {
195
+ color: var(--color-base06);
196
+ transform: scale(1.125);
197
+ }
198
+
199
+ .accordion-chevron-open {
133
200
  transform: rotate(180deg);
134
201
  }
135
202
 
203
+ .accordion-trigger:hover:not(:disabled) .accordion-chevron-open {
204
+ transform: rotate(180deg) scale(1.125);
205
+ }
206
+
207
+ /* Content */
136
208
  .accordion-content {
137
- overflow: hidden;
209
+ background: var(--color-base01);
210
+ border-bottom: 1px solid var(--color-base02);
211
+ }
212
+
213
+ .accordion-item:last-child .accordion-content {
214
+ border-bottom: none;
138
215
  }
139
216
 
140
217
  .accordion-body {
141
- padding: 0 1rem 1rem 1rem;
218
+ padding: 1rem;
142
219
  font-size: 0.875rem;
143
220
  color: var(--color-base05);
144
221
  line-height: 1.6;
@@ -122,9 +122,9 @@
122
122
  {/if}
123
123
 
124
124
  <style>
125
- /* ============================================
125
+ /* --------------------------------------------
126
126
  SEGMENTED CONTROL
127
- ============================================ */
127
+ -------------------------------------------- */
128
128
  .theme-select-segmented {
129
129
  display: inline-flex;
130
130
  align-items: center;
@@ -177,9 +177,9 @@
177
177
  white-space: nowrap;
178
178
  }
179
179
 
180
- /* ============================================
180
+ /* --------------------------------------------
181
181
  SIZES - SEGMENTED
182
- ============================================ */
182
+ -------------------------------------------- */
183
183
  .theme-select-sm .theme-select-option {
184
184
  padding: 2px var(--space-2, 0.5rem);
185
185
  font-size: var(--text-xs, 0.75rem);
@@ -195,9 +195,9 @@
195
195
  font-size: var(--text-base, 1rem);
196
196
  }
197
197
 
198
- /* ============================================
198
+ /* --------------------------------------------
199
199
  DROPDOWN
200
- ============================================ */
200
+ -------------------------------------------- */
201
201
  .theme-select-dropdown {
202
202
  position: relative;
203
203
  display: inline-flex;
@@ -235,9 +235,9 @@
235
235
  color: var(--color-base04, #665c54);
236
236
  }
237
237
 
238
- /* ============================================
238
+ /* --------------------------------------------
239
239
  SIZES - DROPDOWN
240
- ============================================ */
240
+ -------------------------------------------- */
241
241
  .theme-select-dropdown.theme-select-sm .theme-select-native {
242
242
  padding: var(--space-1, 0.25rem) var(--space-6, 1.5rem) var(--space-1, 0.25rem) var(--space-2, 0.5rem);
243
243
  font-size: var(--text-xs, 0.75rem);
@@ -248,9 +248,9 @@
248
248
  font-size: var(--text-base, 1rem);
249
249
  }
250
250
 
251
- /* ============================================
251
+ /* --------------------------------------------
252
252
  REDUCED MOTION
253
- ============================================ */
253
+ -------------------------------------------- */
254
254
  @media (prefers-reduced-motion: reduce) {
255
255
  .theme-select-option,
256
256
  .theme-select-native {
@@ -121,9 +121,9 @@
121
121
  </button>
122
122
 
123
123
  <style>
124
- /* ============================================
124
+ /* --------------------------------------------
125
125
  BASE STYLES
126
- ============================================ */
126
+ -------------------------------------------- */
127
127
  .theme-toggle {
128
128
  position: relative;
129
129
  display: inline-flex;
@@ -141,9 +141,9 @@
141
141
  outline-offset: 2px;
142
142
  }
143
143
 
144
- /* ============================================
144
+ /* --------------------------------------------
145
145
  VARIANTS
146
- ============================================ */
146
+ -------------------------------------------- */
147
147
  .theme-toggle-ghost {
148
148
  background-color: transparent;
149
149
  color: var(--color-base05, #a89984);
@@ -176,9 +176,9 @@
176
176
  color: var(--color-base06, #d5c4a1);
177
177
  }
178
178
 
179
- /* ============================================
179
+ /* --------------------------------------------
180
180
  SIZES
181
- ============================================ */
181
+ -------------------------------------------- */
182
182
  .theme-toggle-sm {
183
183
  padding: var(--space-1, 0.25rem);
184
184
  min-width: 1.75rem;
@@ -197,9 +197,9 @@
197
197
  min-height: 2.75rem;
198
198
  }
199
199
 
200
- /* ============================================
200
+ /* --------------------------------------------
201
201
  ICON ANIMATION
202
- ============================================ */
202
+ -------------------------------------------- */
203
203
  .theme-toggle-icon-wrapper {
204
204
  position: relative;
205
205
  display: flex;
@@ -235,9 +235,9 @@
235
235
  color: var(--color-base0D, #83a598);
236
236
  }
237
237
 
238
- /* ============================================
238
+ /* --------------------------------------------
239
239
  REDUCED MOTION
240
- ============================================ */
240
+ -------------------------------------------- */
241
241
  @media (prefers-reduced-motion: reduce) {
242
242
  .theme-toggle-icon {
243
243
  transition: opacity var(--duration-fast, 150ms) var(--ease-out, ease-out);
@@ -249,9 +249,9 @@
249
249
  }
250
250
  }
251
251
 
252
- /* ============================================
252
+ /* --------------------------------------------
253
253
  LABEL
254
- ============================================ */
254
+ -------------------------------------------- */
255
255
  .theme-toggle-label {
256
256
  font-size: var(--text-sm, 0.875rem);
257
257
  color: var(--color-base05, #a89984);
package/src/index.js CHANGED
@@ -21,9 +21,9 @@
21
21
  * import '@miozu/jera/tokens';
22
22
  */
23
23
 
24
- // ============================================
24
+ // --------------------------------------------
25
25
  // COMPONENTS - Primitives
26
- // ============================================
26
+ // --------------------------------------------
27
27
 
28
28
  export { default as Button } from './components/primitives/Button.svelte';
29
29
  export { default as Badge } from './components/primitives/Badge.svelte';
@@ -43,9 +43,9 @@ export { default as MemberCard } from './components/primitives/MemberCard.svelte
43
43
  export { default as ThemeToggle } from './components/primitives/ThemeToggle.svelte';
44
44
  export { default as ThemeSelect } from './components/primitives/ThemeSelect.svelte';
45
45
 
46
- // ============================================
46
+ // --------------------------------------------
47
47
  // COMPONENTS - Forms
48
- // ============================================
48
+ // --------------------------------------------
49
49
 
50
50
  export { default as Input } from './components/forms/Input.svelte';
51
51
  export { default as IconInput } from './components/forms/IconInput.svelte';
@@ -62,9 +62,9 @@ export { default as SearchInput } from './components/forms/SearchInput.svelte';
62
62
  export { default as PinInput } from './components/forms/PinInput.svelte';
63
63
  export { default as Dropzone } from './components/forms/Dropzone.svelte';
64
64
 
65
- // ============================================
65
+ // --------------------------------------------
66
66
  // COMPONENTS - Feedback
67
- // ============================================
67
+ // --------------------------------------------
68
68
 
69
69
  export {
70
70
  default as Toast,
@@ -79,9 +79,9 @@ export { default as Spinner } from './components/feedback/Spinner.svelte';
79
79
  export { default as EmptyState } from './components/feedback/EmptyState.svelte';
80
80
  export { default as Alert } from './components/feedback/Alert.svelte';
81
81
 
82
- // ============================================
82
+ // --------------------------------------------
83
83
  // COMPONENTS - Overlays
84
- // ============================================
84
+ // --------------------------------------------
85
85
 
86
86
  export { default as Modal } from './components/overlays/Modal.svelte';
87
87
  export { default as Popover } from './components/overlays/Popover.svelte';
@@ -90,9 +90,9 @@ export { default as DropdownItem } from './components/overlays/DropdownItem.svel
90
90
  export { default as DropdownDivider } from './components/overlays/DropdownDivider.svelte';
91
91
  export { default as ConfirmDialog } from './components/overlays/ConfirmDialog.svelte';
92
92
 
93
- // ============================================
93
+ // --------------------------------------------
94
94
  // COMPONENTS - Navigation
95
- // ============================================
95
+ // --------------------------------------------
96
96
 
97
97
  export { default as Tabs } from './components/navigation/Tabs.svelte';
98
98
  export { default as TabNav } from './components/navigation/TabNav.svelte';
@@ -135,32 +135,32 @@ export { default as LeftBarToggle } from './components/navigation/LeftBarToggle.
135
135
  export { default as LeftBarPopover } from './components/navigation/LeftBarPopover.svelte';
136
136
  export { default as DropdownContainer } from './components/navigation/DropdownContainer.svelte';
137
137
 
138
- // ============================================
138
+ // --------------------------------------------
139
139
  // COMPONENTS - Layout
140
- // ============================================
140
+ // --------------------------------------------
141
141
 
142
142
  export { default as PageHeader } from './components/layout/PageHeader.svelte';
143
143
  export { default as SettingCard } from './components/layout/SettingCard.svelte';
144
144
  export { default as SettingItem } from './components/layout/SettingItem.svelte';
145
145
 
146
- // ============================================
146
+ // --------------------------------------------
147
147
  // COMPONENTS - Documentation
148
- // ============================================
148
+ // --------------------------------------------
149
149
 
150
150
  export { default as CodeBlock } from './components/docs/CodeBlock.svelte';
151
151
  export { default as PropsTable } from './components/docs/PropsTable.svelte';
152
152
  export { default as SplitPane } from './components/docs/SplitPane.svelte';
153
153
  export { default as DocSection } from './components/docs/DocSection.svelte';
154
154
 
155
- // ============================================
155
+ // --------------------------------------------
156
156
  // UTILITIES - Class Composition
157
- // ============================================
157
+ // --------------------------------------------
158
158
 
159
159
  export { cn, rcn, cv, mergeClasses, when, match } from './utils/cn.svelte.js';
160
160
 
161
- // ============================================
161
+ // --------------------------------------------
162
162
  // UTILITIES - Reactive State
163
- // ============================================
163
+ // --------------------------------------------
164
164
 
165
165
  export {
166
166
  createReactive,
@@ -194,9 +194,9 @@ export {
194
194
  DEFAULT_LANGUAGES
195
195
  } from './utils/highlighter.js';
196
196
 
197
- // ============================================
197
+ // --------------------------------------------
198
198
  // ACTIONS
199
- // ============================================
199
+ // --------------------------------------------
200
200
 
201
201
  export {
202
202
  clickOutside,