@r2digisolutions/components 0.15.2 → 0.15.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.
@@ -1,5 +1,14 @@
1
1
  <script lang="ts">
2
+ import type { Component } from 'svelte';
3
+ import { untrack } from 'svelte';
2
4
  import Kbd from '../../atoms/Kbd/Kbd.svelte';
5
+ import { i18n } from '../../../utils/i18n.svelte';
6
+
7
+ export type CommandIcon = Component<{
8
+ class?: string;
9
+ size?: number | string;
10
+ strokeWidth?: number | string;
11
+ }>;
3
12
 
4
13
  export interface CommandItem {
5
14
  id: string;
@@ -7,61 +16,127 @@
7
16
  group?: string;
8
17
  shortcut?: string[];
9
18
  disabled?: boolean;
19
+ subtitle?: string;
20
+ keywords?: string;
21
+ href?: string;
22
+ icon?: CommandIcon;
10
23
  }
11
24
 
12
25
  interface CommandPaletteProps {
13
26
  open?: boolean;
14
27
  items?: CommandItem[];
15
28
  placeholder?: string;
29
+ emptyLabel?: string;
30
+ loading?: boolean;
16
31
  class?: string;
17
32
  onselect?: (item: CommandItem) => void;
18
33
  onclose?: () => void;
34
+ onquery?: (query: string) => void;
19
35
  }
20
36
 
21
37
  let {
22
38
  open = $bindable(false),
23
39
  items = [],
24
- placeholder = 'Type a command or search…',
40
+ placeholder,
41
+ emptyLabel,
42
+ loading = false,
25
43
  class: className = '',
26
44
  onselect,
27
- onclose
45
+ onclose,
46
+ onquery
28
47
  }: CommandPaletteProps = $props();
29
48
 
30
49
  let query = $state('');
31
50
  let activeIndex = $state(0);
32
- let inputEl = $state<HTMLInputElement | null>(null);
51
+ let dialogEl = $state<HTMLDialogElement | null>(null);
52
+ let listEl = $state<HTMLDivElement | null>(null);
53
+ /** El click que abre la paleta llega al ::backdrop y la cerraría al instante. */
54
+ let ignoreBackdropUntil = 0;
55
+ const listId = 'command-palette-list';
33
56
 
34
- const filtered = $derived(
35
- items.filter((item) => {
36
- if (!query.trim()) return true;
37
- return item.label.toLowerCase().includes(query.trim().toLowerCase());
38
- })
39
- );
57
+ const resolvedPlaceholder = $derived(placeholder ?? i18n.t('commandPalettePlaceholder'));
58
+ const resolvedEmpty = $derived(emptyLabel ?? i18n.t('noResults'));
59
+ const dialogLabel = $derived(i18n.t('commandPalette'));
60
+
61
+ function normalize(value: string) {
62
+ return value
63
+ .normalize('NFD')
64
+ .replace(/\p{Diacritic}/gu, '')
65
+ .toLowerCase();
66
+ }
67
+
68
+ function matches(item: CommandItem, haystack: string) {
69
+ if (!haystack) return true;
70
+ const blob = normalize([item.label, item.subtitle ?? '', item.keywords ?? ''].join(' '));
71
+ return blob.includes(haystack);
72
+ }
73
+
74
+ const filtered = $derived.by(() => {
75
+ const haystack = normalize(query.trim());
76
+ return items.filter((item) => matches(item, haystack));
77
+ });
40
78
 
41
79
  const groups = $derived.by(() => {
42
- const map = new Map<string, CommandItem[]>();
43
- for (const item of filtered) {
44
- const key = item.group || 'Commands';
80
+ const map = new Map<string, { item: CommandItem; index: number }[]>();
81
+ filtered.forEach((item, index) => {
82
+ const key = item.group || dialogLabel;
45
83
  const list = map.get(key) ?? [];
46
- list.push(item);
84
+ list.push({ item, index });
47
85
  map.set(key, list);
48
- }
86
+ });
49
87
  return [...map.entries()];
50
88
  });
