@flightlesslabs/dodo-ui 0.7.2 → 0.8.0

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.
Files changed (98) hide show
  1. package/dist/index.d.ts +5 -0
  2. package/dist/index.js +3 -0
  3. package/dist/stories/components/Form/Select/Customize/Customize.stories.svelte +16 -1
  4. package/dist/stories/components/Form/Select/DropDownArrow/DropDownArrow.stories.svelte +59 -0
  5. package/dist/stories/components/Form/Select/DropDownArrow/DropDownArrow.stories.svelte.d.ts +18 -0
  6. package/dist/stories/components/Form/Select/Positions/AutoPosition/AutoPosition.stories.svelte +54 -0
  7. package/dist/stories/components/Form/Select/Positions/AutoPosition/AutoPosition.stories.svelte.d.ts +18 -0
  8. package/dist/stories/components/Form/Select/Positions/Positions.stories.svelte +83 -0
  9. package/dist/stories/components/Form/Select/Positions/Positions.stories.svelte.d.ts +18 -0
  10. package/dist/stories/components/Form/Select/Select.svelte +107 -123
  11. package/dist/stories/components/Form/Select/Select.svelte.d.ts +15 -2
  12. package/dist/stories/components/Layout/Menu/DynamicMenu/Customize/Customize.stories.svelte +28 -0
  13. package/dist/stories/components/Layout/Menu/DynamicMenu/Customize/Customize.stories.svelte.d.ts +18 -0
  14. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.stories.svelte +51 -0
  15. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.stories.svelte.d.ts +22 -0
  16. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte +129 -0
  17. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte.d.ts +81 -0
  18. package/dist/stories/components/Layout/Menu/DynamicMenu/Events/Events.stories.svelte +46 -0
  19. package/dist/stories/components/Layout/Menu/DynamicMenu/Events/Events.stories.svelte.d.ts +18 -0
  20. package/dist/stories/components/Layout/Menu/DynamicMenu/KeybaordNavigation/KeybaordNavigation.stories.svelte +27 -0
  21. package/dist/stories/components/Layout/Menu/DynamicMenu/KeybaordNavigation/KeybaordNavigation.stories.svelte.d.ts +18 -0
  22. package/dist/stories/components/Layout/Menu/DynamicMenu/Options/OptionFormat.mdx +72 -0
  23. package/dist/stories/components/Layout/Menu/MenuItem/MenuItem.svelte.d.ts +1 -1
  24. package/dist/stories/developer tools/components/DynamicInput/DynamicInput.svelte +1 -0
  25. package/package.json +15 -17
  26. package/src/lib/index.ts +13 -0
  27. package/src/lib/stories/Home.mdx +59 -0
  28. package/src/lib/stories/assets/dark-theme-toggle.png +0 -0
  29. package/src/lib/stories/components/Form/Button/Button.stories.svelte +61 -0
  30. package/src/lib/stories/components/Form/Button/Color/Color.stories.svelte +43 -0
  31. package/src/lib/stories/components/Form/Button/Events/Events.stories.svelte +36 -0
  32. package/src/lib/stories/components/Form/Button/IconButton/IconButton.stories.svelte +43 -0
  33. package/src/lib/stories/components/Form/Button/Roundness/Roundness.stories.svelte +23 -0
  34. package/src/lib/stories/components/Form/Button/Size/Size.stories.svelte +16 -0
  35. package/src/lib/stories/components/Form/Button/Variant/Variant.stories.svelte +18 -0
  36. package/src/lib/stories/components/Form/FormControl/FormControl.stories.svelte +28 -0
  37. package/src/lib/stories/components/Form/Label/Label.stories.svelte +13 -0
  38. package/src/lib/stories/components/Form/Message/Color/Color.stories.svelte +36 -0
  39. package/src/lib/stories/components/Form/Message/Message.stories.svelte +27 -0
  40. package/src/lib/stories/components/Form/Message/Size/Size.stories.svelte +22 -0
  41. package/src/lib/stories/components/Form/PasswordInput/Events/Events.stories.svelte +110 -0
  42. package/src/lib/stories/components/Form/PasswordInput/PasswordInput.stories.svelte +59 -0
  43. package/src/lib/stories/components/Form/PasswordInput/Roundness/Roundness.stories.svelte +20 -0
  44. package/src/lib/stories/components/Form/PasswordInput/Size/Size.stories.svelte +16 -0
  45. package/src/lib/stories/components/Form/PasswordInput/WithIcon/WithIcon.stories.svelte +31 -0
  46. package/src/lib/stories/components/Form/Select/Customize/Customize.stories.svelte +139 -0
  47. package/src/lib/stories/components/Form/Select/DropDownArrow/DropDownArrow.stories.svelte +63 -0
  48. package/src/lib/stories/components/Form/Select/Events/Events.stories.svelte +144 -0
  49. package/src/lib/stories/components/Form/Select/Options/OptionFormat.mdx +40 -0
  50. package/src/lib/stories/components/Form/Select/Positions/AutoPosition/AutoPosition.stories.svelte +58 -0
  51. package/src/lib/stories/components/Form/Select/Positions/Positions.stories.svelte +87 -0
  52. package/src/lib/stories/components/Form/Select/Roundness/Roundness.stories.svelte +32 -0
  53. package/src/lib/stories/components/Form/Select/Select.stories.svelte +121 -0
  54. package/src/lib/stories/components/Form/Select/Select.svelte +153 -146
  55. package/src/lib/stories/components/Form/Select/Size/Size.stories.svelte +28 -0
  56. package/src/lib/stories/components/Form/Select/WithIcon/WithIcon.stories.svelte +43 -0
  57. package/src/lib/stories/components/Form/TextInput/Events/Events.stories.svelte +97 -0
  58. package/src/lib/stories/components/Form/TextInput/Roundness/Roundness.stories.svelte +21 -0
  59. package/src/lib/stories/components/Form/TextInput/Size/Size.stories.svelte +17 -0
  60. package/src/lib/stories/components/Form/TextInput/TextInput.stories.svelte +43 -0
  61. package/src/lib/stories/components/Form/TextInput/WithIcon/WithIcon.stories.svelte +47 -0
  62. package/src/lib/stories/components/Layout/Menu/DynamicMenu/Customize/Customize.stories.svelte +30 -0
  63. package/src/lib/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.stories.svelte +56 -0
  64. package/src/lib/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte +262 -0
  65. package/src/lib/stories/components/Layout/Menu/DynamicMenu/Events/Events.stories.svelte +48 -0
  66. package/src/lib/stories/components/Layout/Menu/DynamicMenu/KeybaordNavigation/KeybaordNavigation.stories.svelte +29 -0
  67. package/src/lib/stories/components/Layout/Menu/DynamicMenu/Options/OptionFormat.mdx +72 -0
  68. package/src/lib/stories/components/Layout/Menu/Menu.stories.svelte +69 -0
  69. package/src/lib/stories/components/Layout/Menu/MenuItem/MenuItem.stories.svelte +34 -0
  70. package/src/lib/stories/components/Layout/Menu/MenuItem/MenuItem.svelte +1 -1
  71. package/src/lib/stories/components/Layout/Menu/MenuItem/Size/Size.stories.svelte +16 -0
  72. package/src/lib/stories/components/Layout/Menu/MenuItem/Type/Type.stories.svelte +21 -0
  73. package/src/lib/stories/components/Layout/Menu/Size/Size.stories.svelte +37 -0
  74. package/src/lib/stories/components/Layout/Paper/Color/Color.stories.svelte +63 -0
  75. package/src/lib/stories/components/Layout/Paper/Paper.stories.svelte +50 -0
  76. package/src/lib/stories/components/Layout/Paper/Roundness/Roundness.stories.svelte +25 -0
  77. package/src/lib/stories/components/Layout/Paper/Shadow/Shadow.stories.svelte +24 -0
  78. package/src/lib/stories/components/Layout/Separator/Color/Color.stories.svelte +19 -0
  79. package/src/lib/stories/components/Layout/Separator/Separator.stories.svelte +30 -0
  80. package/src/lib/stories/developer tools/Intro.mdx +9 -0
  81. package/src/lib/stories/developer tools/components/DynamicInput/DynamicInput.stories.svelte +53 -0
  82. package/src/lib/stories/developer tools/components/DynamicInput/DynamicInput.svelte +1 -0
  83. package/src/lib/stories/developer tools/components/DynamicInput/Size/Size.stories.svelte +17 -0
  84. package/src/lib/stories/developer tools/components/InputEnclosure/InputEnclosure.stories.svelte +38 -0
  85. package/src/lib/stories/developer tools/components/InputEnclosure/Roundness/Roundness.stories.svelte +20 -0
  86. package/src/lib/stories/developer tools/components/InputEnclosure/Size/Size.stories.svelte +16 -0
  87. package/src/lib/stories/developer tools/components/InputEnclosure/WithIcon/WithIcon.stories.svelte +47 -0
  88. package/src/lib/stories/developer tools/components/Popper/Popper.stories.svelte +124 -0
  89. package/src/lib/stories/developer tools/components/Popper/PopperPopup/PopperPopup.stories.svelte +64 -0
  90. package/src/lib/stories/developer tools/components/Popper/Positions/AutoPosition/AutoPosition.stories.svelte +92 -0
  91. package/src/lib/stories/developer tools/components/Popper/Positions/Positions.stories.svelte +114 -0
  92. package/src/lib/stories/developer tools/components/UtilityButton/Size/Size.stories.svelte +25 -0
  93. package/src/lib/stories/developer tools/components/UtilityButton/UtilityButton.stories.svelte +30 -0
  94. package/src/lib/stories/developer tools/directives/clickOutside/index.mdx +25 -0
  95. package/src/lib/stories/developer tools/philosophy/Colors/Colors.mdx +43 -0
  96. package/src/lib/stories/developer tools/philosophy/Colors/Colors.stories.svelte +22 -0
  97. package/src/lib/stories/developer tools/philosophy/Colors/Opacity.stories.svelte +11 -0
  98. package/src/lib/stories/developer tools/philosophy/Themes.mdx +23 -0
