@aiaiai-pt/design-system 0.5.8 → 0.7.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.
@@ -27,6 +27,21 @@
27
27
 
28
28
  @example Loading
29
29
  <DataTable {columns} rows={[]} loading />
30
+
31
+ @example Custom cell rendering (badges, chips, components)
32
+ Pass a `cell` snippet to override the default per-cell text rendering.
33
+ The default behavior (using `column.render` to produce a string) is
34
+ preserved when no `cell` snippet is provided.
35
+
36
+ <DataTable {columns} {rows}>
37
+ {#snippet cell({ row, column, value })}
38
+ {#if column.key === 'status'}
39
+ <Badge variant={statusVariant(value)}>{value}</Badge>
40
+ {:else}
41
+ {value ?? '-'}
42
+ {/if}
43
+ {/snippet}
44
+ </DataTable>
30
45
  -->
31
46
  <script>
32
47
  /**
@@ -65,6 +80,23 @@
65
80
  on_select = undefined,
66
81
  /** @type {((row: Record<string, unknown>) => void) | undefined} */
67
82
  on_row_click = undefined,
83
+ /**
84
+ * Optional per-cell render override. When provided, called for every td
85
+ * with `{ row, column, value }`; the snippet's output replaces the
86
+ * default text rendering. When omitted, the default text path runs
87
+ * (`column.render` then `String(value)`) so existing consumers keep
88
+ * working unchanged.
89
+ *
90
+ * Why a snippet rather than passing a Svelte component class via
91
+ * `column.render`: snippets accept the full row context (not just the
92
+ * value), let consumers compose any DS primitive (Badge, Tag, Status,
93
+ * Button) without bringing those imports into the DS surface, and
94
+ * Svelte 5's snippet API is the canonical way to parameterize child
95
+ * rendering. `column.render` stays for the common string case.
96
+ *
97
+ * @type {import('svelte').Snippet<[{ row: Record<string, unknown>, column: ColumnDef, value: unknown }]> | undefined}
98
+ */
99
+ cell = undefined,
68
100
  /** @type {import('svelte').Snippet | undefined} */
69
101
  children = undefined,
70
102
  /** @type {string} */
@@ -258,7 +290,11 @@
258
290
  {/if}
259
291
  {#each columns as col}
260
292
  <td class="table-td">
261
- {render_cell(col, row[col.key], row)}
293
+ {#if cell}
294
+ {@render cell({ row, column: col, value: row[col.key] })}
295
+ {:else}
296
+ {render_cell(col, row[col.key], row)}
297
+ {/if}
262
298
  </td>
263
299
  {/each}
264
300
  </tr>
@@ -199,7 +199,8 @@
199
199
 
200
200
  map = new OlMap({
201
201
  target: container,
202
- layers: [tileLayer, vectorLayer], view: new View({
202
+ layers: [tileLayer, vectorLayer],
203
+ view: new View({
203
204
  center: initialCenter,
204
205
  zoom,
205
206
  }),
@@ -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
+ };
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/design-system",
3
- "version": "0.5.8",
3
+ "version": "0.7.0",
4
4
  "description": "Design system tokens and Svelte components for aiaiai products",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
  ═══════════════════════════════════════════════ */