@r2digisolutions/components 0.6.2 → 0.6.3

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.
@@ -6,6 +6,7 @@ declare const meta: {
6
6
  label?: string;
7
7
  disabled?: boolean;
8
8
  compound?: boolean;
9
+ menuItems?: boolean;
9
10
  }, {}, "">;
10
11
  tags: string[];
11
12
  argTypes: {
@@ -18,12 +19,16 @@ declare const meta: {
18
19
  compound: {
19
20
  control: "boolean";
20
21
  };
22
+ menuItems: {
23
+ control: "boolean";
24
+ };
21
25
  };
22
26
  args: {
23
27
  creatable: false;
24
28
  label: string;
25
29
  disabled: false;
26
30
  compound: false;
31
+ menuItems: false;
27
32
  };
28
33
  };
29
34
  export default meta;
@@ -32,3 +37,4 @@ export declare const Default: Story;
32
37
  export declare const Creatable: Story;
33
38
  export declare const Disabled: Story;
34
39
  export declare const WithComboboxItem: Story;
40
+ export declare const WithComboboxMenuItem: Story;
@@ -6,13 +6,15 @@ const meta = {
6
6
  argTypes: {
7
7
  creatable: { control: 'boolean' },
8
8
  disabled: { control: 'boolean' },
9
- compound: { control: 'boolean' }
9
+ compound: { control: 'boolean' },
10
+ menuItems: { control: 'boolean' }
10
11
  },
11
12
  args: {
12
13
  creatable: false,
13
14
  label: 'Framework',
14
15
  disabled: false,
15
- compound: false
16
+ compound: false,
17
+ menuItems: false
16
18
  }
17
19
  };
18
20
  export default meta;
@@ -20,3 +22,4 @@ export const Default = {};
20
22
  export const Creatable = { args: { creatable: true } };
21
23
  export const Disabled = { args: { disabled: true } };
22
24
  export const WithComboboxItem = { args: { compound: true } };
25
+ export const WithComboboxMenuItem = { args: { menuItems: true } };
@@ -1,17 +1,20 @@
1
1
  <script lang="ts">
2
2
  import Combobox, { type ComboboxOption } from './Combobox.svelte';
3
3
  import ComboboxItem from '../ComboboxItem/ComboboxItem.svelte';
4
+ import ComboboxMenuItem from '../ComboboxMenuItem/ComboboxMenuItem.svelte';
4
5
 
5
6
  let {
6
7
  creatable = false,
7
8
  label = 'Framework',
8
9
  disabled = false,
9
- compound = false
10
+ compound = false,
11
+ menuItems = false
10
12
  }: {
11
13
  creatable?: boolean;
12
14
  label?: string;
13
15
  disabled?: boolean;
14
16
  compound?: boolean;
17
+ menuItems?: boolean;
15
18
  } = $props();
16
19
 