@@ -0,0 +1,121 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import Select, { type SelectOption } from './Select.svelte';
4
+ import type { StoryBookArgTypes } from '$lib/storybook-types.js';
5
+ import { componentRoundnessArray } from '$lib/types/roundness.js';
6
+ import { componentSizeArray } from '$lib/types/size.js';
7
+
8
+ export const storySelectArgTypes: StoryBookArgTypes = {
9
+ roundness: {
10
+ control: { type: 'select' },
11
+ options: componentRoundnessArray,
12
+ },
13
+ size: {
14
+ control: { type: 'select' },
15
+ options: componentSizeArray,
16
+ },
17
+ };
18
+
19
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
20
+ const { Story } = defineMeta({
21
+ component: Select,
22
+ tags: ['autodocs'],
23
+ argTypes: storySelectArgTypes,
24
+ parameters: {
25
+ docs: {
26
+ story: {
27
+ height: '400px',
28
+ inline: false,
29
+ },
30
+ },
31
+ },
32
+ });
33
+
34
+ export const selectOptions: SelectOption[] = [
35
+ {
36
+ value: 1,
37
+ label: 'One',
38
+ },
39
+ {
40
+ value: 2,
41
+ label: 'Two',
42
+ },
43
+ {
44
+ value: 3,
45
+ label: 'Three',
46
+ },
47
+ {
48
+ value: 4,
49
+ label: 'Four',
50
+ },
51
+ {
52
+ value: 5,
53
+ label: 'Five',
54
+ },
55
+ {
56
+ value: 6,
57
+ label: 'Six',
58
+ disabled: true,
59
+ },
60
+ {
61
+ value: 7,
62
+ label: 'Seven',
63
+ },
64
+ {
65
+ value: 8,
66
+ label: 'Eight',
67
+ },
68
+ {
69
+ value: 9,
70
+ label: 'Nine',
71
+ },
72
+ ];
73
+
74
+ const options = selectOptions;
75
+
76
+ let value = $state<SelectOption | undefined>(options[0]);
77
+ </script>
78
+
79
+ <!-- Select with default style -->
80
+ <Story name="Default" asChild>
81
+ <Select {options} {value} onselect={(val: SelectOption) => (value = val)} />
82
+ </Story>
83
+
84
+ <Story name="Searchable" asChild>
85
+ <Select
86
+ {options}
87
+ {value}
88
+ searchable
89
+ optionsPlaceholder="No results"
90
+ onselect={(val: SelectOption) => (value = val)}
91
+ />
92
+ </Story>
93
+
94
+ <Story name="Clearable" asChild>
95
+ <Select
96
+ {options}
97
+ {value}
98
+ clearable
99
+ onselect={(val: SelectOption) => (value = val)}
100
+ onclear={() => (value = undefined)}
101
+ placeholder="Select option"
102
+ />
103
+ </Story>
104
+
105
+ <Story name="PopupMaxHeight" asChild>
106
+ <Select
107
+ {options}
108
+ {value}
109
+ onselect={(val: SelectOption) => (value = val)}
110
+ popupMaxHeight="200px"
111
+ />
112
+ </Story>
113
+
114
+ <!-- Format look and feel of input content. [More Customizations](?path=/docs/components-form-select-customize--docs) -->
115
+ <Story name="CustomInputContent" asChild>
116
+ <Select {options} {value} onselect={(val: SelectOption) => (value = val)}>
117
+ {#snippet customInputContent(selectedOption)}
118
+ {selectedOption.label} 💯💯💯
119
+ {/snippet}
120
+ </Select>
121
+ </Story>
@@ -16,6 +16,8 @@
16
16
  disabled?: boolean;
17
17
  };
