@flightlesslabs/dodo-ui 0.7.2 → 0.9.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 (138) hide show
  1. package/dist/index.d.ts +18 -2
  2. package/dist/index.js +11 -0
  3. package/dist/stories/components/Form/NumericInput/Events/Events.stories.svelte +126 -0
  4. package/dist/stories/components/Form/NumericInput/Events/Events.stories.svelte.d.ts +18 -0
  5. package/dist/stories/components/Form/NumericInput/NumericInput.stories.svelte +79 -0
  6. package/dist/stories/components/Form/NumericInput/NumericInput.stories.svelte.d.ts +21 -0
  7. package/dist/stories/components/Form/NumericInput/NumericInput.svelte +161 -0
  8. package/dist/stories/components/Form/NumericInput/NumericInput.svelte.d.ts +69 -0
  9. package/dist/stories/components/Form/NumericInput/Validation/Validation.stories.svelte +84 -0
  10. package/dist/stories/components/Form/NumericInput/Validation/Validation.stories.svelte.d.ts +18 -0
  11. package/dist/stories/components/Form/PasswordInput/Events/Events.stories.svelte +27 -6
  12. package/dist/stories/components/Form/PasswordInput/PasswordInput.svelte +5 -3
  13. package/dist/stories/components/Form/PasswordInput/PasswordInput.svelte.d.ts +7 -1
  14. package/dist/stories/components/Form/Select/Customize/Customize.stories.svelte +16 -1
  15. package/dist/stories/components/Form/Select/DropDownArrow/DropDownArrow.stories.svelte +59 -0
  16. package/dist/stories/components/Form/Select/DropDownArrow/DropDownArrow.stories.svelte.d.ts +18 -0
  17. package/dist/stories/components/Form/Select/Events/Events.stories.svelte +27 -0
  18. package/dist/stories/components/Form/Select/Positions/AutoPosition/AutoPosition.stories.svelte +54 -0
  19. package/dist/stories/components/Form/Select/Positions/AutoPosition/AutoPosition.stories.svelte.d.ts +18 -0
  20. package/dist/stories/components/Form/Select/Positions/Positions.stories.svelte +83 -0
  21. package/dist/stories/components/Form/Select/Positions/Positions.stories.svelte.d.ts +18 -0
  22. package/dist/stories/components/Form/Select/Select.svelte +110 -123
  23. package/dist/stories/components/Form/Select/Select.svelte.d.ts +22 -3
  24. package/dist/stories/components/Form/TextInput/Events/Events.stories.svelte +27 -0
  25. package/dist/stories/components/Form/TextInput/TextInput.svelte +5 -3
  26. package/dist/stories/components/Form/TextInput/TextInput.svelte.d.ts +10 -1
  27. package/dist/stories/components/Layout/Menu/DynamicMenu/Customize/Customize.stories.svelte +28 -0
  28. package/dist/stories/components/Layout/Menu/DynamicMenu/Customize/Customize.stories.svelte.d.ts +18 -0
  29. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.stories.svelte +51 -0
  30. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.stories.svelte.d.ts +22 -0
  31. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte +129 -0
  32. package/dist/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte.d.ts +81 -0
  33. package/dist/stories/components/Layout/Menu/DynamicMenu/Events/Events.stories.svelte +46 -0
  34. package/dist/stories/components/Layout/Menu/DynamicMenu/Events/Events.stories.svelte.d.ts +18 -0
  35. package/dist/stories/components/Layout/Menu/DynamicMenu/KeybaordNavigation/KeybaordNavigation.stories.svelte +27 -0
  36. package/dist/stories/components/Layout/Menu/DynamicMenu/KeybaordNavigation/KeybaordNavigation.stories.svelte.d.ts +18 -0
  37. package/dist/stories/components/Layout/Menu/DynamicMenu/Options/OptionFormat.mdx +72 -0
  38. package/dist/stories/components/Layout/Menu/MenuItem/MenuItem.svelte.d.ts +1 -1
  39. package/dist/stories/developer tools/components/DynamicInput/DynamicInput.svelte +23 -4
  40. package/dist/stories/developer tools/components/DynamicInput/DynamicInput.svelte.d.ts +13 -2
  41. package/dist/stories/developer tools/components/DynamicInput/Events/Events.stories.svelte +115 -0
  42. package/dist/stories/developer tools/components/DynamicInput/Events/Events.stories.svelte.d.ts +18 -0
  43. package/dist/stories/developer tools/helpers/Numbers/cleanNumericString/cleanNumericString.d.ts +13 -0
  44. package/dist/stories/developer tools/helpers/Numbers/cleanNumericString/cleanNumericString.js +26 -0
  45. package/dist/stories/developer tools/helpers/Numbers/cleanNumericString/index.mdx +20 -0
  46. package/dist/stories/developer tools/helpers/Numbers/isValidNumberValue/index.mdx +71 -0
  47. package/dist/stories/developer tools/helpers/Numbers/isValidNumberValue/isValidNumberValue.d.ts +51 -0
  48. package/dist/stories/developer tools/helpers/Numbers/isValidNumberValue/isValidNumberValue.js +96 -0
  49. package/dist/stories/developer tools/helpers/logger/index.mdx +63 -0
  50. package/dist/stories/developer tools/helpers/logger/logger.d.ts +24 -0
  51. package/dist/stories/developer tools/helpers/logger/logger.js +31 -0
  52. package/package.json +15 -17
  53. package/src/lib/index.ts +33 -0
  54. package/src/lib/stories/Home.mdx +59 -0
  55. package/src/lib/stories/assets/dark-theme-toggle.png +0 -0
  56. package/src/lib/stories/components/Form/Button/Button.stories.svelte +61 -0
  57. package/src/lib/stories/components/Form/Button/Color/Color.stories.svelte +43 -0
  58. package/src/lib/stories/components/Form/Button/Events/Events.stories.svelte +36 -0
  59. package/src/lib/stories/components/Form/Button/IconButton/IconButton.stories.svelte +43 -0
  60. package/src/lib/stories/components/Form/Button/Roundness/Roundness.stories.svelte +23 -0
  61. package/src/lib/stories/components/Form/Button/Size/Size.stories.svelte +16 -0
  62. package/src/lib/stories/components/Form/Button/Variant/Variant.stories.svelte +18 -0
  63. package/src/lib/stories/components/Form/FormControl/FormControl.stories.svelte +28 -0
  64. package/src/lib/stories/components/Form/Label/Label.stories.svelte +13 -0
  65. package/src/lib/stories/components/Form/Message/Color/Color.stories.svelte +36 -0
  66. package/src/lib/stories/components/Form/Message/Message.stories.svelte +27 -0
  67. package/src/lib/stories/components/Form/Message/Size/Size.stories.svelte +22 -0
  68. package/src/lib/stories/components/Form/NumericInput/Events/Events.stories.svelte +134 -0
  69. package/src/lib/stories/components/Form/NumericInput/NumericInput.stories.svelte +84 -0
  70. package/src/lib/stories/components/Form/NumericInput/NumericInput.svelte +286 -0
  71. package/src/lib/stories/components/Form/NumericInput/Validation/Validation.stories.svelte +87 -0
  72. package/src/lib/stories/components/Form/PasswordInput/Events/Events.stories.svelte +132 -0
  73. package/src/lib/stories/components/Form/PasswordInput/PasswordInput.stories.svelte +59 -0
  74. package/src/lib/stories/components/Form/PasswordInput/PasswordInput.svelte +15 -3
  75. package/src/lib/stories/components/Form/PasswordInput/Roundness/Roundness.stories.svelte +20 -0
  76. package/src/lib/stories/components/Form/PasswordInput/Size/Size.stories.svelte +16 -0
  77. package/src/lib/stories/components/Form/PasswordInput/WithIcon/WithIcon.stories.svelte +31 -0
  78. package/src/lib/stories/components/Form/Select/Customize/Customize.stories.svelte +139 -0
  79. package/src/lib/stories/components/Form/Select/DropDownArrow/DropDownArrow.stories.svelte +63 -0
  80. package/src/lib/stories/components/Form/Select/Events/Events.stories.svelte +174 -0
  81. package/src/lib/stories/components/Form/Select/Options/OptionFormat.mdx +40 -0
  82. package/src/lib/stories/components/Form/Select/Positions/AutoPosition/AutoPosition.stories.svelte +58 -0
  83. package/src/lib/stories/components/Form/Select/Positions/Positions.stories.svelte +87 -0
  84. package/src/lib/stories/components/Form/Select/Roundness/Roundness.stories.svelte +32 -0
  85. package/src/lib/stories/components/Form/Select/Select.stories.svelte +121 -0
  86. package/src/lib/stories/components/Form/Select/Select.svelte +166 -146
  87. package/src/lib/stories/components/Form/Select/Size/Size.stories.svelte +28 -0
  88. package/src/lib/stories/components/Form/Select/WithIcon/WithIcon.stories.svelte +43 -0
  89. package/src/lib/stories/components/Form/TextInput/Events/Events.stories.svelte +125 -0
  90. package/src/lib/stories/components/Form/TextInput/Roundness/Roundness.stories.svelte +21 -0
  91. package/src/lib/stories/components/Form/TextInput/Size/Size.stories.svelte +17 -0
  92. package/src/lib/stories/components/Form/TextInput/TextInput.stories.svelte +43 -0
  93. package/src/lib/stories/components/Form/TextInput/TextInput.svelte +18 -3
  94. package/src/lib/stories/components/Form/TextInput/WithIcon/WithIcon.stories.svelte +47 -0
  95. package/src/lib/stories/components/Layout/Menu/DynamicMenu/Customize/Customize.stories.svelte +30 -0
  96. package/src/lib/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.stories.svelte +56 -0
  97. package/src/lib/stories/components/Layout/Menu/DynamicMenu/DynamicMenu.svelte +262 -0
  98. package/src/lib/stories/components/Layout/Menu/DynamicMenu/Events/Events.stories.svelte +48 -0
  99. package/src/lib/stories/components/Layout/Menu/DynamicMenu/KeybaordNavigation/KeybaordNavigation.stories.svelte +29 -0
  100. package/src/lib/stories/components/Layout/Menu/DynamicMenu/Options/OptionFormat.mdx +72 -0
  101. package/src/lib/stories/components/Layout/Menu/Menu.stories.svelte +69 -0
  102. package/src/lib/stories/components/Layout/Menu/MenuItem/MenuItem.stories.svelte +34 -0
  103. package/src/lib/stories/components/Layout/Menu/MenuItem/MenuItem.svelte +1 -1
  104. package/src/lib/stories/components/Layout/Menu/MenuItem/Size/Size.stories.svelte +16 -0
  105. package/src/lib/stories/components/Layout/Menu/MenuItem/Type/Type.stories.svelte +21 -0
  106. package/src/lib/stories/components/Layout/Menu/Size/Size.stories.svelte +37 -0
  107. package/src/lib/stories/components/Layout/Paper/Color/Color.stories.svelte +63 -0
  108. package/src/lib/stories/components/Layout/Paper/Paper.stories.svelte +50 -0
  109. package/src/lib/stories/components/Layout/Paper/Roundness/Roundness.stories.svelte +25 -0
  110. package/src/lib/stories/components/Layout/Paper/Shadow/Shadow.stories.svelte +24 -0
  111. package/src/lib/stories/components/Layout/Separator/Color/Color.stories.svelte +19 -0
  112. package/src/lib/stories/components/Layout/Separator/Separator.stories.svelte +30 -0
  113. package/src/lib/stories/developer tools/Intro.mdx +9 -0
  114. package/src/lib/stories/developer tools/components/DynamicInput/DynamicInput.stories.svelte +53 -0
  115. package/src/lib/stories/developer tools/components/DynamicInput/DynamicInput.svelte +43 -3
  116. package/src/lib/stories/developer tools/components/DynamicInput/Events/Events.stories.svelte +121 -0
  117. package/src/lib/stories/developer tools/components/DynamicInput/Size/Size.stories.svelte +17 -0
  118. package/src/lib/stories/developer tools/components/InputEnclosure/InputEnclosure.stories.svelte +38 -0
  119. package/src/lib/stories/developer tools/components/InputEnclosure/Roundness/Roundness.stories.svelte +20 -0
  120. package/src/lib/stories/developer tools/components/InputEnclosure/Size/Size.stories.svelte +16 -0
  121. package/src/lib/stories/developer tools/components/InputEnclosure/WithIcon/WithIcon.stories.svelte +47 -0
  122. package/src/lib/stories/developer tools/components/Popper/Popper.stories.svelte +124 -0
  123. package/src/lib/stories/developer tools/components/Popper/PopperPopup/PopperPopup.stories.svelte +64 -0
  124. package/src/lib/stories/developer tools/components/Popper/Positions/AutoPosition/AutoPosition.stories.svelte +92 -0
  125. package/src/lib/stories/developer tools/components/Popper/Positions/Positions.stories.svelte +114 -0
  126. package/src/lib/stories/developer tools/components/UtilityButton/Size/Size.stories.svelte +25 -0
  127. package/src/lib/stories/developer tools/components/UtilityButton/UtilityButton.stories.svelte +30 -0
  128. package/src/lib/stories/developer tools/directives/clickOutside/index.mdx +25 -0
  129. package/src/lib/stories/developer tools/helpers/Numbers/cleanNumericString/cleanNumericString.ts +27 -0
  130. package/src/lib/stories/developer tools/helpers/Numbers/cleanNumericString/index.mdx +20 -0
  131. package/src/lib/stories/developer tools/helpers/Numbers/isValidNumberValue/index.mdx +71 -0
  132. package/src/lib/stories/developer tools/helpers/Numbers/isValidNumberValue/isValidNumberValue.ts +156 -0
  133. package/src/lib/stories/developer tools/helpers/logger/index.mdx +63 -0
  134. package/src/lib/stories/developer tools/helpers/logger/logger.ts +46 -0
  135. package/src/lib/stories/developer tools/philosophy/Colors/Colors.mdx +43 -0
  136. package/src/lib/stories/developer tools/philosophy/Colors/Colors.stories.svelte +22 -0
  137. package/src/lib/stories/developer tools/philosophy/Colors/Opacity.stories.svelte +11 -0
  138. package/src/lib/stories/developer tools/philosophy/Themes.mdx +23 -0