51
89
 
52
90
  const flat = $derived(filtered);
91
+ const showIconColumn = $derived(flat.some((item) => item.icon));
92
+ const activeItem = $derived(flat[activeIndex] ?? null);
93
+ const activeOptionId = $derived(activeItem ? optionId(activeItem.id) : undefined);
53
94
 
54
- $effect(() => {
55
- if (open) {
56
- query = '';
57
- activeIndex = 0;
58
- queueMicrotask(() => inputEl?.focus());
95
+ function optionId(id: string) {
96
+ return `${listId}-${id}`;
97
+ }
98
+
99
+ function firstEnabledIndex() {
100
+ return flat.findIndex((item) => !item.disabled);
101
+ }
102
+
103
+ function nextEnabled(from: number, dir: 1 | -1) {
104
+ if (!flat.length) return 0;
105
+ let i = from;
106
+ for (let n = 0; n < flat.length; n++) {
107
+ i = (i + dir + flat.length) % flat.length;
108
+ if (!flat[i]?.disabled) return i;
59
109
  }
60
- });
110
+ return from;
111
+ }
112
+
113
+ /** Desplaza solo el listado, no el <dialog> (scrollIntoView arrastra ancestros). */
114
+ function keepVisible(node: HTMLElement) {
115
+ const frame = requestAnimationFrame(() => {
116
+ const list = listEl;
117
+ if (!list) return;
118
+ const listRect = list.getBoundingClientRect();
119
+ const rect = node.getBoundingClientRect();
120
+ if (rect.bottom > listRect.bottom) {
121
+ list.scrollTop += rect.bottom - listRect.bottom;
122
+ } else if (rect.top < listRect.top) {
123
+ list.scrollTop -= listRect.top - rect.top;
124
+ }
125
+ });
126
+ return () => cancelAnimationFrame(frame);
127
+ }
61
128
 
62
129
  $effect(() => {
63
- query;
64
- activeIndex = 0;
130
+ if (!dialogEl) return;
131
+ if (open && !dialogEl.open) {
132
+ query = '';
133
+ activeIndex = Math.max(0, firstEnabledIndex());
134
+ untrack(() => onquery?.(''));
135
+ ignoreBackdropUntil = Date.now() + 400;
136
+ dialogEl.showModal();
137
+ } else if (!open && dialogEl.open) {
138
+ dialogEl.close();
139
+ }
65
140
  });
66
141
 
67
142
  function close() {
@@ -69,6 +144,22 @@
69
144
  onclose?.();
70
145
  }
71
146
 
147
+ function handleDialogClose() {
148
+ if (open) close();
149
+ }
150
+
151
+ function handleBackdropClick(event: MouseEvent) {
152
+ if (!dialogEl) return;
153
+ if (Date.now() < ignoreBackdropUntil) return;
154
+ const rect = dialogEl.getBoundingClientRect();
155
+ const inside =
156
+ event.clientX >= rect.left &&
157
+ event.clientX <= rect.right &&
158
+ event.clientY >= rect.top &&
159
+ event.clientY <= rect.bottom;
160
+ if (!inside) close();
161
+ }
162
+
72
163
  function choose(item: CommandItem) {
73
164
  if (item.disabled) return;
74
165
  onselect?.(item);
@@ -76,94 +167,196 @@
76
167
  }
77
168
 
78
169
  function onKeydown(e: KeyboardEvent) {
79
- if (!open) return;
80
- if (e.key === 'Escape') {
81
- e.preventDefault();
82
- close();
83
- return;
84
- }
170
+ if (!open || !flat.length) return;
85
171
  if (e.key === 'ArrowDown') {
86
172
  e.preventDefault();
87
- activeIndex = Math.min(flat.length - 1, activeIndex + 1);
173
+ activeIndex = nextEnabled(activeIndex, 1);
88
174
  } else if (e.key === 'ArrowUp') {
89
175
  e.preventDefault();
90
- activeIndex = Math.max(0, activeIndex - 1);
176
+ activeIndex = nextEnabled(activeIndex, -1);
177
+ } else if (e.key === 'Home') {
178
+ e.preventDefault();
179
+ activeIndex = nextEnabled(-1, 1);
180
+ } else if (e.key === 'End') {
181
+ e.preventDefault();
182
+ activeIndex = nextEnabled(flat.length, -1);
91
183
  } else if (e.key === 'Enter') {
92
184
  e.preventDefault();
93
185
  const item = flat[activeIndex];
94
186
  if (item) choose(item);
95
187
  }
96
188
  }
189
+
190
+ function onInput() {
191
+ activeIndex = Math.max(0, firstEnabledIndex());
192
+ onquery?.(query);
193
+ }
97
194
  </script>
98
195
 
99
- {#if open}
100
- <div class="fixed inset-0 z-50 flex items-start justify-center pt-[15vh] px-4">
101
- <button
102
- type="button"
103
- class="absolute inset-0 bg-black/40 backdrop-blur-[2px]"
104
- aria-label="Close command palette"
105
- onclick={close}
106
- ></button>
107
-
108
- <div
109
- role="dialog"
110
- aria-modal="true"
111
- aria-label="Command palette"
112
- class={[
113
- 'relative z-10 w-full max-w-lg overflow-hidden rounded-2xl border border-border bg-surface-elevated shadow-2xl',
114
- className
115
- ]}
116
- onkeydown={onKeydown}
117
- >
118
- <div class="flex items-center gap-2 border-b border-border px-3">
119
- <svg class="h-4 w-4 shrink-0 text-muted" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
120
- <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-4.35-4.35M11 18a7 7 0 100-14 7 7 0 000 14z" />
121
- </svg>
122
- <input
123
- bind:this={inputEl}
124
- bind:value={query}
125
- {placeholder}
126
- class="h-12 w-full bg-transparent text-sm text-primary outline-none placeholder:text-muted"
196
+ <dialog
197
+ bind:this={dialogEl}
198
+ class={['command-palette', className]}
199
+ aria-label={dialogLabel}
200
+ onclose={handleDialogClose}
201
+ onclick={handleBackdropClick}
202
+ >
203
+ <div
204
+ class="rounded-2xl border-border bg-surface-elevated shadow-2xl w-full overflow-hidden border"
205
+ >
206
+ <div class="gap-2 border-border px-3 flex items-center border-b">
207
+ <svg
208
+ class="h-4 w-4 text-muted shrink-0"
209
+ viewBox="0 0 24 24"
210
+ fill="none"
211
+ stroke="currentColor"
212
+ stroke-width="2"
213
+ aria-hidden="true"
214
+ >
215
+ <path
216
+ stroke-linecap="round"
217
+ stroke-linejoin="round"
218
+ d="M21 21l-4.35-4.35M11 18a7 7 0 100-14 7 7 0 000 14z"
127
219
  />
128
- <Kbd keys={['Esc']} size="sm" />
129
- </div>
130
-
131
- <div class="max-h-80 overflow-y-auto p-2">
132
- {#if flat.length === 0}
133
- <p class="px-3 py-6 text-center text-sm text-muted">No results</p>
134
- {:else}
135
- {#each groups as [groupName, groupItems] (groupName)}
136
- <p class="px-2 py-1.5 text-[11px] font-semibold uppercase tracking-wide text-muted">
137
- {groupName}
138
- </p>
139
- <ul class="mb-2 flex flex-col gap-0.5">
140
- {#each groupItems as item (item.id)}
141
- {@const index = flat.findIndex((f) => f.id === item.id)}
142
- <li>
143
- <button
144
- type="button"
145
- disabled={item.disabled}
146
- onclick={() => choose(item)}
147
- onmouseenter={() => (activeIndex = index)}
148
- class={[
149
- 'flex w-full items-center justify-between gap-3 rounded-xl px-3 py-2 text-left text-sm transition-colors',
150
- index === activeIndex
151
- ? 'bg-brand-50 text-brand-700 dark:bg-brand-950/40 dark:text-brand-300'
152
- : 'text-primary hover:bg-surface-overlay',
153
- item.disabled && 'cursor-not-allowed opacity-40'
154
- ]}
155
- >
156
- <span>{item.label}</span>
157
- {#if item.shortcut?.length}
158
- <Kbd keys={item.shortcut} size="sm" />
220
+ </svg>
221
+ <input
222
+ bind:value={query}
223
+ placeholder={resolvedPlaceholder}
224
+ class="h-12 text-sm text-primary placeholder:text-muted w-full bg-transparent outline-none"
225
+ autocomplete="off"
226
+ spellcheck="false"
227
+ role="combobox"
228
+ aria-autocomplete="list"
229
+ aria-expanded="true"
230
+ aria-controls={listId}
231
+ aria-activedescendant={activeOptionId}
232
+ oninput={onInput}
233
+ onkeydown={onKeydown}
234
+ />
235
+ {#if loading}
236
+ <span
237
+ class="h-4 w-4 animate-spin border-border border-t-brand-500 shrink-0 rounded-full border-2"
238
+ aria-hidden="true"
239
+ ></span>
240
+ {/if}
241
+ <Kbd keys={['Esc']} size="sm" />
242
+ </div>
243
+
244
+ <div bind:this={listEl} id={listId} class="max-h-80 p-2 overflow-y-auto" role="listbox">
245
+ {#if flat.length === 0}
246
+ <p class="px-3 py-6 text-sm text-muted text-center">{resolvedEmpty}</p>
247
+ {:else}
248
+ {#each groups as [groupName, groupItems] (groupName)}
249
+ <p class="px-2 py-1.5 font-semibold tracking-wide text-muted text-[11px] uppercase">
250
+ {groupName}
251
+ </p>
252
+ <ul class="mb-2 gap-0.5 flex flex-col">
253
+ {#each groupItems as { item, index } (item.id)}
254
+ {@const Icon = item.icon}
255
+ {@const active = index === activeIndex}
256
+ <li>
257
+ <button
258
+ id={optionId(item.id)}
259
+ type="button"
260
+ role="option"
261
+ tabindex="-1"
262
+ aria-selected={active}
263
+ disabled={item.disabled}
264
+ onclick={() => choose(item)}
265
+ onmouseenter={() => {
266
+ if (!item.disabled) activeIndex = index;
267
+ }}
268
+ {@attach active ? keepVisible : undefined}
269
+ class={[
270
+ 'gap-3 rounded-xl px-3 py-2 text-sm flex w-full items-center justify-between text-left transition-colors',
271
+ active
272
+ ? 'bg-brand-50 text-brand-700 dark:bg-brand-950/40 dark:text-brand-300'
273
+ : 'text-primary hover:bg-surface-overlay',
274
+ item.disabled && 'cursor-not-allowed opacity-40'
275
+ ]}
276
+ >
277
+ <span class="gap-3 min-w-0 flex flex-1 items-center">
278
+ {#if showIconColumn}
279
+ <span
280
+ class={[
281
+ 'h-8 w-8 rounded-lg flex shrink-0 items-center justify-center',
282
+ active
283
+ ? 'bg-brand-100/80 text-brand-700 dark:bg-brand-900/50 dark:text-brand-300'
284
+ : 'bg-surface-overlay text-muted'
285
+ ]}
286
+ aria-hidden="true"
287
+ >
288
+ {#if Icon}
289
+ <Icon size={16} strokeWidth={2} />
290
+ {/if}
291
+ </span>
159
292
  {/if}
160
- </button>
161
- </li>
162
- {/each}
163
- </ul>
164
- {/each}
165
- {/if}
166
- </div>
293
+ <span class="min-w-0 flex-1">
294
+ <span class="block truncate">{item.label}</span>
295
+ {#if item.subtitle}
296
+ <span class="mt-0.5 text-xs text-muted block truncate">{item.subtitle}</span
297
+ >
298
+ {/if}
299
+ </span>
300
+ </span>
301
+ {#if item.shortcut?.length}
302
+ <Kbd keys={item.shortcut} size="sm" />
303
+ {/if}
304
+ </button>
305
+ </li>
306
+ {/each}
307
+ </ul>
308
+ {/each}
309
+ {/if}
167
310
  </div>
168
311
  </div>
169
- {/if}
312
+ </dialog>
313
+
314
+ <style>
315
+ .command-palette {
316
+ margin: 12vh auto auto;
317
+ padding: 0;
318
+ border: none;
319
+ background: transparent;
320
+ color: inherit;
321
+ width: calc(100% - 2rem);
322
+ max-width: 36rem;
323
+ overflow: visible;
324
+ }
325
+
326
+ .command-palette::backdrop {
327
+ background: oklch(15% 0.02 265 / 0.45);
328
+ backdrop-filter: blur(6px);
329
+ }
330
+
331
+ :global(.dark) .command-palette::backdrop {
332
+ background: oklch(0% 0 0 / 0.65);
333
+ }
334
+
335
+ .command-palette[open] {
336
+ animation: palette-in 140ms ease-out;
337
+ }
338
+
339
+ .command-palette[open]::backdrop {
340
+ animation: palette-backdrop-in 140ms ease-out;
341
+ }
342
+
343
+ @keyframes palette-in {
344
+ from {
345
+ opacity: 0;
346
+ transform: translateY(6px) scale(0.98);
347
+ }
348
+ to {
349
+ opacity: 1;
350
+ transform: translateY(0) scale(1);
351
+ }
352
+ }
353
+
354
+ @keyframes palette-backdrop-in {
355
+ from {
356
+ opacity: 0;
357
+ }
358
+ to {
359
+ opacity: 1;
360
+ }
361
+ }
362
+ </style>
@@ -1,18 +1,31 @@
1
+ import type { Component } from 'svelte';
2
+ export type CommandIcon = Component<{
3
+ class?: string;
4
+ size?: number | string;
5
+ strokeWidth?: number | string;
6
+ }>;
1
7
  export interface CommandItem {
2
8
  id: string;
3
9
  label: string;
4
10
  group?: string;
5
11
  shortcut?: string[];
6
12
  disabled?: boolean;
13
+ subtitle?: string;
14
+ keywords?: string;
15
+ href?: string;
16
+ icon?: CommandIcon;
7
17
  }
8
18
  interface CommandPaletteProps {
9
19
  open?: boolean;
10
20
  items?: CommandItem[];
11
21
  placeholder?: string;
22
+ emptyLabel?: string;
23
+ loading?: boolean;
12
24
  class?: string;
13
25
  onselect?: (item: CommandItem) => void;
14
26
  onclose?: () => void;
27
+ onquery?: (query: string) => void;
15
28
  }
16
- declare const CommandPalette: import("svelte").Component<CommandPaletteProps, {}, "open">;
29
+ declare const CommandPalette: Component<CommandPaletteProps, {}, "open">;
17
30
  type CommandPalette = ReturnType<typeof CommandPalette>;
18
31
  export default CommandPalette;
@@ -1,14 +1,36 @@
1
1
  <script lang="ts">
2
+ import { FileText, FolderPlus, Search, Settings, UserPlus, Users } from '@lucide/svelte';
2
3
  import CommandPalette, { type CommandItem } from './CommandPalette.svelte';
3
4
  import Button from '../../atoms/Button/Button.svelte';
4
5
  import Kbd from '../../atoms/Kbd/Kbd.svelte';
5
6
 
6
7
  const items: CommandItem[] = [
7
- { id: 'new', label: 'Create project', group: 'Actions', shortcut: ['⌘', 'N'] },
8
- { id: 'search', label: 'Search files', group: 'Actions', shortcut: ['⌘', 'P'] },
9
- { id: 'settings', label: 'Open settings', group: 'Navigation', shortcut: ['⌘', ','] },
10
- { id: 'team', label: 'Invite teammate', group: 'Navigation' },
11
- { id: 'billing', label: 'Billing', group: 'Navigation', disabled: true }
8
+ {
9
+ id: 'new',
10
+ label: 'Create project',
11
+ group: 'Actions',
12
+ shortcut: ['', 'N'],
13
+ icon: FolderPlus
14
+ },
15
+ { id: 'search', label: 'Search files', group: 'Actions', shortcut: ['⌘', 'P'], icon: Search },
16
+ {
17
+ id: 'invoice',
18
+ label: 'FAC-2026-0142',
19
+ subtitle: 'Invoice · Acme S.L.',
20
+ group: 'Documents',
21
+ keywords: 'factura invoice',
22
+ icon: FileText
23
+ },
24
+ {
25
+ id: 'settings',
26
+ label: 'Open settings',
27
+ group: 'Navigation',
28
+ shortcut: ['⌘', ','],
29
+ href: '/settings',
30
+ icon: Settings
31
+ },
32
+ { id: 'team', label: 'Invite teammate', group: 'Navigation', icon: UserPlus },
33
+ { id: 'billing', label: 'Billing', group: 'Navigation', disabled: true, icon: Users }
12
34
  ];
13
35
 
14
36
  let open = $state(false);
@@ -24,7 +46,7 @@
24
46
 
25
47
  <svelte:window onkeydown={onWindowKey} />
26
48
 
27
- <div class="flex flex-col items-center gap-3">
49
+ <div class="gap-3 flex flex-col items-center">
28
50
  <Button size="sm" onclick={() => (open = true)}>
29
51
  Open palette
30
52
  <Kbd keys={['⌘', 'K']} size="sm" />
@@ -34,8 +56,4 @@
34
56
  {/if}
35
57
  </div>
36
58
 
37
- <CommandPalette
38
- bind:open
39
- {items}
40
- onselect={(item) => (last = item.label)}
41
- />
59
+ <CommandPalette bind:open {items} onselect={(item) => (last = item.label)} />
package/dist/index.d.ts CHANGED
@@ -666,7 +666,7 @@ export type { NavbarLink } from './components/organisms/Navbar/Navbar.svelte';
666
666
  export { default as BottomNav } from './components/organisms/BottomNav/BottomNav.svelte';
667
667
  export type { BottomNavItem, BottomNavIcon } from './components/organisms/BottomNav/BottomNav.svelte';
668
668
  export { default as CommandPalette } from './components/organisms/CommandPalette/CommandPalette.svelte';
669
- export type { CommandItem } from './components/organisms/CommandPalette/CommandPalette.svelte';
669
+ export type { CommandItem, CommandIcon } from './components/organisms/CommandPalette/CommandPalette.svelte';
670
670
  export { default as AppShell } from './components/organisms/AppShell/AppShell.svelte';
671
671
  export { getAppChrome, setAppChrome, AppChrome } from './components/organisms/AppShell/app-chrome.svelte.js';
672
672
  export type { AppShellContextual } from './components/organisms/AppShell/app-chrome.svelte.js';
@@ -14,6 +14,10 @@ export interface UiMessages {
14
14
  addItem: string;
15
15
  removeItem: string;
16
16
  noItems: string;
17
+ /** CommandPalette */
18
+ noResults: string;
19
+ commandPalette: string;
20
+ commandPalettePlaceholder: string;
17
21
  /** FileUploader */
18
22
  uploadFiles: string;
19
23
  uploadHelper: string;
@@ -12,6 +12,9 @@ const en = {
12
12
  addItem: 'Add item',
13
13
  removeItem: 'Remove item',
14
14
  noItems: 'No items yet',
15
+ noResults: 'No results',
16
+ commandPalette: 'Command palette',
17
+ commandPalettePlaceholder: 'Type a command or search…',
15
18
  uploadFiles: 'Upload files',
16
19
  uploadHelper: 'SVG, PNG, JPG or GIF (max. 10MB)',
17
20
  dropToUpload: 'Drop files to upload',
@@ -56,6 +59,9 @@ const es = {
56
59
  addItem: 'Añadir elemento',
57
60
  removeItem: 'Eliminar elemento',
58
61
  noItems: 'Sin elementos todavía',
62
+ noResults: 'Sin resultados',
63
+ commandPalette: 'Paleta de comandos',
64
+ commandPalettePlaceholder: 'Escribe un comando o busca…',
59
65
  uploadFiles: 'Subir archivos',
60
66
  uploadHelper: 'SVG, PNG, JPG o GIF (máx. 10MB)',
61
67
  dropToUpload: 'Suelta los archivos para subirlos',
@@ -100,6 +106,9 @@ const pt = {
100
106
  addItem: 'Adicionar item',
101
107
  removeItem: 'Remover item',
102
108
  noItems: 'Ainda sem itens',
109
+ noResults: 'Sem resultados',
110
+ commandPalette: 'Paleta de comandos',
111
+ commandPalettePlaceholder: 'Escreve um comando ou pesquisa…',
103
112
  uploadFiles: 'Carregar ficheiros',
104
113
  uploadHelper: 'SVG, PNG, JPG ou GIF (máx. 10MB)',
105
114
  dropToUpload: 'Largue os ficheiros para carregar',
@@ -144,6 +153,9 @@ const fr = {
144
153
  addItem: 'Ajouter un élément',
145
154
  removeItem: 'Supprimer l’élément',
146
155
  noItems: 'Aucun élément pour l’instant',
156
+ noResults: 'Aucun résultat',
157
+ commandPalette: 'Palette de commandes',
158
+ commandPalettePlaceholder: 'Tapez une commande ou recherchez…',
147
159
  uploadFiles: 'Téléverser des fichiers',
148
160
  uploadHelper: 'SVG, PNG, JPG ou GIF (max. 10MB)',
149
161
  dropToUpload: 'Déposez les fichiers pour les téléverser',
@@ -188,6 +200,9 @@ const de = {
188
200
  addItem: 'Element hinzufügen',
189
201
  removeItem: 'Element entfernen',
190
202
  noItems: 'Noch keine Elemente',
203
+ noResults: 'Keine Ergebnisse',
204
+ commandPalette: 'Befehlspalette',
205
+ commandPalettePlaceholder: 'Befehl eingeben oder suchen…',
191
206
  uploadFiles: 'Dateien hochladen',
192
207
  uploadHelper: 'SVG, PNG, JPG oder GIF (max. 10MB)',
193
208
  dropToUpload: 'Dateien zum Hochladen ablegen',
@@ -232,6 +247,9 @@ const ca = {
232
247
  addItem: 'Afegir element',
233
248
  removeItem: 'Eliminar element',
234
249
  noItems: 'Encara sense elements',
250
+ noResults: 'Sense resultats',
251
+ commandPalette: 'Paleta de comandes',
252
+ commandPalettePlaceholder: 'Escriu una comanda o cerca…',
235
253
  uploadFiles: 'Pujar fitxers',
236
254
  uploadHelper: 'SVG, PNG, JPG o GIF (màx. 10MB)',
237
255
  dropToUpload: 'Deixa anar els fitxers per pujar-los',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",