@insymetri/styleguide 0.1.34 → 0.1.35

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.
@@ -0,0 +1,169 @@
1
+ <script lang="ts">
2
+ import type {Snippet} from 'svelte'
3
+ import {ContextMenu} from 'bits-ui'
4
+ import {cn} from '../utils/cn'
5
+ import {IIIcon} from '../IIIcon'
6
+
7
+ type Item = {
8
+ label: string
9
+ value: string
10
+ icon?: Snippet
11
+ disabled?: boolean
12
+ variant?: 'default' | 'destructive'
13
+ shortcut?: string
14
+ }
15
+
16
+ type SeparatorEntry = {
17
+ type: 'separator'
18
+ }
19
+
20
+ type GroupEntry = {
21
+ type: 'group'
22
+ heading?: string
23
+ items: (Item | SubEntry)[]
24
+ }
25
+
26
+ type SubEntry = {
27
+ type: 'sub'
28
+ label: string
29
+ icon?: Snippet
30
+ disabled?: boolean
31
+ shortcut?: string
32
+ items: MenuEntry[]
33
+ }
34
+
35
+ type MenuEntry = Item | SeparatorEntry | GroupEntry | SubEntry
36
+
37
+ type Props = {
38
+ items: MenuEntry[]
39
+ onSelect: (value: string) => void
40
+ open?: boolean
41
+ children: Snippet
42
+ renderItem?: Snippet<[Item]>
43
+ class?: string
44
+ }
45
+
46
+ let {
47
+ items,
48
+ onSelect,
49
+ open = $bindable(false),
50
+ children,
51
+ renderItem,
52
+ class: className,
53
+ }: Props = $props()
54
+
55
+ function handleSelect(value: string) {
56
+ onSelect(value)
57
+ open = false
58
+ }
59
+
60
+ function isSeparator(entry: MenuEntry): entry is SeparatorEntry {
61
+ return 'type' in entry && entry.type === 'separator'
62
+ }
63
+
64
+ function isGroup(entry: MenuEntry): entry is GroupEntry {
65
+ return 'type' in entry && entry.type === 'group'
66
+ }
67
+
68
+ function isSub(entry: MenuEntry): entry is SubEntry {
69
+ return 'type' in entry && entry.type === 'sub'
70
+ }
71
+
72
+ function isItem(entry: MenuEntry): entry is Item {
73
+ return !('type' in entry)
74
+ }
75
+
76
+ const itemClass = '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'
77
+ const itemDefaultClass = 'text-dropdown-item hover:bg-dropdown-item-hover data-[highlighted]:bg-dropdown-item-hover data-[highlighted]:outline-none'
78
+ const itemDestructiveClass = 'text-error hover:bg-error-bg data-[highlighted]:bg-error-bg data-[highlighted]:outline-none'
79
+ const contentClass = 'min-w-48 max-h-300 overflow-y-auto bg-dropdown-bg border border-dropdown-border rounded-10 shadow-dropdown p-4 z-12 animate-slide-in motion-reduce:animate-none'
80
+ </script>
81
+
82
+ {#snippet itemContent(item: Item)}
83
+ {#if renderItem}
84
+ {@render renderItem(item)}
85
+ {:else}
86
+ {#if item.icon}
87
+ <div class="w-16 h-16 flex items-center justify-center shrink-0 [&_svg]:w-16 [&_svg]:h-16">
88
+ {@render item.icon()}
89
+ </div>
90
+ {/if}
91
+ <span class="flex-1">{item.label}</span>
92
+ {#if item.shortcut}
93
+ <span class="text-tiny text-tertiary ml-8">{item.shortcut}</span>
94
+ {/if}
95
+ {/if}
96
+ {/snippet}
97
+
98
+ {#snippet menuItem(item: Item)}
99
+ <ContextMenu.Item
100
+ disabled={item.disabled}
101
+ class={cn(itemClass, item.variant === 'destructive' ? itemDestructiveClass : itemDefaultClass)}
102
+ onSelect={() => handleSelect(item.value)}
103
+ >
104
+ {@render itemContent(item)}
105
+ </ContextMenu.Item>
106
+ {/snippet}
107
+
108
+ {#snippet subMenu(entry: SubEntry)}
109
+ <ContextMenu.Sub>
110
+ <ContextMenu.SubTrigger
111
+ disabled={entry.disabled}
112
+ class={cn(itemClass, itemDefaultClass)}
113
+ >
114
+ {#if entry.icon}
115
+ <div class="w-16 h-16 flex items-center justify-center shrink-0 [&_svg]:w-16 [&_svg]:h-16">
116
+ {@render entry.icon()}
117
+ </div>
118
+ {/if}
119
+ <span class="flex-1">{entry.label}</span>
120
+ {#if entry.shortcut}
121
+ <span class="text-tiny text-tertiary ml-8">{entry.shortcut}</span>
122
+ {/if}
123
+ <IIIcon iconName="caret-right" class="w-12 h-12 text-tertiary shrink-0" />
124
+ </ContextMenu.SubTrigger>
125
+ <ContextMenu.Portal>
126
+ <ContextMenu.SubContent class={contentClass}>
127
+ {@render menuEntries(entry.items)}
128
+ </ContextMenu.SubContent>
129
+ </ContextMenu.Portal>
130
+ </ContextMenu.Sub>
131
+ {/snippet}
132
+
133
+ {#snippet menuEntries(entries: MenuEntry[])}
134
+ {#each entries as entry, i (i)}
135
+ {#if isSeparator(entry)}
136
+ <ContextMenu.Separator class="h-1 bg-muted mx-4 my-4" />
137
+ {:else if isGroup(entry)}
138
+ <ContextMenu.Group>
139
+ {#if entry.heading}
140
+ <ContextMenu.GroupHeading class="text-tiny-emphasis text-secondary px-12 py-4 uppercase select-none">
141
+ {entry.heading}
142
+ </ContextMenu.GroupHeading>
143
+ {/if}
144
+ {#each entry.items as groupItem (isItem(groupItem) ? groupItem.value : groupItem.label)}
145
+ {#if isSub(groupItem)}
146
+ {@render subMenu(groupItem)}
147
+ {:else}
148
+ {@render menuItem(groupItem)}
149
+ {/if}
150
+ {/each}
151
+ </ContextMenu.Group>
152
+ {:else if isSub(entry)}
153
+ {@render subMenu(entry)}
154
+ {:else if isItem(entry)}
155
+ {@render menuItem(entry)}
156
+ {/if}
157
+ {/each}
158
+ {/snippet}
159
+
160
+ <ContextMenu.Root bind:open>
161
+ <ContextMenu.Trigger class="inline-block">
162
+ {@render children()}
163
+ </ContextMenu.Trigger>
164
+ <ContextMenu.Portal>
165
+ <ContextMenu.Content class={cn(contentClass, className)}>
166
+ {@render menuEntries(items)}
167
+ </ContextMenu.Content>
168
+ </ContextMenu.Portal>
169
+ </ContextMenu.Root>
@@ -0,0 +1,162 @@
1
+ <script lang="ts">
2
+ import IIContextMenu from './IIContextMenu.svelte'
3
+ import {IIIcon} from '../IIIcon'
4
+
5
+ function handleSelect(value: string) {
6
+ console.log('Selected:', value)
7
+ }
8
+ </script>
9
+
10
+ {#snippet statusIcon()}<IIIcon iconName="clock" />{/snippet}
11
+ {#snippet clockIcon()}<IIIcon iconName="clock-clockwise" />{/snippet}
12
+ {#snippet circleIcon()}<IIIcon iconName="circle" />{/snippet}
13
+ {#snippet checkCircleIcon()}<IIIcon iconName="check-circle" />{/snippet}
14
+ {#snippet userIcon()}<IIIcon iconName="user" />{/snippet}
15
+ {#snippet warningIcon()}<IIIcon iconName="warning-circle" />{/snippet}
16
+ {#snippet listIcon()}<IIIcon iconName="list" />{/snippet}
17
+ {#snippet pencilIcon()}<IIIcon iconName="pencil-simple" />{/snippet}
18
+ {#snippet trashIcon()}<IIIcon iconName="trash" />{/snippet}
19
+
20
+ <div class="flex flex-col gap-32">
21
+ <!-- Basic -->
22
+ <section>
23
+ <h2 class="text-default-emphasis text-primary mb-8">Basic</h2>
24
+ <p class="text-small text-secondary mb-12">Right-click the area below to open the context menu.</p>
25
+ <IIContextMenu
26
+ items={[
27
+ {label: 'Edit', value: 'edit'},
28
+ {label: 'Duplicate', value: 'duplicate'},
29
+ {label: 'Archive', value: 'archive'},
30
+ {label: 'Delete', value: 'delete', variant: 'destructive'},
31
+ ]}
32
+ onSelect={handleSelect}
33
+ >
34
+ <div class="w-full h-100 border border-dashed border-muted rounded-8 flex items-center justify-center text-small text-secondary">
35
+ Right-click here
36
+ </div>
37
+ </IIContextMenu>
38
+ </section>
39
+
40
+ <!-- With Separators -->
41
+ <section>
42
+ <h2 class="text-default-emphasis text-primary mb-8">With Separators</h2>
43
+ <p class="text-small text-secondary mb-12">Menu entries can include separator dividers.</p>
44
+ <IIContextMenu
45
+ items={[
46
+ {label: 'Cut', value: 'cut', shortcut: '⌘X'},
47
+ {label: 'Copy', value: 'copy', shortcut: '⌘C'},
48
+ {label: 'Paste', value: 'paste', shortcut: '⌘V'},
49
+ {type: 'separator'},
50
+ {label: 'Delete', value: 'delete', variant: 'destructive'},
51
+ ]}
52
+ onSelect={handleSelect}
53
+ >
54
+ <div class="w-full h-100 border border-dashed border-muted rounded-8 flex items-center justify-center text-small text-secondary">
55
+ Right-click here
56
+ </div>
57
+ </IIContextMenu>
58
+ </section>
59
+
60
+ <!-- With Submenus -->
61
+ <section>
62
+ <h2 class="text-default-emphasis text-primary mb-8">With Submenus</h2>
63
+ <p class="text-small text-secondary mb-12">Hover over an item with an arrow to reveal its submenu.</p>
64
+ <IIContextMenu
65
+ items={[
66
+ {
67
+ type: 'sub',
68
+ label: 'Status',
69
+ icon: statusIcon,
70
+ shortcut: 'S',
71
+ items: [
72
+ {label: 'To Do', value: 'status-todo', icon: circleIcon},
73
+ {label: 'In Progress', value: 'status-progress', icon: clockIcon},
74
+ {label: 'Done', value: 'status-done', icon: checkCircleIcon},
75
+ ],
76
+ },
77
+ {
78
+ type: 'sub',
79
+ label: 'Assignee',
80
+ icon: userIcon,
81
+ shortcut: 'A',
82
+ items: [
83
+ {label: 'No assignee', value: 'assign-none', icon: userIcon},
84
+ {type: 'separator'},
85
+ {
86
+ type: 'group',
87
+ heading: 'Team members',
88
+ items: [
89
+ {label: 'Alice', value: 'assign-alice', icon: userIcon},
90
+ {label: 'Bob', value: 'assign-bob', icon: userIcon},
91
+ ],
92
+ },
93
+ ],
94
+ },
95
+ {
96
+ type: 'sub',
97
+ label: 'Priority',
98
+ icon: warningIcon,
99
+ shortcut: 'P',
100
+ items: [
101
+ {label: 'Urgent', value: 'priority-urgent'},
102
+ {label: 'High', value: 'priority-high'},
103
+ {label: 'Medium', value: 'priority-medium'},
104
+ {label: 'Low', value: 'priority-low'},
105
+ {label: 'No priority', value: 'priority-none'},
106
+ ],
107
+ },
108
+ {
109
+ type: 'sub',
110
+ label: 'Labels',
111
+ icon: listIcon,
112
+ shortcut: 'L',
113
+ items: [
114
+ {label: 'Bug', value: 'label-bug'},
115
+ {label: 'Feature', value: 'label-feature'},
116
+ {label: 'Improvement', value: 'label-improvement'},
117
+ ],
118
+ },
119
+ {type: 'separator'},
120
+ {label: 'Rename...', value: 'rename', icon: pencilIcon, shortcut: 'R'},
121
+ {label: 'Delete', value: 'delete', icon: trashIcon, variant: 'destructive'},
122
+ ]}
123
+ onSelect={handleSelect}
124
+ >
125
+ <div class="w-full h-100 border border-dashed border-muted rounded-8 flex items-center justify-center text-small text-secondary">
126
+ Right-click here
127
+ </div>
128
+ </IIContextMenu>
129
+ </section>
130
+
131
+ <!-- With Groups -->
132
+ <section>
133
+ <h2 class="text-default-emphasis text-primary mb-8">With Groups</h2>
134
+ <p class="text-small text-secondary mb-12">Items organized into labeled groups with headings.</p>
135
+ <IIContextMenu
136
+ items={[
137
+ {
138
+ type: 'group',
139
+ heading: 'Edit',
140
+ items: [
141
+ {label: 'Cut', value: 'cut'},
142
+ {label: 'Copy', value: 'copy'},
143
+ {label: 'Paste', value: 'paste'},
144
+ ],
145
+ },
146
+ {type: 'separator'},
147
+ {
148
+ type: 'group',
149
+ heading: 'Danger Zone',
150
+ items: [
151
+ {label: 'Delete', value: 'delete', variant: 'destructive'},
152
+ ],
153
+ },
154
+ ]}
155
+ onSelect={handleSelect}
156
+ >
157
+ <div class="w-full h-100 border border-dashed border-muted rounded-8 flex items-center justify-center text-small text-secondary">
158
+ Right-click here
159
+ </div>
160
+ </IIContextMenu>
161
+ </section>
162
+ </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 IIContextMenuStories: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type IIContextMenuStories = InstanceType<typeof IIContextMenuStories>;
18
+ export default IIContextMenuStories;
@@ -0,0 +1 @@
1
+ export { default as IIContextMenu } from './IIContextMenu.svelte';
@@ -0,0 +1 @@
1
+ export { default as IIContextMenu } from './IIContextMenu.svelte';
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { IICard } from './IICard';
13
13
  export { IICheckbox } from './IICheckbox';
14
14
  export { IICheckboxList } from './IICheckboxList';
15
15
  export { IICombobox } from './IICombobox';
16
+ export { IIContextMenu } from './IIContextMenu';
16
17
  export { IIDateInput } from './IIDateInput';
17
18
  export { IIDropdownInput } from './IIDropdownInput';
18
19
  export { IIDropdownMenu } from './IIDropdownMenu';
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ export { IICard } from './IICard';
17
17
  export { IICheckbox } from './IICheckbox';
18
18
  export { IICheckboxList } from './IICheckboxList';
19
19
  export { IICombobox } from './IICombobox';
20
+ export { IIContextMenu } from './IIContextMenu';
20
21
  export { IIDateInput } from './IIDateInput';
21
22
  export { IIDropdownInput } from './IIDropdownInput';
22
23
  export { IIDropdownMenu } from './IIDropdownMenu';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insymetri/styleguide",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "Insymetri shared UI component library built with Svelte 5",
5
5
  "type": "module",
6
6
  "scripts": {