@@ -0,0 +1,17 @@
1
+ <script module>
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import TextInput from '../TextInput.svelte';
4
+ import { storyTextInputArgTypes } from '../TextInput.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: TextInput,
9
+ tags: ['autodocs'],
10
+ argTypes: storyTextInputArgTypes,
11
+ args: { value: 'Hello world!' },
12
+ });
13
+ </script>
14
+
15
+ <Story name="Normal" />
16
+ <Story name="Small" args={{ size: 'small' }} />
17
+ <Story name="Large" args={{ size: 'large' }} />
@@ -0,0 +1,43 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import TextInput, { textInputTypeArray } from './TextInput.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 storyTextInputArgTypes: StoryBookArgTypes = {
9
+ type: {
10
+ control: { type: 'select' },
11
+ options: textInputTypeArray,
12
+ },
13
+ roundness: {
14
+ control: { type: 'select' },
15
+ options: componentRoundnessArray,
16
+ },
17
+ size: {
18
+ control: { type: 'select' },
19
+ options: componentSizeArray,
20
+ },
21
+ };
22
+
23
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
24
+ const { Story } = defineMeta({
25
+ component: TextInput,
26
+ tags: ['autodocs'],
27
+ argTypes: storyTextInputArgTypes,
28
+ args: { value: 'Hello world!' },
29
+ });
30
+ </script>
31
+
32
+ <!-- TextInput with default style -->
33
+ <Story name="Default" />
34
+
35
+ <Story name="Placeholder" args={{ value: '', placeholder: 'Type something...' }} />
36
+
37
+ <Story name="No Outline" args={{ outline: false }} />
38
+
39
+ <Story name="Error" args={{ error: true }} />
40
+
41
+ <Story name="Disabled" args={{ disabled: true }} />
42
+
43
+ <Story name="Read only" args={{ readonly: true }} />
@@ -8,6 +8,7 @@
8
8
  ClipboardEventHandler,
