pika-ux 1.0.0-beta.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 (292) hide show
  1. package/LICENSE +9 -0
  2. package/dist/icon-generator/generate-icon-ts-indices.js +78 -0
  3. package/dist/shadcn-postinstall/index.js +114 -0
  4. package/package.json +102 -0
  5. package/readme.md +50 -0
  6. package/scripts/setup.js +100 -0
  7. package/src/App.svelte +51 -0
  8. package/src/app.css +349 -0
  9. package/src/icons/ci/index.d.ts +5009 -0
  10. package/src/icons/lucide/index.d.ts +11274 -0
  11. package/src/index.ts +23 -0
  12. package/src/lib/docsite/Navigation.svelte +77 -0
  13. package/src/lib/docsite/pages/Colors.svelte +35 -0
  14. package/src/lib/docsite/pages/Components.svelte +50 -0
  15. package/src/lib/docsite/pages/GettingStarted.svelte +21 -0
  16. package/src/lib/docsite/pages/Icons.svelte +22 -0
  17. package/src/lib/docsite/pages/components/Button.svelte +40 -0
  18. package/src/main.ts +9 -0
  19. package/src/pika/chip/chip.svelte +95 -0
  20. package/src/pika/chip/index.ts +1 -0
  21. package/src/pika/combobox/combobox-types.ts +5 -0
  22. package/src/pika/combobox/combobox.svelte +221 -0
  23. package/src/pika/combobox/index.ts +2 -0
  24. package/src/pika/confirm-dialog/confirm-dialog.svelte +48 -0
  25. package/src/pika/confirm-dialog/index.ts +1 -0
  26. package/src/pika/copy-button/copy-button.svelte +134 -0
  27. package/src/pika/copy-button/index.ts +1 -0
  28. package/src/pika/create-copy-link-button/create-copy-link-button.svelte +133 -0
  29. package/src/pika/create-copy-link-button/index.ts +1 -0
  30. package/src/pika/date-picker/date-picker.svelte +33 -0
  31. package/src/pika/date-picker/index.ts +1 -0
  32. package/src/pika/date-range-picker/date-range-picker.svelte +48 -0
  33. package/src/pika/date-range-picker/index.ts +1 -0
  34. package/src/pika/date-time-picker/date-time-picker.svelte +336 -0
  35. package/src/pika/date-time-picker/index.ts +1 -0
  36. package/src/pika/expandable-container/expandable-container.svelte +155 -0
  37. package/src/pika/expandable-container/index.ts +1 -0
  38. package/src/pika/index.ts +29 -0
  39. package/src/pika/list/index.ts +2 -0
  40. package/src/pika/list/list-types.ts +5 -0
  41. package/src/pika/list/list.svelte +349 -0
  42. package/src/pika/markdown-editor/github.scss +87 -0
  43. package/src/pika/markdown-editor/index.ts +1 -0
  44. package/src/pika/markdown-editor/markdown-editor.svelte +44 -0
  45. package/src/pika/permanent-toast/index.ts +1 -0
  46. package/src/pika/permanent-toast/permanent-toast.svelte +47 -0
  47. package/src/pika/pika-alert/index.ts +1 -0
  48. package/src/pika/pika-alert/pika-alert.svelte +53 -0
  49. package/src/pika/pika-badge/index.ts +1 -0
  50. package/src/pika/pika-badge/pika-badge.svelte +61 -0
  51. package/src/pika/pika-table/index.ts +7 -0
  52. package/src/pika/pika-table/pika-table-cell.svelte +9 -0
  53. package/src/pika/pika-table/pika-table-checkbox.svelte +8 -0
  54. package/src/pika/pika-table/pika-table-column-header.svelte +88 -0
  55. package/src/pika/pika-table/pika-table-faceted-filter.svelte +109 -0
  56. package/src/pika/pika-table/pika-table-pagination.svelte +95 -0
  57. package/src/pika/pika-table/pika-table-row-actions.svelte +58 -0
  58. package/src/pika/pika-table/pika-table-toolbar.svelte +88 -0
  59. package/src/pika/pika-table/pika-table-view-options.svelte +35 -0
  60. package/src/pika/pika-table/pika-table.svelte +295 -0
  61. package/src/pika/pika-table/types.ts +106 -0
  62. package/src/pika/pika-tabs/index.ts +18 -0
  63. package/src/pika/pika-tabs/tabs-content.svelte +16 -0
  64. package/src/pika/pika-tabs/tabs-list.svelte +12 -0
  65. package/src/pika/pika-tabs/tabs-trigger.svelte +23 -0
  66. package/src/pika/popup-help/index.ts +1 -0
  67. package/src/pika/popup-help/popup-help.svelte +33 -0
  68. package/src/pika/simple-dropdown/index.ts +2 -0
  69. package/src/pika/simple-dropdown/simple-dropdown-types.ts +5 -0
  70. package/src/pika/simple-dropdown/simple-dropdown.svelte +288 -0
  71. package/src/pika/slideout/constants.ts +5 -0
  72. package/src/pika/slideout/context.svelte.ts +110 -0
  73. package/src/pika/slideout/index.ts +19 -0
  74. package/src/pika/slideout/slideout-content.svelte +36 -0
  75. package/src/pika/slideout/slideout-panel.svelte +126 -0
  76. package/src/pika/slideout/slideout-provider.svelte +49 -0
  77. package/src/pika/slideout/slideout-rail.svelte.die +69 -0
  78. package/src/pika/slideout/slideout.svelte +33 -0
  79. package/src/pika/slideout/slideout.svelte.old +113 -0
  80. package/src/pika/text-wave-shimmer/index.ts +1 -0
  81. package/src/pika/text-wave-shimmer/text-wave-shimmer.svelte +43 -0
  82. package/src/pika/tooltip-plus/index.ts +1 -0
  83. package/src/pika/tooltip-plus/tooltip-plus.svelte +42 -0
  84. package/src/shadcn/.DS_Store +0 -0
  85. package/src/shadcn/alert/alert-description.svelte +11 -0
  86. package/src/shadcn/alert/alert-title.svelte +24 -0
  87. package/src/shadcn/alert/alert.svelte +39 -0
  88. package/src/shadcn/alert/index.ts +14 -0
  89. package/src/shadcn/avatar/avatar-fallback.svelte +13 -0
  90. package/src/shadcn/avatar/avatar-image.svelte +13 -0
  91. package/src/shadcn/avatar/avatar.svelte +19 -0
  92. package/src/shadcn/avatar/index.ts +13 -0
  93. package/src/shadcn/badge/badge.svelte +48 -0
  94. package/src/shadcn/badge/index.ts +2 -0
  95. package/src/shadcn/breadcrumb/breadcrumb-ellipsis.svelte +12 -0
  96. package/src/shadcn/breadcrumb/breadcrumb-item.svelte +20 -0
  97. package/src/shadcn/breadcrumb/breadcrumb-link.svelte +31 -0
  98. package/src/shadcn/breadcrumb/breadcrumb-list.svelte +20 -0
  99. package/src/shadcn/breadcrumb/breadcrumb-page.svelte +23 -0
  100. package/src/shadcn/breadcrumb/breadcrumb-separator.svelte +15 -0
  101. package/src/shadcn/breadcrumb/breadcrumb.svelte +15 -0
  102. package/src/shadcn/breadcrumb/index.ts +25 -0
  103. package/src/shadcn/button/button.svelte +81 -0
  104. package/src/shadcn/button/index.ts +17 -0
  105. package/src/shadcn/calendar/calendar-caption.svelte +76 -0
  106. package/src/shadcn/calendar/calendar-cell.svelte +19 -0
  107. package/src/shadcn/calendar/calendar-day.svelte +31 -0
  108. package/src/shadcn/calendar/calendar-grid-body.svelte +12 -0
  109. package/src/shadcn/calendar/calendar-grid-head.svelte +12 -0
  110. package/src/shadcn/calendar/calendar-grid-row.svelte +12 -0
  111. package/src/shadcn/calendar/calendar-grid.svelte +16 -0
  112. package/src/shadcn/calendar/calendar-head-cell.svelte +16 -0
  113. package/src/shadcn/calendar/calendar-header.svelte +16 -0
  114. package/src/shadcn/calendar/calendar-heading.svelte +12 -0
  115. package/src/shadcn/calendar/calendar-month-select.svelte +25 -0
  116. package/src/shadcn/calendar/calendar-month.svelte +15 -0
  117. package/src/shadcn/calendar/calendar-months.svelte +20 -0
  118. package/src/shadcn/calendar/calendar-nav.svelte +19 -0
  119. package/src/shadcn/calendar/calendar-next-button.svelte +19 -0
  120. package/src/shadcn/calendar/calendar-prev-button.svelte +19 -0
  121. package/src/shadcn/calendar/calendar-year-select.svelte +25 -0
  122. package/src/shadcn/calendar/calendar.svelte +61 -0
  123. package/src/shadcn/calendar/index.ts +30 -0
  124. package/src/shadcn/card/card-content.svelte +16 -0
  125. package/src/shadcn/card/card-description.svelte +16 -0
  126. package/src/shadcn/card/card-footer.svelte +16 -0
  127. package/src/shadcn/card/card-header.svelte +16 -0
  128. package/src/shadcn/card/card-title.svelte +25 -0
  129. package/src/shadcn/card/card.svelte +20 -0
  130. package/src/shadcn/card/index.ts +22 -0
  131. package/src/shadcn/carousel/carousel-content.svelte +39 -0
  132. package/src/shadcn/carousel/carousel-item.svelte +26 -0
  133. package/src/shadcn/carousel/carousel-next.svelte +30 -0
  134. package/src/shadcn/carousel/carousel-previous.svelte +30 -0
  135. package/src/shadcn/carousel/carousel.svelte +88 -0
  136. package/src/shadcn/carousel/context.ts +51 -0
  137. package/src/shadcn/carousel/index.ts +19 -0
  138. package/src/shadcn/checkbox/checkbox.svelte +36 -0
  139. package/src/shadcn/checkbox/index.ts +6 -0
  140. package/src/shadcn/collapsible/collapsible-content.svelte +7 -0
  141. package/src/shadcn/collapsible/collapsible-trigger.svelte +7 -0
  142. package/src/shadcn/collapsible/collapsible.svelte +11 -0
  143. package/src/shadcn/collapsible/index.ts +13 -0
  144. package/src/shadcn/command/command-dialog.svelte +40 -0
  145. package/src/shadcn/command/command-empty.svelte +13 -0
  146. package/src/shadcn/command/command-group.svelte +30 -0
  147. package/src/shadcn/command/command-input.svelte +21 -0
  148. package/src/shadcn/command/command-item.svelte +16 -0
  149. package/src/shadcn/command/command-link-item.svelte +16 -0
  150. package/src/shadcn/command/command-list.svelte +13 -0
  151. package/src/shadcn/command/command-separator.svelte +13 -0
  152. package/src/shadcn/command/command-shortcut.svelte +20 -0
  153. package/src/shadcn/command/command.svelte +19 -0
  154. package/src/shadcn/command/index.ts +40 -0
  155. package/src/shadcn/data-table/data-table.svelte.ts +141 -0
  156. package/src/shadcn/data-table/flex-render.svelte +36 -0
  157. package/src/shadcn/data-table/index.ts +3 -0
  158. package/src/shadcn/data-table/render-helpers.ts +111 -0
  159. package/src/shadcn/dialog/dialog-close.svelte +7 -0
  160. package/src/shadcn/dialog/dialog-content.svelte +43 -0
  161. package/src/shadcn/dialog/dialog-description.svelte +13 -0
  162. package/src/shadcn/dialog/dialog-footer.svelte +20 -0
  163. package/src/shadcn/dialog/dialog-header.svelte +20 -0
  164. package/src/shadcn/dialog/dialog-overlay.svelte +16 -0
  165. package/src/shadcn/dialog/dialog-title.svelte +13 -0
  166. package/src/shadcn/dialog/dialog-trigger.svelte +7 -0
  167. package/src/shadcn/dialog/index.ts +37 -0
  168. package/src/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +41 -0
  169. package/src/shadcn/dropdown-menu/dropdown-menu-content.svelte +27 -0
  170. package/src/shadcn/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
  171. package/src/shadcn/dropdown-menu/dropdown-menu-group.svelte +7 -0
  172. package/src/shadcn/dropdown-menu/dropdown-menu-item.svelte +27 -0
  173. package/src/shadcn/dropdown-menu/dropdown-menu-label.svelte +24 -0
  174. package/src/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +16 -0
  175. package/src/shadcn/dropdown-menu/dropdown-menu-radio-item.svelte +26 -0
  176. package/src/shadcn/dropdown-menu/dropdown-menu-separator.svelte +13 -0
  177. package/src/shadcn/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
  178. package/src/shadcn/dropdown-menu/dropdown-menu-sub-content.svelte +16 -0
  179. package/src/shadcn/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
  180. package/src/shadcn/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
  181. package/src/shadcn/dropdown-menu/index.ts +49 -0
  182. package/src/shadcn/index.ts +40 -0
  183. package/src/shadcn/input/index.ts +7 -0
  184. package/src/shadcn/input/input.svelte +51 -0
  185. package/src/shadcn/is-mobile.svelte.ts +9 -0
  186. package/src/shadcn/label/index.ts +7 -0
  187. package/src/shadcn/label/label.svelte +16 -0
  188. package/src/shadcn/popover/index.ts +17 -0
  189. package/src/shadcn/popover/popover-content.svelte +29 -0
  190. package/src/shadcn/popover/popover-trigger.svelte +8 -0
  191. package/src/shadcn/radio-group/index.ts +10 -0
  192. package/src/shadcn/radio-group/radio-group-item.svelte +25 -0
  193. package/src/shadcn/radio-group/radio-group.svelte +19 -0
  194. package/src/shadcn/range-calendar/index.ts +30 -0
  195. package/src/shadcn/range-calendar/range-calendar-cell.svelte +19 -0
  196. package/src/shadcn/range-calendar/range-calendar-day.svelte +35 -0
  197. package/src/shadcn/range-calendar/range-calendar-grid-body.svelte +12 -0
  198. package/src/shadcn/range-calendar/range-calendar-grid-head.svelte +12 -0
  199. package/src/shadcn/range-calendar/range-calendar-grid-row.svelte +12 -0
  200. package/src/shadcn/range-calendar/range-calendar-grid.svelte +16 -0
  201. package/src/shadcn/range-calendar/range-calendar-head-cell.svelte +16 -0
  202. package/src/shadcn/range-calendar/range-calendar-header.svelte +16 -0
  203. package/src/shadcn/range-calendar/range-calendar-heading.svelte +16 -0
  204. package/src/shadcn/range-calendar/range-calendar-months.svelte +20 -0
  205. package/src/shadcn/range-calendar/range-calendar-next-button.svelte +18 -0
  206. package/src/shadcn/range-calendar/range-calendar-prev-button.svelte +18 -0
  207. package/src/shadcn/range-calendar/range-calendar.svelte +57 -0
  208. package/src/shadcn/resizable/index.ts +13 -0
  209. package/src/shadcn/resizable/resizable-handle.svelte +30 -0
  210. package/src/shadcn/resizable/resizable-pane-group.svelte +22 -0
  211. package/src/shadcn/scroll-area/index.ts +10 -0
  212. package/src/shadcn/scroll-area/scroll-area-scrollbar.svelte +28 -0
  213. package/src/shadcn/scroll-area/scroll-area.svelte +35 -0
  214. package/src/shadcn/select/index.ts +37 -0
  215. package/src/shadcn/select/select-content.svelte +38 -0
  216. package/src/shadcn/select/select-group-heading.svelte +21 -0
  217. package/src/shadcn/select/select-group.svelte +7 -0
  218. package/src/shadcn/select/select-item.svelte +31 -0
  219. package/src/shadcn/select/select-label.svelte +20 -0
  220. package/src/shadcn/select/select-scroll-down-button.svelte +11 -0
  221. package/src/shadcn/select/select-scroll-up-button.svelte +11 -0
  222. package/src/shadcn/select/select-separator.svelte +14 -0
  223. package/src/shadcn/select/select-trigger.svelte +30 -0
  224. package/src/shadcn/separator/index.ts +7 -0
  225. package/src/shadcn/separator/separator.svelte +16 -0
  226. package/src/shadcn/sheet/index.ts +36 -0
  227. package/src/shadcn/sheet/sheet-close.svelte +7 -0
  228. package/src/shadcn/sheet/sheet-content.svelte +66 -0
  229. package/src/shadcn/sheet/sheet-description.svelte +13 -0
  230. package/src/shadcn/sheet/sheet-footer.svelte +15 -0
  231. package/src/shadcn/sheet/sheet-header.svelte +15 -0
  232. package/src/shadcn/sheet/sheet-overlay.svelte +16 -0
  233. package/src/shadcn/sheet/sheet-title.svelte +13 -0
  234. package/src/shadcn/sheet/sheet-trigger.svelte +7 -0
  235. package/src/shadcn/sidebar/constants.ts +6 -0
  236. package/src/shadcn/sidebar/context.svelte.ts +80 -0
  237. package/src/shadcn/sidebar/index.ts +75 -0
  238. package/src/shadcn/sidebar/sidebar-content.svelte +24 -0
  239. package/src/shadcn/sidebar/sidebar-footer.svelte +21 -0
  240. package/src/shadcn/sidebar/sidebar-group-action.svelte +36 -0
  241. package/src/shadcn/sidebar/sidebar-group-content.svelte +21 -0
  242. package/src/shadcn/sidebar/sidebar-group-label.svelte +34 -0
  243. package/src/shadcn/sidebar/sidebar-group.svelte +21 -0
  244. package/src/shadcn/sidebar/sidebar-header.svelte +21 -0
  245. package/src/shadcn/sidebar/sidebar-input.svelte +21 -0
  246. package/src/shadcn/sidebar/sidebar-inset.svelte +24 -0
  247. package/src/shadcn/sidebar/sidebar-menu-action.svelte +43 -0
  248. package/src/shadcn/sidebar/sidebar-menu-badge.svelte +29 -0
  249. package/src/shadcn/sidebar/sidebar-menu-button.svelte +101 -0
  250. package/src/shadcn/sidebar/sidebar-menu-item.svelte +21 -0
  251. package/src/shadcn/sidebar/sidebar-menu-skeleton.svelte +36 -0
  252. package/src/shadcn/sidebar/sidebar-menu-sub-button.svelte +43 -0
  253. package/src/shadcn/sidebar/sidebar-menu-sub-item.svelte +21 -0
  254. package/src/shadcn/sidebar/sidebar-menu-sub.svelte +25 -0
  255. package/src/shadcn/sidebar/sidebar-menu.svelte +21 -0
  256. package/src/shadcn/sidebar/sidebar-provider.svelte +46 -0
  257. package/src/shadcn/sidebar/sidebar-rail.svelte +36 -0
  258. package/src/shadcn/sidebar/sidebar-separator.svelte +15 -0
  259. package/src/shadcn/sidebar/sidebar-trigger.svelte +35 -0
  260. package/src/shadcn/sidebar/sidebar.svelte +94 -0
  261. package/src/shadcn/skeleton/index.ts +7 -0
  262. package/src/shadcn/skeleton/skeleton.svelte +17 -0
  263. package/src/shadcn/slider/index.ts +7 -0
  264. package/src/shadcn/slider/slider.svelte +44 -0
  265. package/src/shadcn/sonner/index.ts +1 -0
  266. package/src/shadcn/sonner/sonner.svelte +13 -0
  267. package/src/shadcn/switch/index.ts +7 -0
  268. package/src/shadcn/switch/switch.svelte +27 -0
  269. package/src/shadcn/table/index.ts +28 -0
  270. package/src/shadcn/table/table-body.svelte +15 -0
  271. package/src/shadcn/table/table-caption.svelte +20 -0
  272. package/src/shadcn/table/table-cell.svelte +20 -0
  273. package/src/shadcn/table/table-footer.svelte +20 -0
  274. package/src/shadcn/table/table-head.svelte +23 -0
  275. package/src/shadcn/table/table-header.svelte +15 -0
  276. package/src/shadcn/table/table-row.svelte +23 -0
  277. package/src/shadcn/table/table.svelte +17 -0
  278. package/src/shadcn/tabs/index.ts +18 -0
  279. package/src/shadcn/tabs/tabs-content.svelte +21 -0
  280. package/src/shadcn/tabs/tabs-list.svelte +19 -0
  281. package/src/shadcn/tabs/tabs-trigger.svelte +21 -0
  282. package/src/shadcn/textarea/index.ts +7 -0
  283. package/src/shadcn/textarea/textarea.svelte +22 -0
  284. package/src/shadcn/toggle/index.ts +13 -0
  285. package/src/shadcn/toggle/toggle.svelte +51 -0
  286. package/src/shadcn/toggle-group/index.ts +10 -0
  287. package/src/shadcn/toggle-group/toggle-group-item.svelte +30 -0
  288. package/src/shadcn/toggle-group/toggle-group.svelte +41 -0
  289. package/src/shadcn/tooltip/index.ts +21 -0
  290. package/src/shadcn/tooltip/tooltip-content.svelte +47 -0
  291. package/src/shadcn/tooltip/tooltip-trigger.svelte +7 -0
  292. package/src/shadcn/utils.ts +14 -0
