@insymetri/styleguide 0.1.79 → 0.1.80

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,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()
@@ -152,6 +195,7 @@
152
195
  const match = items[matchIndex]
153
196
  if (!wasOpen) {
154
197
  value = match.value
198
+ onValueChange?.(match.value)
155
199
  onSelect?.(match.value)
156
200
  } else {
157
201
  highlightedIndex = matchIndex
@@ -240,6 +284,9 @@
240
284
  {#if label}
241
285
  <span class="text-small-emphasis text-secondary mb-4">{label}</span>
242
286
  {/if}
287
+ {#if trigger}
288
+ {@render trigger({props: triggerProps, open, selectedItem, selectedLabel})}
289
+ {:else}
243
290
  <button
244
291
  bind:this={triggerEl}
245
292
  type="button"
@@ -266,6 +313,7 @@
266
313
  {/if}
267
314
  <IIIcon iconName="caret-down" />
268
315
  </button>
316
+ {/if}
269
317
 
270
318
  {#if open}
271
319
  <div use:portal use:clickOutside={{onClose: close, ignore: () => triggerEl}}>
@@ -273,7 +321,7 @@
273
321
  bind:this={floatingEl}
274
322
  role="listbox"
275
323
  data-menu-content
276
- class="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"
324
+ 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
325
  transition:fly={{y: -4, duration: 150}}
278
326
  >
279
327
  {#if searchable}
@@ -284,7 +332,7 @@
284
332
  onkeydown={(e) => search.handleKeydown(e, '[data-menu-content]')}
285
333
  />
286
334
  {/if}
287
- <div class="dd-scroll min-h-0 overflow-y-auto -mr-2">
335
+ <div class="ii-menu-scroll min-h-0 overflow-y-auto -mr-2">
288
336
  {#each (searchable ? search.filteredItems as MenuItem[] : items) as item, listIndex (item.value)}
289
337
  {@const index = searchable ? search.getItemIndex(item) : listIndex}
290
338
  {@const isHighlighted = searchable
@@ -346,26 +394,3 @@
346
394
  <span class="text-tiny text-error mt-4" data-field-error>{errorMessage}</span>
347
395
  {/if}
348
396
  </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>
@@ -9,8 +9,8 @@
9
9
  import MenuSearchInput from '../utils/menu/MenuSearchInput.svelte'
10
10
  import MenuNoResults from '../utils/menu/MenuNoResults.svelte'
11
11
  import MenuItemTooltip from '../utils/menu/MenuItemTooltip.svelte'
12
- import IIDropdownMenuSub from './IIDropdownMenuSub.svelte'
13
- import IIDropdownMenuSubSimple from './IIDropdownMenuSubSimple.svelte'
12
+ import IIMenuSub from './IIMenuSub.svelte'
13
+ import IIMenuSubSimple from './IIMenuSubSimple.svelte'
14
14
 
15
15
  type Props = {
16
16
  items: MenuEntry[]
@@ -21,10 +21,14 @@
21
21
  side?: 'top' | 'right' | 'bottom' | 'left'
22
22
  align?: 'start' | 'center' | 'end'
23
23
  collisionPadding?: number
24
+ /** @deprecated Use `class` — it now targets the trigger. */
24
25
  triggerClass?: string
25
26
  searchable?: boolean
26
27
  searchPlaceholder?: string
28
+ /** Classes for the trigger (root) element. */
27
29
  class?: string
30
+ /** Classes for the menu panel (popover surface). */
31
+ contentClass?: string
28
32
  }
29
33
 
30
34
  let {
@@ -40,6 +44,7 @@
40
44
  searchable = false,
41
45
  searchPlaceholder = 'Search...',
42
46
  class: className,
47
+ contentClass,
43
48
  }: Props = $props()
44
49
 
45
50
  let triggerEl = $state<HTMLElement | null>(null)
@@ -64,6 +69,7 @@
64
69
  placement: placement as any,
65
70
  offset: 4,
66
71
  shift: {padding: collisionPadding},
72
+ constrainHeight: true,
67
73
  })
68
74
 
69
75
  const search = createMenuSearch({
@@ -270,7 +276,7 @@
270
276
  </div>
271
277
  {#if isOpen}
272
278
  {#if entry.searchable}
273
- <IIDropdownMenuSub
279
+ <IIMenuSub
274
280
  items={entry.items}
275
281
  onSelect={handleSelect}
276
282
  onClose={() => {
@@ -282,7 +288,7 @@
282
288
  renderItemContent={itemContent}
283
289
  />
284
290
  {:else}
285
- <IIDropdownMenuSubSimple
291
+ <IIMenuSubSimple
286
292
  items={entry.items}
287
293
  onSelect={handleSelect}
288
294
  onClose={() => {
@@ -331,7 +337,7 @@
331
337
  aria-haspopup="menu"
332
338
  aria-expanded={open}
333
339
  data-state={open ? 'open' : 'closed'}
334
- class={cn(children ? cn('[all:unset] cursor-default', triggerClass) : defaultTriggerClass)}
340
+ class={cn(children ? '[all:unset] cursor-default' : defaultTriggerClass, triggerClass, className)}
335
341
  onclick={toggle}
336
342
  >
337
343
  {#if children}
@@ -347,7 +353,7 @@
347
353
  bind:this={floatingEl}
348
354
  role="menu"
349
355
  data-menu-content
350
- class={cn(menuStyles.content, className)}
356
+ class={cn(menuStyles.content, contentClass)}
351
357
  onkeydown={handleMenuKeydown}
352
358
  transition:fly={{y: -4, duration: 150}}
353
359
  >
@@ -9,11 +9,15 @@ type Props = {
9
9
  side?: 'top' | 'right' | 'bottom' | 'left';
10
10
  align?: 'start' | 'center' | 'end';
11
11
  collisionPadding?: number;
12
+ /** @deprecated Use `class` — it now targets the trigger. */
12
13
  triggerClass?: string;
13
14
  searchable?: boolean;
14
15
  searchPlaceholder?: string;
16
+ /** Classes for the trigger (root) element. */
15
17
  class?: string;
18
+ /** Classes for the menu panel (popover surface). */
19
+ contentClass?: string;
16
20
  };
17
- declare const IIDropdownMenu: import("svelte").Component<Props, {}, "open">;
18
- type IIDropdownMenu = ReturnType<typeof IIDropdownMenu>;
19
- export default IIDropdownMenu;
21
+ declare const IIMenu: import("svelte").Component<Props, {}, "open">;
22
+ type IIMenu = ReturnType<typeof IIMenu>;
23
+ export default IIMenu;
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import IIDropdownMenu from './IIDropdownMenu.svelte'
2
+ import IIMenu from './IIMenu.svelte'
3
3
  import {IIButton} from '../IIButton'
4
4
  import {IIIcon} from '../IIIcon'
5
5
 
@@ -13,7 +13,7 @@
13
13
  <section>
14
14
  <h2 class="text-default-emphasis text-primary mb-8">With Separators</h2>
15
15
  <p class="text-small text-secondary mb-12">Menu entries can include separator dividers.</p>
16
- <IIDropdownMenu
16
+ <IIMenu
17
17
  items={[
18
18
  {label: 'Edit', value: 'edit'},
19
19
  {label: 'Duplicate', value: 'duplicate'},
@@ -29,7 +29,7 @@
29
29
  <section>
30
30
  <h2 class="text-default-emphasis text-primary mb-8">With Groups</h2>
31
31
  <p class="text-small text-secondary mb-12">Items organized into labeled groups with headings.</p>
32
- <IIDropdownMenu
32
+ <IIMenu
33
33
  items={[
34
34
  {
35
35
  type: 'group',
@@ -57,7 +57,7 @@
57
57
  <section>
58
58
  <h2 class="text-default-emphasis text-primary mb-8">Custom Trigger</h2>
59
59
  <p class="text-small text-secondary mb-12">Using the children snippet with triggerClass for custom trigger styling.</p>
60
- <IIDropdownMenu
60
+ <IIMenu
61
61
  items={[
62
62
  {label: 'Profile', value: 'profile'},
63
63
  {label: 'Settings', value: 'settings'},
@@ -72,14 +72,14 @@
72
72
  <span>Account</span>
73
73
  <IIIcon iconName="caret-down" class="w-12 h-12" />
74
74
  {/snippet}
75
- </IIDropdownMenu>
75
+ </IIMenu>
76
76
  </section>
77
77
 
78
78
  <!-- Searchable -->
79
79
  <section>
80
80
  <h2 class="text-default-emphasis text-primary mb-8">Searchable</h2>
81
81
  <p class="text-small text-secondary mb-12">A search input at the top filters menu items as you type.</p>
82
- <IIDropdownMenu
82
+ <IIMenu
83
83
  items={[
84
84
  {label: 'Edit', value: 'edit'},
85
85
  {label: 'Duplicate', value: 'duplicate'},
@@ -98,7 +98,7 @@
98
98
  <!-- Mixed Entries -->
99
99
  <section>
100
100
  <h2 class="text-default-emphasis text-primary mb-8">Mixed: Items + Separators + Groups</h2>
101
- <IIDropdownMenu
101
+ <IIMenu
102
102
  items={[
103
103
  {label: 'Quick Action', value: 'quick'},
104
104
  {type: 'separator'},
@@ -11,8 +11,8 @@ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> =
11
11
  };
12
12
  z_$$bindings?: Bindings;
13
13
  }
14
- declare const IIDropdownMenuStories: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
14
+ declare const IIMenuStories: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
15
  [evt: string]: CustomEvent<any>;
16
16
  }, {}, {}, string>;
17
- type IIDropdownMenuStories = InstanceType<typeof IIDropdownMenuStories>;
18
- export default IIDropdownMenuStories;
17
+ type IIMenuStories = InstanceType<typeof IIMenuStories>;
18
+ export default IIMenuStories;
@@ -8,6 +8,6 @@ type Props = {
8
8
  searchPlaceholder?: string;
9
9
  renderItemContent: Snippet<[MenuItem]>;
10
10
  };
11
- declare const IIDropdownMenuSub: import("svelte").Component<Props, {}, "">;
12
- type IIDropdownMenuSub = ReturnType<typeof IIDropdownMenuSub>;
13
- export default IIDropdownMenuSub;
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 IIDropdownMenuSubSimple: import("svelte").Component<Props, {}, "">;
12
- type IIDropdownMenuSubSimple = ReturnType<typeof IIDropdownMenuSubSimple>;
13
- export default IIDropdownMenuSubSimple;
11
+ declare const IIMenuSubSimple: import("svelte").Component<Props, {}, "">;
12
+ type IIMenuSubSimple = ReturnType<typeof IIMenuSubSimple>;
13
+ export default IIMenuSubSimple;
@@ -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 (IIDropdownMenu, bits-ui DropdownMenu/Popover/Select, etc.)
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-strong 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',
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
- overlay === 'none' ? 'shadow-floating' : 'shadow-modal',
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-h2 text-body m-0 shrink-0">{title}</Dialog.Title>
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 { IIDropdownMenu } from './IIDropdownMenu';
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 { IIDropdownMenu } from './IIDropdownMenu';
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';
@@ -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 max-h-300 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: "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: 'min-w-48 max-h-300 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',
7
- 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',
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@insymetri/styleguide",
3
- "version": "0.1.79",
3
+ "version": "0.1.80",
4
4
  "description": "Insymetri shared UI component library built with Svelte 5",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1 +0,0 @@
1
- export { default as IIDropdownMenu } from './IIDropdownMenu.svelte';
@@ -1 +0,0 @@
1
- export { default as IIDropdownMenu } from './IIDropdownMenu.svelte';