18
18
 
19
+ export type SelectDropdownArrowPosition = false | 'before' | 'after';
20
+
19
21
  export interface SelectProps {
20
22
  /** How large should the button be? */
21
23
  size?: ComponentSize;
@@ -76,7 +78,11 @@
76
78
  /** custom Content Formatting for variant button */
77
79
  customMenuItemContent?: (val: SelectOption, selected: boolean) => Snippet;
78
80
  /** Custom Popup Content */
79
- customPopupContent?: (options: SelectOption[], selectedOption: SelectOption) => Snippet;
81
+ customPopupContent?: (
82
+ options: SelectOption[],
83
+ selectedOption: SelectOption,
84
+ onselect: (val: SelectOption) => void,
85
+ ) => Snippet;
80
86
  /** custom Content Formatting for variant button */
81
87
  customPlaceholderMenuItemContent?: () => Snippet;
82
88
  /** PopperPopup Max height. Use css compatible value */
@@ -89,6 +95,18 @@
89
95
  menuProps?: Partial<MenuProps>;
90
96
  /** MenuItem: Menu component props */
91
97
  menuItemProps?: Partial<MenuItemProps>;
98
+ /** Dropdown Arrow Icon */
99
+ customDropdownArrowIcon?: (open: boolean) => Snippet;
100
+ /** Select Dropdown Arrow Position */
101
+ dropdownArrowPosition?: SelectDropdownArrowPosition;
102
+ /** Popup stick horizontally */
103
+ popupPositionX?: PositionX;
104
+ /** Popup stick vertically */
105
+ popupPositionY?: PositionY;
106
+ /** Lock positions, disable auto top, bottom position based */
107
+ lockPoistions?: boolean;
108
+ /** Popper Height For Vertical Position, default 300 */
109
+ popperHeightForVerticalPosition?: number;
92
110
  }