17
20
  const options: ComboboxOption[] = [
@@ -29,7 +32,13 @@
29
32
  </script>
30
33
 
31
34
  <div class="w-80 gap-3 flex flex-col">
32
- {#if compound}
35
+ {#if menuItems}
36
+ <Combobox {label} {disabled} bind:value bind:query bind:open placeholder="Search frameworks…">
37
+ {#each options as option (option.value)}
38
+ <ComboboxMenuItem value={option.value} label={option.label} disabled={option.disabled} />
39
+ {/each}
40
+ </Combobox>
41
+ {:else if compound}
33
42
  <Combobox {label} {disabled} bind:value bind:query bind:open placeholder="Search frameworks…">
34
43
  {#each options as option (option.value)}
35
44
  <ComboboxItem value={option.value} label={option.label} disabled={option.disabled}>
@@ -3,6 +3,7 @@ type $$ComponentProps = {
3
3
  label?: string;
4
4
  disabled?: boolean;
5
5
  compound?: boolean;
6
+ menuItems?: boolean;
6
7
  };
7
8
  declare const ComboboxStory: import("svelte").Component<$$ComponentProps, {}, "">;
8
9
  type ComboboxStory = ReturnType<typeof ComboboxStory>;
@@ -0,0 +1,17 @@
1
+ export interface ComboboxItemInput {
2
+ value: string;
3
+ label: string;
4
+ disabled: boolean;
5
+ keywords: string[];
6
+ selected: boolean | undefined;
7
+ highlighted: boolean | undefined;
8
+ register: boolean;
9
+ onclick?: () => void;
10
+ }
11
+ export declare function createComboboxItemController(getInput: () => ComboboxItemInput): {
12
+ readonly matchesQuery: boolean;
13
+ readonly isSelected: boolean;
14
+ readonly isHighlighted: boolean;
15
+ activate: () => void;
16
+ highlight: () => void;
17
+ };
@@ -0,0 +1,57 @@
1
+ import { getComboboxContext } from './combobox-context.js';
2
+ export function createComboboxItemController(getInput) {
3
+ const ctx = getComboboxContext();
4
+ const matchesQuery = $derived.by(() => {
5
+ const input = getInput();
6
+ if (!input.register || !ctx)
7
+ return true;
8
+ const q = ctx.getQuery().trim().toLowerCase();
9
+ if (!q)
10
+ return true;
11
+ const blob = [input.label, input.value, ...input.keywords].join(' ').toLowerCase();
12
+ return blob.includes(q);
13
+ });
14
+ const isSelected = $derived.by(() => {
15
+ const input = getInput();
16
+ return input.selected ?? (ctx ? ctx.getValue() === input.value : false);
17
+ });
18
+ const isHighlighted = $derived.by(() => {
19
+ const input = getInput();
20
+ return input.highlighted ?? (ctx ? ctx.getHighlighted() === input.value : false);
21
+ });
22
+ $effect(() => {
23
+ const input = getInput();
24
+ if (!input.register || !ctx || input.disabled || !matchesQuery)
25
+ return;
26
+ return ctx.register(input.value, input.disabled, input.label);
27
+ });
28
+ function activate() {
29
+ const input = getInput();
30
+ if (input.disabled)
31
+ return;
32
+ if (input.onclick) {
33
+ input.onclick();
34
+ return;
35
+ }
36
+ ctx?.select(input.value);
37
+ }
38
+ function highlight() {
39
+ const input = getInput();
40
+ if (input.disabled)
41
+ return;
42
+ ctx?.highlight(input.value);
43
+ }
44
+ return {
45
+ get matchesQuery() {
46
+ return matchesQuery;
47
+ },
48
+ get isSelected() {
49
+ return isSelected;
50
+ },
51
+ get isHighlighted() {
52
+ return isHighlighted;
53
+ },
54
+ activate,
55
+ highlight
56
+ };
57
+ }
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import Check from '@lucide/svelte/icons/check';
4
- import { getComboboxContext } from '../Combobox/combobox-context.js';
4
+ import { createComboboxItemController } from '../Combobox/combobox-item.svelte.js';
5
5
 
6
6
  interface ComboboxItemProps {
7
7
  value: string;
@@ -49,54 +49,39 @@
49
49
  onhighlight
50
50
  }: ComboboxItemProps = $props();
51
51
 
52
- const ctx = getComboboxContext();
53
-
54
- const matchesQuery = $derived.by(() => {
55
- if (!register || !ctx) return true;
56
- const q = ctx.getQuery().trim().toLowerCase();
57
- if (!q) return true;
58
- const blob = [label, value, ...keywords].join(' ').toLowerCase();
59
- return blob.includes(q);
60
- });
61
-
62
- const isSelected = $derived(selected ?? (ctx ? ctx.getValue() === value : false));
63
- const isHighlighted = $derived(highlighted ?? (ctx ? ctx.getHighlighted() === value : false));
64
- const active = $derived(!disabled && (isHighlighted || isSelected));
65
-
66
- $effect(() => {
67
- if (!register || !ctx || disabled || !matchesQuery) return;
68
- return ctx.register(value, disabled, label);
69
- });
70
-
71
- function activate() {
72
- if (disabled) return;
73
- if (onclick) {
74
- onclick();
75
- return;
76
- }
77
- ctx?.select(value);
78
- }
52
+ const item = createComboboxItemController(() => ({
53
+ value,
54
+ label,
55
+ disabled,
56
+ keywords,
57
+ selected,
58
+ highlighted,
59
+ register,
60
+ onclick
61
+ }));
79
62
  </script>
80
63
 
81
- {#if matchesQuery}
64
+ {#if item.matchesQuery}
82
65
  <button
83
66
  type="button"
84
67
  role="option"
85
68
  data-value={value}
86
69
  {disabled}
87
- aria-selected={isSelected}
70
+ aria-selected={item.isSelected}
88
71
  aria-disabled={disabled}
89
72
  onpointerenter={() => {
90
- if (disabled) return;
91
73
  onhighlight?.();
92
- ctx?.highlight(value);
74
+ item.highlight();
93
75
  }}
94
- onclick={activate}
76
+ onclick={item.activate}
95
77
  class={[
96
78
  'gap-2 rounded-lg px-2.5 py-2 text-sm flex w-full items-center text-left transition-colors',
97
79
  'focus-visible:ring-brand-500/30 focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset',
98
80
  disabled && 'cursor-not-allowed opacity-40',
99
- !disabled && (active ? 'bg-brand-500 text-white' : 'text-primary hover:bg-surface-overlay'),
81
+ !disabled &&
82
+ (item.isHighlighted || item.isSelected
83
+ ? 'bg-brand-500 text-white'
84
+ : 'text-primary hover:bg-surface-overlay'),
100
85
  className
101
86
  ]}
102
87
  >
@@ -114,7 +99,7 @@
114
99
 
115
100
  {#if trailing}
116
101
  <span class="flex shrink-0 items-center">{@render trailing()}</span>
117
- {:else if isSelected}
102
+ {:else if item.isSelected}
118
103
  <Check class="h-4 w-4 shrink-0" strokeWidth={2.5} />
119
104
  {/if}
120
105
  </button>
@@ -0,0 +1,9 @@
1
+ import type { StoryObj } from '@storybook/svelte';
2
+ declare const meta: {
3
+ title: string;
4
+ component: import("svelte").Component<Record<string, never>, {}, "">;
5
+ tags: string[];
6
+ };
7
+ export default meta;
8
+ type Story = StoryObj<typeof meta>;
9
+ export declare const Default: Story;
@@ -0,0 +1,8 @@
1
+ import ComboboxMenuItemStory from './ComboboxMenuItemStory.svelte';
2
+ const meta = {
3
+ title: 'Molecules/ComboboxMenuItem',
4
+ component: ComboboxMenuItemStory,
5
+ tags: ['autodocs']
6
+ };
7
+ export default meta;
8
+ export const Default = {};
@@ -0,0 +1,84 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import Check from '@lucide/svelte/icons/check';
4
+ import { createComboboxItemController } from '../Combobox/combobox-item.svelte.js';
5
+
6
+ interface ComboboxMenuItemProps {
7
+ value: string;
8
+ label?: string;
9
+ disabled?: boolean;
10
+ keywords?: string[];
11
+ selected?: boolean;
12
+ highlighted?: boolean;
13
+ register?: boolean;
14
+ class?: string;
15
+ children?: Snippet;
16
+ onclick?: () => void;
17
+ onhighlight?: () => void;
18
+ }
19
+
20
+ let {
21
+ value,
22
+ label = '',
23
+ disabled = false,
24
+ keywords = [],
25
+ selected,
26
+ highlighted,
27
+ register = true,
28
+ class: className = '',
29
+ children,
30
+ onclick,
31
+ onhighlight
32
+ }: ComboboxMenuItemProps = $props();
33
+
34
+ const item = createComboboxItemController(() => ({
35
+ value,
36
+ label,
37
+ disabled,
38
+ keywords,
39
+ selected,
40
+ highlighted,
41
+ register,
42
+ onclick
43
+ }));
44
+ </script>
45
+
46
+ {#if item.matchesQuery}
47
+ <button
48
+ type="button"
49
+ role="option"
50
+ data-value={value}
51
+ {disabled}
52
+ aria-selected={item.isSelected}
53
+ aria-disabled={disabled}
54
+ onpointerenter={() => {
55
+ onhighlight?.();
56
+ item.highlight();
57
+ }}
58
+ onclick={item.activate}
59
+ class={[
60
+ 'rounded-lg py-1.5 pr-2 pl-8 text-sm relative flex w-full cursor-default items-center text-left outline-none select-none',
61
+ 'focus-visible:ring-brand-500/30 transition-colors focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset',
62
+ disabled && 'cursor-not-allowed opacity-40',
63
+ !disabled && item.isHighlighted && 'bg-surface-overlay',
64
+ !disabled && !item.isHighlighted && 'hover:bg-surface-overlay',
65
+ !disabled && item.isSelected && 'font-medium text-brand-600 dark:text-brand-400',
66
+ !disabled && !item.isSelected && 'text-primary',
67
+ className
68
+ ]}
69
+ >
70
+ <span class="left-2 h-3.5 w-3.5 absolute flex items-center justify-center" aria-hidden="true">
71
+ {#if item.isSelected}
72
+ <Check class="h-3.5 w-3.5" strokeWidth={2.5} />
73
+ {/if}
74
+ </span>
75
+
76
+ <span class={['min-w-0 flex-1', !children && 'truncate']}>
77
+ {#if children}
78
+ {@render children()}
79
+ {:else}
80
+ {label || value}
81
+ {/if}
82
+ </span>
83
+ </button>
84
+ {/if}
@@ -0,0 +1,17 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface ComboboxMenuItemProps {
3
+ value: string;
4
+ label?: string;
5
+ disabled?: boolean;
6
+ keywords?: string[];
7
+ selected?: boolean;
8
+ highlighted?: boolean;
9
+ register?: boolean;
10
+ class?: string;
11
+ children?: Snippet;
12
+ onclick?: () => void;
13
+ onhighlight?: () => void;
14
+ }
15
+ declare const ComboboxMenuItem: import("svelte").Component<ComboboxMenuItemProps, {}, "">;
16
+ type ComboboxMenuItem = ReturnType<typeof ComboboxMenuItem>;
17
+ export default ComboboxMenuItem;
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import ComboboxMenuItem from './ComboboxMenuItem.svelte';
3
+
4
+ let selected = $state('svelte');
5
+ let highlighted = $state('svelte');
6
+
7
+ const frameworks = [
8
+ { value: 'svelte', label: 'Svelte' },
9
+ { value: 'react', label: 'React' },
10
+ { value: 'vue', label: 'Vue' },
11
+ { value: 'solid', label: 'Solid' },
12
+ { value: 'qwik', label: 'Qwik', disabled: true }
13
+ ];
14
+ </script>
15
+
16
+ <div class="max-w-sm space-y-3 w-full">
17
+ <div class="space-y-1">
18
+ <p class="text-sm font-semibold text-primary">ComboboxMenuItem</p>
19
+ <p class="text-xs text-secondary">
20
+ Fila compacta con check a la izquierda, como el combobox de selección. No pinta el fondo
21
+ brand.
22
+ </p>
23
+ </div>
24
+
25
+ <div class="rounded-xl border-border bg-surface-elevated p-1 shadow-sm border">
26
+ {#each frameworks as fw (fw.value)}
27
+ <ComboboxMenuItem
28
+ value={fw.value}
29
+ label={fw.label}
30
+ disabled={fw.disabled}
31
+ selected={selected === fw.value}
32
+ highlighted={highlighted === fw.value}
33
+ onclick={() => (selected = fw.value)}
34
+ onhighlight={() => (highlighted = fw.value)}
35
+ />
36
+ {/each}
37
+ </div>
38
+
39
+ <p class="text-xs text-muted">
40
+ Value: <span class="text-primary">{selected}</span>
41
+ </p>
42
+ </div>
@@ -0,0 +1,3 @@
1
+ declare const ComboboxMenuItemStory: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type ComboboxMenuItemStory = ReturnType<typeof ComboboxMenuItemStory>;
3
+ export default ComboboxMenuItemStory;
package/dist/index.d.ts CHANGED
@@ -212,6 +212,7 @@ export { default as DateRangePicker } from './components/molecules/DateRangePick
212
212
  export { default as Combobox } from './components/molecules/Combobox/Combobox.svelte';
213
213
  export type { ComboboxOption, ComboboxItemState } from './components/molecules/Combobox/Combobox.svelte';
214
214
  export { default as ComboboxItem } from './components/molecules/ComboboxItem/ComboboxItem.svelte';
215
+ export { default as ComboboxMenuItem } from './components/molecules/ComboboxMenuItem/ComboboxMenuItem.svelte';
215
216
  export { COMBOBOX_CONTEXT, COMBOBOX_CREATE_KEY, getComboboxContext, setComboboxContext } from './components/molecules/Combobox/combobox-context.js';
216
217
  export type { ComboboxContext } from './components/molecules/Combobox/combobox-context.js';
217
218
  export { default as MultiSelect } from './components/molecules/MultiSelect/MultiSelect.svelte';
package/dist/index.js CHANGED
@@ -167,6 +167,7 @@ export { default as DateTimePicker } from './components/molecules/DateTimePicker
167
167
  export { default as DateRangePicker } from './components/molecules/DateRangePicker/DateRangePicker.svelte';
168
168
  export { default as Combobox } from './components/molecules/Combobox/Combobox.svelte';
169
169
  export { default as ComboboxItem } from './components/molecules/ComboboxItem/ComboboxItem.svelte';
170
+ export { default as ComboboxMenuItem } from './components/molecules/ComboboxMenuItem/ComboboxMenuItem.svelte';
170
171
  export { COMBOBOX_CONTEXT, COMBOBOX_CREATE_KEY, getComboboxContext, setComboboxContext } from './components/molecules/Combobox/combobox-context.js';
171
172
  export { default as MultiSelect } from './components/molecules/MultiSelect/MultiSelect.svelte';
172
173
  export { default as ConfirmDialog } from './components/molecules/ConfirmDialog/ConfirmDialog.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r2digisolutions/components",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "private": false,
5
5
  "description": "R2DigiSolutions Svelte 5 component library — Atomic Design, Tailwind 4, Light/Dark mode",
6
6
  "license": "MIT",