@insymetri/styleguide 0.1.79 → 0.1.81
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/IIDropdownInput/IIDropdownInput.svelte +52 -26
- package/dist/IIDropdownInput/IIDropdownInput.svelte.d.ts +21 -0
- package/dist/IIDropdownInput/IIDropdownInputStories.svelte +23 -0
- package/dist/{IIDropdownMenu/IIDropdownMenu.svelte → IIMenu/IIMenu.svelte} +119 -21
- package/dist/IIMenu/IIMenu.svelte.d.ts +41 -0
- package/dist/{IIDropdownMenu/IIDropdownMenuStories.svelte → IIMenu/IIMenuStories.svelte} +48 -7
- package/dist/IIMenu/IIMenuStories.svelte.d.ts +3 -0
- package/dist/{IIDropdownMenu/IIDropdownMenuSub.svelte.d.ts → IIMenu/IIMenuSub.svelte.d.ts} +3 -3
- package/dist/{IIDropdownMenu/IIDropdownMenuSubSimple.svelte.d.ts → IIMenu/IIMenuSubSimple.svelte.d.ts} +3 -3
- package/dist/IIMenu/IIMenuTriggerProof.svelte +41 -0
- package/dist/IIMenu/IIMenuTriggerProof.svelte.d.ts +6 -0
- package/dist/IIMenu/index.d.ts +1 -0
- package/dist/IIMenu/index.js +1 -0
- package/dist/IIModal/IIModal.svelte +5 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/style/base.css +23 -0
- package/dist/utils/menu/menu-styles.d.ts +3 -3
- package/dist/utils/menu/menu-styles.js +6 -3
- package/package.json +1 -1
- package/dist/IIDropdownMenu/IIDropdownMenu.svelte.d.ts +0 -19
- package/dist/IIDropdownMenu/IIDropdownMenuStories.svelte.d.ts +0 -18
- package/dist/IIDropdownMenu/index.d.ts +0 -1
- package/dist/IIDropdownMenu/index.js +0 -1
- /package/dist/{IIDropdownMenu/IIDropdownMenuSub.svelte → IIMenu/IIMenuSub.svelte} +0 -0
- /package/dist/{IIDropdownMenu/IIDropdownMenuSubSimple.svelte → IIMenu/IIMenuSubSimple.svelte} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type {Snippet} from 'svelte'
|
|
3
|
+
import {createAttachmentKey} from 'svelte/attachments'
|
|
3
4
|
import {fly} from 'svelte/transition'
|
|
4
5
|
import {IIIcon} from '../IIIcon'
|
|
5
6
|
import {cn} from '../utils/cn'
|
|
@@ -18,15 +19,34 @@
|
|
|
18
19
|
disabled?: boolean
|
|
19
20
|
error?: boolean
|
|
20
21
|
errorMessage?: string
|
|
22
|
+
/** Fired when the selected value changes. */
|
|
23
|
+
onValueChange?: (value: string) => void
|
|
24
|
+
/** @deprecated Use `onValueChange`. */
|
|
21
25
|
onSelect?: (value: string) => void
|
|
22
26
|
onblur?: () => void
|
|
23
27
|
matchTriggerWidth?: boolean
|
|
24
28
|
renderItem?: Snippet<[item: MenuItem, selected: boolean]>
|
|
25
29
|
renderSelected?: Snippet<[item: MenuItem]>
|
|
30
|
+
/**
|
|
31
|
+
* Replace the default combobox field with a custom trigger. Spread `props`
|
|
32
|
+
* onto your trigger element/component — it carries the ref, click/keydown
|
|
33
|
+
* handlers, and combobox aria attributes. `open`, `selectedItem`, and
|
|
34
|
+
* `selectedLabel` are provided for rendering. When set, `class`, `label`,
|
|
35
|
+
* and `renderSelected` no longer affect the (now custom) trigger.
|
|
36
|
+
*/
|
|
37
|
+
trigger?: Snippet<[{
|
|
38
|
+
props: Record<string, unknown>
|
|
39
|
+
open: boolean
|
|
40
|
+
selectedItem: MenuItem | undefined
|
|
41
|
+
selectedLabel: string
|
|
42
|
+
}]>
|
|
26
43
|
searchable?: boolean
|
|
27
44
|
searchPlaceholder?: string
|
|
28
45
|
autofocus?: boolean
|
|
46
|
+
/** Classes for the trigger (root) element. */
|
|
29
47
|
class?: string
|
|
48
|
+
/** Classes for the dropdown panel (popover surface). */
|
|
49
|
+
contentClass?: string
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
let {
|
|
@@ -37,15 +57,18 @@
|
|
|
37
57
|
disabled = false,
|
|
38
58
|
error = false,
|
|
39
59
|
errorMessage,
|
|
60
|
+
onValueChange,
|
|
40
61
|
onSelect,
|
|
41
62
|
onblur,
|
|
42
63
|
matchTriggerWidth = false,
|
|
43
64
|
renderItem,
|
|
44
65
|
renderSelected,
|
|
66
|
+
trigger,
|
|
45
67
|
searchable = false,
|
|
46
68
|
searchPlaceholder = 'Search...',
|
|
47
69
|
autofocus = false,
|
|
48
70
|
class: className,
|
|
71
|
+
contentClass,
|
|
49
72
|
}: Props = $props()
|
|
50
73
|
|
|
51
74
|
const showError = $derived(error || !!errorMessage)
|
|
@@ -79,6 +102,25 @@
|
|
|
79
102
|
const selectedItem = $derived(items.find(i => i.value === value))
|
|
80
103
|
const selectedLabel = $derived(selectedItem?.label ?? placeholder)
|
|
81
104
|
|
|
105
|
+
// Props a custom `trigger` snippet spreads onto its element. The attachment
|
|
106
|
+
// key and function are stable, so recomputing this derived object when `open`
|
|
107
|
+
// changes (for aria-expanded) does not re-run the ref attachment.
|
|
108
|
+
const attachTrigger = (node: Element) => {
|
|
109
|
+
triggerEl = node as HTMLElement
|
|
110
|
+
return () => { if (triggerEl === node) triggerEl = null }
|
|
111
|
+
}
|
|
112
|
+
const triggerAttachmentKey = createAttachmentKey()
|
|
113
|
+
const triggerProps = $derived({
|
|
114
|
+
role: 'combobox',
|
|
115
|
+
'aria-haspopup': 'listbox',
|
|
116
|
+
'aria-expanded': open,
|
|
117
|
+
type: 'button',
|
|
118
|
+
disabled: disabled || undefined,
|
|
119
|
+
onclick: toggle,
|
|
120
|
+
onkeydown: handleTriggerKeydown,
|
|
121
|
+
[triggerAttachmentKey]: attachTrigger,
|
|
122
|
+
})
|
|
123
|
+
|
|
82
124
|
function scrollHighlightedIntoView() {
|
|
83
125
|
requestAnimationFrame(() => {
|
|
84
126
|
const el = floatingEl?.querySelector<HTMLElement>(`[role="option"][data-index="${highlightedIndex}"]`)
|
|
@@ -118,6 +160,7 @@
|
|
|
118
160
|
if (item.disabled) return
|
|
119
161
|
value = item.value
|
|
120
162
|
open = false
|
|
163
|
+
onValueChange?.(item.value)
|
|
121
164
|
onSelect?.(item.value)
|
|
122
165
|
onblur?.()
|
|
123
166
|
triggerEl?.focus()
|
|
@@ -145,13 +188,15 @@
|
|
|
145
188
|
clearTimeout(typeaheadTimer)
|
|
146
189
|
typeaheadTimer = setTimeout(() => { typeaheadBuffer = '' }, 500)
|
|
147
190
|
|
|
191
|
+
// Substring match (so "tu" finds "Alan Turing"), not just prefix.
|
|
148
192
|
const matchIndex = items.findIndex(
|
|
149
|
-
i => !i.disabled && i.label.toLowerCase().
|
|
193
|
+
i => !i.disabled && i.label.toLowerCase().includes(typeaheadBuffer)
|
|
150
194
|
)
|
|
151
195
|
if (matchIndex >= 0) {
|
|
152
196
|
const match = items[matchIndex]
|
|
153
197
|
if (!wasOpen) {
|
|
154
198
|
value = match.value
|
|
199
|
+
onValueChange?.(match.value)
|
|
155
200
|
onSelect?.(match.value)
|
|
156
201
|
} else {
|
|
157
202
|
highlightedIndex = matchIndex
|
|
@@ -240,6 +285,9 @@
|
|
|
240
285
|
{#if label}
|
|
241
286
|
<span class="text-small-emphasis text-secondary mb-4">{label}</span>
|
|
242
287
|
{/if}
|
|
288
|
+
{#if trigger}
|
|
289
|
+
{@render trigger({props: triggerProps, open, selectedItem, selectedLabel})}
|
|
290
|
+
{:else}
|
|
243
291
|
<button
|
|
244
292
|
bind:this={triggerEl}
|
|
245
293
|
type="button"
|
|
@@ -266,6 +314,7 @@
|
|
|
266
314
|
{/if}
|
|
267
315
|
<IIIcon iconName="caret-down" />
|
|
268
316
|
</button>
|
|
317
|
+
{/if}
|
|
269
318
|
|
|
270
319
|
{#if open}
|
|
271
320
|
<div use:portal use:clickOutside={{onClose: close, ignore: () => triggerEl}}>
|
|
@@ -273,7 +322,7 @@
|
|
|
273
322
|
bind:this={floatingEl}
|
|
274
323
|
role="listbox"
|
|
275
324
|
data-menu-content
|
|
276
|
-
class=
|
|
325
|
+
class={cn('flex flex-col min-w-100 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-dropdown p-4 z-16 pointer-events-auto outline-none overflow-hidden', contentClass)}
|
|
277
326
|
transition:fly={{y: -4, duration: 150}}
|
|
278
327
|
>
|
|
279
328
|
{#if searchable}
|
|
@@ -284,7 +333,7 @@
|
|
|
284
333
|
onkeydown={(e) => search.handleKeydown(e, '[data-menu-content]')}
|
|
285
334
|
/>
|
|
286
335
|
{/if}
|
|
287
|
-
<div class="
|
|
336
|
+
<div class="ii-menu-scroll min-h-0 overflow-y-auto -mr-2">
|
|
288
337
|
{#each (searchable ? search.filteredItems as MenuItem[] : items) as item, listIndex (item.value)}
|
|
289
338
|
{@const index = searchable ? search.getItemIndex(item) : listIndex}
|
|
290
339
|
{@const isHighlighted = searchable
|
|
@@ -346,26 +395,3 @@
|
|
|
346
395
|
<span class="text-tiny text-error mt-4" data-field-error>{errorMessage}</span>
|
|
347
396
|
{/if}
|
|
348
397
|
</div>
|
|
349
|
-
|
|
350
|
-
<style>
|
|
351
|
-
/* Opt back into ::-webkit-scrollbar styling so we can set a custom (narrow)
|
|
352
|
-
thumb width: the global standard scrollbar-width/color (base.css) otherwise
|
|
353
|
-
disables the WebKit pseudo-elements in Chromium. */
|
|
354
|
-
.dd-scroll {
|
|
355
|
-
scrollbar-width: auto;
|
|
356
|
-
scrollbar-color: auto;
|
|
357
|
-
}
|
|
358
|
-
.dd-scroll::-webkit-scrollbar {
|
|
359
|
-
width: 4px;
|
|
360
|
-
}
|
|
361
|
-
.dd-scroll::-webkit-scrollbar-track {
|
|
362
|
-
background: transparent;
|
|
363
|
-
}
|
|
364
|
-
.dd-scroll::-webkit-scrollbar-thumb {
|
|
365
|
-
background-color: var(--ii-gray-300);
|
|
366
|
-
border-radius: 9999px;
|
|
367
|
-
}
|
|
368
|
-
.dd-scroll::-webkit-scrollbar-thumb:hover {
|
|
369
|
-
background-color: var(--ii-gray-400);
|
|
370
|
-
}
|
|
371
|
-
</style>
|
|
@@ -8,15 +8,36 @@ type Props = {
|
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
error?: boolean;
|
|
10
10
|
errorMessage?: string;
|
|
11
|
+
/** Fired when the selected value changes. */
|
|
12
|
+
onValueChange?: (value: string) => void;
|
|
13
|
+
/** @deprecated Use `onValueChange`. */
|
|
11
14
|
onSelect?: (value: string) => void;
|
|
12
15
|
onblur?: () => void;
|
|
13
16
|
matchTriggerWidth?: boolean;
|
|
14
17
|
renderItem?: Snippet<[item: MenuItem, selected: boolean]>;
|
|
15
18
|
renderSelected?: Snippet<[item: MenuItem]>;
|
|
19
|
+
/**
|
|
20
|
+
* Replace the default combobox field with a custom trigger. Spread `props`
|
|
21
|
+
* onto your trigger element/component — it carries the ref, click/keydown
|
|
22
|
+
* handlers, and combobox aria attributes. `open`, `selectedItem`, and
|
|
23
|
+
* `selectedLabel` are provided for rendering. When set, `class`, `label`,
|
|
24
|
+
* and `renderSelected` no longer affect the (now custom) trigger.
|
|
25
|
+
*/
|
|
26
|
+
trigger?: Snippet<[
|
|
27
|
+
{
|
|
28
|
+
props: Record<string, unknown>;
|
|
29
|
+
open: boolean;
|
|
30
|
+
selectedItem: MenuItem | undefined;
|
|
31
|
+
selectedLabel: string;
|
|
32
|
+
}
|
|
33
|
+
]>;
|
|
16
34
|
searchable?: boolean;
|
|
17
35
|
searchPlaceholder?: string;
|
|
18
36
|
autofocus?: boolean;
|
|
37
|
+
/** Classes for the trigger (root) element. */
|
|
19
38
|
class?: string;
|
|
39
|
+
/** Classes for the dropdown panel (popover surface). */
|
|
40
|
+
contentClass?: string;
|
|
20
41
|
};
|
|
21
42
|
declare const IIDropdownInput: import("svelte").Component<Props, {}, "value">;
|
|
22
43
|
type IIDropdownInput = ReturnType<typeof IIDropdownInput>;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import IIDropdownInput from './IIDropdownInput.svelte'
|
|
3
3
|
import {IIIcon} from '../IIIcon'
|
|
4
|
+
import {cn} from '../utils/cn'
|
|
4
5
|
import type {IconName} from '../icons'
|
|
5
6
|
|
|
6
7
|
let basicValue = $state<string | undefined>(undefined)
|
|
7
8
|
let disabledItemValue = $state<string | undefined>(undefined)
|
|
9
|
+
let customTriggerValue = $state<string | undefined>(undefined)
|
|
8
10
|
|
|
9
11
|
const statusItems = [
|
|
10
12
|
{label: 'Active', value: 'active'},
|
|
@@ -85,4 +87,25 @@
|
|
|
85
87
|
</IIDropdownInput>
|
|
86
88
|
</div>
|
|
87
89
|
</section>
|
|
90
|
+
|
|
91
|
+
<!-- Custom Trigger -->
|
|
92
|
+
<section>
|
|
93
|
+
<h2 class="text-default-emphasis text-primary mb-8">Custom Trigger</h2>
|
|
94
|
+
<p class="text-small text-secondary mb-12">Use the <code>trigger</code> snippet to replace the default field with any element. Spread <code>props</code> to wire up the ref, click/keyboard handling, and combobox aria attributes.</p>
|
|
95
|
+
<IIDropdownInput items={statusItems} bind:value={customTriggerValue}>
|
|
96
|
+
{#snippet trigger({props, open, selectedItem, selectedLabel})}
|
|
97
|
+
<button
|
|
98
|
+
{...props}
|
|
99
|
+
class={cn(
|
|
100
|
+
'inline-flex items-center gap-6 py-6 px-12 rounded-control border border-button-secondary bg-input-bg text-small text-button-secondary cursor-default outline-none transition-colors hover:border-button-secondary-hover focus-visible:ring-3 focus-visible:ring-primary',
|
|
101
|
+
open && 'border-button-secondary-hover'
|
|
102
|
+
)}
|
|
103
|
+
>
|
|
104
|
+
<IIIcon iconName="funnel" class="w-14 h-14" />
|
|
105
|
+
<span>{selectedItem ? selectedLabel : 'Filter'}</span>
|
|
106
|
+
<IIIcon iconName="caret-down" class="w-12 h-12" />
|
|
107
|
+
</button>
|
|
108
|
+
{/snippet}
|
|
109
|
+
</IIDropdownInput>
|
|
110
|
+
</section>
|
|
88
111
|
</div>
|
|
@@ -1,30 +1,50 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type {Snippet} from 'svelte'
|
|
3
|
+
import {createAttachmentKey} from 'svelte/attachments'
|
|
3
4
|
import {fly} from 'svelte/transition'
|
|
4
5
|
import {cn} from '../utils/cn'
|
|
5
6
|
import {IIIcon} from '../IIIcon'
|
|
6
|
-
import {createMenuSearch, isItem, isGroup, isSub, isSeparator, menuStyles, menuItemClass, type MenuItem, type MenuEntry, type SubEntry} from '../utils/menu'
|
|
7
|
+
import {createMenuSearch, getSelectableItems, isItem, isGroup, isSub, isSeparator, menuStyles, menuItemClass, type MenuItem, type MenuEntry, type SubEntry} from '../utils/menu'
|
|
7
8
|
import {useFloating, portal, clickOutside} from '../utils/menu'
|
|
8
9
|
import MenuItemContent from '../utils/menu/MenuItemContent.svelte'
|
|
9
10
|
import MenuSearchInput from '../utils/menu/MenuSearchInput.svelte'
|
|
10
11
|
import MenuNoResults from '../utils/menu/MenuNoResults.svelte'
|
|
11
12
|
import MenuItemTooltip from '../utils/menu/MenuItemTooltip.svelte'
|
|
12
|
-
import
|
|
13
|
-
import
|
|
13
|
+
import IIMenuSub from './IIMenuSub.svelte'
|
|
14
|
+
import IIMenuSubSimple from './IIMenuSubSimple.svelte'
|
|
14
15
|
|
|
15
16
|
type Props = {
|
|
16
17
|
items: MenuEntry[]
|
|
17
18
|
onSelect: (value: string) => void
|
|
18
19
|
open?: boolean
|
|
19
20
|
children?: Snippet
|
|
21
|
+
/**
|
|
22
|
+
* Replace the default trigger button with a custom element. Spread `props`
|
|
23
|
+
* onto a single focusable element — it carries the ref, click/keydown
|
|
24
|
+
* handlers, and menu aria attributes. Unlike `children` (which is wrapped in
|
|
25
|
+
* an extra button), this makes your element THE trigger: one tab stop, and
|
|
26
|
+
* keyboard open/navigation works.
|
|
27
|
+
*/
|
|
28
|
+
trigger?: Snippet<[{props: Record<string, unknown>; open: boolean}]>
|
|
29
|
+
/**
|
|
30
|
+
* Enable select-style typeahead (non-searchable menus). Typing on the closed
|
|
31
|
+
* trigger picks the closest label match and fires `onSelect` (like
|
|
32
|
+
* `IIDropdownInput`); typing while open moves focus to the match. Off by
|
|
33
|
+
* default so action menus don't fire on a keypress.
|
|
34
|
+
*/
|
|
35
|
+
typeahead?: boolean
|
|
20
36
|
renderItem?: Snippet<[MenuItem]>
|
|
21
37
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
22
38
|
align?: 'start' | 'center' | 'end'
|
|
23
39
|
collisionPadding?: number
|
|
40
|
+
/** @deprecated Use `class` — it now targets the trigger. */
|
|
24
41
|
triggerClass?: string
|
|
25
42
|
searchable?: boolean
|
|
26
43
|
searchPlaceholder?: string
|
|
44
|
+
/** Classes for the trigger (root) element. */
|
|
27
45
|
class?: string
|
|
46
|
+
/** Classes for the menu panel (popover surface). */
|
|
47
|
+
contentClass?: string
|
|
28
48
|
}
|
|
29
49
|
|
|
30
50
|
let {
|
|
@@ -32,6 +52,8 @@
|
|
|
32
52
|
onSelect,
|
|
33
53
|
open = $bindable(false),
|
|
34
54
|
children,
|
|
55
|
+
trigger,
|
|
56
|
+
typeahead = false,
|
|
35
57
|
renderItem,
|
|
36
58
|
side = 'bottom',
|
|
37
59
|
align = 'end',
|
|
@@ -40,6 +62,7 @@
|
|
|
40
62
|
searchable = false,
|
|
41
63
|
searchPlaceholder = 'Search...',
|
|
42
64
|
class: className,
|
|
65
|
+
contentClass,
|
|
43
66
|
}: Props = $props()
|
|
44
67
|
|
|
45
68
|
let triggerEl = $state<HTMLElement | null>(null)
|
|
@@ -64,6 +87,7 @@
|
|
|
64
87
|
placement: placement as any,
|
|
65
88
|
offset: 4,
|
|
66
89
|
shift: {padding: collisionPadding},
|
|
90
|
+
constrainHeight: true,
|
|
67
91
|
})
|
|
68
92
|
|
|
69
93
|
const search = createMenuSearch({
|
|
@@ -108,6 +132,69 @@
|
|
|
108
132
|
close()
|
|
109
133
|
}
|
|
110
134
|
|
|
135
|
+
// Select-style typeahead (opt-in via `typeahead`, non-searchable only). Buffer
|
|
136
|
+
// keystrokes for 500ms and match on label substring (so "tu" finds "Alan
|
|
137
|
+
// Turing"). Closed → fire onSelect (set the value, like IIDropdownInput); open
|
|
138
|
+
// → move focus to the match.
|
|
139
|
+
let typeaheadBuffer = ''
|
|
140
|
+
let typeaheadTimer: ReturnType<typeof setTimeout> | undefined
|
|
141
|
+
|
|
142
|
+
function handleTypeahead(char: string) {
|
|
143
|
+
if (!typeahead || searchable) return
|
|
144
|
+
const wasOpen = open
|
|
145
|
+
typeaheadBuffer += char.toLowerCase()
|
|
146
|
+
clearTimeout(typeaheadTimer)
|
|
147
|
+
typeaheadTimer = setTimeout(() => {
|
|
148
|
+
typeaheadBuffer = ''
|
|
149
|
+
}, 500)
|
|
150
|
+
|
|
151
|
+
const match = getSelectableItems(items).find(
|
|
152
|
+
i => !i.disabled && i.label.toLowerCase().includes(typeaheadBuffer),
|
|
153
|
+
)
|
|
154
|
+
if (!match) return
|
|
155
|
+
if (wasOpen) {
|
|
156
|
+
floatingEl?.querySelector<HTMLElement>(`[role="menuitem"][data-value="${CSS.escape(match.value)}"]`)?.focus()
|
|
157
|
+
} else {
|
|
158
|
+
onSelect(match.value)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Open the menu from the trigger via keyboard. Enter/Space fire a native click
|
|
163
|
+
// on a <button> trigger (→ toggle); Arrow keys open it too. Once open, focus
|
|
164
|
+
// moves to the first item (effect above) and handleMenuKeydown takes over.
|
|
165
|
+
function handleTriggerKeydown(e: KeyboardEvent) {
|
|
166
|
+
if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && !open) {
|
|
167
|
+
e.preventDefault()
|
|
168
|
+
openSubIndex = null
|
|
169
|
+
open = true
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
if (typeahead && !open && e.key.length === 1 && e.key !== ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
173
|
+
e.preventDefault()
|
|
174
|
+
handleTypeahead(e.key)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Props a custom `trigger` snippet spreads onto its single element — carries the
|
|
179
|
+
// ref (via attachment), click/keydown, and menu aria. Stable attachment key so
|
|
180
|
+
// recomputing on `open` change doesn't re-run the ref attachment.
|
|
181
|
+
const attachTrigger = (node: Element) => {
|
|
182
|
+
triggerEl = node as HTMLElement
|
|
183
|
+
return () => {
|
|
184
|
+
if (triggerEl === node) triggerEl = null
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const triggerAttachmentKey = createAttachmentKey()
|
|
188
|
+
const triggerProps = $derived({
|
|
189
|
+
type: 'button',
|
|
190
|
+
'aria-haspopup': 'menu',
|
|
191
|
+
'aria-expanded': open,
|
|
192
|
+
'data-state': open ? 'open' : 'closed',
|
|
193
|
+
onclick: toggle,
|
|
194
|
+
onkeydown: handleTriggerKeydown,
|
|
195
|
+
[triggerAttachmentKey]: attachTrigger,
|
|
196
|
+
})
|
|
197
|
+
|
|
111
198
|
function getMenuItems(): HTMLElement[] {
|
|
112
199
|
if (!floatingEl) return []
|
|
113
200
|
return Array.from(floatingEl.querySelectorAll<HTMLElement>(':scope > [role="menuitem"]:not([data-disabled]), :scope > [role="group"] > [role="menuitem"]:not([data-disabled])'))
|
|
@@ -173,6 +260,11 @@
|
|
|
173
260
|
e.preventDefault()
|
|
174
261
|
close()
|
|
175
262
|
break
|
|
263
|
+
default:
|
|
264
|
+
if (typeahead && e.key.length === 1 && e.key !== ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
265
|
+
e.preventDefault()
|
|
266
|
+
handleTypeahead(e.key)
|
|
267
|
+
}
|
|
176
268
|
}
|
|
177
269
|
}
|
|
178
270
|
|
|
@@ -207,6 +299,7 @@
|
|
|
207
299
|
<div
|
|
208
300
|
role="menuitem"
|
|
209
301
|
tabindex="-1"
|
|
302
|
+
data-value={item.value}
|
|
210
303
|
data-disabled={item.disabled ? '' : undefined}
|
|
211
304
|
class={menuItemClass({variant: item.variant, searchable, isHighlighted: !item.disabled && index === search.highlightedIndex})}
|
|
212
305
|
data-search-item={searchable ? '' : undefined}
|
|
@@ -270,7 +363,7 @@
|
|
|
270
363
|
</div>
|
|
271
364
|
{#if isOpen}
|
|
272
365
|
{#if entry.searchable}
|
|
273
|
-
<
|
|
366
|
+
<IIMenuSub
|
|
274
367
|
items={entry.items}
|
|
275
368
|
onSelect={handleSelect}
|
|
276
369
|
onClose={() => {
|
|
@@ -282,7 +375,7 @@
|
|
|
282
375
|
renderItemContent={itemContent}
|
|
283
376
|
/>
|
|
284
377
|
{:else}
|
|
285
|
-
<
|
|
378
|
+
<IIMenuSubSimple
|
|
286
379
|
items={entry.items}
|
|
287
380
|
onSelect={handleSelect}
|
|
288
381
|
onClose={() => {
|
|
@@ -325,21 +418,26 @@
|
|
|
325
418
|
{/each}
|
|
326
419
|
{/snippet}
|
|
327
420
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
{
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
421
|
+
{#if trigger}
|
|
422
|
+
{@render trigger({props: triggerProps, open})}
|
|
423
|
+
{:else}
|
|
424
|
+
<button
|
|
425
|
+
bind:this={triggerEl}
|
|
426
|
+
type="button"
|
|
427
|
+
aria-haspopup="menu"
|
|
428
|
+
aria-expanded={open}
|
|
429
|
+
data-state={open ? 'open' : 'closed'}
|
|
430
|
+
class={cn(children ? '[all:unset] cursor-default' : defaultTriggerClass, triggerClass, className)}
|
|
431
|
+
onclick={toggle}
|
|
432
|
+
onkeydown={handleTriggerKeydown}
|
|
433
|
+
>
|
|
434
|
+
{#if children}
|
|
435
|
+
{@render children()}
|
|
436
|
+
{:else}
|
|
437
|
+
<IIIcon iconName="dots-three-vertical" />
|
|
438
|
+
{/if}
|
|
439
|
+
</button>
|
|
440
|
+
{/if}
|
|
343
441
|
|
|
344
442
|
{#if open}
|
|
345
443
|
<div use:portal use:clickOutside={{onClose: close, ignore: () => triggerEl}}>
|
|
@@ -347,7 +445,7 @@
|
|
|
347
445
|
bind:this={floatingEl}
|
|
348
446
|
role="menu"
|
|
349
447
|
data-menu-content
|
|
350
|
-
class={cn(menuStyles.content,
|
|
448
|
+
class={cn(menuStyles.content, contentClass)}
|
|
351
449
|
onkeydown={handleMenuKeydown}
|
|
352
450
|
transition:fly={{y: -4, duration: 150}}
|
|
353
451
|
>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import { type MenuItem, type MenuEntry } from '../utils/menu';
|
|
3
|
+
type Props = {
|
|
4
|
+
items: MenuEntry[];
|
|
5
|
+
onSelect: (value: string) => void;
|
|
6
|
+
open?: boolean;
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
/**
|
|
9
|
+
* Replace the default trigger button with a custom element. Spread `props`
|
|
10
|
+
* onto a single focusable element — it carries the ref, click/keydown
|
|
11
|
+
* handlers, and menu aria attributes. Unlike `children` (which is wrapped in
|
|
12
|
+
* an extra button), this makes your element THE trigger: one tab stop, and
|
|
13
|
+
* keyboard open/navigation works.
|
|
14
|
+
*/
|
|
15
|
+
trigger?: Snippet<[{
|
|
16
|
+
props: Record<string, unknown>;
|
|
17
|
+
open: boolean;
|
|
18
|
+
}]>;
|
|
19
|
+
/**
|
|
20
|
+
* Enable select-style typeahead (non-searchable menus). Typing on the closed
|
|
21
|
+
* trigger picks the closest label match and fires `onSelect` (like
|
|
22
|
+
* `IIDropdownInput`); typing while open moves focus to the match. Off by
|
|
23
|
+
* default so action menus don't fire on a keypress.
|
|
24
|
+
*/
|
|
25
|
+
typeahead?: boolean;
|
|
26
|
+
renderItem?: Snippet<[MenuItem]>;
|
|
27
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
28
|
+
align?: 'start' | 'center' | 'end';
|
|
29
|
+
collisionPadding?: number;
|
|
30
|
+
/** @deprecated Use `class` — it now targets the trigger. */
|
|
31
|
+
triggerClass?: string;
|
|
32
|
+
searchable?: boolean;
|
|
33
|
+
searchPlaceholder?: string;
|
|
34
|
+
/** Classes for the trigger (root) element. */
|
|
35
|
+
class?: string;
|
|
36
|
+
/** Classes for the menu panel (popover surface). */
|
|
37
|
+
contentClass?: string;
|
|
38
|
+
};
|
|
39
|
+
declare const IIMenu: import("svelte").Component<Props, {}, "open">;
|
|
40
|
+
type IIMenu = ReturnType<typeof IIMenu>;
|
|
41
|
+
export default IIMenu;
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import IIMenu from './IIMenu.svelte'
|
|
3
3
|
import {IIButton} from '../IIButton'
|
|
4
4
|
import {IIIcon} from '../IIIcon'
|
|
5
5
|
|
|
6
6
|
function handleSelect(value: string) {
|
|
7
7
|
console.log('Selected:', value)
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
// Custom-trigger demo: reflect the selection in the trigger so typeahead is
|
|
11
|
+
// visibly proven (type "al" → the label becomes "Alan Turing").
|
|
12
|
+
const assigneeLabels: Record<string, string> = {
|
|
13
|
+
me: 'Assign to me',
|
|
14
|
+
none: 'Unassigned',
|
|
15
|
+
ada: 'Ada Lovelace',
|
|
16
|
+
alan: 'Alan Turing',
|
|
17
|
+
}
|
|
18
|
+
let assigneeValue = $state('')
|
|
9
19
|
</script>
|
|
10
20
|
|
|
11
21
|
<div class="flex flex-col gap-32">
|
|
@@ -13,7 +23,7 @@
|
|
|
13
23
|
<section>
|
|
14
24
|
<h2 class="text-default-emphasis text-primary mb-8">With Separators</h2>
|
|
15
25
|
<p class="text-small text-secondary mb-12">Menu entries can include separator dividers.</p>
|
|
16
|
-
<
|
|
26
|
+
<IIMenu
|
|
17
27
|
items={[
|
|
18
28
|
{label: 'Edit', value: 'edit'},
|
|
19
29
|
{label: 'Duplicate', value: 'duplicate'},
|
|
@@ -29,7 +39,7 @@
|
|
|
29
39
|
<section>
|
|
30
40
|
<h2 class="text-default-emphasis text-primary mb-8">With Groups</h2>
|
|
31
41
|
<p class="text-small text-secondary mb-12">Items organized into labeled groups with headings.</p>
|
|
32
|
-
<
|
|
42
|
+
<IIMenu
|
|
33
43
|
items={[
|
|
34
44
|
{
|
|
35
45
|
type: 'group',
|
|
@@ -57,7 +67,7 @@
|
|
|
57
67
|
<section>
|
|
58
68
|
<h2 class="text-default-emphasis text-primary mb-8">Custom Trigger</h2>
|
|
59
69
|
<p class="text-small text-secondary mb-12">Using the children snippet with triggerClass for custom trigger styling.</p>
|
|
60
|
-
<
|
|
70
|
+
<IIMenu
|
|
61
71
|
items={[
|
|
62
72
|
{label: 'Profile', value: 'profile'},
|
|
63
73
|
{label: 'Settings', value: 'settings'},
|
|
@@ -72,14 +82,14 @@
|
|
|
72
82
|
<span>Account</span>
|
|
73
83
|
<IIIcon iconName="caret-down" class="w-12 h-12" />
|
|
74
84
|
{/snippet}
|
|
75
|
-
</
|
|
85
|
+
</IIMenu>
|
|
76
86
|
</section>
|
|
77
87
|
|
|
78
88
|
<!-- Searchable -->
|
|
79
89
|
<section>
|
|
80
90
|
<h2 class="text-default-emphasis text-primary mb-8">Searchable</h2>
|
|
81
91
|
<p class="text-small text-secondary mb-12">A search input at the top filters menu items as you type.</p>
|
|
82
|
-
<
|
|
92
|
+
<IIMenu
|
|
83
93
|
items={[
|
|
84
94
|
{label: 'Edit', value: 'edit'},
|
|
85
95
|
{label: 'Duplicate', value: 'duplicate'},
|
|
@@ -98,7 +108,7 @@
|
|
|
98
108
|
<!-- Mixed Entries -->
|
|
99
109
|
<section>
|
|
100
110
|
<h2 class="text-default-emphasis text-primary mb-8">Mixed: Items + Separators + Groups</h2>
|
|
101
|
-
<
|
|
111
|
+
<IIMenu
|
|
102
112
|
items={[
|
|
103
113
|
{label: 'Quick Action', value: 'quick'},
|
|
104
114
|
{type: 'separator'},
|
|
@@ -118,4 +128,35 @@
|
|
|
118
128
|
align="start"
|
|
119
129
|
/>
|
|
120
130
|
</section>
|
|
131
|
+
|
|
132
|
+
<!-- Custom Trigger -->
|
|
133
|
+
<section>
|
|
134
|
+
<h2 class="text-default-emphasis text-primary mb-8">Custom Trigger</h2>
|
|
135
|
+
<p class="text-small text-secondary mb-12">
|
|
136
|
+
Use the <code>trigger</code> snippet to make any element the trigger. Spread
|
|
137
|
+
<code>props</code> onto a single focusable element — it carries the ref, click/keyboard
|
|
138
|
+
handling, and menu aria. Unlike <code>children</code> (wrapped in an extra button), this is
|
|
139
|
+
one tab stop and supports keyboard open + arrow navigation. With <code>typeahead</code>,
|
|
140
|
+
typing on the closed trigger picks the closest match and fires <code>onSelect</code> (like
|
|
141
|
+
<code>IIDropdownInput</code>).
|
|
142
|
+
</p>
|
|
143
|
+
<IIMenu
|
|
144
|
+
items={[
|
|
145
|
+
{label: 'Assign to me', value: 'me'},
|
|
146
|
+
{label: 'Unassign', value: 'none'},
|
|
147
|
+
{type: 'separator'},
|
|
148
|
+
{label: 'Ada Lovelace', value: 'ada'},
|
|
149
|
+
{label: 'Alan Turing', value: 'alan'},
|
|
150
|
+
]}
|
|
151
|
+
onSelect={value => (assigneeValue = value)}
|
|
152
|
+
align="start"
|
|
153
|
+
typeahead
|
|
154
|
+
>
|
|
155
|
+
{#snippet trigger({props})}
|
|
156
|
+
<IIButton {...props} variant="ghost" iconName="user">
|
|
157
|
+
{assigneeValue ? assigneeLabels[assigneeValue] : 'Assignee'}
|
|
158
|
+
</IIButton>
|
|
159
|
+
{/snippet}
|
|
160
|
+
</IIMenu>
|
|
161
|
+
</section>
|
|
121
162
|
</div>
|
|
@@ -8,6 +8,6 @@ type Props = {
|
|
|
8
8
|
searchPlaceholder?: string;
|
|
9
9
|
renderItemContent: Snippet<[MenuItem]>;
|
|
10
10
|
};
|
|
11
|
-
declare const
|
|
12
|
-
type
|
|
13
|
-
export default
|
|
11
|
+
declare const IIMenuSub: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type IIMenuSub = ReturnType<typeof IIMenuSub>;
|
|
13
|
+
export default IIMenuSub;
|
|
@@ -8,6 +8,6 @@ type Props = {
|
|
|
8
8
|
renderItemContent: Snippet<[MenuItem]>;
|
|
9
9
|
renderSubMenu: Snippet<[SubEntry]>;
|
|
10
10
|
};
|
|
11
|
-
declare const
|
|
12
|
-
type
|
|
13
|
-
export default
|
|
11
|
+
declare const IIMenuSubSimple: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type IIMenuSubSimple = ReturnType<typeof IIMenuSubSimple>;
|
|
13
|
+
export default IIMenuSubSimple;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import IIMenu from './IIMenu.svelte'
|
|
3
|
+
import {IIButton} from '../IIButton'
|
|
4
|
+
|
|
5
|
+
// Minimal fixture for the keyboard/tab-order/typeahead play tests: a single
|
|
6
|
+
// IIMenu driven by the `trigger` snippet — spread `props` onto one focusable
|
|
7
|
+
// element (here IIButton) so it is THE trigger (one tab stop, keyboard works).
|
|
8
|
+
// The consumer owns the displayed value, so we reflect the selection here.
|
|
9
|
+
let {onSelect}: {onSelect?: (value: string) => void} = $props()
|
|
10
|
+
|
|
11
|
+
const labels: Record<string, string> = {
|
|
12
|
+
me: 'Assign to me',
|
|
13
|
+
none: 'Unassigned',
|
|
14
|
+
ada: 'Ada Lovelace',
|
|
15
|
+
alan: 'Alan Turing',
|
|
16
|
+
}
|
|
17
|
+
let selected = $state('')
|
|
18
|
+
|
|
19
|
+
function handleSelect(value: string) {
|
|
20
|
+
selected = value
|
|
21
|
+
onSelect?.(value)
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<IIMenu
|
|
26
|
+
items={[
|
|
27
|
+
{label: 'Assign to me', value: 'me'},
|
|
28
|
+
{label: 'Unassign', value: 'none'},
|
|
29
|
+
{type: 'separator'},
|
|
30
|
+
{label: 'Ada Lovelace', value: 'ada'},
|
|
31
|
+
{label: 'Alan Turing', value: 'alan'},
|
|
32
|
+
]}
|
|
33
|
+
onSelect={handleSelect}
|
|
34
|
+
typeahead
|
|
35
|
+
>
|
|
36
|
+
{#snippet trigger({props})}
|
|
37
|
+
<IIButton {...props} variant="ghost" iconName="user">
|
|
38
|
+
{selected ? labels[selected] : 'Assignee'}
|
|
39
|
+
</IIButton>
|
|
40
|
+
{/snippet}
|
|
41
|
+
</IIMenu>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as IIMenu } from './IIMenu.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as IIMenu } from './IIMenu.svelte';
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
onOpenChange?.(newOpen)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// Portaled menu/listbox content (
|
|
52
|
+
// Portaled menu/listbox content (IIMenu, bits-ui DropdownMenu/Popover/Select, etc.)
|
|
53
53
|
// lives in document.body outside Dialog.Content, so bits-ui's DismissibleLayer would treat
|
|
54
54
|
// interactions with them as "outside" the modal and close it. Preserve those events.
|
|
55
55
|
function isInsidePortaledMenu(target: Element | null): boolean {
|
|
@@ -89,12 +89,13 @@
|
|
|
89
89
|
// property — both compose with the open/close slide animation, which owns `transform`
|
|
90
90
|
// (translateY). A transform-based center would be clobbered by the animation and
|
|
91
91
|
// shift the modal for the animation's duration.
|
|
92
|
-
'fixed left-0 right-0 mx-auto bg-surface border border-
|
|
92
|
+
'fixed left-0 right-0 mx-auto bg-surface border border-primary rounded-12 w-[calc(100%-20px)] flex flex-col z-15 data-[state=open]:animate-modal-in data-[state=closed]:animate-modal-out motion-reduce:animate-none focus:outline-none',
|
|
93
93
|
// Vertical: anchored 15% from the top (default) vs. centered. When anchored, height
|
|
94
94
|
// is capped so a tall modal still clears the bottom (15% + 78vh leaves ~7vh below);
|
|
95
95
|
// centered can safely use the full 90vh since -50% keeps it within the viewport.
|
|
96
96
|
centered ? 'top-1/2 [translate:0_-50%] max-h-[90vh]' : 'top-[15%] max-h-[78vh]',
|
|
97
|
-
|
|
97
|
+
// Same edge for every overlay: shadow-modal (previously no-overlay used shadow-floating).
|
|
98
|
+
'shadow-modal',
|
|
98
99
|
size === 'sm' && 'max-w-400',
|
|
99
100
|
size === 'md' && 'max-w-500',
|
|
100
101
|
size === 'lg' && 'max-w-700',
|
|
@@ -107,7 +108,7 @@
|
|
|
107
108
|
<div class="flex items-start justify-between gap-12 px-24 pt-24 shrink-0">
|
|
108
109
|
<div class="flex flex-col min-w-0 flex-1">
|
|
109
110
|
<div class="flex items-center gap-12 min-w-0">
|
|
110
|
-
<Dialog.Title class="text-
|
|
111
|
+
<Dialog.Title class="text-h3 text-body m-0 shrink-0">{title}</Dialog.Title>
|
|
111
112
|
{#if titleAccessory}
|
|
112
113
|
{@render titleAccessory()}
|
|
113
114
|
{/if}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,9 @@ export { IICombobox } from './IICombobox';
|
|
|
16
16
|
export { IIContextMenu } from './IIContextMenu';
|
|
17
17
|
export { IIDateInput } from './IIDateInput';
|
|
18
18
|
export { IIDropdownInput } from './IIDropdownInput';
|
|
19
|
-
export {
|
|
19
|
+
export { IIMenu } from './IIMenu';
|
|
20
|
+
/** @deprecated Renamed to `IIMenu` (it is an action menu, not a select). Use `IIMenu`. */
|
|
21
|
+
export { IIMenu as IIDropdownMenu } from './IIMenu';
|
|
20
22
|
export { IIEditableBadges } from './IIEditableBadges';
|
|
21
23
|
export { IIEditableText } from './IIEditableText';
|
|
22
24
|
export { IIEmptyState } from './IIEmptyState';
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,9 @@ export { IICombobox } from './IICombobox';
|
|
|
20
20
|
export { IIContextMenu } from './IIContextMenu';
|
|
21
21
|
export { IIDateInput } from './IIDateInput';
|
|
22
22
|
export { IIDropdownInput } from './IIDropdownInput';
|
|
23
|
-
export {
|
|
23
|
+
export { IIMenu } from './IIMenu';
|
|
24
|
+
/** @deprecated Renamed to `IIMenu` (it is an action menu, not a select). Use `IIMenu`. */
|
|
25
|
+
export { IIMenu as IIDropdownMenu } from './IIMenu';
|
|
24
26
|
export { IIEditableBadges } from './IIEditableBadges';
|
|
25
27
|
export { IIEditableText } from './IIEditableText';
|
|
26
28
|
export { IIEmptyState } from './IIEmptyState';
|
package/dist/style/base.css
CHANGED
|
@@ -77,3 +77,26 @@
|
|
|
77
77
|
background-color: var(--ii-gray-400);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
/* Shared narrow, rounded scrollbar for menu/dropdown scroll areas. Unlayered so
|
|
82
|
+
it outranks the @layer base `*` rules above. Setting scrollbar-width/color to
|
|
83
|
+
auto opts this element back into ::-webkit-scrollbar styling — the global
|
|
84
|
+
`scrollbar-width: thin` otherwise disables the WebKit pseudo-elements in
|
|
85
|
+
Chromium, so a custom (narrow) thumb width can't be applied. */
|
|
86
|
+
.ii-menu-scroll {
|
|
87
|
+
scrollbar-width: auto;
|
|
88
|
+
scrollbar-color: auto;
|
|
89
|
+
}
|
|
90
|
+
.ii-menu-scroll::-webkit-scrollbar {
|
|
91
|
+
width: 4px;
|
|
92
|
+
}
|
|
93
|
+
.ii-menu-scroll::-webkit-scrollbar-track {
|
|
94
|
+
background: transparent;
|
|
95
|
+
}
|
|
96
|
+
.ii-menu-scroll::-webkit-scrollbar-thumb {
|
|
97
|
+
background-color: var(--ii-gray-300);
|
|
98
|
+
border-radius: 9999px;
|
|
99
|
+
}
|
|
100
|
+
.ii-menu-scroll::-webkit-scrollbar-thumb:hover {
|
|
101
|
+
background-color: var(--ii-gray-400);
|
|
102
|
+
}
|
|
@@ -2,10 +2,10 @@ export declare const menuStyles: {
|
|
|
2
2
|
readonly item: "flex items-center gap-8 px-12 py-8 rounded-4 text-small cursor-default select-none outline-none data-[disabled]:opacity-50 motion-reduce:transition-none";
|
|
3
3
|
readonly itemDefault: "text-dropdown-item hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none data-[state=open]:bg-dropdown-item-hover data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent";
|
|
4
4
|
readonly itemDestructive: "text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent";
|
|
5
|
-
readonly content: "min-w-48
|
|
6
|
-
readonly subContent: "min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 outline-none animate-fade-in motion-reduce:animate-none";
|
|
5
|
+
readonly content: "ii-menu-scroll min-w-48 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-dropdown p-4 z-12 pointer-events-auto outline-none";
|
|
6
|
+
readonly subContent: "ii-menu-scroll min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 outline-none animate-fade-in motion-reduce:animate-none";
|
|
7
7
|
readonly searchableSubContent: "min-w-48 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 pointer-events-auto outline-none overflow-hidden animate-fade-in motion-reduce:animate-none";
|
|
8
|
-
readonly scrollableItems: "max-h-250 overflow-y-auto overflow-x-hidden";
|
|
8
|
+
readonly scrollableItems: "ii-menu-scroll max-h-250 overflow-y-auto overflow-x-hidden";
|
|
9
9
|
readonly separator: "h-1 bg-muted -mx-4 my-4";
|
|
10
10
|
readonly groupHeading: "text-tiny-emphasis text-secondary px-12 py-4 uppercase select-none";
|
|
11
11
|
};
|
|
@@ -3,10 +3,13 @@ export const menuStyles = {
|
|
|
3
3
|
item: 'flex items-center gap-8 px-12 py-8 rounded-4 text-small cursor-default select-none outline-none data-[disabled]:opacity-50 motion-reduce:transition-none',
|
|
4
4
|
itemDefault: 'text-dropdown-item hover:bg-dropdown-item-hover focus:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none data-[state=open]:bg-dropdown-item-hover data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent',
|
|
5
5
|
itemDestructive: 'text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none data-[disabled]:hover:bg-transparent data-[disabled]:focus:bg-transparent',
|
|
6
|
-
content:
|
|
7
|
-
|
|
6
|
+
// content: no fixed max-height — IIMenu passes constrainHeight to
|
|
7
|
+
// useFloating, which sizes maxHeight to the available viewport space so the
|
|
8
|
+
// menu grows to fit its items and only scrolls near the screen edge.
|
|
9
|
+
content: 'ii-menu-scroll min-w-48 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-dropdown p-4 z-12 pointer-events-auto outline-none',
|
|
10
|
+
subContent: 'ii-menu-scroll min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 outline-none animate-fade-in motion-reduce:animate-none',
|
|
8
11
|
searchableSubContent: 'min-w-48 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 pointer-events-auto outline-none overflow-hidden animate-fade-in motion-reduce:animate-none',
|
|
9
|
-
scrollableItems: 'max-h-250 overflow-y-auto overflow-x-hidden',
|
|
12
|
+
scrollableItems: 'ii-menu-scroll max-h-250 overflow-y-auto overflow-x-hidden',
|
|
10
13
|
separator: 'h-1 bg-muted -mx-4 my-4',
|
|
11
14
|
groupHeading: 'text-tiny-emphasis text-secondary px-12 py-4 uppercase select-none',
|
|
12
15
|
};
|
package/package.json
CHANGED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Snippet } from 'svelte';
|
|
2
|
-
import { type MenuItem, type MenuEntry } from '../utils/menu';
|
|
3
|
-
type Props = {
|
|
4
|
-
items: MenuEntry[];
|
|
5
|
-
onSelect: (value: string) => void;
|
|
6
|
-
open?: boolean;
|
|
7
|
-
children?: Snippet;
|
|
8
|
-
renderItem?: Snippet<[MenuItem]>;
|
|
9
|
-
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
10
|
-
align?: 'start' | 'center' | 'end';
|
|
11
|
-
collisionPadding?: number;
|
|
12
|
-
triggerClass?: string;
|
|
13
|
-
searchable?: boolean;
|
|
14
|
-
searchPlaceholder?: string;
|
|
15
|
-
class?: string;
|
|
16
|
-
};
|
|
17
|
-
declare const IIDropdownMenu: import("svelte").Component<Props, {}, "open">;
|
|
18
|
-
type IIDropdownMenu = ReturnType<typeof IIDropdownMenu>;
|
|
19
|
-
export default IIDropdownMenu;
|
|
@@ -1,18 +0,0 @@
|
|
|
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;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as IIDropdownMenu } from './IIDropdownMenu.svelte';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as IIDropdownMenu } from './IIDropdownMenu.svelte';
|
|
File without changes
|
/package/dist/{IIDropdownMenu/IIDropdownMenuSubSimple.svelte → IIMenu/IIMenuSubSimple.svelte}
RENAMED
|
File without changes
|