@widgetic/design-system 0.5.22 → 0.5.24

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 (258) hide show
  1. package/README.md +2 -0
  2. package/dist/components/CopyableHeader.svelte +260 -448
  3. package/dist/components/CopyableHeader.svelte.d.ts +8 -5
  4. package/dist/components/EmptyState.svelte +100 -128
  5. package/dist/components/EmptyState.svelte.d.ts +16 -19
  6. package/dist/components/GenerateLoader.svelte +17 -10
  7. package/dist/components/PasswordInput.svelte +41 -44
  8. package/dist/components/PasswordInput.svelte.d.ts +2 -0
  9. package/dist/components/ThumbHashImage.svelte +117 -119
  10. package/dist/components/WScrollArea.svelte +47 -41
  11. package/dist/components/chat/ActivityRow.svelte +115 -0
  12. package/dist/components/chat/ActivityRow.svelte.d.ts +18 -0
  13. package/dist/components/chat/ChatHeader.svelte +114 -0
  14. package/dist/components/chat/ChatHeader.svelte.d.ts +26 -0
  15. package/dist/components/chat/Composer.svelte +172 -0
  16. package/dist/components/chat/Composer.svelte.d.ts +27 -0
  17. package/dist/components/chat/Message.svelte +158 -0
  18. package/dist/components/chat/Message.svelte.d.ts +28 -0
  19. package/dist/components/chat/OptionRow.svelte +59 -0
  20. package/dist/components/chat/OptionRow.svelte.d.ts +14 -0
  21. package/dist/components/chat/PlanCard.svelte +220 -0
  22. package/dist/components/chat/PlanCard.svelte.d.ts +34 -0
  23. package/dist/components/chat/QuestionCard.svelte +265 -0
  24. package/dist/components/chat/QuestionCard.svelte.d.ts +38 -0
  25. package/dist/components/chat/ScrollToBottom.svelte +51 -0
  26. package/dist/components/chat/ScrollToBottom.svelte.d.ts +11 -0
  27. package/dist/components/chat/SuggestionCard.svelte +79 -0
  28. package/dist/components/chat/SuggestionCard.svelte.d.ts +20 -0
  29. package/dist/components/chat/TaskRow.svelte +59 -0
  30. package/dist/components/chat/TaskRow.svelte.d.ts +11 -0
  31. package/dist/components/chat/composerLogic.d.ts +16 -0
  32. package/dist/components/chat/composerLogic.js +29 -0
  33. package/dist/components/chat/index.d.ts +16 -0
  34. package/dist/components/chat/index.js +16 -0
  35. package/dist/components/chat/questionCardLogic.d.ts +11 -0
  36. package/dist/components/chat/questionCardLogic.js +39 -0
  37. package/dist/components/index.d.ts +10 -1
  38. package/dist/components/index.js +10 -0
  39. package/dist/components/modals/ActionModal.svelte +186 -180
  40. package/dist/components/modals/BaseModal.svelte +495 -401
  41. package/dist/components/modals/BaseModal.svelte.d.ts +4 -0
  42. package/dist/components/modals/NewItemDialog.svelte +198 -243
  43. package/dist/components/modals/Portal.svelte +109 -105
  44. package/dist/components/modals/README.md +16 -2
  45. package/dist/components/modals/RenameDialog.svelte +232 -292
  46. package/dist/components/modals/modalStore.d.ts +9 -1
  47. package/dist/components/modals/modalStore.js +44 -11
  48. package/dist/components/navigation/Breadcrumbs.svelte +421 -377
  49. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +3 -2
  50. package/dist/components/navigation/breadcrumbsAvailableWidth.d.ts +10 -0
  51. package/dist/components/navigation/breadcrumbsAvailableWidth.js +10 -0
  52. package/dist/components/navigation/types.d.ts +2 -0
  53. package/dist/components/pdf/PDFViewer.svelte +1 -1
  54. package/dist/components/search-input/SearchInput.svelte +63 -104
  55. package/dist/components/search-input/SearchInput.svelte.d.ts +13 -27
  56. package/dist/components/search-input/index2.d.ts +14 -9
  57. package/dist/components/shad-editor/editor.css +1 -1
  58. package/dist/components/shad-editor/i18n/messages.d.ts +1 -1
  59. package/dist/components/shad-editor/i18n/messages.js +1 -1
  60. package/dist/components/shell/AppShell.svelte +52 -0
  61. package/dist/components/shell/AppShell.svelte.d.ts +13 -0
  62. package/dist/components/shell/Banner.svelte +90 -0
  63. package/dist/components/shell/Banner.svelte.d.ts +61 -0
  64. package/dist/components/shell/ErrorState.svelte +60 -0
  65. package/dist/components/shell/ErrorState.svelte.d.ts +13 -0
  66. package/dist/components/shell/Link.svelte +63 -0
  67. package/dist/components/shell/Link.svelte.d.ts +53 -0
  68. package/dist/components/shell/NavDrawer.svelte +117 -0
  69. package/dist/components/shell/NavDrawer.svelte.d.ts +10 -0
  70. package/dist/components/shell/NavRow.svelte +128 -0
  71. package/dist/components/shell/NavRow.svelte.d.ts +65 -0
  72. package/dist/components/shell/NavSectionLabel.svelte +27 -0
  73. package/dist/components/shell/NavSectionLabel.svelte.d.ts +9 -0
  74. package/dist/components/shell/NotificationRow.svelte +76 -0
  75. package/dist/components/shell/NotificationRow.svelte.d.ts +16 -0
  76. package/dist/components/shell/PageHeader.svelte +42 -0
  77. package/dist/components/shell/PageHeader.svelte.d.ts +12 -0
  78. package/dist/components/shell/ProfileRow.svelte +95 -0
  79. package/dist/components/shell/ProfileRow.svelte.d.ts +54 -0
  80. package/dist/components/shell/SettingsDialog.svelte +163 -0
  81. package/dist/components/shell/SettingsDialog.svelte.d.ts +19 -0
  82. package/dist/components/shell/SettingsNav.svelte +46 -0
  83. package/dist/components/shell/SettingsNav.svelte.d.ts +11 -0
  84. package/dist/components/shell/Sidebar.svelte +82 -0
  85. package/dist/components/shell/Sidebar.svelte.d.ts +25 -0
  86. package/dist/components/shell/Stat.svelte +63 -0
  87. package/dist/components/shell/Stat.svelte.d.ts +58 -0
  88. package/dist/components/shell/TopBar.svelte +37 -0
  89. package/dist/components/shell/TopBar.svelte.d.ts +11 -0
  90. package/dist/components/shell/WidgetRow.svelte +126 -0
  91. package/dist/components/shell/WidgetRow.svelte.d.ts +78 -0
  92. package/dist/components/shell/error-state-messages.d.ts +7 -0
  93. package/dist/components/shell/error-state-messages.js +9 -0
  94. package/dist/components/shell/index.d.ts +22 -0
  95. package/dist/components/shell/index.js +25 -0
  96. package/dist/components/shell/link-external.d.ts +2 -0
  97. package/dist/components/shell/link-external.js +7 -0
  98. package/dist/components/shell/messages.d.ts +7 -0
  99. package/dist/components/shell/messages.js +5 -0
  100. package/dist/components/shell/nested-modal-guard.d.ts +25 -0
  101. package/dist/components/shell/nested-modal-guard.js +33 -0
  102. package/dist/components/shell/settings-nav-types.d.ts +12 -0
  103. package/dist/components/shell/settings-nav-types.js +3 -0
  104. package/dist/components/shell/shell-utils.d.ts +7 -0
  105. package/dist/components/shell/shell-utils.js +27 -0
  106. package/dist/components/shell/stat-tone.d.ts +3 -0
  107. package/dist/components/shell/stat-tone.js +11 -0
  108. package/dist/components/toast/ToasterModal.svelte +102 -168
  109. package/dist/components/toast/ToasterModal.svelte.d.ts +2 -12
  110. package/dist/components/ui/accordion/accordion-content.svelte +3 -2
  111. package/dist/components/ui/accordion/accordion-item.svelte +10 -1
  112. package/dist/components/ui/accordion/accordion-trigger.svelte +4 -3
  113. package/dist/components/ui/alert/alert-description.svelte +5 -1
  114. package/dist/components/ui/alert/alert-title.svelte +2 -1
  115. package/dist/components/ui/alert/index.d.ts +13 -11
  116. package/dist/components/ui/alert/index.js +4 -4
  117. package/dist/components/ui/avatar/avatar-utils.d.ts +2 -0
  118. package/dist/components/ui/avatar/avatar-utils.js +9 -0
  119. package/dist/components/ui/avatar/avatar.svelte +63 -0
  120. package/dist/components/ui/avatar/avatar.svelte.d.ts +80 -0
  121. package/dist/components/ui/avatar/index.d.ts +4 -0
  122. package/dist/components/ui/avatar/index.js +4 -0
  123. package/dist/components/ui/badge/badge.svelte +22 -10
  124. package/dist/components/ui/badge/badge.svelte.d.ts +14 -15
  125. package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  126. package/dist/components/ui/breadcrumb/breadcrumb-link.svelte +1 -1
  127. package/dist/components/ui/breadcrumb/breadcrumb-page.svelte +1 -1
  128. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +2 -2
  129. package/dist/components/ui/button/button-padding.d.ts +9 -0
  130. package/dist/components/ui/button/button-padding.js +15 -0
  131. package/dist/components/ui/button/button.svelte +81 -31
  132. package/dist/components/ui/button/button.svelte.d.ts +48 -29
  133. package/dist/components/ui/calendar/calendar-cell.svelte +1 -1
  134. package/dist/components/ui/calendar/calendar-head-cell.svelte +1 -1
  135. package/dist/components/ui/calendar/calendar-next-button.svelte +2 -2
  136. package/dist/components/ui/calendar/calendar-prev-button.svelte +2 -2
  137. package/dist/components/ui/calendar/calendar.svelte.d.ts +1 -1
  138. package/dist/components/ui/card/card-description.svelte +1 -1
  139. package/dist/components/ui/card/card-title.svelte +1 -1
  140. package/dist/components/ui/card/card.svelte +1 -1
  141. package/dist/components/ui/checkbox/checkbox.svelte +9 -5
  142. package/dist/components/ui/chip/chip.svelte +48 -0
  143. package/dist/components/ui/chip/chip.svelte.d.ts +13 -0
  144. package/dist/components/ui/chip/index.d.ts +2 -0
  145. package/dist/components/ui/chip/index.js +2 -0
  146. package/dist/components/ui/command/command-dialog.svelte +1 -1
  147. package/dist/components/ui/command/command-input.svelte +4 -4
  148. package/dist/components/ui/command/command-item.svelte +1 -1
  149. package/dist/components/ui/command/command-link-item.svelte +1 -1
  150. package/dist/components/ui/command/command.svelte +1 -1
  151. package/dist/components/ui/dialog/dialog-content.svelte +11 -4
  152. package/dist/components/ui/dialog/dialog-description.svelte +1 -1
  153. package/dist/components/ui/dialog/dialog-overlay.svelte +8 -1
  154. package/dist/components/ui/dialog/dialog-title.svelte +1 -1
  155. package/dist/components/ui/drawer/drawer-content.svelte +1 -1
  156. package/dist/components/ui/drawer/drawer-overlay.svelte +1 -1
  157. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +5 -5
  158. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  159. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  160. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -2
  161. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +4 -0
  162. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +5 -1
  163. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +3 -3
  164. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -1
  165. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  166. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +3 -3
  167. package/dist/components/ui/field/field-ids.d.ts +7 -0
  168. package/dist/components/ui/field/field-ids.js +15 -0
  169. package/dist/components/ui/field/field.svelte +62 -0
  170. package/dist/components/ui/field/field.svelte.d.ts +21 -0
  171. package/dist/components/ui/field/index.d.ts +3 -0
  172. package/dist/components/ui/field/index.js +3 -0
  173. package/dist/components/ui/hover-card/hover-card-content.svelte +1 -1
  174. package/dist/components/ui/icon-button/icon-button.svelte +72 -0
  175. package/dist/components/ui/icon-button/icon-button.svelte.d.ts +90 -0
  176. package/dist/components/ui/icon-button/index.d.ts +2 -0
  177. package/dist/components/ui/icon-button/index.js +4 -0
  178. package/dist/components/ui/input/index.d.ts +1 -0
  179. package/dist/components/ui/input/input.svelte +11 -4
  180. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  181. package/dist/components/ui/input-otp/input-otp-separator.svelte +2 -2
  182. package/dist/components/ui/input-otp/input-otp-slot.svelte +5 -4
  183. package/dist/components/ui/input-otp/input-otp-slot.svelte.d.ts +1 -1
  184. package/dist/components/ui/input-otp/input-otp.svelte +1 -1
  185. package/dist/components/ui/label/label.svelte +3 -2
  186. package/dist/components/ui/popover/popover-content.svelte +1 -1
  187. package/dist/components/ui/progress/progress.svelte +19 -5
  188. package/dist/components/ui/progress/progress.svelte.d.ts +4 -2
  189. package/dist/components/ui/radio-group/radio-group-item.svelte +4 -4
  190. package/dist/components/ui/range-calendar/range-calendar-head-cell.svelte +1 -1
  191. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +2 -2
  192. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +2 -2
  193. package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
  194. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +4 -1
  195. package/dist/components/ui/scroll-area/scroll-area.svelte +1 -1
  196. package/dist/components/ui/select/select-content.svelte +1 -3
  197. package/dist/components/ui/select/select-item.svelte +5 -4
  198. package/dist/components/ui/select/select-scroll-down-button.svelte +2 -2
  199. package/dist/components/ui/select/select-scroll-up-button.svelte +2 -2
  200. package/dist/components/ui/select/select-trigger.svelte +4 -3
  201. package/dist/components/ui/skeleton/index.d.ts +2 -0
  202. package/dist/components/ui/skeleton/index.js +2 -0
  203. package/dist/components/ui/skeleton/skeleton.svelte +24 -0
  204. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +8 -0
  205. package/dist/components/ui/slider/slider.svelte +5 -10
  206. package/dist/components/ui/sonner/sonner.svelte +19 -13
  207. package/dist/components/ui/stepper-button/index.d.ts +3 -0
  208. package/dist/components/ui/stepper-button/index.js +2 -0
  209. package/dist/components/ui/stepper-button/stepper-button.svelte +37 -0
  210. package/dist/components/ui/stepper-button/stepper-button.svelte.d.ts +9 -0
  211. package/dist/components/ui/switch/switch.svelte +8 -3
  212. package/dist/components/ui/table/table-cell.svelte +1 -1
  213. package/dist/components/ui/table/table-head.svelte +1 -1
  214. package/dist/components/ui/table/table-row.svelte +1 -1
  215. package/dist/components/ui/tabs/tabs-list.svelte +1 -1
  216. package/dist/components/ui/tabs/tabs-trigger.svelte +1 -1
  217. package/dist/components/ui/tag/index.d.ts +2 -0
  218. package/dist/components/ui/tag/index.js +2 -0
  219. package/dist/components/ui/tag/tag.svelte +42 -0
  220. package/dist/components/ui/tag/tag.svelte.d.ts +11 -0
  221. package/dist/components/ui/textarea/textarea.svelte +4 -3
  222. package/dist/components/ui/textarea/textarea.svelte.d.ts +2 -2
  223. package/dist/components/ui/toggle/toggle.svelte +9 -9
  224. package/dist/components/ui/toggle/toggle.svelte.d.ts +14 -21
  225. package/dist/components/ui/tooltip/tooltip-content.svelte +1 -1
  226. package/dist/fonts/Inter/Inter-Black.woff2 +0 -0
  227. package/dist/fonts/Inter/Inter-BlackItalic.woff2 +0 -0
  228. package/dist/fonts/Inter/Inter-Bold.woff2 +0 -0
  229. package/dist/fonts/Inter/Inter-BoldItalic.woff2 +0 -0
  230. package/dist/fonts/Inter/Inter-ExtraBold.woff2 +0 -0
  231. package/dist/fonts/Inter/Inter-ExtraBoldItalic.woff2 +0 -0
  232. package/dist/fonts/Inter/Inter-ExtraLight-BETA.woff2 +0 -0
  233. package/dist/fonts/Inter/Inter-ExtraLightItalic-BETA.woff2 +0 -0
  234. package/dist/fonts/Inter/Inter-Italic.woff2 +0 -0
  235. package/dist/fonts/Inter/Inter-Light-BETA.woff2 +0 -0
  236. package/dist/fonts/Inter/Inter-LightItalic-BETA.woff2 +0 -0
  237. package/dist/fonts/Inter/Inter-Medium.woff2 +0 -0
  238. package/dist/fonts/Inter/Inter-MediumItalic.woff2 +0 -0
  239. package/dist/fonts/Inter/Inter-Regular.woff2 +0 -0
  240. package/dist/fonts/Inter/Inter-SemiBold.woff2 +0 -0
  241. package/dist/fonts/Inter/Inter-SemiBoldItalic.woff2 +0 -0
  242. package/dist/fonts/Inter/Inter-Thin-BETA.woff2 +0 -0
  243. package/dist/fonts/Inter/Inter-ThinItalic-BETA.woff2 +0 -0
  244. package/dist/fonts/Inter/Inter-italic.var.woff2 +0 -0
  245. package/dist/fonts/Inter/Inter-upright.var.woff2 +0 -0
  246. package/dist/fonts/Inter/Inter.var.woff2 +0 -0
  247. package/dist/fonts/JetBrainsMono/JetBrainsMono-Regular.woff2 +0 -0
  248. package/dist/i18n/audit/designSystemWords.d.ts +1 -1
  249. package/dist/i18n/audit/designSystemWords.js +1 -1
  250. package/dist/styles/fonts.css +18 -14
  251. package/dist/styles/global.css +10 -21
  252. package/dist/styles/tokens.css +332 -0
  253. package/dist/utils/overlayMotion.d.ts +49 -0
  254. package/dist/utils/overlayMotion.js +71 -0
  255. package/dist/utils.d.ts +27 -0
  256. package/dist/utils.js +33 -2
  257. package/package.json +2 -1
  258. package/tailwind.config.ts +7 -47
