@insymetri/styleguide 0.1.13 → 0.1.14
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/dist/IICalendar/IICalendar.svelte +101 -0
- package/dist/IICalendar/IICalendar.svelte.d.ts +13 -0
- package/dist/IICalendar/IICalendarStories.svelte +60 -0
- package/dist/IICalendar/IICalendarStories.svelte.d.ts +3 -0
- package/dist/IICalendar/index.d.ts +1 -0
- package/dist/IICalendar/index.js +1 -0
- package/dist/IICombobox/IICombobox.svelte +75 -29
- package/dist/IICombobox/IICombobox.svelte.d.ts +6 -0
- package/dist/IICombobox/IIComboboxStories.svelte +97 -0
- package/dist/IICombobox/IIComboboxStories.svelte.d.ts +3 -0
- package/dist/IIDropdownInput/IIDropdownInput.svelte +30 -6
- package/dist/IIDropdownInput/IIDropdownInput.svelte.d.ts +5 -0
- package/dist/IIDropdownInput/IIDropdownInputStories.svelte +88 -0
- package/dist/IIDropdownInput/IIDropdownInputStories.svelte.d.ts +3 -0
- package/dist/IIDropdownMenu/IIDropdownMenu.svelte +90 -23
- package/dist/IIDropdownMenu/IIDropdownMenu.svelte.d.ts +14 -2
- package/dist/IIDropdownMenu/IIDropdownMenuStories.svelte +101 -0
- package/dist/IIDropdownMenu/IIDropdownMenuStories.svelte.d.ts +18 -0
- package/dist/IIMultiSelect/IIMultiSelect.svelte +141 -0
- package/dist/IIMultiSelect/IIMultiSelect.svelte.d.ts +20 -0
- package/dist/IIMultiSelect/IIMultiSelectStories.svelte +78 -0
- package/dist/IIMultiSelect/IIMultiSelectStories.svelte.d.ts +3 -0
- package/dist/IIMultiSelect/index.d.ts +1 -0
- package/dist/IIMultiSelect/index.js +1 -0
- package/dist/IIPopover/IIPopover.svelte +48 -0
- package/dist/IIPopover/IIPopover.svelte.d.ts +15 -0
- package/dist/IIPopover/IIPopoverStories.svelte +108 -0
- package/dist/IIPopover/IIPopoverStories.svelte.d.ts +3 -0
- package/dist/IIPopover/index.d.ts +1 -0
- package/dist/IIPopover/index.js +1 -0
- package/dist/IIToggle/IIToggle.svelte +52 -0
- package/dist/IIToggle/IIToggle.svelte.d.ts +15 -0
- package/dist/IIToggle/IIToggleStories.svelte +89 -0
- package/dist/IIToggle/IIToggleStories.svelte.d.ts +3 -0
- package/dist/IIToggle/index.d.ts +1 -0
- package/dist/IIToggle/index.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/package.json +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import IIDropdownInput from './IIDropdownInput.svelte'
|
|
3
|
+
import {IIIcon} from '../IIIcon'
|
|
4
|
+
import type {IconName} from '../icons'
|
|
5
|
+
|
|
6
|
+
let basicValue = $state<string | undefined>(undefined)
|
|
7
|
+
let disabledItemValue = $state<string | undefined>(undefined)
|
|
8
|
+
|
|
9
|
+
const statusItems = [
|
|
10
|
+
{label: 'Active', value: 'active'},
|
|
11
|
+
{label: 'Pending', value: 'pending'},
|
|
12
|
+
{label: 'Closed', value: 'closed'},
|
|
13
|
+
{label: 'Delinquent', value: 'delinquent'},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const disabledItems = [
|
|
17
|
+
{label: 'Active', value: 'active'},
|
|
18
|
+
{label: 'Pending', value: 'pending', disabled: true},
|
|
19
|
+
{label: 'Closed', value: 'closed'},
|
|
20
|
+
{label: 'Archived', value: 'archived', disabled: true},
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
const iconMap: Record<string, IconName> = {
|
|
24
|
+
active: 'check-circle',
|
|
25
|
+
pending: 'clock-countdown',
|
|
26
|
+
closed: 'warning-circle',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const colorMap: Record<string, string> = {
|
|
30
|
+
active: 'text-success',
|
|
31
|
+
pending: 'text-warning',
|
|
32
|
+
closed: 'text-error',
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div class="flex flex-col gap-32">
|
|
37
|
+
<!-- With Custom Render Item (icons) -->
|
|
38
|
+
<section>
|
|
39
|
+
<h2 class="text-default-emphasis text-primary mb-8">Custom Item Rendering with Icons</h2>
|
|
40
|
+
<p class="text-small text-secondary mb-12">Use renderItem to add icons and custom layout to dropdown items.</p>
|
|
41
|
+
<div class="w-200">
|
|
42
|
+
<IIDropdownInput
|
|
43
|
+
items={[
|
|
44
|
+
{label: 'Active', value: 'active'},
|
|
45
|
+
{label: 'Pending', value: 'pending'},
|
|
46
|
+
{label: 'Closed', value: 'closed'},
|
|
47
|
+
]}
|
|
48
|
+
bind:value={basicValue}
|
|
49
|
+
placeholder="Select status..."
|
|
50
|
+
>
|
|
51
|
+
{#snippet renderItem(item, selected)}
|
|
52
|
+
<span class="flex items-center gap-8 flex-1">
|
|
53
|
+
<IIIcon iconName={iconMap[item.value]} class={colorMap[item.value]} />
|
|
54
|
+
<span class="flex-1">{item.label}</span>
|
|
55
|
+
{#if selected}
|
|
56
|
+
<IIIcon iconName="check-circle" class="w-14 h-14 text-accent shrink-0" />
|
|
57
|
+
{/if}
|
|
58
|
+
</span>
|
|
59
|
+
{/snippet}
|
|
60
|
+
</IIDropdownInput>
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
63
|
+
|
|
64
|
+
<!-- Disabled Items -->
|
|
65
|
+
<section>
|
|
66
|
+
<h2 class="text-default-emphasis text-primary mb-8">Per-Item Disabled</h2>
|
|
67
|
+
<p class="text-small text-secondary mb-12">Individual items can be disabled.</p>
|
|
68
|
+
<div class="w-200">
|
|
69
|
+
<IIDropdownInput items={disabledItems} bind:value={disabledItemValue} placeholder="Select status..." />
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
73
|
+
<!-- Custom Render Selected -->
|
|
74
|
+
<section>
|
|
75
|
+
<h2 class="text-default-emphasis text-primary mb-8">Custom Selected Display</h2>
|
|
76
|
+
<p class="text-small text-secondary mb-12">Use renderSelected to customize how the selected item appears in the trigger.</p>
|
|
77
|
+
<div class="w-200">
|
|
78
|
+
<IIDropdownInput items={statusItems} bind:value={basicValue} placeholder="Select status...">
|
|
79
|
+
{#snippet renderSelected(item)}
|
|
80
|
+
<span class="flex items-center gap-6">
|
|
81
|
+
<span class="w-8 h-8 rounded-full bg-success"></span>
|
|
82
|
+
<span class="text-small-emphasis">{item.label}</span>
|
|
83
|
+
</span>
|
|
84
|
+
{/snippet}
|
|
85
|
+
</IIDropdownInput>
|
|
86
|
+
</div>
|
|
87
|
+
</section>
|
|
88
|
+
</div>
|
|
@@ -12,28 +12,96 @@
|
|
|
12
12
|
variant?: 'default' | 'destructive'
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
type
|
|
15
|
+
type SeparatorEntry = {
|
|
16
|
+
type: 'separator'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type GroupEntry = {
|
|
20
|
+
type: 'group'
|
|
21
|
+
heading?: string
|
|
16
22
|
items: Item[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type MenuEntry = Item | SeparatorEntry | GroupEntry
|
|
26
|
+
|
|
27
|
+
type Props = {
|
|
28
|
+
items: MenuEntry[]
|
|
17
29
|
onSelect: (value: string) => void
|
|
30
|
+
open?: boolean
|
|
18
31
|
children?: Snippet
|
|
32
|
+
renderItem?: Snippet<[Item]>
|
|
19
33
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
20
34
|
align?: 'start' | 'center' | 'end'
|
|
35
|
+
triggerClass?: string
|
|
21
36
|
class?: string
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
let {
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
let {
|
|
40
|
+
items,
|
|
41
|
+
onSelect,
|
|
42
|
+
open = $bindable(false),
|
|
43
|
+
children,
|
|
44
|
+
renderItem,
|
|
45
|
+
side = 'bottom',
|
|
46
|
+
align = 'end',
|
|
47
|
+
triggerClass,
|
|
48
|
+
class: className,
|
|
49
|
+
}: Props = $props()
|
|
27
50
|
|
|
28
51
|
function handleSelect(value: string) {
|
|
29
52
|
onSelect(value)
|
|
30
53
|
open = false
|
|
31
54
|
}
|
|
55
|
+
|
|
56
|
+
function isSeparator(entry: MenuEntry): entry is SeparatorEntry {
|
|
57
|
+
return 'type' in entry && entry.type === 'separator'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isGroup(entry: MenuEntry): entry is GroupEntry {
|
|
61
|
+
return 'type' in entry && entry.type === 'group'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isItem(entry: MenuEntry): entry is Item {
|
|
65
|
+
return !('type' in entry)
|
|
66
|
+
}
|
|
32
67
|
</script>
|
|
33
68
|
|
|
69
|
+
{#snippet itemContent(item: Item)}
|
|
70
|
+
{#if renderItem}
|
|
71
|
+
{@render renderItem(item)}
|
|
72
|
+
{:else}
|
|
73
|
+
{#if item.icon}
|
|
74
|
+
<div class="w-16 h-16 flex items-center justify-center shrink-0 [&_svg]:w-16 [&_svg]:h-16">
|
|
75
|
+
{@render item.icon()}
|
|
76
|
+
</div>
|
|
77
|
+
{/if}
|
|
78
|
+
<span class="flex-1">{item.label}</span>
|
|
79
|
+
{/if}
|
|
80
|
+
{/snippet}
|
|
81
|
+
|
|
82
|
+
{#snippet menuItem(item: Item)}
|
|
83
|
+
<DropdownMenu.Item
|
|
84
|
+
disabled={item.disabled}
|
|
85
|
+
class={cn(
|
|
86
|
+
'flex items-center gap-8 px-12 py-8 rounded-4 text-small cursor-default select-none outline-none data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none motion-reduce:transition-none',
|
|
87
|
+
item.variant === 'destructive'
|
|
88
|
+
? 'text-error hover:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none'
|
|
89
|
+
: 'text-dropdown-item hover:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none'
|
|
90
|
+
)}
|
|
91
|
+
onSelect={() => handleSelect(item.value)}
|
|
92
|
+
>
|
|
93
|
+
{@render itemContent(item)}
|
|
94
|
+
</DropdownMenu.Item>
|
|
95
|
+
{/snippet}
|
|
96
|
+
|
|
34
97
|
<DropdownMenu.Root bind:open>
|
|
35
98
|
<DropdownMenu.Trigger
|
|
36
|
-
class=
|
|
99
|
+
class={cn(
|
|
100
|
+
children && triggerClass
|
|
101
|
+
? triggerClass
|
|
102
|
+
: '[all:unset] cursor-default inline-flex items-center justify-center p-4 rounded-4 text-secondary transition-all duration-fast hover:bg-background hover:text-body data-[state=open]:bg-background data-[state=open]:text-body motion-reduce:transition-none',
|
|
103
|
+
!children && !triggerClass && ''
|
|
104
|
+
)}
|
|
37
105
|
>
|
|
38
106
|
{#if children}
|
|
39
107
|
{@render children()}
|
|
@@ -50,24 +118,23 @@
|
|
|
50
118
|
className
|
|
51
119
|
)}
|
|
52
120
|
>
|
|
53
|
-
{#each items as
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
</DropdownMenu.Item>
|
|
121
|
+
{#each items as entry, i (i)}
|
|
122
|
+
{#if isSeparator(entry)}
|
|
123
|
+
<DropdownMenu.Separator class="h-1 bg-muted mx-4 my-4" />
|
|
124
|
+
{:else if isGroup(entry)}
|
|
125
|
+
<DropdownMenu.Group>
|
|
126
|
+
{#if entry.heading}
|
|
127
|
+
<DropdownMenu.GroupHeading class="text-tiny-emphasis text-secondary px-12 py-4 uppercase select-none">
|
|
128
|
+
{entry.heading}
|
|
129
|
+
</DropdownMenu.GroupHeading>
|
|
130
|
+
{/if}
|
|
131
|
+
{#each entry.items as item (item.value)}
|
|
132
|
+
{@render menuItem(item)}
|
|
133
|
+
{/each}
|
|
134
|
+
</DropdownMenu.Group>
|
|
135
|
+
{:else if isItem(entry)}
|
|
136
|
+
{@render menuItem(entry)}
|
|
137
|
+
{/if}
|
|
71
138
|
{/each}
|
|
72
139
|
</DropdownMenu.Content>
|
|
73
140
|
</DropdownMenu.Portal>
|
|
@@ -6,14 +6,26 @@ type Item = {
|
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
variant?: 'default' | 'destructive';
|
|
8
8
|
};
|
|
9
|
-
type
|
|
9
|
+
type SeparatorEntry = {
|
|
10
|
+
type: 'separator';
|
|
11
|
+
};
|
|
12
|
+
type GroupEntry = {
|
|
13
|
+
type: 'group';
|
|
14
|
+
heading?: string;
|
|
10
15
|
items: Item[];
|
|
16
|
+
};
|
|
17
|
+
type MenuEntry = Item | SeparatorEntry | GroupEntry;
|
|
18
|
+
type Props = {
|
|
19
|
+
items: MenuEntry[];
|
|
11
20
|
onSelect: (value: string) => void;
|
|
21
|
+
open?: boolean;
|
|
12
22
|
children?: Snippet;
|
|
23
|
+
renderItem?: Snippet<[Item]>;
|
|
13
24
|
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
14
25
|
align?: 'start' | 'center' | 'end';
|
|
26
|
+
triggerClass?: string;
|
|
15
27
|
class?: string;
|
|
16
28
|
};
|
|
17
|
-
declare const IIDropdownMenu: import("svelte").Component<Props, {}, "">;
|
|
29
|
+
declare const IIDropdownMenu: import("svelte").Component<Props, {}, "open">;
|
|
18
30
|
type IIDropdownMenu = ReturnType<typeof IIDropdownMenu>;
|
|
19
31
|
export default IIDropdownMenu;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import IIDropdownMenu from './IIDropdownMenu.svelte'
|
|
3
|
+
import {IIButton} from '../IIButton'
|
|
4
|
+
import {IIIcon} from '../IIIcon'
|
|
5
|
+
|
|
6
|
+
function handleSelect(value: string) {
|
|
7
|
+
console.log('Selected:', value)
|
|
8
|
+
}
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div class="flex flex-col gap-32">
|
|
12
|
+
<!-- Separators -->
|
|
13
|
+
<section>
|
|
14
|
+
<h2 class="text-default-emphasis text-primary mb-8">With Separators</h2>
|
|
15
|
+
<p class="text-small text-secondary mb-12">Menu entries can include separator dividers.</p>
|
|
16
|
+
<IIDropdownMenu
|
|
17
|
+
items={[
|
|
18
|
+
{label: 'Edit', value: 'edit'},
|
|
19
|
+
{label: 'Duplicate', value: 'duplicate'},
|
|
20
|
+
{type: 'separator'},
|
|
21
|
+
{label: 'Archive', value: 'archive'},
|
|
22
|
+
{label: 'Delete', value: 'delete', variant: 'destructive'},
|
|
23
|
+
]}
|
|
24
|
+
onSelect={handleSelect}
|
|
25
|
+
/>
|
|
26
|
+
</section>
|
|
27
|
+
|
|
28
|
+
<!-- Groups -->
|
|
29
|
+
<section>
|
|
30
|
+
<h2 class="text-default-emphasis text-primary mb-8">With Groups</h2>
|
|
31
|
+
<p class="text-small text-secondary mb-12">Items organized into labeled groups with headings.</p>
|
|
32
|
+
<IIDropdownMenu
|
|
33
|
+
items={[
|
|
34
|
+
{
|
|
35
|
+
type: 'group',
|
|
36
|
+
heading: 'Actions',
|
|
37
|
+
items: [
|
|
38
|
+
{label: 'Edit', value: 'edit'},
|
|
39
|
+
{label: 'Duplicate', value: 'duplicate'},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{type: 'separator'},
|
|
43
|
+
{
|
|
44
|
+
type: 'group',
|
|
45
|
+
heading: 'Danger Zone',
|
|
46
|
+
items: [
|
|
47
|
+
{label: 'Archive', value: 'archive'},
|
|
48
|
+
{label: 'Delete', value: 'delete', variant: 'destructive'},
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
]}
|
|
52
|
+
onSelect={handleSelect}
|
|
53
|
+
/>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<!-- Custom Trigger with triggerClass -->
|
|
57
|
+
<section>
|
|
58
|
+
<h2 class="text-default-emphasis text-primary mb-8">Custom Trigger</h2>
|
|
59
|
+
<p class="text-small text-secondary mb-12">Using the children snippet with triggerClass for custom trigger styling.</p>
|
|
60
|
+
<IIDropdownMenu
|
|
61
|
+
items={[
|
|
62
|
+
{label: 'Profile', value: 'profile'},
|
|
63
|
+
{label: 'Settings', value: 'settings'},
|
|
64
|
+
{type: 'separator'},
|
|
65
|
+
{label: 'Sign Out', value: 'signout'},
|
|
66
|
+
]}
|
|
67
|
+
onSelect={handleSelect}
|
|
68
|
+
triggerClass="inline-flex items-center gap-4 px-12 py-5 rounded-10 border border-button-secondary text-small text-button-secondary hover:border-button-secondary-hover cursor-default"
|
|
69
|
+
>
|
|
70
|
+
{#snippet children()}
|
|
71
|
+
<IIIcon iconName="user" class="w-14 h-14" />
|
|
72
|
+
<span>Account</span>
|
|
73
|
+
<IIIcon iconName="caret-down" class="w-12 h-12" />
|
|
74
|
+
{/snippet}
|
|
75
|
+
</IIDropdownMenu>
|
|
76
|
+
</section>
|
|
77
|
+
|
|
78
|
+
<!-- Mixed Entries -->
|
|
79
|
+
<section>
|
|
80
|
+
<h2 class="text-default-emphasis text-primary mb-8">Mixed: Items + Separators + Groups</h2>
|
|
81
|
+
<IIDropdownMenu
|
|
82
|
+
items={[
|
|
83
|
+
{label: 'Quick Action', value: 'quick'},
|
|
84
|
+
{type: 'separator'},
|
|
85
|
+
{
|
|
86
|
+
type: 'group',
|
|
87
|
+
heading: 'File',
|
|
88
|
+
items: [
|
|
89
|
+
{label: 'New', value: 'new'},
|
|
90
|
+
{label: 'Open', value: 'open'},
|
|
91
|
+
{label: 'Save', value: 'save'},
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
{type: 'separator'},
|
|
95
|
+
{label: 'Exit', value: 'exit', variant: 'destructive'},
|
|
96
|
+
]}
|
|
97
|
+
onSelect={handleSelect}
|
|
98
|
+
align="start"
|
|
99
|
+
/>
|
|
100
|
+
</section>
|
|
101
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const IIDropdownMenuStories: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type IIDropdownMenuStories = InstanceType<typeof IIDropdownMenuStories>;
|
|
18
|
+
export default IIDropdownMenuStories;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type {Snippet} from 'svelte'
|
|
3
|
+
import {DropdownMenu} from 'bits-ui'
|
|
4
|
+
import {IIIcon} from '../IIIcon'
|
|
5
|
+
import {cn} from '../utils/cn'
|
|
6
|
+
|
|
7
|
+
type Item = {
|
|
8
|
+
label: string
|
|
9
|
+
value: string
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
items: Item[]
|
|
15
|
+
values: string[]
|
|
16
|
+
placeholder?: string
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
onValuesChange?: (values: string[]) => void
|
|
19
|
+
matchTriggerWidth?: boolean
|
|
20
|
+
maxDisplay?: number
|
|
21
|
+
renderItem?: Snippet<[item: Item, selected: boolean]>
|
|
22
|
+
class?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
items,
|
|
27
|
+
values = $bindable([]),
|
|
28
|
+
placeholder = 'Select...',
|
|
29
|
+
disabled = false,
|
|
30
|
+
onValuesChange,
|
|
31
|
+
matchTriggerWidth = false,
|
|
32
|
+
maxDisplay,
|
|
33
|
+
renderItem,
|
|
34
|
+
class: className,
|
|
35
|
+
}: Props = $props()
|
|
36
|
+
|
|
37
|
+
let open = $state(false)
|
|
38
|
+
let triggerEl = $state<HTMLElement | null>(null)
|
|
39
|
+
let triggerWidth = $state<number | undefined>(undefined)
|
|
40
|
+
|
|
41
|
+
const selectedItems = $derived(items.filter(i => values.includes(i.value)))
|
|
42
|
+
|
|
43
|
+
const displayedChips = $derived(
|
|
44
|
+
maxDisplay && selectedItems.length > maxDisplay ? selectedItems.slice(0, maxDisplay) : selectedItems
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const overflowCount = $derived(
|
|
48
|
+
maxDisplay && selectedItems.length > maxDisplay ? selectedItems.length - maxDisplay : 0
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
function toggleValue(itemValue: string) {
|
|
52
|
+
if (values.includes(itemValue)) {
|
|
53
|
+
values = values.filter(v => v !== itemValue)
|
|
54
|
+
} else {
|
|
55
|
+
values = [...values, itemValue]
|
|
56
|
+
}
|
|
57
|
+
onValuesChange?.(values)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function removeValue(itemValue: string, e: MouseEvent) {
|
|
61
|
+
e.stopPropagation()
|
|
62
|
+
values = values.filter(v => v !== itemValue)
|
|
63
|
+
onValuesChange?.(values)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
$effect(() => {
|
|
67
|
+
if (matchTriggerWidth && open && triggerEl) {
|
|
68
|
+
triggerWidth = triggerEl.offsetWidth
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<DropdownMenu.Root bind:open>
|
|
74
|
+
<DropdownMenu.Trigger
|
|
75
|
+
bind:ref={triggerEl}
|
|
76
|
+
{disabled}
|
|
77
|
+
class={cn(
|
|
78
|
+
'flex items-center gap-4 py-5 pl-12 pr-8 border rounded-10 bg-button-secondary cursor-default text-small text-button-secondary box-border appearance-none font-inherit outline-none transition-colors duration-base ease-in-out hover:text-button-secondary-hover hover:border-button-secondary-hover focus:border-accent focus:ring-3 focus:ring-primary disabled:opacity-50 disabled:cursor-not-allowed',
|
|
79
|
+
open ? 'border-button-secondary-hover' : 'border-button-secondary',
|
|
80
|
+
className
|
|
81
|
+
)}
|
|
82
|
+
>
|
|
83
|
+
<span class="flex-1 flex items-center gap-4 flex-wrap min-w-0">
|
|
84
|
+
{#if selectedItems.length === 0}
|
|
85
|
+
<span class="text-small text-input-placeholder">{placeholder}</span>
|
|
86
|
+
{:else}
|
|
87
|
+
{#each displayedChips as chip (chip.value)}
|
|
88
|
+
<span class="inline-flex items-center gap-2 px-6 py-1 rounded-6 bg-badge-grey text-badge-grey text-tiny">
|
|
89
|
+
{chip.label}
|
|
90
|
+
<button
|
|
91
|
+
type="button"
|
|
92
|
+
class="inline-flex items-center justify-center w-12 h-12 rounded-full hover:bg-gray-300 transition-colors duration-fast [&_svg]:w-8 [&_svg]:h-8"
|
|
93
|
+
onclick={(e) => removeValue(chip.value, e)}
|
|
94
|
+
>
|
|
95
|
+
<IIIcon iconName="x" />
|
|
96
|
+
</button>
|
|
97
|
+
</span>
|
|
98
|
+
{/each}
|
|
99
|
+
{#if overflowCount > 0}
|
|
100
|
+
<span class="text-tiny text-secondary">+{overflowCount} more</span>
|
|
101
|
+
{/if}
|
|
102
|
+
{/if}
|
|
103
|
+
</span>
|
|
104
|
+
<IIIcon iconName="caret-down" class="w-14 h-14 shrink-0" />
|
|
105
|
+
</DropdownMenu.Trigger>
|
|
106
|
+
<DropdownMenu.Portal>
|
|
107
|
+
<DropdownMenu.Content
|
|
108
|
+
class="min-w-100 bg-dropdown-bg border border-dropdown-border rounded-10 shadow-dropdown p-4 z-16 outline-none max-h-300 overflow-y-auto"
|
|
109
|
+
sideOffset={2}
|
|
110
|
+
side="bottom"
|
|
111
|
+
align="start"
|
|
112
|
+
>
|
|
113
|
+
{#snippet child({props, wrapperProps})}
|
|
114
|
+
<div {...wrapperProps} style:min-width={matchTriggerWidth && triggerWidth ? `${triggerWidth}px` : undefined}>
|
|
115
|
+
<div {...props}>
|
|
116
|
+
{#each items as item (item.value)}
|
|
117
|
+
<DropdownMenu.CheckboxItem
|
|
118
|
+
checked={values.includes(item.value)}
|
|
119
|
+
onCheckedChange={() => toggleValue(item.value)}
|
|
120
|
+
disabled={item.disabled}
|
|
121
|
+
class={cn(
|
|
122
|
+
'flex items-center gap-8 px-12 py-6 rounded-6 text-small text-dropdown-item cursor-default outline-none data-[highlighted]:bg-dropdown-item-hover data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed',
|
|
123
|
+
values.includes(item.value) && 'text-dropdown-item-selected'
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{#if renderItem}
|
|
127
|
+
{@render renderItem(item, values.includes(item.value))}
|
|
128
|
+
{:else}
|
|
129
|
+
<span class="flex-1">{item.label}</span>
|
|
130
|
+
{#if values.includes(item.value)}
|
|
131
|
+
<IIIcon iconName="check" class="w-14 h-14 text-accent shrink-0" />
|
|
132
|
+
{/if}
|
|
133
|
+
{/if}
|
|
134
|
+
</DropdownMenu.CheckboxItem>
|
|
135
|
+
{/each}
|
|
136
|
+
</div>
|
|
137
|
+
</div>
|
|
138
|
+
{/snippet}
|
|
139
|
+
</DropdownMenu.Content>
|
|
140
|
+
</DropdownMenu.Portal>
|
|
141
|
+
</DropdownMenu.Root>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
type Item = {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type Props = {
|
|
8
|
+
items: Item[];
|
|
9
|
+
values: string[];
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
onValuesChange?: (values: string[]) => void;
|
|
13
|
+
matchTriggerWidth?: boolean;
|
|
14
|
+
maxDisplay?: number;
|
|
15
|
+
renderItem?: Snippet<[item: Item, selected: boolean]>;
|
|
16
|
+
class?: string;
|
|
17
|
+
};
|
|
18
|
+
declare const IIMultiSelect: import("svelte").Component<Props, {}, "values">;
|
|
19
|
+
type IIMultiSelect = ReturnType<typeof IIMultiSelect>;
|
|
20
|
+
export default IIMultiSelect;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import IIMultiSelect from './IIMultiSelect.svelte'
|
|
3
|
+
|
|
4
|
+
let basicValues = $state<string[]>([])
|
|
5
|
+
let presetValues = $state(['react', 'svelte'])
|
|
6
|
+
let maxValues = $state(['js', 'ts', 'py', 'go', 'rs'])
|
|
7
|
+
|
|
8
|
+
const tagItems = [
|
|
9
|
+
{label: 'React', value: 'react'},
|
|
10
|
+
{label: 'Svelte', value: 'svelte'},
|
|
11
|
+
{label: 'Vue', value: 'vue'},
|
|
12
|
+
{label: 'Angular', value: 'angular'},
|
|
13
|
+
{label: 'Solid', value: 'solid'},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const languageItems = [
|
|
17
|
+
{label: 'JavaScript', value: 'js'},
|
|
18
|
+
{label: 'TypeScript', value: 'ts'},
|
|
19
|
+
{label: 'Python', value: 'py'},
|
|
20
|
+
{label: 'Go', value: 'go'},
|
|
21
|
+
{label: 'Rust', value: 'rs'},
|
|
22
|
+
{label: 'Java', value: 'java'},
|
|
23
|
+
{label: 'C#', value: 'csharp'},
|
|
24
|
+
{label: 'Ruby', value: 'ruby', disabled: true},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
const roleItems = [
|
|
28
|
+
{label: 'Admin', value: 'admin'},
|
|
29
|
+
{label: 'Editor', value: 'editor'},
|
|
30
|
+
{label: 'Viewer', value: 'viewer'},
|
|
31
|
+
{label: 'Guest', value: 'guest', disabled: true},
|
|
32
|
+
]
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="flex flex-col gap-32">
|
|
36
|
+
<!-- Basic -->
|
|
37
|
+
<section>
|
|
38
|
+
<h2 class="text-default-emphasis text-primary mb-8">Basic Multi-Select</h2>
|
|
39
|
+
<p class="text-small text-secondary mb-12">Select multiple items. Chips appear in the trigger with X to remove.</p>
|
|
40
|
+
<div class="w-280">
|
|
41
|
+
<IIMultiSelect items={tagItems} bind:values={basicValues} placeholder="Select frameworks..." />
|
|
42
|
+
</div>
|
|
43
|
+
<p class="text-small text-secondary mt-8">Selected: {basicValues.length ? basicValues.join(', ') : 'None'}</p>
|
|
44
|
+
</section>
|
|
45
|
+
|
|
46
|
+
<!-- Preselected -->
|
|
47
|
+
<section>
|
|
48
|
+
<h2 class="text-default-emphasis text-primary mb-8">With Preset Values</h2>
|
|
49
|
+
<div class="w-280">
|
|
50
|
+
<IIMultiSelect items={tagItems} bind:values={presetValues} placeholder="Select frameworks..." />
|
|
51
|
+
</div>
|
|
52
|
+
</section>
|
|
53
|
+
|
|
54
|
+
<!-- Max Display -->
|
|
55
|
+
<section>
|
|
56
|
+
<h2 class="text-default-emphasis text-primary mb-8">Max Display (Overflow)</h2>
|
|
57
|
+
<p class="text-small text-secondary mb-12">When maxDisplay is set, excess chips collapse to "+N more".</p>
|
|
58
|
+
<div class="w-280">
|
|
59
|
+
<IIMultiSelect items={languageItems} bind:values={maxValues} maxDisplay={3} placeholder="Select languages..." />
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<!-- Match Trigger Width -->
|
|
64
|
+
<section>
|
|
65
|
+
<h2 class="text-default-emphasis text-primary mb-8">Match Trigger Width</h2>
|
|
66
|
+
<div class="w-320">
|
|
67
|
+
<IIMultiSelect items={roleItems} values={[]} matchTriggerWidth placeholder="Assign roles..." />
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<!-- Disabled -->
|
|
72
|
+
<section>
|
|
73
|
+
<h2 class="text-default-emphasis text-primary mb-8">Disabled</h2>
|
|
74
|
+
<div class="w-280">
|
|
75
|
+
<IIMultiSelect items={tagItems} values={['react', 'svelte']} disabled placeholder="Select frameworks..." />
|
|
76
|
+
</div>
|
|
77
|
+
</section>
|
|
78
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as IIMultiSelect } from './IIMultiSelect.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as IIMultiSelect } from './IIMultiSelect.svelte';
|