@miozu/jera 0.8.2 → 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 +2 -2
- package/src/components/layout/SettingCard.svelte +11 -82
- package/src/components/layout/SettingItem.svelte +116 -0
- package/src/components/navigation/Accordion.svelte +14 -23
- package/src/components/navigation/AccordionItem.svelte +115 -38
- package/src/components/navigation/TabNav.svelte +27 -1
- package/src/components/navigation/Tabs.svelte +20 -20
- package/src/components/primitives/ThemeSelect.svelte +10 -10
- package/src/components/primitives/ThemeToggle.svelte +12 -12
- package/src/index.js +21 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miozu/jera",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Zero-dependency, AI-first component library for Svelte 5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"svelte": "^5.
|
|
39
|
+
"svelte": "^5.49.2"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"svelte",
|
|
@@ -2,28 +2,24 @@
|
|
|
2
2
|
@component SettingCard
|
|
3
3
|
|
|
4
4
|
A card container for settings sections with optional danger variant.
|
|
5
|
-
|
|
5
|
+
Use with SettingItem for structured setting rows.
|
|
6
6
|
|
|
7
7
|
@example Basic settings card
|
|
8
8
|
<SettingCard title="Account Settings">
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<Input value={name} />
|
|
15
|
-
</div>
|
|
9
|
+
<SettingItem label="Display Name" description="Your public display name">
|
|
10
|
+
{#snippet action()}
|
|
11
|
+
<Input value={name} />
|
|
12
|
+
{/snippet}
|
|
13
|
+
</SettingItem>
|
|
16
14
|
</SettingCard>
|
|
17
15
|
|
|
18
16
|
@example Danger zone
|
|
19
17
|
<SettingCard title="Danger Zone" variant="danger">
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<Button variant="danger">Delete</Button>
|
|
26
|
-
</div>
|
|
18
|
+
<SettingItem label="Delete Account" description="This cannot be undone">
|
|
19
|
+
{#snippet action()}
|
|
20
|
+
<Button variant="danger">Delete</Button>
|
|
21
|
+
{/snippet}
|
|
22
|
+
</SettingItem>
|
|
27
23
|
</SettingCard>
|
|
28
24
|
-->
|
|
29
25
|
<script>
|
|
@@ -83,71 +79,4 @@
|
|
|
83
79
|
display: flex;
|
|
84
80
|
flex-direction: column;
|
|
85
81
|
}
|
|
86
|
-
|
|
87
|
-
/* Utility classes for setting items - exposed globally */
|
|
88
|
-
:global(.setting-item) {
|
|
89
|
-
display: flex;
|
|
90
|
-
align-items: center;
|
|
91
|
-
justify-content: space-between;
|
|
92
|
-
gap: var(--space-4);
|
|
93
|
-
padding: var(--space-4) 0;
|
|
94
|
-
border-bottom: 1px solid var(--color-base02);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
:global(.setting-item:last-child) {
|
|
98
|
-
border-bottom: none;
|
|
99
|
-
padding-bottom: 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
:global(.setting-item:first-child) {
|
|
103
|
-
padding-top: 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
:global(.setting-content) {
|
|
107
|
-
flex: 1;
|
|
108
|
-
min-width: 0;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
:global(.setting-label) {
|
|
112
|
-
margin: 0 0 var(--space-1);
|
|
113
|
-
font-size: var(--text-sm);
|
|
114
|
-
font-weight: 500;
|
|
115
|
-
color: var(--color-base06);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
:global(.setting-description) {
|
|
119
|
-
margin: 0;
|
|
120
|
-
font-size: var(--text-xs);
|
|
121
|
-
color: var(--color-base04);
|
|
122
|
-
line-height: 1.5;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
:global(.setting-item-icon) {
|
|
126
|
-
display: flex;
|
|
127
|
-
align-items: center;
|
|
128
|
-
gap: var(--space-3);
|
|
129
|
-
color: color-mix(in srgb, var(--color-base04) 80%, transparent);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
:global(.setting-item-with-icon) {
|
|
133
|
-
display: flex;
|
|
134
|
-
align-items: flex-start;
|
|
135
|
-
gap: var(--space-3);
|
|
136
|
-
padding: var(--space-4) 0;
|
|
137
|
-
border-bottom: 1px solid var(--color-base02);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
:global(.setting-item-with-icon:last-child) {
|
|
141
|
-
border-bottom: none;
|
|
142
|
-
padding-bottom: 0;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/* Responsive */
|
|
146
|
-
@media (max-width: 640px) {
|
|
147
|
-
:global(.setting-item) {
|
|
148
|
-
flex-direction: column;
|
|
149
|
-
align-items: flex-start;
|
|
150
|
-
gap: var(--space-3);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
82
|
</style>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component SettingItem
|
|
3
|
+
|
|
4
|
+
A structured setting row for use inside SettingCard.
|
|
5
|
+
Replaces magic class names with a composable component API.
|
|
6
|
+
|
|
7
|
+
@example Basic setting
|
|
8
|
+
<SettingItem label="Display Name" description="Your public display name">
|
|
9
|
+
{#snippet action()}
|
|
10
|
+
<Input value={name} />
|
|
11
|
+
{/snippet}
|
|
12
|
+
</SettingItem>
|
|
13
|
+
|
|
14
|
+
@example With icon
|
|
15
|
+
<SettingItem label="Active Sessions" description="Manage your devices">
|
|
16
|
+
{#snippet leading()}
|
|
17
|
+
<Monitor size={16} />
|
|
18
|
+
{/snippet}
|
|
19
|
+
{#snippet action()}
|
|
20
|
+
<Button size="sm">Manage</Button>
|
|
21
|
+
{/snippet}
|
|
22
|
+
</SettingItem>
|
|
23
|
+
-->
|
|
24
|
+
<script>
|
|
25
|
+
let {
|
|
26
|
+
label = '',
|
|
27
|
+
description = '',
|
|
28
|
+
leading,
|
|
29
|
+
action,
|
|
30
|
+
class: className = ''
|
|
31
|
+
} = $props();
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<div class="setting-item {className}" class:has-leading={leading}>
|
|
35
|
+
{#if leading}
|
|
36
|
+
<div class="setting-leading">
|
|
37
|
+
{@render leading()}
|
|
38
|
+
</div>
|
|
39
|
+
{/if}
|
|
40
|
+
<div class="setting-content">
|
|
41
|
+
{#if label}
|
|
42
|
+
<h4 class="setting-label">{label}</h4>
|
|
43
|
+
{/if}
|
|
44
|
+
{#if description}
|
|
45
|
+
<p class="setting-description">{description}</p>
|
|
46
|
+
{/if}
|
|
47
|
+
</div>
|
|
48
|
+
{#if action}
|
|
49
|
+
<div class="setting-action">
|
|
50
|
+
{@render action()}
|
|
51
|
+
</div>
|
|
52
|
+
{/if}
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<style>
|
|
56
|
+
.setting-item {
|
|
57
|
+
display: flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
justify-content: space-between;
|
|
60
|
+
gap: var(--space-4);
|
|
61
|
+
padding: var(--space-4) 0;
|
|
62
|
+
border-bottom: 1px solid var(--color-base02);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.setting-item:last-child {
|
|
66
|
+
border-bottom: none;
|
|
67
|
+
padding-bottom: 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.setting-item:first-child {
|
|
71
|
+
padding-top: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.has-leading {
|
|
75
|
+
justify-content: flex-start;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.setting-leading {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
color: color-mix(in srgb, var(--color-base04) 80%, transparent);
|
|
82
|
+
flex-shrink: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.setting-content {
|
|
86
|
+
flex: 1;
|
|
87
|
+
min-width: 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.setting-label {
|
|
91
|
+
margin: 0 0 var(--space-1);
|
|
92
|
+
font-size: var(--text-sm);
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
color: var(--color-base06);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.setting-description {
|
|
98
|
+
margin: 0;
|
|
99
|
+
font-size: var(--text-xs);
|
|
100
|
+
color: var(--color-base04);
|
|
101
|
+
line-height: 1.5;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.setting-action {
|
|
105
|
+
flex-shrink: 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Responsive */
|
|
109
|
+
@media (max-width: 640px) {
|
|
110
|
+
.setting-item:not(.has-leading) {
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
align-items: flex-start;
|
|
113
|
+
gap: var(--space-3);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component Accordion
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
Group container for collapsible sections. Controls shared expand/collapse state.
|
|
5
5
|
|
|
6
|
-
@example Single
|
|
6
|
+
@example Single-open (default)
|
|
7
7
|
<Accordion>
|
|
8
|
-
<AccordionItem title="Section 1">
|
|
9
|
-
|
|
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
|
|
17
|
-
<Accordion bind:expanded={
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
4
|
+
Collapsible content section. Works in two modes:
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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 (
|
|
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
|
|
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-
|
|
58
|
-
class:accordion-
|
|
59
|
-
width="
|
|
60
|
-
height="
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
140
|
+
gap: 0.5rem;
|
|
102
141
|
width: 100%;
|
|
103
|
-
|
|
104
|
-
|
|
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-
|
|
107
|
-
font-
|
|
108
|
-
|
|
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(
|
|
115
|
-
background: var(--color-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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:
|
|
218
|
+
padding: 1rem;
|
|
142
219
|
font-size: 0.875rem;
|
|
143
220
|
color: var(--color-base05);
|
|
144
221
|
line-height: 1.6;
|
|
@@ -45,10 +45,34 @@
|
|
|
45
45
|
active = tab.id;
|
|
46
46
|
onchange?.(tab);
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
function handleKeydown(e, index) {
|
|
50
|
+
let nextIndex = index;
|
|
51
|
+
|
|
52
|
+
if (e.key === 'ArrowRight') {
|
|
53
|
+
nextIndex = (index + 1) % tabs.length;
|
|
54
|
+
} else if (e.key === 'ArrowLeft') {
|
|
55
|
+
nextIndex = (index - 1 + tabs.length) % tabs.length;
|
|
56
|
+
} else if (e.key === 'Home') {
|
|
57
|
+
nextIndex = 0;
|
|
58
|
+
} else if (e.key === 'End') {
|
|
59
|
+
nextIndex = tabs.length - 1;
|
|
60
|
+
} else {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
const nextTab = tabs[nextIndex];
|
|
66
|
+
if (!nextTab.disabled) {
|
|
67
|
+
handleTabClick(nextTab);
|
|
68
|
+
const buttons = e.currentTarget.parentElement.querySelectorAll('[role="tab"]');
|
|
69
|
+
buttons[nextIndex]?.focus();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
48
72
|
</script>
|
|
49
73
|
|
|
50
74
|
<nav class="tab-nav tab-nav-{variant} tab-nav-{size} {className}" role="tablist">
|
|
51
|
-
{#each tabs as tab}
|
|
75
|
+
{#each tabs as tab, index}
|
|
52
76
|
<button
|
|
53
77
|
type="button"
|
|
54
78
|
class="tab-item"
|
|
@@ -57,7 +81,9 @@
|
|
|
57
81
|
role="tab"
|
|
58
82
|
aria-selected={active === tab.id}
|
|
59
83
|
aria-disabled={tab.disabled}
|
|
84
|
+
tabindex={active === tab.id ? 0 : -1}
|
|
60
85
|
onclick={() => handleTabClick(tab)}
|
|
86
|
+
onkeydown={(e) => handleKeydown(e, index)}
|
|
61
87
|
>
|
|
62
88
|
{#if tab.icon}
|
|
63
89
|
<span class="tab-icon">
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@component Tabs
|
|
3
3
|
|
|
4
|
-
Tabbed navigation component.
|
|
4
|
+
Tabbed navigation component with keyboard navigation.
|
|
5
5
|
|
|
6
6
|
@example Basic usage
|
|
7
7
|
<Tabs
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
bind:active={activeTab}
|
|
14
14
|
/>
|
|
15
15
|
|
|
16
|
-
@example With icons
|
|
16
|
+
@example With icons (component reference)
|
|
17
17
|
<Tabs
|
|
18
18
|
tabs={[
|
|
19
19
|
{ id: 'home', label: 'Home', icon: HomeIcon },
|
|
@@ -64,7 +64,6 @@
|
|
|
64
64
|
const nextTab = tabs[nextIndex];
|
|
65
65
|
if (!nextTab.disabled) {
|
|
66
66
|
selectTab(nextTab);
|
|
67
|
-
// Focus the next tab button
|
|
68
67
|
const buttons = e.currentTarget.parentElement.querySelectorAll('[role="tab"]');
|
|
69
68
|
buttons[nextIndex]?.focus();
|
|
70
69
|
}
|
|
@@ -90,8 +89,9 @@
|
|
|
90
89
|
onkeydown={(e) => handleKeydown(e, tab, index)}
|
|
91
90
|
>
|
|
92
91
|
{#if tab.icon}
|
|
92
|
+
{@const Icon = tab.icon}
|
|
93
93
|
<span class="tab-icon">
|
|
94
|
-
<
|
|
94
|
+
<Icon size={16} />
|
|
95
95
|
</span>
|
|
96
96
|
{/if}
|
|
97
97
|
{#if tab.label}
|
|
@@ -107,10 +107,10 @@
|
|
|
107
107
|
<style>
|
|
108
108
|
.tabs {
|
|
109
109
|
display: inline-flex;
|
|
110
|
-
gap:
|
|
110
|
+
gap: var(--space-1);
|
|
111
111
|
background: var(--color-base01);
|
|
112
|
-
border-radius:
|
|
113
|
-
padding:
|
|
112
|
+
border-radius: var(--radius-lg);
|
|
113
|
+
padding: var(--space-1);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
.tabs-full-width {
|
|
@@ -126,12 +126,12 @@
|
|
|
126
126
|
.tab {
|
|
127
127
|
display: inline-flex;
|
|
128
128
|
align-items: center;
|
|
129
|
-
gap:
|
|
130
|
-
padding:
|
|
129
|
+
gap: var(--space-2);
|
|
130
|
+
padding: var(--space-2) var(--space-4);
|
|
131
131
|
background: transparent;
|
|
132
132
|
border: none;
|
|
133
|
-
border-radius:
|
|
134
|
-
font-size:
|
|
133
|
+
border-radius: var(--radius-md);
|
|
134
|
+
font-size: var(--text-sm);
|
|
135
135
|
font-weight: 500;
|
|
136
136
|
color: var(--color-base05);
|
|
137
137
|
cursor: pointer;
|
|
@@ -141,13 +141,13 @@
|
|
|
141
141
|
|
|
142
142
|
/* Size variants */
|
|
143
143
|
.tabs-sm .tab {
|
|
144
|
-
padding:
|
|
145
|
-
font-size:
|
|
144
|
+
padding: var(--space-1) var(--space-3);
|
|
145
|
+
font-size: var(--text-xs);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
.tabs-lg .tab {
|
|
149
|
-
padding:
|
|
150
|
-
font-size:
|
|
149
|
+
padding: var(--space-3) var(--space-5);
|
|
150
|
+
font-size: var(--text-base);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
.tab:hover:not(.tab-disabled) {
|
|
@@ -180,12 +180,12 @@
|
|
|
180
180
|
display: inline-flex;
|
|
181
181
|
align-items: center;
|
|
182
182
|
justify-content: center;
|
|
183
|
-
min-width:
|
|
184
|
-
height:
|
|
185
|
-
padding: 0
|
|
183
|
+
min-width: var(--space-5);
|
|
184
|
+
height: var(--space-5);
|
|
185
|
+
padding: 0 var(--space-1);
|
|
186
186
|
background: var(--color-base03);
|
|
187
187
|
border-radius: 9999px;
|
|
188
|
-
font-size:
|
|
188
|
+
font-size: var(--text-xs);
|
|
189
189
|
font-weight: 600;
|
|
190
190
|
}
|
|
191
191
|
|
|
@@ -224,7 +224,7 @@
|
|
|
224
224
|
.tabs-pills {
|
|
225
225
|
background: transparent;
|
|
226
226
|
padding: 0;
|
|
227
|
-
gap:
|
|
227
|
+
gap: var(--space-2);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
.tabs-pills .tab {
|
|
@@ -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,31 +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
|
+
export { default as SettingItem } from './components/layout/SettingItem.svelte';
|
|
144
145
|
|
|
145
|
-
//
|
|
146
|
+
// --------------------------------------------
|
|
146
147
|
// COMPONENTS - Documentation
|
|
147
|
-
//
|
|
148
|
+
// --------------------------------------------
|
|
148
149
|
|
|
149
150
|
export { default as CodeBlock } from './components/docs/CodeBlock.svelte';
|
|
150
151
|
export { default as PropsTable } from './components/docs/PropsTable.svelte';
|
|
151
152
|
export { default as SplitPane } from './components/docs/SplitPane.svelte';
|
|
152
153
|
export { default as DocSection } from './components/docs/DocSection.svelte';
|
|
153
154
|
|
|
154
|
-
//
|
|
155
|
+
// --------------------------------------------
|
|
155
156
|
// UTILITIES - Class Composition
|
|
156
|
-
//
|
|
157
|
+
// --------------------------------------------
|
|
157
158
|
|
|
158
159
|
export { cn, rcn, cv, mergeClasses, when, match } from './utils/cn.svelte.js';
|
|
159
160
|
|
|
160
|
-
//
|
|
161
|
+
// --------------------------------------------
|
|
161
162
|
// UTILITIES - Reactive State
|
|
162
|
-
//
|
|
163
|
+
// --------------------------------------------
|
|
163
164
|
|
|
164
165
|
export {
|
|
165
166
|
createReactive,
|
|
@@ -193,9 +194,9 @@ export {
|
|
|
193
194
|
DEFAULT_LANGUAGES
|
|
194
195
|
} from './utils/highlighter.js';
|
|
195
196
|
|
|
196
|
-
//
|
|
197
|
+
// --------------------------------------------
|
|
197
198
|
// ACTIONS
|
|
198
|
-
//
|
|
199
|
+
// --------------------------------------------
|
|
199
200
|
|
|
200
201
|
export {
|
|
201
202
|
clickOutside,
|