@@ -1,385 +1,429 @@
1
1
  <script lang="ts">
2
- // Svelte Imports
3
- import { onMount, onDestroy } from 'svelte';
4
-
5
- // Components Imports
6
- import * as Breadcrumb from "../ui/breadcrumb";
7
- import * as DropdownMenu from "../ui/dropdown-menu";
8
- import * as Tooltip from "../ui/tooltip";
9
-
10
- // Icons Imports
11
- // import { IconArrowLeft, IconArrowRight } from '@widgetic/design-system/icons';
12
- import { IconArrowLeft, IconArrowRight } from '@tabler/icons-svelte';
13
-
14
- // Types Import
15
- import type { BreadcrumbsProps } from './types.js';
16
- import { dirForUserText, useTextDirection } from '../textDirection.js';
17
-
18
- // Environment Import
19
- // import { browser } from '$app/environment';
20
- const browser = typeof window !== 'undefined';
21
-
22
- /***
23
- * COMPONENT PROPS
24
- **/
25
- // class && style props
26
- let className = '';
27
- export { className as class };
28
- export let style = '';
29
- // Breadcrumbs
30
- export let clientHeight: number = 0;
31
- // path settings
32
- export let currentPath: BreadcrumbsProps['currentPath'] = [];
33
- export let currentPathNames: BreadcrumbsProps['currentPathNames'] = [];
34
- // loading settings
35
- export let isLoading: BreadcrumbsProps['isLoading'] = false;
36
- // navigation settings
37
- export let showNavigationButtons: BreadcrumbsProps['showNavigationButtons'] = true;
38
- export let canGoBack: BreadcrumbsProps['canGoBack'] = false;
39
- export let canGoForward: BreadcrumbsProps['canGoForward'] = false;
40
- // words (tooltips and screen readers): a page that shows this in another language passes its own
41
- export let backLabel: string = 'Back';
42
- export let forwardLabel: string = 'Forward';
43
- export let rootLabel: string = 'Files';
44
- export let breadcrumbLabel: string = 'breadcrumb';
45
- // event handlers
46
- export let onNavigate: BreadcrumbsProps['onNavigate'] = () => {};
47
- export let onBack: BreadcrumbsProps['onBack'] = () => {};
48
- export let onForward: BreadcrumbsProps['onForward'] = () => {};
49
- // debug settings
50
- export let showDebugVisualizer: boolean = false;
51
-
52
- // A name a visitor wrote keeps a direction of its own on a right to left screen (a Latin folder name reads left to
53
- // right inside it); on a left to right screen no attribute is written and nothing changes.
54
- const textDirection = useTextDirection();
55
- $: nameDirection = dirForUserText($textDirection);
56
-
57
- /***
58
- * COMPONENT STATE VARIABLES
59
- **/
60
- let navElement: HTMLElement;
61
- let headerElement: HTMLElement | null = null;
62
- let isDesktop = false;
63
- let mediaQuery: MediaQueryList | null = null;
64
- let shouldShowEllipsis = false;
65
- let availableWidth = 0;
66
- let itemsToShow = 0;
67
- let breadcrumbsContainer: HTMLElement;
68
- let breadcrumbsPositioned = false;
69
- let showLogs = false;
70
- let showBackTooltip = false;
71
- let showForwardTooltip = false;
72
- // Calculate how many items we can show based on available width
73
- // Assuming each item takes roughly 150px on average (including separator)
74
- $: if (availableWidth > 0) {
75
- const averageItemWidth = 200; // Adjust this value based on actual measurements
76
- const maxItems = Math.floor(availableWidth / averageItemWidth);
77
- // Always show at least 2 items (first and last)
78
- itemsToShow = Math.max(2, maxItems);
79
- shouldShowEllipsis = currentPath?.length > itemsToShow;
80
-
81
- // Mark breadcrumbs as positioned once we have calculated the available width
82
- breadcrumbsPositioned = true;
83
- }
84
-
85
- // Only log on mount and significant changes
86
- $: if (browser && (currentPath?.length)) {
87
- // if(showLogs) console.log('Breadcrumbs significant update:', {
88
- // currentPath,
89
- // currentPathNames,
90
- // shouldShowEllipsis,
91
- // availableWidth,
92
- // itemsToShow,
93
- // breadcrumbsPositioned
94
- // });
95
- }
96
-
97
- /***
98
- * COMPONENT METHODS
99
- **/
100
- function updateAvailableWidth() {
101
- if (!browser || !navElement || !breadcrumbsContainer) return;
102
-
103
- // First try to get the header from FileBrowser structure
104
- const containerElement = navElement.closest('header') || navElement;
105
- headerElement = containerElement;
106
-
107
- // Get all the measurements we need
108
- const measurements = {
109
- container: containerElement.getBoundingClientRect(),
110
- navButtons: showNavigationButtons
111
- ? navElement.querySelector('.nav-buttons')?.getBoundingClientRect()
112
- : null,
113
- rightContent: containerElement.querySelector('.pe-5, .pr-5, .right-content')?.getBoundingClientRect(),
114
- breadcrumbs: breadcrumbsContainer.getBoundingClientRect()
115
- };
116
-
117
- // Calculate fixed spaces
118
- const leftOffset = measurements.navButtons ? measurements.navButtons.width : 0;
119
- const rightOffset = measurements.rightContent ? measurements.rightContent.width : 24;
120
-
121
- // Calculate available width for breadcrumbs
122
- availableWidth = measurements.container.width - leftOffset - rightOffset - 72; // 72px for padding
123
- }
124
-
125
- function handleMediaQuery(e: MediaQueryListEvent | MediaQueryList) {
126
- isDesktop = e.matches;
127
- }
128
-
129
- $: showFullPath = isDesktop;
130
-
131
- function handleClick(event: MouseEvent, index: number) {
132
- event.preventDefault();
133
- if (!isLoading) {
134
- onNavigate(index);
135
- }
136
- }
137
-
138
- function handleKeyDown(event: KeyboardEvent, index: number) {
139
- if (event.key === 'Enter') {
140
- event.preventDefault();
141
- if (!isLoading) {
142
- onNavigate(index);
143
- }
144
- }
145
- }
146
-
147
- /***
148
- * COMPONENT LIFECYCLE METHODS
149
- **/
150
- onMount(() => {
151
- if (!browser) return;
152
-
153
- mediaQuery = window.matchMedia('(min-width: 768px)');
154
- handleMediaQuery(mediaQuery);
155
- mediaQuery.addEventListener('change', handleMediaQuery);
156
-
157
- // Get the header from FileBrowser structure
158
- if (navElement) {
159
- const header = navElement.closest('header');
160
- if (header) {
161
- headerElement = header;
162
- }
163
- }
164
-
165
- // Initial width calculation
166
- updateAvailableWidth();
167
-
168
- // Set up resize observer
169
- const resizeObserver = new ResizeObserver(() => {
170
- updateAvailableWidth();
171
- });
172
-
173
- resizeObserver.observe(navElement);
174
- if (headerElement) {
175
- resizeObserver.observe(headerElement);
176
- }
177
-
178
- return () => {
179
- resizeObserver.disconnect();
180
- };
181
- });
182
-
183
- onDestroy(() => {
184
- if (browser && mediaQuery) {
185
- mediaQuery.removeEventListener('change', handleMediaQuery);
186
- }
187
- });
2
+ // Svelte Imports
3
+ import { onMount, onDestroy } from 'svelte';
4
+
5
+ // Components Imports
6
+ import * as Breadcrumb from '../ui/breadcrumb';
7
+ import * as DropdownMenu from '../ui/dropdown-menu';
8
+ import * as Tooltip from '../ui/tooltip';
9
+ import { IconButton } from '../ui/icon-button';
10
+
11
+ // Icons Imports
12
+ // import { IconArrowLeft, IconArrowRight } from '@widgetic/design-system/icons';
13
+ import { IconArrowLeft, IconArrowRight } from '@tabler/icons-svelte';
14
+
15
+ // Types Import
16
+ import type { BreadcrumbsProps } from './types.js';
17
+ import { dirForUserText, useTextDirection } from '../textDirection.js';
18
+ import { breadcrumbsAvailableWidthPx } from './breadcrumbsAvailableWidth.js';
19
+
20
+ // Environment Import
21
+ // import { browser } from '$app/environment';
22
+ const browser = typeof window !== 'undefined';
23
+
24
+ /***
25
+ * COMPONENT PROPS
26
+ **/
27
+ // class && style props
28
+ let className = '';
29
+ export { className as class };
30
+ export let style = '';
31
+ // Breadcrumbs
32
+ export let clientHeight: number = 0;
33
+ // path settings
34
+ export let currentPath: BreadcrumbsProps['currentPath'] = [];
35
+ export let currentPathNames: BreadcrumbsProps['currentPathNames'] = [];
36
+ // loading settings
37
+ export let isLoading: BreadcrumbsProps['isLoading'] = false;
38
+ // navigation settings
39
+ export let showNavigationButtons: BreadcrumbsProps['showNavigationButtons'] = true;
40
+ export let canGoBack: BreadcrumbsProps['canGoBack'] = false;
41
+ export let canGoForward: BreadcrumbsProps['canGoForward'] = false;
42
+ // words (tooltips and screen readers): a page that shows this in another language passes its own
43
+ export let backLabel: string = 'Back';
44
+ export let forwardLabel: string = 'Forward';
45
+ export let rootLabel: string = 'Files';
46
+ export let breadcrumbLabel: string = 'breadcrumb';
47
+ // Figma Crumbs Size=Compact (a header under 700px wide): Heading/Section text and a 4px gap instead of Heading/Page and 6px
48
+ export let compact: boolean = false;
49
+ // event handlers
50
+ export let onNavigate: BreadcrumbsProps['onNavigate'] = () => {};
51
+ export let onBack: BreadcrumbsProps['onBack'] = () => {};
52
+ export let onForward: BreadcrumbsProps['onForward'] = () => {};
53
+ // debug settings
54
+ export let showDebugVisualizer: boolean = false;
55
+
56
+ // A name a visitor wrote keeps a direction of its own on a right to left screen (a Latin folder name reads left to
57
+ // right inside it); on a left to right screen no attribute is written and nothing changes.
58
+ const textDirection = useTextDirection();
59
+ $: nameDirection = dirForUserText($textDirection);
60
+
61
+ /***
62
+ * COMPONENT STATE VARIABLES
63
+ **/
64
+ let navElement: HTMLElement;
65
+ let headerElement: HTMLElement | null = null;
66
+ let isDesktop = false;
67
+ let mediaQuery: MediaQueryList | null = null;
68
+ let shouldShowEllipsis = false;
69
+ let availableWidth = 0;
70
+ let itemsToShow = 0;
71
+ let breadcrumbsContainer: HTMLElement;
72
+ let breadcrumbsPositioned = false;
73
+ let showLogs = false;
74
+ let showBackTooltip = false;
75
+ let showForwardTooltip = false;
76
+ // The text style and the gap between crumbs come from the Figma size of the crumbs
77
+ $: crumbTextClass = compact ? 'text-heading-section' : 'text-heading-page';
78
+ $: crumbGapClass = compact ? 'gap-1' : 'gap-1.5';
79
+ // Calculate how many items we can show based on available width
80
+ // Assuming each item takes roughly 150px on average (including separator)
81
+ $: if (availableWidth > 0) {
82
+ const averageItemWidth = 200; // Adjust this value based on actual measurements
83
+ const maxItems = Math.floor(availableWidth / averageItemWidth);
84
+ // Always show at least 2 items (first and last)
85
+ itemsToShow = Math.max(2, maxItems);
86
+ shouldShowEllipsis = currentPath?.length > itemsToShow;
87
+
88
+ // Mark breadcrumbs as positioned once we have calculated the available width
89
+ breadcrumbsPositioned = true;
90
+ }
91
+
92
+ // Only log on mount and significant changes
93
+ $: if (browser && currentPath?.length) {
94
+ // if(showLogs) console.log('Breadcrumbs significant update:', {
95
+ // currentPath,
96
+ // currentPathNames,
97
+ // shouldShowEllipsis,
98
+ // availableWidth,
99
+ // itemsToShow,
100
+ // breadcrumbsPositioned
101
+ // });
102
+ }
103
+
104
+ /***
105
+ * COMPONENT METHODS
106
+ **/
107
+ function updateAvailableWidth() {
108
+ if (!browser || !navElement || !breadcrumbsContainer) return;
109
+
110
+ const headerAncestor = navElement.closest('header');
111
+ const containerElement = headerAncestor || navElement;
112
+ headerElement = containerElement;
113
+
114
+ // Get all the measurements we need
115
+ const measurements = {
116
+ container: containerElement.getBoundingClientRect(),
117
+ navButtons: showNavigationButtons
118
+ ? navElement.querySelector('.nav-buttons')?.getBoundingClientRect()
119
+ : null,
120
+ rightContent: containerElement
121
+ .querySelector('.pe-5, .pr-5, .right-content')
122
+ ?.getBoundingClientRect(),
123
+ breadcrumbs: breadcrumbsContainer.getBoundingClientRect()
124
+ };
125
+
126
+ // Calculate fixed spaces
127
+ const leftOffset = measurements.navButtons ? measurements.navButtons.width : 0;
128
+ const rightOffset = measurements.rightContent ? measurements.rightContent.width : 24;
129
+
130
+ // Calculate available width for breadcrumbs
131
+ availableWidth = breadcrumbsAvailableWidthPx({
132
+ containerWidthPx: measurements.container.width,
133
+ leftOffsetPx: leftOffset,
134
+ rightOffsetPx: rightOffset,
135
+ headerAncestor
136
+ });
137
+ }
138
+
139
+ function handleMediaQuery(e: MediaQueryListEvent | MediaQueryList) {
140
+ isDesktop = e.matches;
141
+ }
142
+
143
+ $: showFullPath = isDesktop;
144
+
145
+ function handleClick(event: MouseEvent, index: number) {
146
+ event.preventDefault();
147
+ if (!isLoading) {
148
+ onNavigate(index);
149
+ }
150
+ }
151
+
152
+ function handleKeyDown(event: KeyboardEvent, index: number) {
153
+ if (event.key === 'Enter') {
154
+ event.preventDefault();
155
+ if (!isLoading) {
156
+ onNavigate(index);
157
+ }
158
+ }
159
+ }
160
+
161
+ /***
162
+ * COMPONENT LIFECYCLE METHODS
163
+ **/
164
+ onMount(() => {
165
+ if (!browser) return;
166
+
167
+ mediaQuery = window.matchMedia('(min-width: 768px)');
168
+ handleMediaQuery(mediaQuery);
169
+ mediaQuery.addEventListener('change', handleMediaQuery);
170
+
171
+ // First try to get the header from FileBrowser structure
172
+ if (navElement) {
173
+ const header = navElement.closest('header');
174
+ if (header) {
175
+ headerElement = header;
176
+ }
177
+ }
178
+
179
+ // Initial width calculation
180
+ updateAvailableWidth();
181
+
182
+ // Set up resize observer
183
+ const resizeObserver = new ResizeObserver(() => {
184
+ updateAvailableWidth();
185
+ });
186
+
187
+ resizeObserver.observe(navElement);
188
+ if (headerElement) {
189
+ resizeObserver.observe(headerElement);
190
+ }
191
+
192
+ return () => {
193
+ resizeObserver.disconnect();
194
+ };
195
+ });
196
+
197
+ onDestroy(() => {
198
+ if (browser && mediaQuery) {
199
+ mediaQuery.removeEventListener('change', handleMediaQuery);
200
+ }
201
+ });
188
202
  </script>