93
111
  </script>
94
112
 
@@ -98,14 +116,16 @@
98
116
  import Icon from '@iconify/svelte';
99
117
  import {
100
118
  DynamicInput,
101
- Menu,
102
- MenuItem,
119
+ DynamicMenu,
103
120
  Popper,
104
121
  type DynamicInputFocusEvent,
122
+ type DynamicMenuItemOption,
105
123
  type MenuItemProps,
106
124
  type MenuProps,
107
125
  type PaperProps,
108
126
  type PopperProps,
127
+ type PositionX,
128
+ type PositionY,
109
129
  } from '$lib/index.js';
110
130
  import type { TextInputInputEvent } from '../TextInput/TextInput.svelte';
111
131
  import type { ButtonClickEvent } from '../Button/Button.svelte';
@@ -141,37 +161,56 @@
141
161
  customMenuItemContent: customMenuItemContentInternal,
142
162
  customPopupContent: customPopupContentInternal,
143
163
  customPlaceholderMenuItemContent: customPlaceholderMenuItemContentInternal,
164
+ customDropdownArrowIcon: customDropdownArrowIconInternal,
144
165
  popupMaxHeight = '300px',
145
166
  paperProps,
146
167
  popperProps,
147
168
  menuProps,
148
169
  menuItemProps,