@@ -0,0 +1,288 @@
1
+ <script lang="ts" generics="T">
2
+ import Check from '$icons/lucide/check';
3
+ import ChevronsUpDown from '$icons/lucide/chevrons-up-down';
4
+ import X from '$icons/lucide/x';
5
+ import { Button } from '../../shadcn/button';
6
+ import * as Command from '../../shadcn/command';
7
+ import * as Popover from '../../shadcn/popover';
8
+ import { cn } from '../../shadcn/utils';
9
+ import indefinite from 'indefinite';
10
+ import plur from 'plur';
11
+ import { tick } from 'svelte';
12
+ import type { SimpleDropdownMapping } from './simple-dropdown-types';
13
+
14
+ let {
15
+ value = $bindable(),
16
+ mapping,
17
+ options,
18
+ inputPlaceholder,
19
+ searchPlaceholder,
20
+ optionTypeName = 'option',
21
+ // We will figure out the plural form of the data type name using the plur library if not provided
22
+ optionTypeNamePlural,
23
+ onValueChanged,
24
+ wrapperClasses = '',
25
+ buttonClasses = '',
26
+ popupWidthClasses = '',
27
+ loading = false,
28
+ showValueInListEntries = false,
29
+ disabled = false,
30
+ allowArbitraryValues,
31
+ dontShowSearchInput = false,
32
+ allowClear = false,
33
+ multiSelect = false
34
+ }: {
35
+ value: T | undefined | T[];
36
+ mapping: SimpleDropdownMapping<T>;
37
+ options: T[] | undefined;
38
+ inputPlaceholder?: string;
39
+ searchPlaceholder?: string;
40
+ onValueChanged?: (value: T | undefined | T[]) => void;
41
+ wrapperClasses?: string;
42
+ buttonClasses?: string;
43
+ // This is the name of the type of data in the dropdown that a user will understand
44
+ optionTypeName?: string;
45
+ optionTypeNamePlural?: string;
46
+ loading?: boolean;
47
+ showValueInListEntries?: boolean;
48
+ disabled?: boolean;
49
+ dontShowSearchInput?: boolean;
50
+ popupWidthClasses?: string;
51
+ allowClear?: boolean;
52
+ multiSelect?: boolean;
53
+ allowArbitraryValues?: {
54
+ convertValueToType: (arbitraryValue: string) => T;
55
+ };
56
+ } = $props();
57
+
58
+ $effect(() => {
59
+ // Throw an exception if there is an options array and if all values are not unique
60
+ if (options && options.length > 0 && new Set(options.map((opt) => getValue(opt))).size !== options.length) {
61
+ throw new Error(`All values in the options (returned from your mappings.getValue fn) array must be unique: ${JSON.stringify(options)}`);
62
+ }
63
+
64
+ // Validate that value is compatible with multiSelect mode
65
+ if (multiSelect && value !== undefined && !Array.isArray(value)) {
66
+ throw new Error(`When multiSelect is true, value must be undefined or an array. Received: ${typeof value}`);
67
+ }
68
+ });
69
+
70
+ const getValue = (item: T) => mapping.value(item);
71
+ const getLabel = (item: T) => mapping.label(item);
72
+ const getSecondaryLabel = (item: T) => mapping.secondaryLabel?.(item);
73
+
74
+ // Helper functions for multi-select
75
+ const selectedValues = $derived(multiSelect ? (value as T[]) || [] : value ? [value as T] : []);
76
+
77
+ const isSelected = (option: T) => {
78
+ return selectedValues.some((selected) => getValue(selected) === getValue(option));
79
+ };
80
+
81
+ const toggleSelection = (option: T) => {
82
+ if (!multiSelect) {
83
+ // Single select mode
84
+ const optionValue = getValue(option);
85
+ if (!value || getValue(value as T) !== optionValue) {
86
+ value = option;
87
+ if (onValueChanged) onValueChanged(value);
88
+ }
89
+ closeAndFocusTrigger();
90
+ } else {
91
+ // Multi-select mode
92
+ const currentSelected = (value as T[]) || [];
93
+ const optionValue = getValue(option);
94
+ const isCurrentlySelected = currentSelected.some((selected) => getValue(selected) === optionValue);
95
+
96
+ if (isCurrentlySelected) {
97
+ // Remove from selection
98
+ const newSelection = currentSelected.filter((selected) => getValue(selected) !== optionValue);
99
+ value = newSelection;
100
+ } else {
101
+ // Add to selection
102
+ const newSelection = [...currentSelected, option];
103
+ value = newSelection;
104
+ }
105
+
106
+ if (onValueChanged) onValueChanged(value);
107
+ // Don't close popup in multi-select mode
108
+ }
109
+ };
110
+
111
+ const plurarFormOfOptionTypeName = $derived(optionTypeNamePlural ?? plur(optionTypeName));
112
+ const optionTypeNamePrecededByArticle = $derived(indefinite(optionTypeName));
113
+ const selectAnOptionText = $derived(`Select ${optionTypeNamePrecededByArticle}...`);
114
+ let open = $state(false);
115
+ let triggerRef = $state<HTMLButtonElement>(null!);
116
+ let searchValue = $state('');
117
+
118
+ const labelToDisplayInButton = $derived.by(() => {
119
+ if (multiSelect) {
120
+ const selected = (value as T[]) || [];
121
+ if (selected.length === 0) {
122
+ return inputPlaceholder ?? `Select ${plurarFormOfOptionTypeName}...`;
123
+ } else {
124
+ return selected.map((item) => getLabel(item)).join(', ');
125
+ }
126
+ } else {
127
+ if (!value) return inputPlaceholder ?? selectAnOptionText;
128
+ return getLabel(value as T);
129
+ }
130
+ });
131
+
132
+ // Add the current value to the options if it's not already in the options
133
+ const normalizedOptions = $derived(options ? [...options] : []);
134
+
135
+ // Show all options when dropdown opens, optionally filter by search text
136
+ const visibleOptions = $derived.by(() => {
137
+ if (loading) {
138
+ return []; // Don't show any options if we are loading
139
+ }
140
+
141
+ if (!searchValue.trim()) {
142
+ return normalizedOptions; // Show all when no search text
143
+ }
144
+
145
+ // Filter options by search text for better UX
146
+ return normalizedOptions.filter((option) => getLabel(option).toLowerCase().includes(searchValue.toLowerCase()));
147
+ });
148
+
149
+ // We want to refocus the trigger button when the user selects
150
+ // an item from the list so users can continue navigating the
151
+ // rest of the form with the keyboard.
152
+ function closeAndFocusTrigger() {
153
+ open = false;
154
+ tick().then(() => {
155
+ triggerRef.focus();
156
+ });
157
+ }
158
+
159
+ // Handle input changes for arbitrary values
160
+ function handleInputChange(e: Event) {
161
+ const inputValue = (e.target as HTMLInputElement).value;
162
+
163
+ if (allowArbitraryValues && !multiSelect) {
164
+ // Convert the string to type T using the user's conversion function
165
+ const convertedValue = allowArbitraryValues.convertValueToType(inputValue);
166
+ value = convertedValue;
167
+ if (onValueChanged) onValueChanged(value);
168
+ }
169
+ // If arbitrary values not allowed, searchValue is just for filtering
170
+ // Note: Arbitrary values with multi-select mode not supported yet
171
+ }
172
+
173
+ // Handle Enter key for arbitrary values
174
+ function handleKeyDown(e: KeyboardEvent) {
175
+ if (e.key === 'Enter' && allowArbitraryValues && searchValue.trim() && !multiSelect) {
176
+ e.preventDefault();
177
+ // Convert the arbitrary value and set it
178
+ const convertedValue = allowArbitraryValues.convertValueToType(searchValue.trim());
179
+ value = convertedValue;
180
+ if (onValueChanged) onValueChanged(value);
181
+ searchValue = ''; // Clear the search input
182
+ closeAndFocusTrigger();
183
+ }
184
+ }
185
+
186
+ // Handle clear button click
187
+ function handleClear(e: Event) {
188
+ e.stopPropagation();
189
+ value = undefined;
190
+ if (onValueChanged) onValueChanged(value);
191
+ }
192
+ </script>
193
+
194
+ <div class="flex items-center ${wrapperClasses} gap-2">
195
+ <Popover.Root bind:open>
196
+ <Popover.Trigger bind:ref={triggerRef} class="flex-1">
197
+ {#snippet child({ props })}
198
+ <Button variant="outline" {...props} class={`flex items-center justify-between w-full ${buttonClasses}`} role="combobox" aria-expanded={open} {disabled}>
199
+ <span class="flex-1 flex items-center min-w-0">
200
+ <span class={cn('text-left truncate min-w-0 flex-1 w-0', !value && 'text-muted-foreground')}>{labelToDisplayInButton}</span>
201
+ {#if showValueInListEntries && value && !multiSelect}
202
+ <span class="text-xs flex min-w-0 text-muted-foreground/70 font-mono truncate ml-1">
203
+ ({getValue(value as T)})
204
+ </span>
205
+ {/if}
206
+ </span>
207
+
208
+ <ChevronsUpDown class="ml-2 shrink-0 opacity-50" />
209
+ </Button>
210
+ {/snippet}
211
+ </Popover.Trigger>
212
+ <Popover.Content class={cn('p-0', popupWidthClasses)}>
213
+ <Command.Root shouldFilter={false} class="">
214
+ {#if !dontShowSearchInput}
215
+ <Command.Input
216
+ bind:value={searchValue}
217
+ oninput={handleInputChange}
218
+ onkeydown={handleKeyDown}
219
+ placeholder={searchPlaceholder ?? `Search ${plurarFormOfOptionTypeName}...`}
220
+ class="h-9"
221
+ />
222
+ {/if}
223
+ <Command.List>
224
+ {#if loading}
225
+ <Command.Loading>
226
+ <div class="flex items-center justify-center py-6 text-sm text-muted-foreground">
227
+ <div class="flex items-center gap-2">
228
+ <div class="h-4 w-4 animate-spin rounded-full border-2 border-muted-foreground border-t-transparent"></div>
229
+ Loading {plurarFormOfOptionTypeName}...
230
+ </div>
231
+ </div>
232
+ </Command.Loading>
233
+ {:else}
234
+ <Command.Empty>
235
+ {#if allowArbitraryValues && searchValue.trim()}
236
+ Press Enter to use "{searchValue}"
237
+ {:else}
238
+ No {optionTypeName} found.
239
+ {/if}
240
+ </Command.Empty>
241
+ {/if}
242
+ <Command.Group value={plurarFormOfOptionTypeName}>
243
+ {#key visibleOptions}
244
+ {#each visibleOptions as option (getValue(option))}
245
+ <Command.Item
246
+ value={getValue(option)}
247
+ onSelect={() => {
248
+ toggleSelection(option);
249
+ }}
250
+ class={cn('flex items-start gap-2 px-2 py-2', (getSecondaryLabel(option) || showValueInListEntries) && 'py-2.5 min-h-[3rem]')}
251
+ >
252
+ <Check class={cn('mt-1 flex-shrink-0', !isSelected(option) && 'text-transparent')} />
253
+ <div class="flex-1 min-w-0">
254
+ <!-- Primary label -->
255
+ <div class="font-medium text-sm leading-tight truncate">
256
+ {getLabel(option)}
257
+ {#if showValueInListEntries}
258
+ <span class="text-xs text-muted-foreground/70 font-mono truncate ml-1">
259
+ ({getValue(option)})
260
+ </span>
261
+ {/if}
262
+ </div>
263
+
264
+ <!-- Secondary and tertiary info in a row -->
265
+ {#if getSecondaryLabel(option)}
266
+ <div class="flex items-center gap-2 mt-0.5">
267
+ {#if getSecondaryLabel(option)}
268
+ <span class="text-xs text-muted-foreground">
269
+ {getSecondaryLabel(option)}
270
+ </span>
271
+ {/if}
272
+ </div>
273
+ {/if}
274
+ </div>
275
+ </Command.Item>
276
+ {/each}
277
+ {/key}
278
+ </Command.Group>
279
+ </Command.List>
280
+ </Command.Root>
281
+ </Popover.Content>
282
+ </Popover.Root>
283
+ {#if allowClear && !disabled}
284
+ <Button variant="ghost" size="icon" class="h-4 w-4 text-muted-foreground " onclick={handleClear}>
285
+ <X />
286
+ </Button>
287
+ {/if}
288
+ </div>
@@ -0,0 +1,5 @@
1
+ export const SLIDEOUT_WIDTH = '16rem';
2
+ export const SLIDEOUT_WIDTH_MOBILE = '18rem';
3
+ export const SLIDEOUT_WIDTH_ICON = '3rem';
4
+ export const SLIDEOUT_KEYBOARD_SHORTCUT = ''; // No keyboard shortcut by default
5
+ export const SLIDEOUT_DEFAULT_WIDTH = 320;
@@ -0,0 +1,110 @@
1
+ import { IsMobile } from '../../shadcn/is-mobile.svelte.js';
2
+ import { getContext, setContext } from 'svelte';
3
+
4
+ type Getter<T> = () => T;
5
+
6
+ export type SlideoutStateProps = {
7
+ /**
8
+ * A getter function that returns the current open state of the slideout.
9
+ * We use a getter function here to support `bind:open` on the provider component.
10
+ */
11
+ open: Getter<boolean>;
12
+
13
+ /**
14
+ * A function that sets the open state of the slideout.
15
+ */
16
+ setOpen: (open: boolean) => void;
17
+
18
+ /**
19
+ * The side that the slideout appears on
20
+ */
21
+ side?: 'left' | 'right';
22
+
23
+ initialWidth?: number; // Allow setting initial width
24
+ };
25
+
26
+ export class SlideoutState {
27
+ readonly props: SlideoutStateProps;
28
+ open = $derived.by(() => this.props.open());
29
+ openMobile = $state(false);
30
+ setOpen: SlideoutStateProps['setOpen'];
31
+ side = $state<'left' | 'right'>('right');
32
+ panelWidth = $state(320);
33
+ isDragging = $state(false);
34
+ #isMobile: IsMobile;
35
+ state = $derived.by(() => (this.open ? 'expanded' : 'collapsed'));
36
+ widthBeforeMaximized: number | undefined = $state(undefined);
37
+ isMaximized = $state(false);
38
+ isAnimating = $state(false);
39
+
40
+ constructor(props: SlideoutStateProps) {
41
+ this.setOpen = props.setOpen;
42
+ this.#isMobile = new IsMobile();
43
+ this.props = props;
44
+ this.side = props.side || 'right';
45
+ this.panelWidth = props.initialWidth || 320;
46
+
47
+ $effect(() => {
48
+ this.openMobile = this.open;
49
+ });
50
+ }
51
+
52
+ // Convenience getter for checking if mobile
53
+ get isMobile() {
54
+ return this.#isMobile.current;
55
+ }
56
+
57
+ toggleMaximize = () => {
58
+ if (this.isMobile) return;
59
+
60
+ this.isMaximized = !this.isMaximized;
61
+ if (this.isMaximized) {
62
+ this.widthBeforeMaximized = this.panelWidth;
63
+ } else {
64
+ this.panelWidth = this.widthBeforeMaximized || 320;
65
+ }
66
+ };
67
+
68
+ setPanelWidth = (width: number) => {
69
+ // Apply constraints
70
+ this.panelWidth = Math.max(200, Math.min(window.innerWidth * 0.8, width)); // Example: max 80% viewport
71
+ };
72
+
73
+ setIsDragging = (dragging: boolean) => {
74
+ this.isDragging = dragging;
75
+ };
76
+
77
+ // Event handler for keyboard shortcuts
78
+ handleShortcutKeydown = (e: KeyboardEvent) => {
79
+ // Add keyboard shortcut handling here if needed
80
+ };
81
+
82
+ setOpenMobile = (value: boolean) => {
83
+ this.openMobile = value;
84
+ this.setOpen(value);
85
+ };
86
+
87
+ toggle = () => {
88
+ return this.#isMobile.current ? (this.openMobile = !this.openMobile) : this.setOpen(!this.open);
89
+ };
90
+ }
91
+
92
+ const SYMBOL_KEY = 'scn-slideout';
93
+
94
+ /**
95
+ * Instantiates a new `SlideoutState` instance and sets it in the context.
96
+ *
97
+ * @param props The constructor props for the `SlideoutState` class.
98
+ * @returns The `SlideoutState` instance.
99
+ */
100
+ export function setSlideout(props: SlideoutStateProps): SlideoutState {
101
+ return setContext(Symbol.for(SYMBOL_KEY), new SlideoutState(props));
102
+ }
103
+
104
+ /**
105
+ * Retrieves the `SlideoutState` instance from the context.
106
+ * @returns The `SlideoutState` instance.
107
+ */
108
+ export function useSlideout(): SlideoutState {
109
+ return getContext(Symbol.for(SYMBOL_KEY));
110
+ }
@@ -0,0 +1,19 @@
1
+ import { useSlideout } from './context.svelte.js';
2
+ import Content from './slideout-content.svelte';
3
+ import Panel from './slideout-panel.svelte';
4
+ import Provider from './slideout-provider.svelte';
5
+ import Root from './slideout.svelte';
6
+
7
+ export {
8
+ Content,
9
+ Panel,
10
+ Provider,
11
+ Root,
12
+ // Aliased exports
13
+ Root as Slideout,
14
+ Content as SlideoutContent,
15
+ Panel as SlideoutPanel,
16
+ Provider as SlideoutProvider,
17
+ // Hooks
18
+ useSlideout
19
+ };
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../shadcn/utils.js';
3
+ import type { WithElementRef } from 'bits-ui';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ import { useSlideout } from './context.svelte.js';
6
+
7
+ let {
8
+ ref = $bindable(null),
9
+ class: className,
10
+ children,
11
+ ...restProps
12
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
13
+
14
+ const slideout = useSlideout();
15
+ const isPanelOpen = $derived(slideout.open || (slideout.isMobile && slideout.openMobile));
16
+
17
+ // add max-hieght: 100vh (max-h-screen)
18
+ </script>
19
+
20
+ <div
21
+ bind:this={ref}
22
+ data-slideout="content"
23
+ class={cn(
24
+ // 'flex flex-col h-full flex-1 overflow-auto transition-all duration-300 ease-in-out',
25
+ 'max-h-screen flex flex-col h-full flex-1 overflow-auto transition-all duration-300 ease-in-out min-w-0', // Added min-w-0
26
+ // slideout.isMobile && 'data-[panel-open=true]:hidden',
27
+ // You might hide it explicitly on mobile, or let flexbox handle it (panel width 100%)
28
+ slideout.isMobile && isPanelOpen && 'hidden', // Keep explicit hide for mobile clarity
29
+ className
30
+ )}
31
+ data-panel-open={isPanelOpen}
32
+ {...restProps}
33
+ >
34
+ {@render children?.()}
35
+ </div>
36
+ <!-- data-panel-open={slideout.open || (slideout.isMobile && slideout.openMobile)} -->
@@ -0,0 +1,126 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../shadcn/utils.js';
3
+ import type { WithElementRef } from 'bits-ui';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ import { useSlideout } from './context.svelte.js';
6
+
7
+ let {
8
+ ref = $bindable(null),
9
+ class: className,
10
+ children,
11
+ ...restProps
12
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
13
+
14
+ // Get the slideout context
15
+ const slideout = useSlideout();
16
+
17
+ // State for drag operations
18
+ let startX = $state(0);
19
+ let startWidth = $state(slideout.panelWidth);
20
+
21
+ // Reference to the rail element
22
+ let railRef = $state<HTMLButtonElement | null>(null);
23
+
24
+ // Handle drag start
25
+ function handleDragStart(e: MouseEvent) {
26
+ slideout.setIsDragging(true);
27
+ startX = e.clientX;
28
+ startWidth = slideout.panelWidth;
29
+
30
+ // Add event listeners for dragging
31
+ window.addEventListener('mousemove', handleDragMove);
32
+ window.addEventListener('mouseup', handleDragEnd);
33
+
34
+ // Prevent text selection while dragging
35
+ document.body.style.userSelect = 'none';
36
+ }
37
+
38
+ // Handle drag move
39
+ function handleDragMove(e: MouseEvent) {
40
+ if (!slideout.isDragging) return;
41
+
42
+ const dx = e.clientX - startX;
43
+ const newWidth = slideout.side === 'right' ? startWidth - dx : startWidth + dx;
44
+
45
+ // Limit width between reasonable values
46
+ // currentWidth = Math.max(200, Math.min(600, newWidth));
47
+ slideout.setPanelWidth(newWidth);
48
+ }
49
+
50
+ // Handle drag end
51
+ function handleDragEnd() {
52
+ slideout.setIsDragging(false);
53
+
54
+ // Remove event listeners
55
+ window.removeEventListener('mousemove', handleDragMove);
56
+ window.removeEventListener('mouseup', handleDragEnd);
57
+
58
+ // Restore text selection
59
+ document.body.style.userSelect = '';
60
+ }
61
+
62
+ // Cleanup on component destruction
63
+ function cleanup() {
64
+ window.removeEventListener('mousemove', handleDragMove);
65
+ window.removeEventListener('mouseup', handleDragEnd);
66
+ }
67
+
68
+ const effectiveWidth = $derived.by(() => {
69
+ const isOpen = slideout.open || (slideout.isMobile && slideout.openMobile);
70
+ if (!isOpen) {
71
+ return '0px'; // Use 0 width when closed
72
+ }
73
+ if (slideout.isMobile || slideout.isMaximized) {
74
+ return '100%'; // Full width on mobile when open or when maximized
75
+ }
76
+
77
+ return `${slideout.panelWidth}px`; // Use context width on desktop when open
78
+ });
79
+
80
+ // add h-screen
81
+ </script>
82
+
83
+ <svelte:window on:beforeunload={cleanup} />
84
+ <div
85
+ bind:this={ref}
86
+ data-slideout="panel"
87
+ data-side={slideout.side}
88
+ data-state={slideout.state}
89
+ style="width: {effectiveWidth}"
90
+ ontransitionstart={() => (slideout.isAnimating = true)}
91
+ ontransitionend={() => (slideout.isAnimating = false)}
92
+ ontransitioncancel={() => (slideout.isAnimating = false)}
93
+ class={cn(
94
+ 'h-screen flex-shrink-0 overflow-auto relative flex flex-col',
95
+ 'transition-all duration-300 ease-in-out',
96
+ // Hide rail when collapsed or on mobile or when maximied unless specified otherwise
97
+ (slideout.state === 'collapsed' || slideout.isMobile || slideout.isMaximized) &&
98
+ '[&>[data-slideout=rail]]:hidden',
99
+ className
100
+ )}
101
+ {...restProps}
102
+ >
103
+ <!-- Panel content -->
104
+ <div class="flex-1 overflow-auto min-w-0 h-full">
105
+ {@render children?.()}
106
+ </div>
107
+
108
+ <!-- Rail/handle for resizing - integrated directly into panel -->
109
+ <button
110
+ bind:this={railRef}
111
+ data-slideout="rail"
112
+ aria-label="Resize Slideout"
113
+ tabIndex={-1}
114
+ onmousedown={handleDragStart}
115
+ title="Resize Slideout"
116
+ class={cn(
117
+ 'after:bg-sidebar-accent hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
118
+ '[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
119
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
120
+ 'group-data-[collapsible=offcanvas]:hover:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
121
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2 [[data-side=right][data-collapsible=offcanvas]_&]:-left-2'
122
+ )}
123
+ >
124
+ <!-- Optional: Add visual indicator for the rail for drag drop resize -->
125
+ </button>
126
+ </div>
@@ -0,0 +1,49 @@
1
+ <script lang="ts">
2
+ import { cn } from '../../shadcn/utils.js';
3
+ import type { WithElementRef } from 'bits-ui';
4
+ import type { HTMLAttributes } from 'svelte/elements';
5
+ import { SLIDEOUT_DEFAULT_WIDTH, SLIDEOUT_WIDTH, SLIDEOUT_WIDTH_ICON } from './constants.js';
6
+ import { setSlideout } from './context.svelte.js';
7
+
8
+ interface Props extends WithElementRef<HTMLAttributes<HTMLDivElement>> {
9
+ open?: boolean;
10
+ onOpenChange?: (open: boolean) => void;
11
+ side?: 'left' | 'right';
12
+ initialWidth?: number;
13
+ }
14
+
15
+ let {
16
+ ref = $bindable(null),
17
+ open = $bindable(false),
18
+ onOpenChange = () => {},
19
+ side = 'right',
20
+ initialWidth = SLIDEOUT_DEFAULT_WIDTH,
21
+ class: className,
22
+ style,
23
+ children,
24
+ ...restProps
25
+ }: Props = $props();
26
+
27
+ const slideout = setSlideout({
28
+ open: () => open,
29
+ setOpen: (value: boolean) => {
30
+ open = value;
31
+ onOpenChange(value);
32
+ },
33
+ side,
34
+ initialWidth,
35
+ });
36
+ </script>
37
+
38
+ <svelte:window onkeydown={slideout.handleShortcutKeydown} />
39
+
40
+ <div
41
+ style="--slideout-width: {SLIDEOUT_WIDTH}; --slideout-width-icon: {SLIDEOUT_WIDTH_ICON}; {style}"
42
+ class={cn('group/slideout-wrapper flex h-full w-full overflow-hidden', className)}
43
+ data-side={side}
44
+ data-state={slideout.state}
45
+ bind:this={ref}
46
+ {...restProps}
47
+ >
48
+ {@render children?.()}
49
+ </div>