9
9
  FocusEventHandler,
10
10
  FormEventHandler,
11
+ KeyboardEventHandler,
11
12
  } from 'svelte/elements';
12
13
 
13
14
  export type TextInputType = 'text' | 'tel' | 'email' | 'password' | 'url' | 'number';
@@ -33,6 +34,10 @@
33
34
  currentTarget: EventTarget & HTMLInputElement;
34
35
  };
35
36
 
37
+ export type TextInputKeyboardEvent = KeyboardEvent & {
38
+ currentTarget: EventTarget & HTMLInputElement;
39
+ };
40
+
36
41
  export interface TextInputProps {
37
42
  /** Input type? */
38
43
  type?: TextInputType;
@@ -78,6 +83,12 @@
78
83
  oncopy?: ClipboardEventHandler<HTMLInputElement>;
79
84
  /** oncut event handler */
80
85
  oncut?: ClipboardEventHandler<HTMLInputElement>;
86
+ /** onkeydown event handler */
87
+ onkeydown?: KeyboardEventHandler<HTMLInputElement>;
88
+ /** onkeypress event handler */
89
+ onkeypress?: KeyboardEventHandler<HTMLInputElement>;
90
+ /** onkeyup event handler */
91
+ onkeyup?: KeyboardEventHandler<HTMLInputElement>;
81
92
  }