189
203
 
190
- <nav
191
- bind:this={navElement}
192
- bind:clientHeight
193
- aria-label={breadcrumbLabel}
194
- class="{className ? className + ' ' : ''}breadcrumbs-component flex items-center widgetic-colors h-[56px] bg-white border-b relative w-full"
195
- style={style}
204
+ <!--
205
+ Figma: Crumbs (component set 268:1008) with the back and forward buttons of Browser Header (268:1088).
206
+ Size=Page is `Heading/Page` with a 6px gap; Size=Compact (the `compact` prop) is `Heading/Section` with a 4px gap.
207
+ The last crumb is the page you are on (foreground); the ones before it are links (muted, foreground on hover).
208
+ -->
209
+ <nav
210
+ bind:this={navElement}
211
+ bind:clientHeight
212
+ aria-label={breadcrumbLabel}
213
+ class="{className
214
+ ? className + ' '
215
+ : ''}breadcrumbs-component relative flex min-h-8 w-full items-center gap-2"
216
+ {style}
196
217
  >
197
- <!-- NAVIGATION BUTTONS -->
198
- {#if showNavigationButtons}
199
- <Tooltip.Provider delayDuration={300}>
200
- <div class="nav-buttons flex items-center shrink-0">
201
- <Tooltip.Root open={showBackTooltip}>
202
- <button
203
- type="button"
204
- class="breadcrumbs-nav-button inline-flex items-center justify-center w-6 h-6 border-0 bg-transparent p-0 focus:outline-none"
205
- style="cursor: {canGoBack && !isLoading ? 'pointer' : 'default'}; opacity: {canGoBack && !isLoading ? 1 : 0.35};"
206
- aria-label={backLabel}
207
- aria-disabled={!canGoBack || isLoading}
208
- on:mouseenter={() => { showBackTooltip = true; }}
209
- on:mouseleave={() => { showBackTooltip = false; }}
210
- on:click={() => { if (!isLoading) onBack(); }}
211
- >
212
- <IconArrowLeft
213
- class="w-6 h-6 text-[hsl(var(--grey6))] rtl:-scale-x-100"
214
- style="min-width: 24px; min-height: 24px;"
215
- />
216
- </button>
217
- {#if showBackTooltip}
218
- <!-- Portaled to <body>: the header row around this nav is overflow-hidden, which
218
+ <!-- NAVIGATION BUTTONS -->
219
+ {#if showNavigationButtons}
220
+ <Tooltip.Provider delayDuration={300}>
221
+ <div class="nav-buttons flex shrink-0 items-center gap-1">
222
+ <Tooltip.Root open={showBackTooltip}>
223
+ <!-- Figma Icon Button Ghost 32px: the disabled state is the 50% opacity of the Figma "Disabled" state. -->
224
+ <IconButton
225
+ variant="ghost"
226
+ label={backLabel}
227
+ disabled={!canGoBack || isLoading}
228
+ onmouseenter={() => {
229
+ showBackTooltip = true;
230
+ }}
231
+ onmouseleave={() => {
232
+ showBackTooltip = false;
233
+ }}
234
+ onclick={() => {
235
+ if (!isLoading) onBack();
236
+ }}
237
+ >
238
+ <IconArrowLeft aria-hidden="true" class="rtl:-scale-x-100" />
239
+ </IconButton>
240
+ {#if showBackTooltip}
241
+ <!-- Portaled to <body>: the header row around this nav is overflow-hidden, which
219
242
  otherwise clips the tooltip at the header's bottom edge. -->
220
- <Tooltip.Portal>
221
- <Tooltip.Content side="bottom" sideOffset={6} class="bg-[hsl(var(--dark))] text-white caption px-2 py-1 rounded-md z-[200000]">
222
- {backLabel}
223
- </Tooltip.Content>
224
- </Tooltip.Portal>
225
- {/if}
226
- </Tooltip.Root>
227
-
228
- <Tooltip.Root open={showForwardTooltip}>
229
- <button
230
- type="button"
231
- class="breadcrumbs-nav-button ms-4 inline-flex items-center justify-center w-6 h-6 border-0 bg-transparent p-0 focus:outline-none"
232
- style="cursor: {canGoForward && !isLoading ? 'pointer' : 'default'}; opacity: {canGoForward && !isLoading ? 1 : 0.35};"
233
- aria-label={forwardLabel}
234
- aria-disabled={!canGoForward || isLoading}
235
- on:mouseenter={() => { showForwardTooltip = true; }}
236
- on:mouseleave={() => { showForwardTooltip = false; }}
237
- on:click={() => { if (!isLoading) onForward(); }}
238
- >
239
- <IconArrowRight
240
- class="w-6 h-6 text-[hsl(var(--grey6))] rtl:-scale-x-100"
241
- style="min-width: 24px; min-height: 24px;"
242
- />
243
- </button>
244
- {#if showForwardTooltip}
245
- <Tooltip.Portal>
246
- <Tooltip.Content side="bottom" sideOffset={6} class="bg-[hsl(var(--dark))] text-white caption px-2 py-1 rounded-md z-[200000]">
247
- {forwardLabel}
248
- </Tooltip.Content>
249
- </Tooltip.Portal>
250
- {/if}
251
- </Tooltip.Root>
252
- </div>
253
- </Tooltip.Provider>
254
- {/if}
255
-
256
- <!-- BREADCRUMBS CONTAINER -->
257
- <div bind:this={breadcrumbsContainer} class="flex-1 ps-[18px] overflow-hidden min-w-0">
258
- <Breadcrumb.Root aria-label={breadcrumbLabel}>
259
- <Breadcrumb.List class="inline-flex items-center min-w-0 breadcrumbs-list">
260
- <div class="inline-flex items-center min-w-0 min-h-[24px]">
261
- <!-- Root Breadcrumb Item -->
262
- <Breadcrumb.Item class="shrink-0">
263
- <!-- svelte-ignore a11y-click-events-have-key-events -->
264
- <span
265
- role="link"
266
- tabindex="0"
267
- class="h5 text-[hsl(var(--grey6))] transition-colors hover:text-[hsl(var(--grey7))] hover:underline hover:decoration-[hsl(var(--grey7))] cursor-pointer whitespace-nowrap"
268
- class:opacity-50={isLoading}
269
- style="pointer-events: {isLoading ? 'none' : 'auto'};"
270
- on:click={(e) => handleClick(e, -1)}
271
- on:keydown={(e) => handleKeyDown(e, -1)}
272
- >
273
- {rootLabel}
274
- </span>
275
- </Breadcrumb.Item>
276
-
277
-
278
- {#if !isLoading && currentPath.length > 0 && breadcrumbsPositioned}
279
- {#if shouldShowEllipsis && currentPath.length > itemsToShow}
280
- <!-- Breadcrumb Separator -->
281
- <Breadcrumb.Separator class="h5 text-[hsl(var(--grey6))] shrink-0">/</Breadcrumb.Separator>
282
-
283
- <!-- Dropdown Menu for Ellipsis -->
284
- <Breadcrumb.Item class="shrink-0">
285
- <DropdownMenu.Root>
286
- <DropdownMenu.Trigger class="flex items-center gap-1 cursor-pointer">
287
- <span class="h5 text-[hsl(var(--grey6))]">...</span>
288
- </DropdownMenu.Trigger>
289
- <DropdownMenu.Content align="start" class="min-w-[200px]">
290
- {#each currentPath.slice(0, -itemsToShow + 1) as _, index}
291
- <DropdownMenu.Item>
292
- <!-- svelte-ignore a11y-click-events-have-key-events -->
293
- <span dir={nameDirection}
294
- role="link"
295
- tabindex="0"
296
- class="block w-full cursor-pointer"
297
- on:click={(e) => handleClick(e, index)}
298
- >
299
- {currentPathNames[index]}
300
- </span>
301
- </DropdownMenu.Item>
302
- {/each}
303
- </DropdownMenu.Content>
304
- </DropdownMenu.Root>
305
- </Breadcrumb.Item>
306
-
307
- <!-- Always show last N segments based on available width -->
308
- {#each currentPath.slice(-itemsToShow + 1) as _, index}
309
- <Breadcrumb.Separator class="h5 text-[hsl(var(--grey6))] shrink-0">/</Breadcrumb.Separator>
310
- <Breadcrumb.Item class="shrink-0">
311
- {#if index === itemsToShow - 2}
312
- <span dir={nameDirection} class="h5 text-[hsl(var(--grey3))]">
313
- {currentPathNames[currentPath.length - 1]}
314
- </span>
315
- {:else}
316
- <!-- svelte-ignore a11y-click-events-have-key-events -->
317
- <span dir={nameDirection}
318
- role="link"
319
- tabindex="0"
320
- class="h5 text-[hsl(var(--grey6))] transition-colors hover:text-[hsl(var(--grey7))] hover:underline hover:decoration-[hsl(var(--grey7))] cursor-pointer"
321
- class:opacity-50={isLoading}
322
- style="pointer-events: {isLoading ? 'none' : 'auto'};"
323
- on:click={(e) => handleClick(e, currentPath.length - itemsToShow + 1 + index)}
324
- on:keydown={(e) => handleKeyDown(e, currentPath.length - itemsToShow + 1 + index)}
325
- >
326
- {currentPathNames[currentPath.length - itemsToShow + 1 + index]}
327
- </span>
328
- {/if}
329
- </Breadcrumb.Item>
330
- {/each}
331
- {:else}
332
- {#each currentPath as _, index}
333
- <Breadcrumb.Separator class="h5 text-[hsl(var(--grey6))] shrink-0">/</Breadcrumb.Separator>
334
- <Breadcrumb.Item class="shrink-0">
335
- {#if index === currentPath.length - 1}
336
- <span dir={nameDirection} class="h5 text-[hsl(var(--grey3))]">
337
- {currentPathNames[index]}
338
- </span>
339
- {:else}
340
- <!-- svelte-ignore a11y-click-events-have-key-events -->
341
- <span dir={nameDirection}
342
- role="link"
343
- tabindex="0"
344
- class="h5 text-[hsl(var(--grey6))] transition-colors hover:text-[hsl(var(--grey7))] hover:underline hover:decoration-[hsl(var(--grey7))] cursor-pointer"
345
- class:opacity-50={isLoading}
346
- style="pointer-events: {isLoading ? 'none' : 'auto'};"
347
- on:click={(e) => handleClick(e, index)}
348
- on:keydown={(e) => handleKeyDown(e, index)}
349
- >
350
- {currentPathNames[index]}
351
- </span>
352
- {/if}
353
- </Breadcrumb.Item>
354
- {/each}
355
- {/if}
356
- {/if}
357
- </div>
358
- </Breadcrumb.List>
359
- </Breadcrumb.Root>
360
- </div>
361
-
362
- {#if showDebugVisualizer}
363
- <div class="absolute bottom-1 end-1 text-xs text-blue-600 bg-white px-1 rounded shadow-2xs">
364
- Available width: {Math.round(availableWidth)}px
365
- <br>
366
- Items to show: {itemsToShow}
367
- <br>
368
- Should show ellipsis: {shouldShowEllipsis}
369
- <br>
370
- Positioned: {breadcrumbsPositioned}
371
- <br>
372
- Context: {navElement?.closest('header') ? 'FileBrowser' : 'Direct'}
373
- </div>
374
- {/if}
243
+ <Tooltip.Portal>
244
+ <Tooltip.Content
245
+ side="bottom"
246
+ sideOffset={6}
247
+ class="bg-inverse text-inverse-foreground text-caption z-[200000] rounded-lg px-2 py-1"
248
+ >
249
+ {backLabel}
250
+ </Tooltip.Content>
251
+ </Tooltip.Portal>
252
+ {/if}
253
+ </Tooltip.Root>
254
+
255
+ <Tooltip.Root open={showForwardTooltip}>
256
+ <IconButton
257
+ variant="ghost"
258
+ label={forwardLabel}
259
+ disabled={!canGoForward || isLoading}
260
+ onmouseenter={() => {
261
+ showForwardTooltip = true;
262
+ }}
263
+ onmouseleave={() => {
264
+ showForwardTooltip = false;
265
+ }}
266
+ onclick={() => {
267
+ if (!isLoading) onForward();
268
+ }}
269
+ >
270
+ <IconArrowRight aria-hidden="true" class="rtl:-scale-x-100" />
271
+ </IconButton>
272
+ {#if showForwardTooltip}
273
+ <Tooltip.Portal>
274
+ <Tooltip.Content
275
+ side="bottom"
276
+ sideOffset={6}
277
+ class="bg-inverse text-inverse-foreground text-caption z-[200000] rounded-lg px-2 py-1"
278
+ >
279
+ {forwardLabel}
280
+ </Tooltip.Content>
281
+ </Tooltip.Portal>
282
+ {/if}
283
+ </Tooltip.Root>
284
+ </div>
285
+ </Tooltip.Provider>
286
+ {/if}
287
+
288
+ <!-- BREADCRUMBS CONTAINER -->
289
+ <div bind:this={breadcrumbsContainer} class="min-w-0 flex-1 overflow-hidden">
290
+ <Breadcrumb.Root aria-label={breadcrumbLabel}>
291
+ <Breadcrumb.List class="breadcrumbs-list inline-flex min-w-0 items-center">
292
+ <div class="inline-flex min-h-5 min-w-0 items-center {crumbGapClass}">
293
+ <!-- Root Breadcrumb Item -->
294
+ <Breadcrumb.Item class="shrink-0">
295
+ {#if currentPath.length === 0}
296
+ <!-- At the root the root crumb is the page you are on (Figma Crumbs Depth=Root: foreground) -->
297
+ <span class="{crumbTextClass} text-foreground whitespace-nowrap" aria-current="page">
298
+ {rootLabel}
299
+ </span>
300
+ {:else}
301
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
302
+ <span
303
+ role="link"
304
+ tabindex="0"
305
+ class="{crumbTextClass} text-muted-foreground hover:text-foreground cursor-pointer whitespace-nowrap transition-colors duration-fast hover:underline"
306
+ class:opacity-50={isLoading}
307
+ style="pointer-events: {isLoading ? 'none' : 'auto'};"
308
+ on:click={(e) => handleClick(e, -1)}
309
+ on:keydown={(e) => handleKeyDown(e, -1)}
310
+ >
311
+ {rootLabel}
312
+ </span>
313
+ {/if}
314
+ </Breadcrumb.Item>
315
+
316
+ {#if !isLoading && currentPath.length > 0 && breadcrumbsPositioned}
317
+ {#if shouldShowEllipsis && currentPath.length > itemsToShow}
318
+ <!-- Breadcrumb Separator -->
319
+ <Breadcrumb.Separator class="text-muted-foreground shrink-0 [&>svg]:size-4" />
320
+
321
+ <!-- Dropdown Menu for Ellipsis -->
322
+ <Breadcrumb.Item class="shrink-0">
323
+ <DropdownMenu.Root>
324
+ <DropdownMenu.Trigger class="flex cursor-pointer items-center gap-1">
325
+ <span class="{crumbTextClass} text-muted-foreground">...</span>
326
+ </DropdownMenu.Trigger>
327
+ <DropdownMenu.Content align="start" class="min-w-[200px]">
328
+ {#each currentPath.slice(0, -itemsToShow + 1) as _, index}
329
+ <DropdownMenu.Item>
330
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
331
+ <span
332
+ dir={nameDirection}
333
+ role="link"
334
+ tabindex="0"
335
+ class="block w-full cursor-pointer"
336
+ on:click={(e) => handleClick(e, index)}
337
+ >
338
+ {currentPathNames[index]}
339
+ </span>
340
+ </DropdownMenu.Item>
341
+ {/each}
342
+ </DropdownMenu.Content>
343
+ </DropdownMenu.Root>
344
+ </Breadcrumb.Item>
345
+
346
+ <!-- Always show last N segments based on available width -->
347
+ {#each currentPath.slice(-itemsToShow + 1) as _, index}
348
+ <Breadcrumb.Separator class="text-muted-foreground shrink-0 [&>svg]:size-4" />
349
+ <Breadcrumb.Item class="shrink-0">
350
+ {#if index === itemsToShow - 2}
351
+ <span dir={nameDirection} class="{crumbTextClass} text-foreground" aria-current="page">
352
+ {currentPathNames[currentPath.length - 1]}
353
+ </span>
354
+ {:else}
355
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
356
+ <span
357
+ dir={nameDirection}
358
+ role="link"
359
+ tabindex="0"
360
+ class="{crumbTextClass} text-muted-foreground hover:text-foreground cursor-pointer transition-colors duration-fast hover:underline"
361
+ class:opacity-50={isLoading}
362
+ style="pointer-events: {isLoading ? 'none' : 'auto'};"
363
+ on:click={(e) => handleClick(e, currentPath.length - itemsToShow + 1 + index)}
364
+ on:keydown={(e) =>
365
+ handleKeyDown(e, currentPath.length - itemsToShow + 1 + index)}
366
+ >
367
+ {currentPathNames[currentPath.length - itemsToShow + 1 + index]}
368
+ </span>
369
+ {/if}
370
+ </Breadcrumb.Item>
371
+ {/each}
372
+ {:else}
373
+ {#each currentPath as _, index}
374
+ <Breadcrumb.Separator class="text-muted-foreground shrink-0 [&>svg]:size-4" />
375
+ <Breadcrumb.Item class="shrink-0">
376
+ {#if index === currentPath.length - 1}
377
+ <span dir={nameDirection} class="{crumbTextClass} text-foreground" aria-current="page">
378
+ {currentPathNames[index]}
379
+ </span>
380
+ {:else}
381
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
382
+ <span
383
+ dir={nameDirection}
384
+ role="link"
385
+ tabindex="0"
386
+ class="{crumbTextClass} text-muted-foreground hover:text-foreground cursor-pointer transition-colors duration-fast hover:underline"
387
+ class:opacity-50={isLoading}
388
+ style="pointer-events: {isLoading ? 'none' : 'auto'};"
389
+ on:click={(e) => handleClick(e, index)}
390
+ on:keydown={(e) => handleKeyDown(e, index)}
391
+ >
392
+ {currentPathNames[index]}
393
+ </span>
394
+ {/if}
395
+ </Breadcrumb.Item>
396
+ {/each}
397
+ {/if}
398
+ {/if}
399
+ </div>
400
+ </Breadcrumb.List>
401
+ </Breadcrumb.Root>
402
+ </div>
403
+
404
+ {#if showDebugVisualizer}
405
+ <div
406
+ class="bg-background text-caption text-brand absolute end-1 bottom-1 rounded-sm px-1 shadow-2xs"
407
+ >
408
+ Available width: {Math.round(availableWidth)}px
409
+ <br />
410
+ Items to show: {itemsToShow}
411
+ <br />
412
+ Should show ellipsis: {shouldShowEllipsis}
413
+ <br />
414
+ Positioned: {breadcrumbsPositioned}
415
+ <br />
416
+ Context: {navElement?.closest('header') ? 'FileBrowser' : 'Direct'}
417
+ </div>
418
+ {/if}
375
419
  </nav>
376
420
 
377
421
  <style>
378
- :global(.breadcrumb-separator) {
379
- margin: 0 0.5rem;
380
- }
381
-
382
- :global(.breadcrumbs-list) {
383
- width: 100%;
384
- }
385
- </style>
422
+ :global(.breadcrumb-separator) {
423
+ margin: 0 0.5rem;
424
+ }
425
+
426
+ :global(.breadcrumbs-list) {
427
+ width: 100%;
428
+ }
429
+ </style>