149
170
  optionsPlaceholder = 'No Options',
171
+ dropdownArrowPosition = 'after',
172
+ popupPositionX,
173
+ popupPositionY,
174
+ lockPoistions,
175
+ popperHeightForVerticalPosition,
150
176
  }: SelectProps = $props();
151
177
 
178
+ function convertOptionsToDynamicMenuItemOptions(
179
+ options: SelectOption[],
180
+ ): DynamicMenuItemOption<SelectOption>[] {
181
+ const newOptions: DynamicMenuItemOption<SelectOption>[] = options.map((option) => ({
182
+ id: `opt-${option.value}`,
183
+ disabled: option.disabled,
184
+ label: option.label,
185
+ meta: option,
186
+ type: 'button',
187
+ }));
188
+
189
+ return newOptions;
190
+ }
191
+
152
192
  let open: boolean = $state(false);
153
193
  let onInputStart: boolean = $state(false);
154
194
  const selectedOption = $derived(value);
155
195
  let searchTerm = $state(value?.label.trim() || '');
156
- let options = $state(optionsRaw);
196
+ let options = $state(convertOptionsToDynamicMenuItemOptions(optionsRaw));
157
197
  let menuRef = $state<HTMLUListElement | undefined>(undefined);
158
- let menuItemIndex = $state(0);
159
198
 
160
199
  $effect(() => {
161
200
  if (!onInputStart) {
162
- options = optionsRaw;
201
+ options = convertOptionsToDynamicMenuItemOptions(optionsRaw);
163
202
  return;
164
203
  }
165
204
 
166
205
  const searchTermSimplified = searchTerm.trim().toLowerCase();
167
206
 
168
207
  if (!searchTermSimplified) {
169
- options = optionsRaw;
208
+ options = convertOptionsToDynamicMenuItemOptions(optionsRaw);
170
209
  return;
171
210
  }
172
211
 
173
- options = optionsRaw.filter((item) =>
174
- item.label.trim().toLowerCase().includes(searchTermSimplified),
212
+ options = convertOptionsToDynamicMenuItemOptions(optionsRaw).filter((item) =>
213
+ item?.meta?.label.trim().toLowerCase().includes(searchTermSimplified),
175
214
  );
176
215
  });
177
216
 
@@ -187,23 +226,17 @@
187
226
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
188
227
  let customPlaceholderMenuItemContentTyped = customPlaceholderMenuItemContentInternal as any;
189
228
 
229
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
230
+ let customDropdownArrowIconTyped = customDropdownArrowIconInternal as any;
231
+
190
232
  function closeMenu() {
191
233
  open = false;
192
- menuItemIndex = 0;
193
234
 
194
235
  ref?.blur();
195
236
  }
196
237
 
197
238
  function openMenu() {
198
239
  open = true;
199
-
200
- const menuItemIndexNew = options.findIndex((item) => item.value === selectedOption?.value);
201
-
202
- if (menuItemIndexNew < 0) {
203
- menuItemIndex = 0;
204
- } else {
205
- menuItemIndex = menuItemIndexNew;
206
- }
207
240
  }
208
241
 
209
242
  function onfocusMod(e: DynamicInputFocusEvent) {
@@ -259,7 +292,9 @@
259
292
  }
260
293
  }
