@widgetic/design-system 0.5.21 → 0.5.23

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