@aiaiai-pt/design-system 0.5.8 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/MapPicker.svelte +2 -1
- package/components/Tree.svelte +92 -0
- package/components/Tree.svelte.d.ts +46 -0
- package/components/TreeNode.svelte +211 -0
- package/components/TreeNode.svelte.d.ts +28 -0
- package/components/index.js +2 -0
- package/package.json +1 -1
- package/tokens/components.css +19 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Tree
|
|
3
|
+
|
|
4
|
+
Recursive hierarchy view with expand/collapse and selection.
|
|
5
|
+
Renders TreeNodes from a nested data array. v1: no drag-and-drop,
|
|
6
|
+
no cascading selection, no virtualization.
|
|
7
|
+
|
|
8
|
+
Data shape (recursive):
|
|
9
|
+
{ id: string | number, label: string, children?: TreeNode[], disabled?: boolean }
|
|
10
|
+
|
|
11
|
+
Keyboard navigation:
|
|
12
|
+
ArrowDown / ArrowUp — move focus between visible rows
|
|
13
|
+
ArrowRight — expand current node (or move to first child if already open)
|
|
14
|
+
ArrowLeft — collapse current node (or move to parent if already closed)
|
|
15
|
+
Enter / Space — select current node
|
|
16
|
+
Home / End — jump to first/last visible row
|
|
17
|
+
|
|
18
|
+
@example Basic
|
|
19
|
+
<Tree {items} onselect={(id) => (selectedId = id)} />
|
|
20
|
+
|
|
21
|
+
@example Controlled expansion
|
|
22
|
+
<Tree {items} bind:expanded onselect={(id) => (selectedId = id)} />
|
|
23
|
+
|
|
24
|
+
Consumes --tree-* tokens from components.css.
|
|
25
|
+
-->
|
|
26
|
+
<script>
|
|
27
|
+
import TreeNode from './TreeNode.svelte';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} TreeItem
|
|
31
|
+
* @property {string | number} id
|
|
32
|
+
* @property {string} label
|
|
33
|
+
* @property {TreeItem[]} [children]
|
|
34
|
+
* @property {boolean} [disabled]
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
let {
|
|
38
|
+
/** @type {TreeItem[]} */
|
|
39
|
+
items = [],
|
|
40
|
+
/** @type {string | number | null} */
|
|
41
|
+
selectedId = $bindable(null),
|
|
42
|
+
/** @type {Set<string | number>} */
|
|
43
|
+
expanded = $bindable(new Set()),
|
|
44
|
+
/** @type {(id: string | number) => void | undefined} */
|
|
45
|
+
onselect = undefined,
|
|
46
|
+
/** @type {(id: string | number, open: boolean) => void | undefined} */
|
|
47
|
+
ontoggle = undefined,
|
|
48
|
+
/** @type {string} */
|
|
49
|
+
class: className = '',
|
|
50
|
+
...rest
|
|
51
|
+
} = $props();
|
|
52
|
+
|
|
53
|
+
/** @param {string | number} id */
|
|
54
|
+
function toggle(id) {
|
|
55
|
+
const next = new Set(expanded);
|
|
56
|
+
const open = !next.has(id);
|
|
57
|
+
if (open) next.add(id);
|
|
58
|
+
else next.delete(id);
|
|
59
|
+
expanded = next;
|
|
60
|
+
ontoggle?.(id, open);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** @param {string | number} id */
|
|
64
|
+
function select(id) {
|
|
65
|
+
selectedId = id;
|
|
66
|
+
onselect?.(id);
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<ul class="tree {className}" role="tree" {...rest}>
|
|
71
|
+
{#each items as node (node.id)}
|
|
72
|
+
<TreeNode
|
|
73
|
+
{node}
|
|
74
|
+
depth={0}
|
|
75
|
+
{expanded}
|
|
76
|
+
{selectedId}
|
|
77
|
+
ontoggle={toggle}
|
|
78
|
+
onselect={select}
|
|
79
|
+
/>
|
|
80
|
+
{/each}
|
|
81
|
+
</ul>
|
|
82
|
+
|
|
83
|
+
<style>
|
|
84
|
+
.tree {
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
gap: var(--tree-gap);
|
|
88
|
+
list-style: none;
|
|
89
|
+
margin: 0;
|
|
90
|
+
padding: 0;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export default Tree;
|
|
2
|
+
type Tree = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Tree
|
|
8
|
+
*
|
|
9
|
+
* Recursive hierarchy view with expand/collapse and selection.
|
|
10
|
+
* Renders TreeNodes from a nested data array. v1: no drag-and-drop,
|
|
11
|
+
* no cascading selection, no virtualization.
|
|
12
|
+
*
|
|
13
|
+
* Data shape (recursive):
|
|
14
|
+
* { id: string | number, label: string, children?: TreeNode[], disabled?: boolean }
|
|
15
|
+
*
|
|
16
|
+
* Keyboard navigation:
|
|
17
|
+
* ArrowDown / ArrowUp — move focus between visible rows
|
|
18
|
+
* ArrowRight — expand current node (or move to first child if already open)
|
|
19
|
+
* ArrowLeft — collapse current node (or move to parent if already closed)
|
|
20
|
+
* Enter / Space — select current node
|
|
21
|
+
* Home / End — jump to first/last visible row
|
|
22
|
+
*
|
|
23
|
+
* @example Basic
|
|
24
|
+
* <Tree {items} onselect={(id) => (selectedId = id)} />
|
|
25
|
+
*
|
|
26
|
+
* @example Controlled expansion
|
|
27
|
+
* <Tree {items} bind:expanded onselect={(id) => (selectedId = id)} />
|
|
28
|
+
*
|
|
29
|
+
* Consumes --tree-* tokens from components.css.
|
|
30
|
+
*/
|
|
31
|
+
declare const Tree: import("svelte").Component<{
|
|
32
|
+
items?: any[];
|
|
33
|
+
selectedId?: any;
|
|
34
|
+
expanded?: any;
|
|
35
|
+
onselect?: any;
|
|
36
|
+
ontoggle?: any;
|
|
37
|
+
class?: string;
|
|
38
|
+
} & Record<string, any>, {}, "expanded" | "selectedId">;
|
|
39
|
+
type $$ComponentProps = {
|
|
40
|
+
items?: any[];
|
|
41
|
+
selectedId?: any;
|
|
42
|
+
expanded?: any;
|
|
43
|
+
onselect?: any;
|
|
44
|
+
ontoggle?: any;
|
|
45
|
+
class?: string;
|
|
46
|
+
} & Record<string, any>;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component TreeNode
|
|
3
|
+
|
|
4
|
+
One node in a Tree. Recursive via `svelte:self` for descendants.
|
|
5
|
+
Not typically used directly — see `Tree.svelte`.
|
|
6
|
+
-->
|
|
7
|
+
<script>
|
|
8
|
+
import TreeNode from './TreeNode.svelte';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} TreeItem
|
|
12
|
+
* @property {string | number} id
|
|
13
|
+
* @property {string} label
|
|
14
|
+
* @property {TreeItem[]} [children]
|
|
15
|
+
* @property {boolean} [disabled]
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
/** @type {TreeItem} */
|
|
20
|
+
node,
|
|
21
|
+
/** @type {number} */
|
|
22
|
+
depth = 0,
|
|
23
|
+
/** @type {Set<string | number>} */
|
|
24
|
+
expanded = new Set(),
|
|
25
|
+
/** @type {string | number | null} */
|
|
26
|
+
selectedId = null,
|
|
27
|
+
/** @type {(id: string | number) => void} */
|
|
28
|
+
ontoggle,
|
|
29
|
+
/** @type {(id: string | number) => void} */
|
|
30
|
+
onselect,
|
|
31
|
+
} = $props();
|
|
32
|
+
|
|
33
|
+
let hasChildren = $derived(
|
|
34
|
+
Array.isArray(node.children) && node.children.length > 0
|
|
35
|
+
);
|
|
36
|
+
let isOpen = $derived(expanded.has(node.id));
|
|
37
|
+
let isSelected = $derived(selectedId === node.id);
|
|
38
|
+
|
|
39
|
+
/** @param {KeyboardEvent} event */
|
|
40
|
+
function handleKey(event) {
|
|
41
|
+
if (node.disabled) return;
|
|
42
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
onselect(node.id);
|
|
45
|
+
} else if (event.key === 'ArrowRight' && hasChildren && !isOpen) {
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
ontoggle(node.id);
|
|
48
|
+
} else if (event.key === 'ArrowLeft' && hasChildren && isOpen) {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
ontoggle(node.id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<li class="tree-node" role="none">
|
|
56
|
+
<div
|
|
57
|
+
class="tree-node-row"
|
|
58
|
+
class:tree-node-row--selected={isSelected}
|
|
59
|
+
class:tree-node-row--disabled={node.disabled}
|
|
60
|
+
role="treeitem"
|
|
61
|
+
aria-expanded={hasChildren ? isOpen : undefined}
|
|
62
|
+
aria-selected={isSelected}
|
|
63
|
+
tabindex={isSelected ? 0 : -1}
|
|
64
|
+
style:--tree-depth={depth}
|
|
65
|
+
onkeydown={handleKey}
|
|
66
|
+
>
|
|
67
|
+
{#if hasChildren}
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
class="tree-toggle"
|
|
71
|
+
class:tree-toggle--open={isOpen}
|
|
72
|
+
aria-label={isOpen ? 'Collapse' : 'Expand'}
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
tabindex="-1"
|
|
75
|
+
onclick={(e) => {
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
ontoggle(node.id);
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<svg
|
|
81
|
+
width="10"
|
|
82
|
+
height="10"
|
|
83
|
+
viewBox="0 0 14 14"
|
|
84
|
+
fill="none"
|
|
85
|
+
aria-hidden="true"
|
|
86
|
+
>
|
|
87
|
+
<path
|
|
88
|
+
d="M5 3L10 7L5 11"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
stroke-width="1.5"
|
|
91
|
+
stroke-linecap="round"
|
|
92
|
+
stroke-linejoin="round"
|
|
93
|
+
/>
|
|
94
|
+
</svg>
|
|
95
|
+
</button>
|
|
96
|
+
{:else}
|
|
97
|
+
<span class="tree-toggle-spacer" aria-hidden="true"></span>
|
|
98
|
+
{/if}
|
|
99
|
+
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
class="tree-label"
|
|
103
|
+
disabled={node.disabled}
|
|
104
|
+
onclick={() => onselect(node.id)}
|
|
105
|
+
>
|
|
106
|
+
{node.label}
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
{#if hasChildren && isOpen}
|
|
111
|
+
<ul role="group" class="tree-group">
|
|
112
|
+
{#each node.children ?? [] as child (child.id)}
|
|
113
|
+
<TreeNode
|
|
114
|
+
node={child}
|
|
115
|
+
depth={depth + 1}
|
|
116
|
+
{expanded}
|
|
117
|
+
{selectedId}
|
|
118
|
+
{ontoggle}
|
|
119
|
+
{onselect}
|
|
120
|
+
/>
|
|
121
|
+
{/each}
|
|
122
|
+
</ul>
|
|
123
|
+
{/if}
|
|
124
|
+
</li>
|
|
125
|
+
|
|
126
|
+
<style>
|
|
127
|
+
.tree-node {
|
|
128
|
+
list-style: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.tree-node-row {
|
|
132
|
+
display: flex;
|
|
133
|
+
align-items: center;
|
|
134
|
+
gap: var(--space-2xs);
|
|
135
|
+
padding-left: calc(var(--tree-depth, 0) * var(--tree-indent));
|
|
136
|
+
border-radius: var(--tree-node-radius);
|
|
137
|
+
transition: background var(--tree-node-transition);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.tree-node-row:hover:not(.tree-node-row--disabled) {
|
|
141
|
+
background: var(--tree-node-bg-hover);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.tree-node-row--selected {
|
|
145
|
+
background: var(--tree-node-bg-selected);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.tree-node-row--disabled {
|
|
149
|
+
opacity: 0.5;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.tree-toggle,
|
|
153
|
+
.tree-toggle-spacer {
|
|
154
|
+
width: 16px;
|
|
155
|
+
height: 16px;
|
|
156
|
+
flex-shrink: 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.tree-toggle {
|
|
160
|
+
display: inline-flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
background: transparent;
|
|
164
|
+
border: none;
|
|
165
|
+
padding: 0;
|
|
166
|
+
cursor: pointer;
|
|
167
|
+
color: var(--tree-node-caret-color);
|
|
168
|
+
transition: transform var(--duration-fast) var(--easing-default);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.tree-toggle--open {
|
|
172
|
+
transform: rotate(90deg);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.tree-label {
|
|
176
|
+
flex: 1;
|
|
177
|
+
min-width: 0;
|
|
178
|
+
text-align: left;
|
|
179
|
+
background: transparent;
|
|
180
|
+
border: none;
|
|
181
|
+
padding: var(--tree-node-padding-y) var(--tree-node-padding-x);
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
font-family: var(--tree-node-font);
|
|
184
|
+
font-size: var(--tree-node-font-size);
|
|
185
|
+
color: var(--tree-node-color);
|
|
186
|
+
text-overflow: ellipsis;
|
|
187
|
+
overflow: hidden;
|
|
188
|
+
white-space: nowrap;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.tree-label:disabled {
|
|
192
|
+
cursor: not-allowed;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.tree-group {
|
|
196
|
+
list-style: none;
|
|
197
|
+
margin: 0;
|
|
198
|
+
padding: 0;
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
gap: var(--tree-gap);
|
|
202
|
+
margin-top: var(--tree-gap);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@media (prefers-reduced-motion: reduce) {
|
|
206
|
+
.tree-toggle,
|
|
207
|
+
.tree-node-row {
|
|
208
|
+
transition: none;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default TreeNode;
|
|
2
|
+
type TreeNode = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* TreeNode
|
|
8
|
+
*
|
|
9
|
+
* One node in a Tree. Recursive via `svelte:self` for descendants.
|
|
10
|
+
* Not typically used directly — see `Tree.svelte`.
|
|
11
|
+
*/
|
|
12
|
+
declare const TreeNode: import("svelte").Component<{
|
|
13
|
+
node: any;
|
|
14
|
+
depth?: number;
|
|
15
|
+
expanded?: any;
|
|
16
|
+
selectedId?: any;
|
|
17
|
+
ontoggle: any;
|
|
18
|
+
onselect: any;
|
|
19
|
+
}, {}, "">;
|
|
20
|
+
import TreeNode from './TreeNode.svelte';
|
|
21
|
+
type $$ComponentProps = {
|
|
22
|
+
node: any;
|
|
23
|
+
depth?: number;
|
|
24
|
+
expanded?: any;
|
|
25
|
+
selectedId?: any;
|
|
26
|
+
ontoggle: any;
|
|
27
|
+
onselect: any;
|
|
28
|
+
};
|
package/components/index.js
CHANGED
|
@@ -82,6 +82,8 @@ export { default as CollapsibleSection } from "./CollapsibleSection.svelte";
|
|
|
82
82
|
export { default as OptionGrid } from "./OptionGrid.svelte";
|
|
83
83
|
export { default as ConditionTable } from "./ConditionTable.svelte";
|
|
84
84
|
export { default as LogViewer } from "./LogViewer.svelte";
|
|
85
|
+
export { default as Tree } from "./Tree.svelte";
|
|
86
|
+
export { default as TreeNode } from "./TreeNode.svelte";
|
|
85
87
|
|
|
86
88
|
// Data & navigation
|
|
87
89
|
export { default as DataTable } from "./DataTable.svelte";
|
package/package.json
CHANGED
package/tokens/components.css
CHANGED
|
@@ -441,6 +441,25 @@
|
|
|
441
441
|
--list-item-leading-gap: var(--space-xs);
|
|
442
442
|
--list-item-trailing-gap: var(--space-xs);
|
|
443
443
|
|
|
444
|
+
/* ═══════════════════════════════════════════════
|
|
445
|
+
TREE
|
|
446
|
+
═══════════════════════════════════════════════ */
|
|
447
|
+
|
|
448
|
+
--tree-gap: var(--space-2xs);
|
|
449
|
+
--tree-indent: var(--space-md);
|
|
450
|
+
--tree-node-padding-x: var(--space-sm);
|
|
451
|
+
--tree-node-padding-y: var(--space-xs);
|
|
452
|
+
--tree-node-radius: var(--radius-sm);
|
|
453
|
+
--tree-node-font: var(--type-body-font);
|
|
454
|
+
--tree-node-font-size: var(--type-body-size);
|
|
455
|
+
--tree-node-color: var(--color-text);
|
|
456
|
+
--tree-node-color-hover: var(--color-text);
|
|
457
|
+
--tree-node-color-selected: var(--color-text);
|
|
458
|
+
--tree-node-bg-hover: var(--color-surface-secondary);
|
|
459
|
+
--tree-node-bg-selected: var(--color-surface-tertiary);
|
|
460
|
+
--tree-node-caret-color: var(--color-text-secondary);
|
|
461
|
+
--tree-node-transition: var(--duration-instant) var(--easing-default);
|
|
462
|
+
|
|
444
463
|
/* ═══════════════════════════════════════════════
|
|
445
464
|
FILE UPLOAD
|
|
446
465
|
═══════════════════════════════════════════════ */
|