261
294
 
262
- function onKeyBoardEnter(selectedItemIndex: number) {
295
+ function onKeyBoardEnter(e: KeyboardEvent, selectedItemIndex: number) {
296
+ e.preventDefault();
297
+
263
298
  const targetOption = options[selectedItemIndex];
264
299
 
265
300
  if (!targetOption) {
@@ -270,106 +305,35 @@
270
305
  return;
271
306
  }
272
307
 
273
- onselectMod(targetOption);
274
- }
275
-
276
- function onKeyboardNavigation(e: KeyboardEvent) {
277
- let keyHit: string | undefined = undefined;
278
-
279
- if (!menuRef) {
280
- return;
281
- }
282
-
283
- if (!open) {
284
- return;
285
- }
286
-
287
- switch (e.key) {
288
- case 'ArrowDown':
289
- case 'ArrowUp':
290
- case 'Enter':
291
- keyHit = e.key;
292
- e.preventDefault();
293
- break;
294
- default:
295
- break;
296
- }
297
-
298
- if (!keyHit) {
299
- return;
300
- }
301
-
302
- const listItems = menuRef.querySelectorAll(':scope > li.dodo-ui-MenuItem');
303
-
304
- if (!listItems.length) {
305
- return;
306
- }
307
-
308
- for (let index = 0; index < listItems.length; index++) {
309
- const element = listItems[index];
310
-
311
- element.classList.remove('hover');
312
- }
313
-
314
- let newMenuItemIndex = menuItemIndex;
315
-
316
- if (keyHit === 'ArrowDown') {
317
- if (listItems[newMenuItemIndex + 1]) {
318
- newMenuItemIndex = newMenuItemIndex + 1;
319
- } else {
320
- newMenuItemIndex = 0;
321
- }
322
- } else if (keyHit === 'ArrowUp') {
323
- if (listItems[newMenuItemIndex - 1]) {
324
- newMenuItemIndex = newMenuItemIndex - 1;
325
- } else {
326
- newMenuItemIndex = listItems.length - 1;
327
- }
328
- } else if (keyHit === 'Enter') {
329
- onKeyBoardEnter(newMenuItemIndex);
330
- return;
331
- }
332
-
333
- const targetItem = listItems[newMenuItemIndex] as HTMLLIElement;
334
-
335
- targetItem.classList.add('hover');
336
-
337
- targetItem.focus();
338
- targetItem.scrollIntoView({ block: 'nearest' });
339
-
340
- menuItemIndex = newMenuItemIndex;
308
+ onselectMod(targetOption.meta as SelectOption);
341
309
  }
342
-
343
- $effect(() => {
344
- if (!menuRef) {
345
- return;
346
- }
347
-
348
- if (!open) {
349
- return;
350
- }
351
-
352
- const targetItem = menuRef.querySelector(':scope > li.dodo-ui-MenuItem.selected') as
353
- | HTMLLIElement
354
- | undefined;
355
-
356
- if (targetItem) {
357
- targetItem.classList.add('hover');
358
-
359
- targetItem.focus();
360
- targetItem.scrollIntoView({ block: 'nearest' });
361
- }
362
-
363
- window.addEventListener('keydown', onKeyboardNavigation);
364
-
365
- return () => {
366
- window.removeEventListener('keydown', onKeyboardNavigation);
367
- };
368
- });
369
310
  </script>
370
311
 
312
+ {#snippet selectDropdownArrowIcon()}
313
+ <UtilityButton {size} title="Dropdown" onclick={onfocusMod}>
314
+ {#if customDropdownArrowIconTyped}
315
+ {@render customDropdownArrowIconTyped(open)}
316
+ {:else if open}
317
+ <Icon icon="material-symbols:arrow-drop-up-rounded" width="28" height="28" />
318
+ {:else}
319
+ <Icon icon="material-symbols:arrow-drop-down-rounded" width="28" height="28" />
320
+ {/if}
321
+ </UtilityButton>
322
+ {/snippet}
323
+
371
324
  <div class={['dodo-ui-Select', className].join(' ')}>
372
- <Popper fullWidth {open} {onClickOutside} {...popperProps} {popupMaxHeight} {paperProps}>
325
+ <Popper
326
+ fullWidth
327
+ {open}
328
+ {onClickOutside}
329
+ {...popperProps}
330
+ {popupMaxHeight}
331
+ {popupPositionX}
332
+ {popupPositionY}
333
+ {paperProps}
334
+ {lockPoistions}
335
+ {popperHeightForVerticalPosition}
336
+ >
373
337
  <div
374
338
  class:outline
375
339
  class:disabled
@@ -392,6 +356,12 @@
392
356
  {before}
393
357
  {after}
394
358
  >
359
+ {#if dropdownArrowPosition === 'before'}
360
+ <div class:before class:open class="DropdownArrow">
361
+ {@render selectDropdownArrowIcon()}
362
+ </div>
363
+ {/if}
364
+
395
365
  <DynamicInput
396
366
  type="text"
397
367
  {name}
@@ -426,43 +396,50 @@
426
396
  </UtilityButton>
427
397
  </div>
428
398
  {/if}
399
+
400
+ {#if dropdownArrowPosition === 'after'}
401
+ <div class:after class:open class="DropdownArrow">
402
+ {@render selectDropdownArrowIcon()}
403
+ </div>
404
+ {/if}
429
405
  </InputEnclosure>
430
406
  </div>
431
407
 
432
408
  {#snippet popupChildren()}
433
409
  {#if customPopupContentTyped}
434
- {@render customPopupContentTyped(options, selectedOption)}
410
+ {@render customPopupContentTyped(optionsRaw, selectedOption, onselectMod)}
435
411
  {:else}
436
- <Menu bind:ref={menuRef} {...menuProps}>
437
- {#if options.length}
438
- {#each options as option (option.value)}
439
- <MenuItem
440
- onclick={() => onselectMod(option)}
441
- type="button"
442
- disabled={option.disabled}
443
- selected={selectedOption?.value === option.value}
444
- {...menuItemProps}
445
- >
446
- {#if customMenuItemContentTyped}
447
- {@render customMenuItemContentTyped(
448
- option,
449
- selectedOption?.value === option.value,
450
- )}
451
- {:else}
452
- {option.label}
453
- {/if}
454
- </MenuItem>
455
- {/each}
456
- {:else}
457
- <MenuItem type="text" disabled={true} {...menuItemProps}>
458
- {#if customPlaceholderMenuItemContentTyped}
459
- {@render customPlaceholderMenuItemContentTyped()}
460
- {:else}
461
- {optionsPlaceholder}
462
- {/if}
463
- </MenuItem>
464
- {/if}
465
- </Menu>
412
+ <DynamicMenu
413
+ bind:ref={menuRef}
414
+ {menuItemProps}
415
+ {options}
416
+ keyboardNavigation
417
+ onEnter={onKeyBoardEnter}
418
+ selectedOption={options.find((item) => item.meta?.value === selectedOption?.value)}
419
+ onclick={(_e, option: DynamicMenuItemOption) => onselectMod(option.meta as SelectOption)}
420
+ showOptionsPlaceholder
421
+ {...menuProps}
422
+ >
423
+ {#snippet customMenuItemContent(option, selectedOption)}
424
+ {#if customMenuItemContentTyped}
425
+ {@render customMenuItemContentTyped(
426
+ option?.meta as SelectOption,
427
+ (selectedOption?.meta as SelectOption).value ===
428
+ (option?.meta as SelectOption).value,
429
+ )}
430
+ {:else}
431
+ {(option?.meta as SelectOption).label}
432
+ {/if}
433
+ {/snippet}
434
+
435
+ {#snippet customPlaceholderMenuItemContent()}
436
+ {#if customPlaceholderMenuItemContentTyped}
437
+ {@render customPlaceholderMenuItemContentTyped()}
438
+ {:else}
439
+ {optionsPlaceholder}
440
+ {/if}
441
+ {/snippet}
442
+ </DynamicMenu>
466
443
  {/if}
467
444
  {/snippet}
468
445
  </Popper>
@@ -478,6 +455,16 @@
478
455
  margin-right: calc(var(--dodo-ui-space-small) * 2);
479
456
  }
480
457
  }
458
+
459
+ .DropdownArrow {
460
+ &.after {
461
+ margin-right: calc(var(--dodo-ui-space-small) * 2);
462
+ }
463
+
464
+ &.before {
465
+ margin-right: calc(var(--dodo-ui-space-small) * 2);
466
+ }
467
+ }
481
468
  }
482
469
 
483
470
  &--small {
@@ -486,6 +473,16 @@
486
473
  margin-right: var(--dodo-ui-space);
487
474
  }
488
475
  }
476
+
477
+ .DropdownArrow {
478
+ &.after {
479
+ margin-right: var(--dodo-ui-space);
480
+ }
481
+
482
+ &.before {
483
+ margin-right: var(--dodo-ui-space);
484
+ }
485
+ }
489
486
  }
490
487
 
491
488
  &--large {
@@ -494,6 +491,16 @@
494
491
  margin-right: calc(var(--dodo-ui-space) * 2);
495
492
  }
496
493
  }
494
+
495
+ .DropdownArrow {
496
+ &.after {
497
+ margin-right: calc(var(--dodo-ui-space) * 2);
498
+ }
499
+
500
+ &.before {
501
+ margin-right: calc(var(--dodo-ui-space) * 2);
502
+ }
503
+ }
497
504
  }
498
505
  }
499
506
  }
@@ -0,0 +1,28 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import Select, { type SelectOption } from '../Select.svelte';
4
+ import { selectOptions, storySelectArgTypes } from '../Select.stories.svelte';
5
+
6
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
7
+ const { Story } = defineMeta({
8
+ component: Select,
9
+ tags: ['autodocs'],
10
+ argTypes: storySelectArgTypes,
11
+ parameters: {
12
+ docs: {
13
+ story: {
14
+ height: '400px',
15
+ inline: false,
16
+ },
17
+ },
18
+ },
19
+ });
20
+
21
+ const options = selectOptions;
22
+
23
+ let value = $state<SelectOption>(options[0]);
24
+ </script>
25
+
26
+ <Story name="Normal" args={{ options, value }} />
27
+ <Story name="Small" args={{ options, value, size: 'small' }} />
28
+ <Story name="Large" args={{ options, value, size: 'large' }} />
@@ -0,0 +1,43 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import Select, { type SelectOption } from '../Select.svelte';
4
+ import { selectOptions, storySelectArgTypes } from '../Select.stories.svelte';
5
+ import Icon from '@iconify/svelte';
6
+
7
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
8
+ const { Story } = defineMeta({
9
+ component: Select,
10
+ tags: ['autodocs'],
11
+ argTypes: storySelectArgTypes,
12
+ parameters: {
13
+ docs: {
14
+ story: {
15
+ height: '400px',
16
+ inline: false,
17
+ },
18
+ },
19
+ },
20
+ });
21
+
22
+ const options = selectOptions;
23
+
24
+ let value = $state<SelectOption>(options[0]);
25
+ </script>
26
+
27
+ <!-- Select icon in front. -->
28
+ <Story name="Icon Before" asChild>
29
+ <Select {value} {options}>
30
+ {#snippet before()}
31
+ <Icon icon="material-symbols:content-copy" />
32
+ {/snippet}
33
+ </Select>
34
+ </Story>
35
+
36
+ <!-- Select icon in front. -->
37
+ <Story name="Icon After" asChild>
38
+ <Select {value} {options}>
39
+ {#snippet after()}
40
+ <Icon icon="material-symbols:download-2" />
41
+ {/snippet}
42
+ </Select>
43
+ </Story>