@insymetri/styleguide 0.1.47 → 0.1.48

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.
@@ -36,6 +36,19 @@
36
36
  open = newOpen
37
37
  onOpenChange?.(newOpen)
38
38
  }
39
+
40
+ // Portaled menu/listbox content (IIDropdownMenu, bits-ui DropdownMenu/Popover/Select, etc.)
41
+ // lives in document.body outside Dialog.Content, so bits-ui's DismissibleLayer would treat
42
+ // interactions with them as "outside" the modal and close it. Preserve those events.
43
+ function isInsidePortaledMenu(target: Element | null): boolean {
44
+ if (!target) return false
45
+ return !!(
46
+ target.closest('[data-menu-content]') ||
47
+ target.closest('[data-popper-content-wrapper]') ||
48
+ target.closest('[role="menu"]') ||
49
+ target.closest('[role="listbox"]')
50
+ )
51
+ }
39
52
  </script>
40
53
 
41
54
  <Dialog.Root {open} onOpenChange={handleOpenChange}>
@@ -50,6 +63,14 @@
50
63
  />
51
64
  <Dialog.Content
52
65
  onOpenAutoFocus={e => e.preventDefault()}
66
+ onInteractOutside={e => {
67
+ const target = e.target as Element | null
68
+ if (isInsidePortaledMenu(target)) e.preventDefault()
69
+ }}
70
+ onFocusOutside={e => {
71
+ const target = e.target as Element | null
72
+ if (isInsidePortaledMenu(target)) e.preventDefault()
73
+ }}
53
74
  class={cn(
54
75
  'fixed top-[30%] left-1/2 -translate-x-1/2 -translate-y-1/2 bg-surface rounded-12 w-[calc(100%-20px)] max-h-[90vh] flex flex-col z-15 data-[state=open]:animate-modal-in data-[state=closed]:animate-modal-out motion-reduce:animate-none focus:outline-none',
55
76
  overlay === 'none' ? 'shadow-floating' : 'shadow-modal',
@@ -1,94 +1,90 @@
1
1
  <script lang="ts">
2
2
  import type {Snippet} from 'svelte'
3
- import {DropdownMenu, Tabs, type WithoutChildrenOrChild} from 'bits-ui'
3
+ import {DropdownMenu, Tabs} from 'bits-ui'
4
4
  import {IIIcon} from '../IIIcon'
5
+ import IIButton from '../IIButton/IIButton.svelte'
5
6
  import {cn} from '../utils/cn'
6
- import {useDensity} from '../density'
7
7
 
8
8
  type Tab = {
9
9
  value: string
10
10
  label: string
11
11
  icon?: Snippet
12
12
  disabled?: boolean
13
- trigger?: Snippet
14
- content: Snippet
15
13
  }
16
14
 
17
- type Props = WithoutChildrenOrChild<Tabs.RootProps> & {
15
+ type Props = {
18
16
  tabs: Tab[]
17
+ value: string
18
+ onChange: (value: string) => void
19
19
  class?: string
20
- listClass?: string
21
- contentClass?: string
22
20
  }
23
21
 
24
- let {
25
- value = $bindable(),
26
- tabs,
27
- class: className,
28
- listClass,
29
- contentClass,
30
- onValueChange,
31
- ...restProps
32
- }: Props = $props()
22
+ let {tabs, value, onChange, class: className}: Props = $props()
33
23
 
34
- let internalRef = $state(null)
35
24
  let listEl = $state<HTMLElement | null>(null)
36
25
  let overflowing = $state(false)
37
26
  let dropdownOpen = $state(false)
38
27
 
39
- const density = useDensity()
40
-
41
- const densityRadius = {
42
- compact: 'rounded-control',
43
- default: 'rounded-control',
44
- comfortable: 'rounded-control',
45
- mobile: 'rounded-control',
46
- } as const
47
-
48
28
  const activeLabel = $derived(tabs.find(t => t.value === value)?.label ?? '')
49
29
 
50
30
  $effect(() => {
51
31
  if (!listEl) return
52
-
53
32
  function check() {
54
33
  if (!listEl) return
55
34
  overflowing = listEl.scrollWidth > listEl.clientWidth + 1
56
35
  }
57
-
58
36
  check()
59
37
  const observer = new ResizeObserver(check)
60
38
  observer.observe(listEl)
61
39
  return () => observer.disconnect()
62
40
  })
63
41
 
64
- function handleTabSelect(tab: Tab) {
65
- value = tab.value
66
- onValueChange?.(tab.value)
42
+ function handleSelect(tab: Tab) {
43
+ onChange(tab.value)
67
44
  dropdownOpen = false
68
45
  }
69
46
  </script>
70
47
 
71
- <Tabs.Root
72
- bind:value
73
- bind:ref={internalRef}
74
- {onValueChange}
75
- {...restProps}
76
- class={cn('flex flex-col w-full h-full', className)}
77
- >
48
+ <Tabs.Root {value} onValueChange={onChange} class={cn('relative w-full h-full', className)}>
49
+ <Tabs.List
50
+ bind:ref={listEl}
51
+ class={cn(
52
+ 'flex gap-16 pl-24 overflow-hidden h-full w-full',
53
+ overflowing && 'invisible pointer-events-none'
54
+ )}
55
+ >
56
+ {#each tabs as tab (tab.value)}
57
+ <Tabs.Trigger
58
+ value={tab.value}
59
+ disabled={tab.disabled}
60
+ class="appearance-none border-none bg-transparent outline-none inline-flex items-center gap-8 p-0 h-full text-small font-normal text-tertiary cursor-default -mb-px whitespace-nowrap relative shrink-0 hover:text-secondary data-[state=active]:text-body data-[disabled]:text-tertiary data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 motion-reduce:[transition:none] [transition:color_0.15s_ease] after:content-[''] after:absolute after:bottom-0 after:left-1/2 after:w-0 after:-translate-x-1/2 after:[height:1.5px] after:[transition:width_0.15s_ease,background-color_0.15s_ease] data-[state=active]:after:w-full data-[state=active]:after:bg-dark [&:hover:not([data-disabled]):not([data-state=active])]:after:w-3/5 [&:hover:not([data-disabled]):not([data-state=active])]:after:bg-dark-secondary"
61
+ >
62
+ {#if tab.icon}
63
+ <div class="w-[1.25em] h-[1.25em] flex items-center justify-center">
64
+ {@render tab.icon()}
65
+ </div>
66
+ {/if}
67
+ {tab.label}
68
+ </Tabs.Trigger>
69
+ {/each}
70
+ </Tabs.List>
71
+
78
72
  {#if overflowing}
79
- <div class="flex items-center border-b border-primary px-24" style="padding-left: calc(2.4rem - 1.2rem - 1px)">
73
+ <div
74
+ class="absolute inset-0 flex items-center"
75
+ style="padding-left: calc(2.4rem - 1.2rem - 1px)"
76
+ >
80
77
  <DropdownMenu.Root bind:open={dropdownOpen}>
81
- <DropdownMenu.Trigger
82
- class={cn(
83
- '[all:unset] inline-flex items-center gap-4 py-4 pr-8 pl-12 border border-primary bg-surface cursor-default transition-all duration-fast hover:border-strong motion-reduce:transition-none',
84
- densityRadius[density.value]
85
- )}
86
- >
87
- <span class="text-small-emphasis text-body">{activeLabel}</span>
88
- <IIIcon iconName="caret-down" class="w-14 h-14 text-secondary shrink-0" />
78
+ <DropdownMenu.Trigger>
79
+ {#snippet child({props})}
80
+ <IIButton {...props} variant="ghost">
81
+ {activeLabel}
82
+ <IIIcon iconName="caret-down" class="w-14 h-14 shrink-0" />
83
+ </IIButton>
84
+ {/snippet}
89
85
  </DropdownMenu.Trigger>
90
86
  <DropdownMenu.Content
91
- class="min-w-100 bg-surface border border-primary rounded-10 shadow-dropdown p-4 z-12"
87
+ class="min-w-100 bg-surface border border-primary rounded-10 shadow-dropdown p-4 z-12 pointer-events-auto"
92
88
  side="bottom"
93
89
  align="start"
94
90
  >
@@ -99,7 +95,7 @@
99
95
  'flex items-center justify-between gap-12 px-12 py-8 rounded-4 text-small-emphasis text-secondary cursor-default outline-none transition-all duration-fast hover:bg-background hover:text-body data-[highlighted]:bg-background data-[highlighted]:text-body data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none motion-reduce:transition-none',
100
96
  value === tab.value && 'text-body'
101
97
  )}
102
- onSelect={() => handleTabSelect(tab)}
98
+ onSelect={() => handleSelect(tab)}
103
99
  >
104
100
  <span>{tab.label}</span>
105
101
  {#if value === tab.value}
@@ -111,42 +107,4 @@
111
107
  </DropdownMenu.Root>
112
108
  </div>
113
109
  {/if}
114
- <Tabs.List
115
- bind:ref={listEl}
116
- class={cn(
117
- 'flex border-b border-primary gap-16 pl-24 overflow-hidden',
118
- overflowing && '!h-0 invisible border-b-0 py-0',
119
- listClass
120
- )}
121
- >
122
- {#each tabs as tab (tab.value)}
123
- <Tabs.Trigger
124
- value={tab.value}
125
- disabled={tab.disabled}
126
- class="appearance-none border-none bg-transparent outline-none inline-flex items-center gap-8 p-0 h-full text-small font-normal text-tertiary cursor-default -mb-px whitespace-nowrap relative shrink-0 hover:text-secondary data-[state=active]:text-body data-[disabled]:text-tertiary data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 motion-reduce:[transition:none] [transition:color_0.15s_ease] after:content-[''] after:absolute after:bottom-0 after:left-1/2 after:w-0 after:-translate-x-1/2 after:[height:1.5px] after:[transition:width_0.15s_ease,background-color_0.15s_ease] data-[state=active]:after:w-full data-[state=active]:after:bg-dark [&:hover:not([data-disabled]):not([data-state=active])]:after:w-3/5 [&:hover:not([data-disabled]):not([data-state=active])]:after:bg-dark-secondary"
127
- >
128
- {#if tab.trigger}
129
- {@render tab.trigger()}
130
- {:else}
131
- {#if tab.icon}
132
- <div class="w-[1.25em] h-[1.25em] flex items-center justify-center">
133
- {@render tab.icon()}
134
- </div>
135
- {/if}
136
- {tab.label}
137
- {/if}
138
- </Tabs.Trigger>
139
- {/each}
140
- </Tabs.List>
141
- {#each tabs as tab (tab.value)}
142
- <Tabs.Content
143
- value={tab.value}
144
- class={cn(
145
- 'flex-1 min-h-0 outline-none focus-visible:outline-2 focus-visible:outline-primary focus-visible:outline-offset-2 focus-visible:rounded-4',
146
- contentClass
147
- )}
148
- >
149
- {@render tab.content()}
150
- </Tabs.Content>
151
- {/each}
152
110
  </Tabs.Root>
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Tab = {
3
+ value: string;
4
+ label: string;
5
+ icon?: Snippet;
6
+ disabled?: boolean;
7
+ };
8
+ type Props = {
9
+ tabs: Tab[];
10
+ value: string;
11
+ onChange: (value: string) => void;
12
+ class?: string;
13
+ };
14
+ declare const IITabBar: import("svelte").Component<Props, {}, "">;
15
+ type IITabBar = ReturnType<typeof IITabBar>;
16
+ export default IITabBar;
@@ -0,0 +1 @@
1
+ export { default as IITabBar } from './IITabBar.svelte';
@@ -0,0 +1 @@
1
+ export { default as IITabBar } from './IITabBar.svelte';
package/dist/index.d.ts CHANGED
@@ -37,7 +37,7 @@ export { IIStepper } from './IIStepper';
37
37
  export { IISwitch } from './IISwitch';
38
38
  export { IITable } from './IITable';
39
39
  export { IITableSkeleton } from './IITableSkeleton';
40
- export { IITabs } from './IITabs';
40
+ export { IITabBar } from './IITabBar';
41
41
  export { IITaskCardSkeleton } from './IITaskCardSkeleton';
42
42
  export { IITextarea } from './IITextarea';
43
43
  export { IIToggle } from './IIToggle';
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ export { IIStepper } from './IIStepper';
41
41
  export { IISwitch } from './IISwitch';
42
42
  export { IITable } from './IITable';
43
43
  export { IITableSkeleton } from './IITableSkeleton';
44
- export { IITabs } from './IITabs';
44
+ export { IITabBar } from './IITabBar';
45
45
  export { IITaskCardSkeleton } from './IITaskCardSkeleton';
46
46
  export { IITextarea } from './IITextarea';
47
47
  export { IIToggle } from './IIToggle';
@@ -2,9 +2,9 @@ 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 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none 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";
4
4
  readonly itemDestructive: "text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none";
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 outline-none origin-top-left animate-scale-in motion-reduce:animate-none";
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 origin-top-left animate-scale-in motion-reduce:animate-none";
6
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 origin-top-left animate-scale-in motion-reduce:animate-none";
7
- readonly searchableSubContent: "min-w-48 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 outline-none overflow-hidden origin-top-left animate-scale-in motion-reduce:animate-none";
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 origin-top-left animate-scale-in motion-reduce:animate-none";
8
8
  readonly scrollableItems: "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";
@@ -3,9 +3,9 @@ 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 data-[disabled]:cursor-not-allowed data-[disabled]:pointer-events-none 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',
5
5
  itemDestructive: 'text-error hover:bg-error-bg focus:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none',
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 outline-none origin-top-left animate-scale-in motion-reduce:animate-none',
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 origin-top-left animate-scale-in motion-reduce:animate-none',
7
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 origin-top-left animate-scale-in motion-reduce:animate-none',
8
- searchableSubContent: 'min-w-48 bg-dropdown-bg border-[0.5px] border-dropdown-border rounded-10 shadow-submenu p-4 z-12 outline-none overflow-hidden origin-top-left animate-scale-in motion-reduce:animate-none',
8
+ 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 origin-top-left animate-scale-in motion-reduce:animate-none',
9
9
  scrollableItems: 'max-h-250 overflow-y-auto overflow-x-hidden',
10
10
  separator: 'h-1 bg-muted -mx-4 my-4',
11
11
  groupHeading: 'text-tiny-emphasis text-secondary px-12 py-4 uppercase select-none',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insymetri/styleguide",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
4
4
  "description": "Insymetri shared UI component library built with Svelte 5",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,19 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- import { Tabs, type WithoutChildrenOrChild } from 'bits-ui';
3
- type Tab = {
4
- value: string;
5
- label: string;
6
- icon?: Snippet;
7
- disabled?: boolean;
8
- trigger?: Snippet;
9
- content: Snippet;
10
- };
11
- type Props = WithoutChildrenOrChild<Tabs.RootProps> & {
12
- tabs: Tab[];
13
- class?: string;
14
- listClass?: string;
15
- contentClass?: string;
16
- };
17
- declare const IITabs: import("svelte").Component<Props, {}, "value">;
18
- type IITabs = ReturnType<typeof IITabs>;
19
- export default IITabs;
@@ -1 +0,0 @@
1
- export { default as IITabs } from './IITabs.svelte';
@@ -1 +0,0 @@
1
- export { default as IITabs } from './IITabs.svelte';