82
93
  </script>
83
94
 
@@ -103,6 +114,9 @@
103
114
  onpaste,
104
115
  oncopy,
105
116
  oncut,
117
+ onkeydown,
118
+ onkeypress,
119
+ onkeyup,
106
120
  before,
107
121
  after,
108
122
  error = false,
@@ -116,8 +130,6 @@
116
130
 
117
131
  function onfocusMod(e: DynamicInputFocusEvent) {
118
132
  const eTyped = e as TextInputFocusEvent;
119
- focused = true;
120
-
121
133
  if (onfocus) {
122
134
  onfocus(eTyped);
123
135
  }
@@ -125,7 +137,6 @@
125
137
 
126
138
  function onblurMod(e: DynamicInputFocusEvent) {
127
139
  const eTyped = e as TextInputFocusEvent;
128
- focused = false;
129
140
 
130
141
  if (onblur) {
131
142
  onblur(eTyped);
@@ -147,6 +158,7 @@
147
158
  {id}
148
159
  {disabled}
149
160
  bind:ref
161
+ bind:focused
150
162
  {oninput}
151
163
  {onchange}
152
164
  onfocus={onfocusMod}
@@ -154,6 +166,9 @@
154
166
  {onpaste}
155
167
  {oncopy}
156
168
  {oncut}
169
+ onkeydown={onkeydown ? (e) => onkeydown(e as TextInputKeyboardEvent) : undefined}
170
+ onkeypress={onkeypress ? (e) => onkeypress(e as TextInputKeyboardEvent) : undefined}
171
+ onkeyup={onkeyup ? (e) => onkeyup(e as TextInputKeyboardEvent) : undefined}
157
172
  {placeholder}
158
173
  bind:value
159
174
  {readonly}
@@ -0,0 +1,47 @@
1
+ <script module>
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import TextInput from '../TextInput.svelte';
4
+ import { storyTextInputArgTypes } from '../TextInput.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: TextInput,
10
+ tags: ['autodocs'],
11
+ argTypes: storyTextInputArgTypes,
12
+ });
13
+ </script>
14
+
15
+ <!-- TextInput icon in front. -->
16
+ <Story name="Icon Before" asChild>
17
+ <TextInput>
18
+ {#snippet before()}
19
+ <Icon icon="material-symbols:content-copy" />
20
+ {/snippet}
21
+ </TextInput>
22
+ </Story>
23
+
24
+ <!-- TextInput icon in front. -->
25
+ <Story name="Icon After" asChild>
26
+ <TextInput>
27
+ {#snippet after()}
28
+ <Icon icon="material-symbols:download-2" />
29
+ {/snippet}
30
+ </TextInput>
31
+ </Story>
32
+
33
+ <Story name="Small" args={{ size: 'small' }} asChild>
34
+ <TextInput size="small">
35
+ {#snippet before()}
36
+ <Icon icon="material-symbols:content-copy" />
37
+ {/snippet}
38
+ </TextInput>
39
+ </Story>
40
+
41
+ <Story name="Large" args={{ size: 'large' }} asChild>
42
+ <TextInput size="large">
43
+ {#snippet before()}
44
+ <Icon icon="material-symbols:content-copy" />
45
+ {/snippet}
46
+ </TextInput>
47
+ </Story>
@@ -0,0 +1,30 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import DynamicMenu from '../DynamicMenu.svelte';
4
+ import { dynamicMenuItemOptions, storyDynamicMenuArgTypes } from '../DynamicMenu.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: DynamicMenu,
9
+ tags: ['autodocs'],
10
+ argTypes: storyDynamicMenuArgTypes,
11
+ });
12
+ </script>
13
+
14
+ <!-- Custom Menu Item Content -->
15
+ <Story name="CustomMenuItemContent" asChild>
16
+ <DynamicMenu options={dynamicMenuItemOptions}>
17
+ {#snippet customMenuItemContent(option, selectedOption)}
18
+ {selectedOption?.id === option.id ? '✅' : ''} {option.label} 💯💯💯
19
+ {/snippet}
20
+ </DynamicMenu>
21
+ </Story>
22
+
23
+ <!-- Custom Menu Item Placeholder Content -->
24
+ <Story name="CustomPlaceholderMenuItemContent" asChild>
25
+ <DynamicMenu options={[]} showOptionsPlaceholder>
26
+ {#snippet customPlaceholderMenuItemContent()}
27
+ No dice 💯💯💯
28
+ {/snippet}
29
+ </DynamicMenu>
30
+ </Story>
@@ -0,0 +1,56 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import DynamicMenu, { type DynamicMenuItemOption } from './DynamicMenu.svelte';
4
+ import type { StoryBookArgTypes } from '$lib/storybook-types.js';
5
+
6
+ export const storyDynamicMenuArgTypes: StoryBookArgTypes = {};
7
+
8
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
9
+ const { Story } = defineMeta({
10
+ component: DynamicMenu,
11
+ tags: ['autodocs'],
12
+ argTypes: storyDynamicMenuArgTypes,
13
+ });
14
+
15
+ export const dynamicMenuItemOptions: DynamicMenuItemOption[] = [
16
+ {
17
+ id: '1',
18
+ label: 'One',
19
+ },
20
+ {
21
+ id: '2',
22
+ label: 'Two',
23
+ },
24
+ {
25
+ id: '3',
26
+ label: 'Three',
27
+ },
28
+ {
29
+ id: '4',
30
+ label: 'Four',
31
+ },
32
+ {
33
+ id: '5',
34
+ label: 'Five',
35
+ },
36
+ {
37
+ id: '6',
38
+ disabled: true,
39
+ label: 'Five',
40
+ },
41
+ ];
42
+ </script>
43
+
44
+ <!-- Default DynamicMenu -->
45
+ <Story name="Default" args={{ options: dynamicMenuItemOptions }} />
46
+
47
+ <!-- Selected Option -->
48
+ <Story
49
+ name="SelectedOption"
50
+ args={{ options: dynamicMenuItemOptions, selectedOption: dynamicMenuItemOptions[1] }}
51
+ />
52
+
53
+ <Story
54
+ name="ShowPlaceholder"
55
+ args={{ options: [], showOptionsPlaceholder: true, optionsPlaceholder: 'No content found.' }}
56
+ />
@@ -0,0 +1,262 @@
1
+ <script lang="ts" module>
2
+ import type { ComponentSize } from '$lib/types/size.js';
3
+ import type { MenuItemProps, MenuItemType } from '../MenuItem/MenuItem.svelte';
4
+
5
+ export type DynamicMenuItemOption<TMeta = unknown> = {
6
+ id: string;
7
+ /** label */
8
+ label?: string;
9
+ /** Custom Metadata */
10
+ meta?: TMeta;
11
+ /** Menu Item type */
12
+ type?: MenuItemType;
13
+ /** Menu Separator */
14
+ separator?: boolean;
15
+ /** How large should the Menu Items be? */
16
+ size?: ComponentSize;
17
+ /** The onclick event handler */
18
+ onclick?: MouseEventHandler<HTMLButtonElement>;
19
+ /** disabled state */
20
+ disabled?: boolean;
21
+ /** link href */
22
+ href?: string;
23
+ /** Link button: download */
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ download?: any;
26
+ /** Link button: hreflang */
27
+ hreflang?: string | undefined | null;
28
+ /** Link button: media */
29
+ media?: string | undefined | null;
30
+ /** Link button: ping */
31
+ ping?: string | undefined | null;
32
+ /** Link button: rel */
33
+ rel?: string | undefined | null;
34
+ /** Link button: target */
35
+ target?: ButtonLinkTarget;
36
+ /** Link button: Type */
37
+ anchorMediaType?: string | undefined | null;
38
+ /** Link button: referrerpolicy */
39
+ referrerpolicy?: ButtonLinkReferrerpolicy;
40
+ };
41
+
42
+ export interface DynamicMenuProps {
43
+ /** Menu ref */
44
+ ref?: HTMLUListElement;
45
+ /** Custom css class */
46
+ class?: string;
47
+ /** Menu Width */
48
+ width?: string;
49
+ /** Menu Height */
50
+ height?: string;
51
+ /** How large should the Menu Items be? */
52
+ size?: ComponentSize;
53
+ /** Menu Separator */
54
+ separator?: boolean;
55
+ /** Id */
56
+ id?: string;
57
+ /** Options */
58
+ options?: DynamicMenuItemOption[];
59
+ /** Selected Option */
60
+ selectedOption?: DynamicMenuItemOption;
61
+ /** Custom Menu Item Content */
62
+ customMenuItemContent?: (
63
+ option: DynamicMenuItemOption,
64
+ selectedOption?: DynamicMenuItemOption,
65
+ ) => Snippet;
66
+ /** Show Placeholder if no options */
67
+ showOptionsPlaceholder?: boolean;
68
+ /** placeholder Text if no options */
69
+ optionsPlaceholder?: string;
70
+ /** Custom Menu Item Placeholder Content */
71
+ customPlaceholderMenuItemContent?: () => Snippet;
72
+ /** MenuItem: Menu component props */
73
+ menuItemProps?: Partial<MenuItemProps>;
74
+ /** Enable Keyboard Navigation */
75
+ keyboardNavigation?: boolean;
76
+ /** Keyboard Navigation onEnter */
77
+ onEnter?: (e: KeyboardEvent, menuItemIndex: number) => void;
78
+ /** Keyboard Navigation onEsc */
79
+ onEsc?: (e: KeyboardEvent, menuItemIndex: number) => void;
80
+ /** On menu item click */
81
+ onclick?: (e: MouseEvent, option: DynamicMenuItemOption) => void;
82
+ }
83
+ </script>
84
+
85
+ <script lang="ts">
86
+ import Menu from '../Menu.svelte';
87
+ import type { MouseEventHandler } from 'svelte/elements';
88
+ import type {
89
+ ButtonLinkReferrerpolicy,
90
+ ButtonLinkTarget,
91
+ } from '$lib/stories/components/Form/Button/Button.svelte';
92
+ import { MenuItem } from '$lib/index.js';
93
+ import type { Snippet } from 'svelte';
94
+
95
+ let {
96
+ id,
97
+ class: className = '',
98
+ ref = $bindable<HTMLUListElement>(),
99
+ size,
100
+ separator,
101
+ width,
102
+ height,
103
+ options,
104
+ selectedOption,
105
+ customMenuItemContent,
106
+ customPlaceholderMenuItemContent,
107
+ menuItemProps,
108
+ keyboardNavigation = false,
109
+ onEnter,
110
+ onEsc,
111
+ onclick,
112
+ showOptionsPlaceholder = false,
113
+ optionsPlaceholder = 'No options found',
114
+ }: DynamicMenuProps = $props();
115
+
116
+ let menuItemIndex = $state(0);
117
+
118
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
119
+ let customMenuItemContentTyped = customMenuItemContent as any;
120
+
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ let customPlaceholderMenuItemContentTyped = customPlaceholderMenuItemContent as any;
123
+
124
+ function onKeyboardNavigation(e: KeyboardEvent) {
125
+ let keyHit: string | undefined = undefined;
126
+
127
+ if (!ref) {
128
+ return;
129
+ }
130
+
131
+ switch (e.key) {
132
+ case 'ArrowDown':
133
+ case 'ArrowUp':
134
+ keyHit = e.key;
135
+ e.preventDefault();
136
+ break;
137
+ case 'Enter':
138
+ case 'Esc':
139
+ keyHit = e.key;
140
+ break;
141
+ default:
142
+ break;
143
+ }
144
+
145
+ if (!keyHit) {
146
+ return;
147
+ }
148
+
149
+ if (keyHit === 'Enter') {
150
+ if (onEnter) {
151
+ onEnter(e, menuItemIndex);
152
+ }
153
+
154
+ return;
155
+ } else if (keyHit === 'Escape') {
156
+ if (onEsc) {
157
+ onEsc(e, menuItemIndex);
158
+ }
159
+
160
+ return;
161
+ }
162
+
163
+ const listItems = ref.querySelectorAll(':scope > li.dodo-ui-MenuItem');
164
+
165
+ if (!listItems.length) {
166
+ return;
167
+ }
168
+
169
+ for (let index = 0; index < listItems.length; index++) {
170
+ const element = listItems[index];
171
+
172
+ element.classList.remove('hover');
173
+ }
174
+
175
+ let newMenuItemIndex = menuItemIndex;
176
+
177
+ if (keyHit === 'ArrowDown') {
178
+ if (listItems[newMenuItemIndex + 1]) {
179
+ newMenuItemIndex = newMenuItemIndex + 1;
180
+ } else {
181
+ newMenuItemIndex = 0;
182
+ }
183
+ } else if (keyHit === 'ArrowUp') {
184
+ if (listItems[newMenuItemIndex - 1]) {
185
+ newMenuItemIndex = newMenuItemIndex - 1;
186
+ } else {
187
+ newMenuItemIndex = listItems.length - 1;
188
+ }
189
+ }
190
+
191
+ const targetItem = listItems[newMenuItemIndex] as HTMLLIElement;
192
+
193
+ targetItem.classList.add('hover');
194
+
195
+ targetItem.focus();
196
+ targetItem.scrollIntoView({ block: 'nearest' });
197
+
198
+ menuItemIndex = newMenuItemIndex;
199
+ }
200
+
201
+ $effect(() => {
202
+ if (!keyboardNavigation) {
203
+ return;
204
+ }
205
+
206
+ const targetItem = ref.querySelector(':scope > li.dodo-ui-MenuItem.selected') as
207
+ | HTMLLIElement
208
+ | undefined;
209
+
210
+ if (targetItem) {
211
+ const selectedIndex = options?.findIndex((option) => option.id === selectedOption?.id);
212
+
213
+ if (selectedIndex !== undefined && selectedIndex >= 0) {
214
+ menuItemIndex = selectedIndex;
215
+ }
216
+
217
+ targetItem.focus();
218
+ targetItem.scrollIntoView({ block: 'nearest' });
219
+ }
220
+
221
+ window.addEventListener('keydown', onKeyboardNavigation);
222
+
223
+ return () => {
224
+ window.removeEventListener('keydown', onKeyboardNavigation);
225
+ };
226
+ });
227
+ </script>
228
+
229
+ <Menu
230
+ class={['dodo-ui-DynamicMenu', className].join(' ')}
231
+ {id}
232
+ {size}
233
+ {width}
234
+ {height}
235
+ {separator}
236
+ bind:ref
237
+ >
238
+ {#if options?.length}
239
+ {#each options as option (option.id)}
240
+ <MenuItem
241
+ {...option}
242
+ selected={selectedOption?.id === option.id}
243
+ onclick={(e) => (onclick ? onclick(e, option) : undefined)}
244
+ {...menuItemProps}
245
+ >
246
+ {#if customMenuItemContentTyped}
247
+ {@render customMenuItemContentTyped(option, selectedOption)}
248
+ {:else}
249
+ {option.label || ''}
250
+ {/if}
251
+ </MenuItem>
252
+ {/each}
253
+ {:else if showOptionsPlaceholder}
254
+ <MenuItem type="text" disabled={true} {...menuItemProps}>
255
+ {#if customPlaceholderMenuItemContentTyped}
256
+ {@render customPlaceholderMenuItemContentTyped()}
257
+ {:else}
258
+ {optionsPlaceholder}
259
+ {/if}
260
+ </MenuItem>
261
+ {/if}
262
+ </Menu>
@@ -0,0 +1,48 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import { dynamicMenuItemOptions, storyDynamicMenuArgTypes } from '../DynamicMenu.stories.svelte';
4
+ import DynamicMenu, { type DynamicMenuItemOption } from '../DynamicMenu.svelte';
5
+
6
+ // More on how to set up stories at: https://storybook.js.org/docs/writing-stories
7
+ const { Story } = defineMeta({
8
+ component: DynamicMenu,
9
+ tags: ['autodocs'],
10
+ argTypes: storyDynamicMenuArgTypes,
11
+ });
12
+ </script>
13
+
14
+ <!-- Menu Item onclick -->
15
+ <Story name="onclick" asChild>
16
+ <DynamicMenu
17
+ keyboardNavigation
18
+ options={dynamicMenuItemOptions}
19
+ onclick={(e: MouseEvent, option: DynamicMenuItemOption) => {
20
+ e.preventDefault();
21
+ console.log(option);
22
+ }}
23
+ />
24
+ </Story>
25
+
26
+ <!-- KeyboardNavigation Event: onEnter -->
27
+ <Story name="onEnter" asChild>
28
+ <DynamicMenu
29
+ keyboardNavigation
30
+ options={dynamicMenuItemOptions}
31
+ onEnter={(e: KeyboardEvent, menuItemIndex: number) => {
32
+ e.preventDefault();
33
+ console.log(menuItemIndex);
34
+ }}
35
+ />
36
+ </Story>
37
+
38
+ <!-- KeyboardNavigation Event: onEsc -->
39
+ <Story name="onEsc" asChild>
40
+ <DynamicMenu
41
+ keyboardNavigation
42
+ options={dynamicMenuItemOptions}
43
+ onEsc={(e: KeyboardEvent, menuItemIndex: number) => {
44
+ e.preventDefault();
45
+ console.log(menuItemIndex);
46
+ }}
47
+ />
48
+ </Story>
@@ -0,0 +1,29 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+ import DynamicMenu from '../DynamicMenu.svelte';
4
+ import { dynamicMenuItemOptions, storyDynamicMenuArgTypes } from '../DynamicMenu.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: DynamicMenu,
9
+ tags: ['autodocs'],
10
+ argTypes: storyDynamicMenuArgTypes,
11
+ });
12
+ </script>
13
+
14
+ <!-- KeyboardNavigationOn -->
15
+ <Story
16
+ name="KeyboardNavigationOn"
17
+ args={{
18
+ options: dynamicMenuItemOptions,
19
+ keyboardNavigation: true,
20
+ onEnter: (e: KeyboardEvent, menuItemIndex: number) => {
21
+ e.preventDefault();
22
+ console.log(menuItemIndex);
23
+ },
24
+ onEsc: (e: KeyboardEvent, menuItemIndex: number) => {
25
+ e.preventDefault();
26
+ console.log(menuItemIndex);
27
+ },
28
+ }}
29
+ />
@@ -0,0 +1,72 @@
1
+ import { Source } from '@storybook/blocks';
2
+
3
+ # Option Format
4
+
5
+ ## DynamicMenuItemOption
6
+
7
+ <Source
8
+ dark
9
+ language="ts"
10
+ code={`
11
+ type DynamicMenuItemOption<TMeta = unknown> = {
12
+ id: string;
13
+ /** label */
14
+ label?: string;
15
+ /** Custom Metadata */
16
+ meta?: TMeta;
17
+ /** Menu Item type */
18
+ type?: MenuItemType;
19
+ /** Menu Separator */
20
+ separator?: boolean;
21
+ /** How large should the Menu Items be? */
22
+ size?: ComponentSize;
23
+ /** The onclick event handler */
24
+ onclick?: MouseEventHandler<HTMLButtonElement>;
25
+ /** disabled state */
26
+ disabled?: boolean;
27
+ /** link href */
28
+ href?: string;
29
+ /** Link button: download */
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ download?: any;
32
+ /** Link button: hreflang */
33
+ hreflang?: string | undefined | null;
34
+ /** Link button: media */
35
+ media?: string | undefined | null;
36
+ /** Link button: ping */
37
+ ping?: string | undefined | null;
38
+ /** Link button: rel */
39
+ rel?: string | undefined | null;
40
+ /** Link button: target */
41
+ target?: ButtonLinkTarget;
42
+ /** Link button: Type */
43
+ anchorMediaType?: string | undefined | null;
44
+ /** Link button: referrerpolicy */
45
+ referrerpolicy?: ButtonLinkReferrerpolicy;
46
+ };
47
+ `}
48
+ />
49
+
50
+ ## Example values
51
+
52
+ <Source
53
+ dark
54
+ language="ts"
55
+ code={`
56
+ const options: DynamicMenuItemOption[] = [
57
+ {
58
+ id: '1',
59
+ label: 'One',
60
+ },
61
+ {
62
+ id: '2',
63
+ label: 'Two',
64
+ },
65
+ {
66
+ id: '3',
67
+ label: 'Three',
68
+ disabled: true,
69
+ },
70
+ ];
71
+ `}
72
+ />