@furystack/shades-common-components 10.0.35 → 11.0.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 (295) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/esm/components/animations.spec.d.ts +2 -0
  3. package/esm/components/animations.spec.d.ts.map +1 -0
  4. package/esm/components/animations.spec.js +201 -0
  5. package/esm/components/animations.spec.js.map +1 -0
  6. package/esm/components/app-bar-link.js +21 -20
  7. package/esm/components/app-bar-link.js.map +1 -1
  8. package/esm/components/app-bar-link.spec.d.ts +2 -0
  9. package/esm/components/app-bar-link.spec.d.ts.map +1 -0
  10. package/esm/components/app-bar-link.spec.js +252 -0
  11. package/esm/components/app-bar-link.spec.js.map +1 -0
  12. package/esm/components/app-bar.js +21 -21
  13. package/esm/components/app-bar.js.map +1 -1
  14. package/esm/components/app-bar.spec.d.ts +2 -0
  15. package/esm/components/app-bar.spec.d.ts.map +1 -0
  16. package/esm/components/app-bar.spec.js +117 -0
  17. package/esm/components/app-bar.spec.js.map +1 -0
  18. package/esm/components/avatar.d.ts.map +1 -1
  19. package/esm/components/avatar.js +15 -19
  20. package/esm/components/avatar.js.map +1 -1
  21. package/esm/components/avatar.spec.d.ts +2 -0
  22. package/esm/components/avatar.spec.d.ts.map +1 -0
  23. package/esm/components/avatar.spec.js +114 -0
  24. package/esm/components/avatar.spec.js.map +1 -0
  25. package/esm/components/button.d.ts.map +1 -1
  26. package/esm/components/button.js +145 -156
  27. package/esm/components/button.js.map +1 -1
  28. package/esm/components/button.spec.d.ts +2 -0
  29. package/esm/components/button.spec.d.ts.map +1 -0
  30. package/esm/components/button.spec.js +155 -0
  31. package/esm/components/button.spec.js.map +1 -0
  32. package/esm/components/command-palette/command-palette-input.d.ts.map +1 -1
  33. package/esm/components/command-palette/command-palette-input.js +18 -16
  34. package/esm/components/command-palette/command-palette-input.js.map +1 -1
  35. package/esm/components/command-palette/command-palette-input.spec.d.ts +2 -0
  36. package/esm/components/command-palette/command-palette-input.spec.d.ts.map +1 -0
  37. package/esm/components/command-palette/command-palette-input.spec.js +233 -0
  38. package/esm/components/command-palette/command-palette-input.spec.js.map +1 -0
  39. package/esm/components/command-palette/command-palette-manager.spec.d.ts +2 -0
  40. package/esm/components/command-palette/command-palette-manager.spec.d.ts.map +1 -0
  41. package/esm/components/command-palette/command-palette-manager.spec.js +362 -0
  42. package/esm/components/command-palette/command-palette-manager.spec.js.map +1 -0
  43. package/esm/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
  44. package/esm/components/command-palette/command-palette-suggestion-list.js +42 -46
  45. package/esm/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  46. package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts +2 -0
  47. package/esm/components/command-palette/command-palette-suggestion-list.spec.d.ts.map +1 -0
  48. package/esm/components/command-palette/command-palette-suggestion-list.spec.js +376 -0
  49. package/esm/components/command-palette/command-palette-suggestion-list.spec.js.map +1 -0
  50. package/esm/components/command-palette/index.d.ts.map +1 -1
  51. package/esm/components/command-palette/index.js +100 -110
  52. package/esm/components/command-palette/index.js.map +1 -1
  53. package/esm/components/command-palette/index.spec.d.ts +2 -0
  54. package/esm/components/command-palette/index.spec.d.ts.map +1 -0
  55. package/esm/components/command-palette/index.spec.js +509 -0
  56. package/esm/components/command-palette/index.spec.js.map +1 -0
  57. package/esm/components/data-grid/body.js +1 -1
  58. package/esm/components/data-grid/body.js.map +1 -1
  59. package/esm/components/data-grid/body.spec.d.ts +2 -0
  60. package/esm/components/data-grid/body.spec.d.ts.map +1 -0
  61. package/esm/components/data-grid/body.spec.js +228 -0
  62. package/esm/components/data-grid/body.spec.js.map +1 -0
  63. package/esm/components/data-grid/data-grid-row.d.ts.map +1 -1
  64. package/esm/components/data-grid/data-grid-row.js +49 -73
  65. package/esm/components/data-grid/data-grid-row.js.map +1 -1
  66. package/esm/components/data-grid/data-grid-row.spec.d.ts +2 -0
  67. package/esm/components/data-grid/data-grid-row.spec.d.ts.map +1 -0
  68. package/esm/components/data-grid/data-grid-row.spec.js +296 -0
  69. package/esm/components/data-grid/data-grid-row.spec.js.map +1 -0
  70. package/esm/components/data-grid/data-grid.d.ts.map +1 -1
  71. package/esm/components/data-grid/data-grid.js +35 -28
  72. package/esm/components/data-grid/data-grid.js.map +1 -1
  73. package/esm/components/data-grid/data-grid.spec.d.ts +2 -0
  74. package/esm/components/data-grid/data-grid.spec.d.ts.map +1 -0
  75. package/esm/components/data-grid/data-grid.spec.js +544 -0
  76. package/esm/components/data-grid/data-grid.spec.js.map +1 -0
  77. package/esm/components/data-grid/footer.js +21 -15
  78. package/esm/components/data-grid/footer.js.map +1 -1
  79. package/esm/components/data-grid/footer.spec.d.ts +2 -0
  80. package/esm/components/data-grid/footer.spec.d.ts.map +1 -0
  81. package/esm/components/data-grid/footer.spec.js +264 -0
  82. package/esm/components/data-grid/footer.spec.js.map +1 -0
  83. package/esm/components/data-grid/header.d.ts.map +1 -1
  84. package/esm/components/data-grid/header.js +55 -33
  85. package/esm/components/data-grid/header.js.map +1 -1
  86. package/esm/components/data-grid/header.spec.d.ts +2 -0
  87. package/esm/components/data-grid/header.spec.d.ts.map +1 -0
  88. package/esm/components/data-grid/header.spec.js +421 -0
  89. package/esm/components/data-grid/header.spec.js.map +1 -0
  90. package/esm/components/data-grid/selection-cell.d.ts.map +1 -1
  91. package/esm/components/data-grid/selection-cell.js +13 -6
  92. package/esm/components/data-grid/selection-cell.js.map +1 -1
  93. package/esm/components/data-grid/selection-cell.spec.d.ts +2 -0
  94. package/esm/components/data-grid/selection-cell.spec.d.ts.map +1 -0
  95. package/esm/components/data-grid/selection-cell.spec.js +118 -0
  96. package/esm/components/data-grid/selection-cell.spec.js.map +1 -0
  97. package/esm/components/fab.d.ts.map +1 -1
  98. package/esm/components/fab.js +10 -1
  99. package/esm/components/fab.js.map +1 -1
  100. package/esm/components/fab.spec.d.ts +2 -0
  101. package/esm/components/fab.spec.d.ts.map +1 -0
  102. package/esm/components/fab.spec.js +95 -0
  103. package/esm/components/fab.spec.js.map +1 -0
  104. package/esm/components/form.spec.d.ts +2 -0
  105. package/esm/components/form.spec.d.ts.map +1 -0
  106. package/esm/components/form.spec.js +314 -0
  107. package/esm/components/form.spec.js.map +1 -0
  108. package/esm/components/grid.d.ts.map +1 -1
  109. package/esm/components/grid.js +40 -37
  110. package/esm/components/grid.js.map +1 -1
  111. package/esm/components/grid.spec.d.ts +2 -0
  112. package/esm/components/grid.spec.d.ts.map +1 -0
  113. package/esm/components/grid.spec.js +316 -0
  114. package/esm/components/grid.spec.js.map +1 -0
  115. package/esm/components/inputs/autocomplete.spec.d.ts +2 -0
  116. package/esm/components/inputs/autocomplete.spec.d.ts.map +1 -0
  117. package/esm/components/inputs/autocomplete.spec.js +194 -0
  118. package/esm/components/inputs/autocomplete.spec.js.map +1 -0
  119. package/esm/components/inputs/input.d.ts.map +1 -1
  120. package/esm/components/inputs/input.js +141 -109
  121. package/esm/components/inputs/input.js.map +1 -1
  122. package/esm/components/inputs/input.spec.d.ts +2 -0
  123. package/esm/components/inputs/input.spec.d.ts.map +1 -0
  124. package/esm/components/inputs/input.spec.js +577 -0
  125. package/esm/components/inputs/input.spec.js.map +1 -0
  126. package/esm/components/inputs/text-area.d.ts.map +1 -1
  127. package/esm/components/inputs/text-area.js +54 -58
  128. package/esm/components/inputs/text-area.js.map +1 -1
  129. package/esm/components/inputs/text-area.spec.d.ts +2 -0
  130. package/esm/components/inputs/text-area.spec.d.ts.map +1 -0
  131. package/esm/components/inputs/text-area.spec.js +214 -0
  132. package/esm/components/inputs/text-area.spec.js.map +1 -0
  133. package/esm/components/loader.js +1 -1
  134. package/esm/components/loader.js.map +1 -1
  135. package/esm/components/loader.spec.d.ts +2 -0
  136. package/esm/components/loader.spec.d.ts.map +1 -0
  137. package/esm/components/loader.spec.js +251 -0
  138. package/esm/components/loader.spec.js.map +1 -0
  139. package/esm/components/modal.d.ts.map +1 -1
  140. package/esm/components/modal.js +11 -9
  141. package/esm/components/modal.js.map +1 -1
  142. package/esm/components/modal.spec.d.ts +2 -0
  143. package/esm/components/modal.spec.d.ts.map +1 -0
  144. package/esm/components/modal.spec.js +227 -0
  145. package/esm/components/modal.spec.js.map +1 -0
  146. package/esm/components/noty-list.d.ts.map +1 -1
  147. package/esm/components/noty-list.js +39 -40
  148. package/esm/components/noty-list.js.map +1 -1
  149. package/esm/components/noty-list.spec.d.ts +2 -0
  150. package/esm/components/noty-list.spec.d.ts.map +1 -0
  151. package/esm/components/noty-list.spec.js +486 -0
  152. package/esm/components/noty-list.spec.js.map +1 -0
  153. package/esm/components/paper.d.ts.map +1 -1
  154. package/esm/components/paper.js +15 -12
  155. package/esm/components/paper.js.map +1 -1
  156. package/esm/components/paper.spec.d.ts +2 -0
  157. package/esm/components/paper.spec.d.ts.map +1 -0
  158. package/esm/components/paper.spec.js +63 -0
  159. package/esm/components/paper.spec.js.map +1 -0
  160. package/esm/components/skeleton.js +1 -1
  161. package/esm/components/skeleton.js.map +1 -1
  162. package/esm/components/skeleton.spec.d.ts +2 -0
  163. package/esm/components/skeleton.spec.d.ts.map +1 -0
  164. package/esm/components/skeleton.spec.js +159 -0
  165. package/esm/components/skeleton.spec.js.map +1 -0
  166. package/esm/components/styles.spec.d.ts +2 -0
  167. package/esm/components/styles.spec.d.ts.map +1 -0
  168. package/esm/components/styles.spec.js +56 -0
  169. package/esm/components/styles.spec.js.map +1 -0
  170. package/esm/components/suggest/index.d.ts.map +1 -1
  171. package/esm/components/suggest/index.js +74 -83
  172. package/esm/components/suggest/index.js.map +1 -1
  173. package/esm/components/suggest/index.spec.d.ts +2 -0
  174. package/esm/components/suggest/index.spec.d.ts.map +1 -0
  175. package/esm/components/suggest/index.spec.js +515 -0
  176. package/esm/components/suggest/index.spec.js.map +1 -0
  177. package/esm/components/suggest/suggest-input.d.ts.map +1 -1
  178. package/esm/components/suggest/suggest-input.js +16 -17
  179. package/esm/components/suggest/suggest-input.js.map +1 -1
  180. package/esm/components/suggest/suggest-input.spec.d.ts +2 -0
  181. package/esm/components/suggest/suggest-input.spec.d.ts.map +1 -0
  182. package/esm/components/suggest/suggest-input.spec.js +138 -0
  183. package/esm/components/suggest/suggest-input.spec.js.map +1 -0
  184. package/esm/components/suggest/suggest-manager.spec.d.ts +2 -0
  185. package/esm/components/suggest/suggest-manager.spec.d.ts.map +1 -0
  186. package/esm/components/suggest/suggest-manager.spec.js +308 -0
  187. package/esm/components/suggest/suggest-manager.spec.js.map +1 -0
  188. package/esm/components/suggest/suggestion-list.d.ts.map +1 -1
  189. package/esm/components/suggest/suggestion-list.js +43 -48
  190. package/esm/components/suggest/suggestion-list.js.map +1 -1
  191. package/esm/components/suggest/suggestion-list.spec.d.ts +2 -0
  192. package/esm/components/suggest/suggestion-list.spec.d.ts.map +1 -0
  193. package/esm/components/suggest/suggestion-list.spec.js +252 -0
  194. package/esm/components/suggest/suggestion-list.spec.js.map +1 -0
  195. package/esm/components/tabs.d.ts.map +1 -1
  196. package/esm/components/tabs.js +32 -18
  197. package/esm/components/tabs.js.map +1 -1
  198. package/esm/components/tabs.spec.d.ts +2 -0
  199. package/esm/components/tabs.spec.d.ts.map +1 -0
  200. package/esm/components/tabs.spec.js +187 -0
  201. package/esm/components/tabs.spec.js.map +1 -0
  202. package/esm/components/wizard/index.d.ts.map +1 -1
  203. package/esm/components/wizard/index.js +10 -7
  204. package/esm/components/wizard/index.js.map +1 -1
  205. package/esm/components/wizard/index.spec.d.ts +2 -0
  206. package/esm/components/wizard/index.spec.d.ts.map +1 -0
  207. package/esm/components/wizard/index.spec.js +171 -0
  208. package/esm/components/wizard/index.spec.js.map +1 -0
  209. package/esm/services/collection-service.spec.js +391 -2
  210. package/esm/services/collection-service.spec.js.map +1 -1
  211. package/esm/services/css-variable-theme.d.ts.map +1 -1
  212. package/esm/services/css-variable-theme.js +21 -1
  213. package/esm/services/css-variable-theme.js.map +1 -1
  214. package/esm/services/css-variable-theme.spec.d.ts +2 -0
  215. package/esm/services/css-variable-theme.spec.d.ts.map +1 -0
  216. package/esm/services/css-variable-theme.spec.js +169 -0
  217. package/esm/services/css-variable-theme.spec.js.map +1 -0
  218. package/esm/services/default-palette.d.ts +4 -0
  219. package/esm/services/default-palette.d.ts.map +1 -1
  220. package/esm/services/default-palette.js +22 -0
  221. package/esm/services/default-palette.js.map +1 -1
  222. package/esm/services/theme-provider-service.d.ts +59 -1
  223. package/esm/services/theme-provider-service.d.ts.map +1 -1
  224. package/esm/services/theme-provider-service.js.map +1 -1
  225. package/esm/services/theme-provider-service.spec.d.ts +2 -0
  226. package/esm/services/theme-provider-service.spec.d.ts.map +1 -0
  227. package/esm/services/theme-provider-service.spec.js +166 -0
  228. package/esm/services/theme-provider-service.spec.js.map +1 -0
  229. package/package.json +2 -2
  230. package/src/components/animations.spec.ts +299 -0
  231. package/src/components/app-bar-link.spec.tsx +341 -0
  232. package/src/components/app-bar-link.tsx +21 -21
  233. package/src/components/app-bar.spec.tsx +142 -0
  234. package/src/components/app-bar.tsx +22 -22
  235. package/src/components/avatar.spec.tsx +146 -0
  236. package/src/components/avatar.tsx +17 -20
  237. package/src/components/button.spec.tsx +193 -0
  238. package/src/components/button.tsx +162 -197
  239. package/src/components/command-palette/command-palette-input.spec.tsx +320 -0
  240. package/src/components/command-palette/command-palette-input.tsx +19 -22
  241. package/src/components/command-palette/command-palette-manager.spec.ts +470 -0
  242. package/src/components/command-palette/command-palette-suggestion-list.spec.tsx +499 -0
  243. package/src/components/command-palette/command-palette-suggestion-list.tsx +42 -46
  244. package/src/components/command-palette/index.spec.tsx +684 -0
  245. package/src/components/command-palette/index.tsx +107 -136
  246. package/src/components/data-grid/body.spec.tsx +340 -0
  247. package/src/components/data-grid/body.tsx +1 -1
  248. package/src/components/data-grid/data-grid-row.spec.tsx +382 -0
  249. package/src/components/data-grid/data-grid-row.tsx +50 -82
  250. package/src/components/data-grid/data-grid.spec.tsx +939 -0
  251. package/src/components/data-grid/data-grid.tsx +38 -35
  252. package/src/components/data-grid/footer.spec.tsx +344 -0
  253. package/src/components/data-grid/footer.tsx +19 -19
  254. package/src/components/data-grid/header.spec.tsx +563 -0
  255. package/src/components/data-grid/header.tsx +53 -44
  256. package/src/components/data-grid/selection-cell.spec.tsx +150 -0
  257. package/src/components/data-grid/selection-cell.tsx +12 -6
  258. package/src/components/fab.spec.tsx +108 -0
  259. package/src/components/fab.tsx +10 -1
  260. package/src/components/form.spec.tsx +481 -0
  261. package/src/components/grid.spec.tsx +334 -0
  262. package/src/components/grid.tsx +57 -63
  263. package/src/components/inputs/autocomplete.spec.tsx +258 -0
  264. package/src/components/inputs/input.spec.tsx +808 -0
  265. package/src/components/inputs/input.tsx +153 -139
  266. package/src/components/inputs/text-area.spec.tsx +285 -0
  267. package/src/components/inputs/text-area.tsx +53 -79
  268. package/src/components/loader.spec.tsx +346 -0
  269. package/src/components/loader.tsx +1 -1
  270. package/src/components/modal.spec.tsx +304 -0
  271. package/src/components/modal.tsx +11 -9
  272. package/src/components/noty-list.spec.tsx +631 -0
  273. package/src/components/noty-list.tsx +39 -50
  274. package/src/components/paper.spec.tsx +72 -0
  275. package/src/components/paper.tsx +15 -13
  276. package/src/components/skeleton.spec.tsx +219 -0
  277. package/src/components/skeleton.tsx +1 -1
  278. package/src/components/styles.spec.ts +70 -0
  279. package/src/components/suggest/index.spec.tsx +861 -0
  280. package/src/components/suggest/index.tsx +74 -101
  281. package/src/components/suggest/suggest-input.spec.tsx +181 -0
  282. package/src/components/suggest/suggest-input.tsx +16 -24
  283. package/src/components/suggest/suggest-manager.spec.ts +409 -0
  284. package/src/components/suggest/suggestion-list.spec.tsx +334 -0
  285. package/src/components/suggest/suggestion-list.tsx +43 -48
  286. package/src/components/tabs.spec.tsx +236 -0
  287. package/src/components/tabs.tsx +33 -21
  288. package/src/components/wizard/index.spec.tsx +224 -0
  289. package/src/components/wizard/index.tsx +10 -9
  290. package/src/services/collection-service.spec.ts +492 -3
  291. package/src/services/css-variable-theme.spec.ts +204 -0
  292. package/src/services/css-variable-theme.ts +21 -1
  293. package/src/services/default-palette.ts +22 -0
  294. package/src/services/theme-provider-service.spec.ts +195 -0
  295. package/src/services/theme-provider-service.ts +60 -2
@@ -0,0 +1,341 @@
1
+ import { Injector } from '@furystack/inject'
2
+ import { createComponent, initializeShadeRoot, LocationService } from '@furystack/shades'
3
+ import { sleepAsync, usingAsync } from '@furystack/utils'
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5
+ import { AppBarLink } from './app-bar-link.js'
6
+
7
+ describe('AppBarLink component', () => {
8
+ beforeEach(() => {
9
+ document.body.innerHTML = '<div id="root"></div>'
10
+ })
11
+
12
+ afterEach(() => {
13
+ document.body.innerHTML = ''
14
+ })
15
+
16
+ describe('rendering', () => {
17
+ it('should render the shade-app-bar-link custom element', async () => {
18
+ await usingAsync(new Injector(), async (injector) => {
19
+ const rootElement = document.getElementById('root') as HTMLDivElement
20
+
21
+ initializeShadeRoot({
22
+ injector,
23
+ rootElement,
24
+ jsxElement: <AppBarLink href="/test">Link</AppBarLink>,
25
+ })
26
+
27
+ await sleepAsync(50)
28
+
29
+ const appBarLink = document.querySelector('shade-app-bar-link')
30
+ expect(appBarLink).not.toBeNull()
31
+ expect(appBarLink?.tagName.toLowerCase()).toBe('shade-app-bar-link')
32
+ })
33
+ })
34
+
35
+ it('should render children through RouteLink', async () => {
36
+ await usingAsync(new Injector(), async (injector) => {
37
+ const rootElement = document.getElementById('root') as HTMLDivElement
38
+
39
+ initializeShadeRoot({
40
+ injector,
41
+ rootElement,
42
+ jsxElement: <AppBarLink href="/test">Test Link Text</AppBarLink>,
43
+ })
44
+
45
+ await sleepAsync(50)
46
+
47
+ expect(document.body.innerHTML).toContain('Test Link Text')
48
+ })
49
+ })
50
+
51
+ it('should render RouteLink with correct href', async () => {
52
+ await usingAsync(new Injector(), async (injector) => {
53
+ const rootElement = document.getElementById('root') as HTMLDivElement
54
+
55
+ initializeShadeRoot({
56
+ injector,
57
+ rootElement,
58
+ jsxElement: <AppBarLink href="/my-route">Link</AppBarLink>,
59
+ })
60
+
61
+ await sleepAsync(50)
62
+
63
+ expect(document.body.innerHTML).toContain('href="/my-route"')
64
+ })
65
+ })
66
+ })
67
+
68
+ describe('route matching', () => {
69
+ it('should have active class when current URL matches href', async () => {
70
+ await usingAsync(new Injector(), async (injector) => {
71
+ history.pushState(null, '', '/dashboard')
72
+
73
+ const rootElement = document.getElementById('root') as HTMLDivElement
74
+
75
+ initializeShadeRoot({
76
+ injector,
77
+ rootElement,
78
+ jsxElement: <AppBarLink href="/dashboard">Dashboard</AppBarLink>,
79
+ })
80
+
81
+ await sleepAsync(50)
82
+
83
+ const appBarLink = document.querySelector('shade-app-bar-link')
84
+ expect(appBarLink?.classList.contains('active')).toBe(true)
85
+ })
86
+ })
87
+
88
+ it('should not have active class when current URL does not match href', async () => {
89
+ await usingAsync(new Injector(), async (injector) => {
90
+ history.pushState(null, '', '/other-page')
91
+
92
+ const rootElement = document.getElementById('root') as HTMLDivElement
93
+
94
+ initializeShadeRoot({
95
+ injector,
96
+ rootElement,
97
+ jsxElement: <AppBarLink href="/dashboard">Dashboard</AppBarLink>,
98
+ })
99
+
100
+ await sleepAsync(50)
101
+
102
+ const appBarLink = document.querySelector('shade-app-bar-link')
103
+ expect(appBarLink?.classList.contains('active')).toBe(false)
104
+ })
105
+ })
106
+
107
+ it('should update active class when location changes', async () => {
108
+ await usingAsync(new Injector(), async (injector) => {
109
+ history.pushState(null, '', '/home')
110
+
111
+ const rootElement = document.getElementById('root') as HTMLDivElement
112
+
113
+ initializeShadeRoot({
114
+ injector,
115
+ rootElement,
116
+ jsxElement: <AppBarLink href="/dashboard">Dashboard</AppBarLink>,
117
+ })
118
+
119
+ await sleepAsync(50)
120
+
121
+ const appBarLink = document.querySelector('shade-app-bar-link')
122
+ expect(appBarLink?.classList.contains('active')).toBe(false)
123
+
124
+ // Navigate to matching route
125
+ history.pushState(null, '', '/dashboard')
126
+ injector.getInstance(LocationService).updateState()
127
+
128
+ await sleepAsync(50)
129
+
130
+ expect(appBarLink?.classList.contains('active')).toBe(true)
131
+ })
132
+ })
133
+
134
+ it('should remove active class when navigating away', async () => {
135
+ await usingAsync(new Injector(), async (injector) => {
136
+ history.pushState(null, '', '/dashboard')
137
+
138
+ const rootElement = document.getElementById('root') as HTMLDivElement
139
+
140
+ initializeShadeRoot({
141
+ injector,
142
+ rootElement,
143
+ jsxElement: <AppBarLink href="/dashboard">Dashboard</AppBarLink>,
144
+ })
145
+
146
+ await sleepAsync(50)
147
+
148
+ const appBarLink = document.querySelector('shade-app-bar-link')
149
+ expect(appBarLink?.classList.contains('active')).toBe(true)
150
+
151
+ // Navigate away
152
+ history.pushState(null, '', '/other')
153
+ injector.getInstance(LocationService).updateState()
154
+
155
+ await sleepAsync(50)
156
+
157
+ expect(appBarLink?.classList.contains('active')).toBe(false)
158
+ })
159
+ })
160
+
161
+ it('should match routes with parameters', async () => {
162
+ await usingAsync(new Injector(), async (injector) => {
163
+ history.pushState(null, '', '/users/123')
164
+
165
+ const rootElement = document.getElementById('root') as HTMLDivElement
166
+
167
+ initializeShadeRoot({
168
+ injector,
169
+ rootElement,
170
+ jsxElement: <AppBarLink href="/users/:id">Users</AppBarLink>,
171
+ })
172
+
173
+ await sleepAsync(50)
174
+
175
+ const appBarLink = document.querySelector('shade-app-bar-link')
176
+ expect(appBarLink?.classList.contains('active')).toBe(true)
177
+ })
178
+ })
179
+
180
+ it('should support routingOptions with end: false for prefix matching', async () => {
181
+ await usingAsync(new Injector(), async (injector) => {
182
+ history.pushState(null, '', '/admin/settings/security')
183
+
184
+ const rootElement = document.getElementById('root') as HTMLDivElement
185
+
186
+ initializeShadeRoot({
187
+ injector,
188
+ rootElement,
189
+ jsxElement: (
190
+ <AppBarLink href="/admin" routingOptions={{ end: false }}>
191
+ Admin
192
+ </AppBarLink>
193
+ ),
194
+ })
195
+
196
+ await sleepAsync(50)
197
+
198
+ const appBarLink = document.querySelector('shade-app-bar-link')
199
+ expect(appBarLink?.classList.contains('active')).toBe(true)
200
+ })
201
+ })
202
+
203
+ it('should not match prefix by default (end: true)', async () => {
204
+ await usingAsync(new Injector(), async (injector) => {
205
+ history.pushState(null, '', '/admin/settings')
206
+
207
+ const rootElement = document.getElementById('root') as HTMLDivElement
208
+
209
+ initializeShadeRoot({
210
+ injector,
211
+ rootElement,
212
+ jsxElement: <AppBarLink href="/admin">Admin</AppBarLink>,
213
+ })
214
+
215
+ await sleepAsync(50)
216
+
217
+ const appBarLink = document.querySelector('shade-app-bar-link')
218
+ expect(appBarLink?.classList.contains('active')).toBe(false)
219
+ })
220
+ })
221
+ })
222
+
223
+ describe('multiple links', () => {
224
+ it('should only activate the matching link', async () => {
225
+ await usingAsync(new Injector(), async (injector) => {
226
+ history.pushState(null, '', '/settings')
227
+
228
+ const rootElement = document.getElementById('root') as HTMLDivElement
229
+
230
+ initializeShadeRoot({
231
+ injector,
232
+ rootElement,
233
+ jsxElement: (
234
+ <div>
235
+ <AppBarLink href="/home">Home</AppBarLink>
236
+ <AppBarLink href="/settings">Settings</AppBarLink>
237
+ <AppBarLink href="/about">About</AppBarLink>
238
+ </div>
239
+ ),
240
+ })
241
+
242
+ await sleepAsync(50)
243
+
244
+ const links = document.querySelectorAll('shade-app-bar-link')
245
+ expect(links[0]?.classList.contains('active')).toBe(false)
246
+ expect(links[1]?.classList.contains('active')).toBe(true)
247
+ expect(links[2]?.classList.contains('active')).toBe(false)
248
+ })
249
+ })
250
+
251
+ it('should update all links when location changes', async () => {
252
+ await usingAsync(new Injector(), async (injector) => {
253
+ history.pushState(null, '', '/home')
254
+
255
+ const rootElement = document.getElementById('root') as HTMLDivElement
256
+
257
+ initializeShadeRoot({
258
+ injector,
259
+ rootElement,
260
+ jsxElement: (
261
+ <div>
262
+ <AppBarLink href="/home">Home</AppBarLink>
263
+ <AppBarLink href="/settings">Settings</AppBarLink>
264
+ </div>
265
+ ),
266
+ })
267
+
268
+ await sleepAsync(50)
269
+
270
+ const links = document.querySelectorAll('shade-app-bar-link')
271
+ expect(links[0]?.classList.contains('active')).toBe(true)
272
+ expect(links[1]?.classList.contains('active')).toBe(false)
273
+
274
+ // Navigate to settings
275
+ history.pushState(null, '', '/settings')
276
+ injector.getInstance(LocationService).updateState()
277
+
278
+ await sleepAsync(50)
279
+
280
+ expect(links[0]?.classList.contains('active')).toBe(false)
281
+ expect(links[1]?.classList.contains('active')).toBe(true)
282
+ })
283
+ })
284
+ })
285
+
286
+ describe('styling', () => {
287
+ it('should have flex display', async () => {
288
+ await usingAsync(new Injector(), async (injector) => {
289
+ const rootElement = document.getElementById('root') as HTMLDivElement
290
+
291
+ initializeShadeRoot({
292
+ injector,
293
+ rootElement,
294
+ jsxElement: <AppBarLink href="/test">Link</AppBarLink>,
295
+ })
296
+
297
+ await sleepAsync(50)
298
+
299
+ const appBarLink = document.querySelector('shade-app-bar-link') as HTMLElement
300
+ const computedStyle = window.getComputedStyle(appBarLink)
301
+ expect(computedStyle.display).toBe('flex')
302
+ })
303
+ })
304
+
305
+ it('should have pointer cursor', async () => {
306
+ await usingAsync(new Injector(), async (injector) => {
307
+ const rootElement = document.getElementById('root') as HTMLDivElement
308
+
309
+ initializeShadeRoot({
310
+ injector,
311
+ rootElement,
312
+ jsxElement: <AppBarLink href="/test">Link</AppBarLink>,
313
+ })
314
+
315
+ await sleepAsync(50)
316
+
317
+ const appBarLink = document.querySelector('shade-app-bar-link') as HTMLElement
318
+ const computedStyle = window.getComputedStyle(appBarLink)
319
+ expect(computedStyle.cursor).toBe('pointer')
320
+ })
321
+ })
322
+
323
+ it('should have transition for animations', async () => {
324
+ await usingAsync(new Injector(), async (injector) => {
325
+ const rootElement = document.getElementById('root') as HTMLDivElement
326
+
327
+ initializeShadeRoot({
328
+ injector,
329
+ rootElement,
330
+ jsxElement: <AppBarLink href="/test">Link</AppBarLink>,
331
+ })
332
+
333
+ await sleepAsync(50)
334
+
335
+ const appBarLink = document.querySelector('shade-app-bar-link') as HTMLElement
336
+ const computedStyle = window.getComputedStyle(appBarLink)
337
+ expect(computedStyle.transition).toContain('color')
338
+ })
339
+ })
340
+ })
341
+ })
@@ -1,32 +1,32 @@
1
1
  import type { RouteLinkProps } from '@furystack/shades'
2
- import { attachProps, createComponent, LocationService, RouteLink, Shade } from '@furystack/shades'
2
+ import { createComponent, LocationService, RouteLink, Shade } from '@furystack/shades'
3
3
  import { match, type MatchOptions } from 'path-to-regexp'
4
- import { ThemeProviderService } from '../services/theme-provider-service.js'
4
+ import { cssVariableTheme } from '../services/css-variable-theme.js'
5
5
 
6
6
  export const AppBarLink = Shade<RouteLinkProps & { routingOptions?: MatchOptions }>({
7
7
  shadowDomName: 'shade-app-bar-link',
8
+ css: {
9
+ display: 'flex',
10
+ height: '100%',
11
+ textDecoration: 'none',
12
+ alignItems: 'center',
13
+ padding: '0 8px',
14
+ transition: 'color .2s ease-in-out, transform .2s ease-in-out',
15
+ cursor: 'pointer',
16
+ color: cssVariableTheme.text.secondary,
17
+ opacity: '0.8',
18
+ transform: 'scale(0.9)',
19
+ '&.active': {
20
+ color: cssVariableTheme.text.primary,
21
+ opacity: '1',
22
+ transform: 'scale(1)',
23
+ },
24
+ },
8
25
  render: ({ children, props, useObservable, injector, element }) => {
9
- const getAnchorStyle = (currentUrl: string) => {
10
- const isActive = !!match(props.href as string, props.routingOptions)(currentUrl)
11
- const themeProviderService = injector.getInstance(ThemeProviderService)
12
- const { theme } = themeProviderService
13
- return {
14
- display: 'flex',
15
- height: '100%',
16
- textDecoration: 'none',
17
- alignItems: 'center',
18
- padding: '0 8px',
19
- transition: 'color .2s ease-in-out, transform .2s ease-in-out',
20
- color: isActive ? theme.text.primary : theme.text.secondary,
21
- opacity: isActive ? '1' : '0.8',
22
- transform: isActive ? 'scale(1)' : 'scale(0.9)',
23
- cursor: 'pointer',
24
- }
25
- }
26
-
27
26
  const [currentUrl] = useObservable('locationChange', injector.getInstance(LocationService).onLocationPathChanged)
28
27
 
29
- attachProps(element as HTMLElement, { style: getAnchorStyle(currentUrl) })
28
+ const isActive = !!match(props.href as string, props.routingOptions)(currentUrl)
29
+ element.classList.toggle('active', isActive)
30
30
 
31
31
  return <RouteLink {...props}>{children}</RouteLink>
32
32
  },
@@ -0,0 +1,142 @@
1
+ import { Injector } from '@furystack/inject'
2
+ import { createComponent, initializeShadeRoot } from '@furystack/shades'
3
+ import { sleepAsync } from '@furystack/utils'
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5
+ import { AppBar } from './app-bar.js'
6
+
7
+ describe('AppBar component', () => {
8
+ beforeEach(() => {
9
+ document.body.innerHTML = '<div id="root"></div>'
10
+ })
11
+
12
+ afterEach(() => {
13
+ document.body.innerHTML = ''
14
+ })
15
+
16
+ const renderAppBar = async (children: JSX.Element) => {
17
+ const injector = new Injector()
18
+ const rootElement = document.getElementById('root') as HTMLDivElement
19
+
20
+ initializeShadeRoot({
21
+ injector,
22
+ rootElement,
23
+ jsxElement: <AppBar>{children}</AppBar>,
24
+ })
25
+
26
+ await sleepAsync(50)
27
+
28
+ return {
29
+ injector,
30
+ appBar: document.querySelector('shade-app-bar') as HTMLElement,
31
+ }
32
+ }
33
+
34
+ describe('rendering', () => {
35
+ it('should render the shade-app-bar custom element', async () => {
36
+ const { appBar } = await renderAppBar(<span>Content</span>)
37
+ expect(appBar).not.toBeNull()
38
+ expect(appBar.tagName.toLowerCase()).toBe('shade-app-bar')
39
+ })
40
+
41
+ it('should render children in shadow DOM', async () => {
42
+ const { appBar } = await renderAppBar(<span id="child-content">Test Content</span>)
43
+ // Children are rendered inside shadow DOM - verify via the element itself
44
+ expect(appBar).not.toBeNull()
45
+ })
46
+
47
+ it('should render multiple children', async () => {
48
+ const injector = new Injector()
49
+ const rootElement = document.getElementById('root') as HTMLDivElement
50
+
51
+ initializeShadeRoot({
52
+ injector,
53
+ rootElement,
54
+ jsxElement: (
55
+ <AppBar>
56
+ <span id="logo">Logo</span>
57
+ <nav id="navigation">Nav</nav>
58
+ <button id="action">Action</button>
59
+ </AppBar>
60
+ ),
61
+ })
62
+
63
+ await sleepAsync(50)
64
+
65
+ const appBar = document.querySelector('shade-app-bar')
66
+ expect(appBar).not.toBeNull()
67
+ })
68
+ })
69
+
70
+ describe('positioning', () => {
71
+ it('should have fixed positioning', async () => {
72
+ const { appBar } = await renderAppBar(<span>Content</span>)
73
+ const computedStyle = window.getComputedStyle(appBar)
74
+ expect(computedStyle.position).toBe('fixed')
75
+ })
76
+
77
+ it('should have z-index of 1', async () => {
78
+ const { appBar } = await renderAppBar(<span>Content</span>)
79
+ const computedStyle = window.getComputedStyle(appBar)
80
+ expect(computedStyle.zIndex).toBe('1')
81
+ })
82
+
83
+ it('should have full width', async () => {
84
+ const { appBar } = await renderAppBar(<span>Content</span>)
85
+ const computedStyle = window.getComputedStyle(appBar)
86
+ expect(computedStyle.width).toBe('100%')
87
+ })
88
+ })
89
+
90
+ describe('fade-in animation', () => {
91
+ it('should add visible class after construction', async () => {
92
+ const { appBar } = await renderAppBar(<span>Content</span>)
93
+ expect(appBar.classList.contains('visible')).toBe(true)
94
+ })
95
+
96
+ it('should have opacity 1 when visible class is applied', async () => {
97
+ const { appBar } = await renderAppBar(<span>Content</span>)
98
+ const computedStyle = window.getComputedStyle(appBar)
99
+ expect(computedStyle.opacity).toBe('1')
100
+ })
101
+
102
+ it('should have transition styles for animation', async () => {
103
+ const { appBar } = await renderAppBar(<span>Content</span>)
104
+ const computedStyle = window.getComputedStyle(appBar)
105
+ expect(computedStyle.transition).toContain('opacity')
106
+ })
107
+ })
108
+
109
+ describe('layout', () => {
110
+ it('should have flex display', async () => {
111
+ const { appBar } = await renderAppBar(<span>Content</span>)
112
+ const computedStyle = window.getComputedStyle(appBar)
113
+ expect(computedStyle.display).toBe('flex')
114
+ })
115
+
116
+ it('should align items center', async () => {
117
+ const { appBar } = await renderAppBar(<span>Content</span>)
118
+ const computedStyle = window.getComputedStyle(appBar)
119
+ expect(computedStyle.alignItems).toBe('center')
120
+ })
121
+
122
+ it('should justify content to flex-start', async () => {
123
+ const { appBar } = await renderAppBar(<span>Content</span>)
124
+ const computedStyle = window.getComputedStyle(appBar)
125
+ expect(computedStyle.justifyContent).toBe('flex-start')
126
+ })
127
+ })
128
+
129
+ describe('styling', () => {
130
+ it('should have box shadow', async () => {
131
+ const { appBar } = await renderAppBar(<span>Content</span>)
132
+ const computedStyle = window.getComputedStyle(appBar)
133
+ expect(computedStyle.boxShadow).not.toBe('none')
134
+ })
135
+
136
+ it('should have semi-transparent background', async () => {
137
+ const { appBar } = await renderAppBar(<span>Content</span>)
138
+ const computedStyle = window.getComputedStyle(appBar)
139
+ expect(computedStyle.background).toContain('rgba')
140
+ })
141
+ })
142
+ })
@@ -1,32 +1,32 @@
1
- import { Shade, createComponent, attachProps } from '@furystack/shades'
2
- import { ThemeProviderService } from '../services/theme-provider-service.js'
1
+ import { Shade, createComponent } from '@furystack/shades'
2
+ import { cssVariableTheme } from '../services/css-variable-theme.js'
3
3
 
4
4
  export const AppBar = Shade({
5
5
  shadowDomName: 'shade-app-bar',
6
+ css: {
7
+ width: '100%',
8
+ background: 'rgba(128,128,128,0.2)',
9
+ backdropFilter: 'blur(15px)',
10
+ display: 'flex',
11
+ justifyContent: 'flex-start',
12
+ alignItems: 'center',
13
+ boxShadow: '0 0 12px rgba(0,0,0,0.6)',
14
+ transition:
15
+ 'opacity .35s cubic-bezier(0.550, 0.085, 0.680, 0.530), padding .2s cubic-bezier(0.550, 0.085, 0.680, 0.530)',
16
+ opacity: '0',
17
+ position: 'fixed',
18
+ zIndex: '1',
19
+ color: cssVariableTheme.text.secondary,
20
+ '&.visible': {
21
+ opacity: '1',
22
+ },
23
+ },
6
24
  constructed: ({ element }) => {
7
25
  requestAnimationFrame(() => {
8
- element.style.opacity = '1'
26
+ element.classList.add('visible')
9
27
  })
10
28
  },
11
- render: ({ children, injector, element }) => {
12
- const { theme } = injector.getInstance(ThemeProviderService)
13
- attachProps(element, {
14
- style: {
15
- width: '100%',
16
- background: 'rgba(128,128,128,0.2)',
17
- backdropFilter: 'blur(15px)',
18
- display: 'flex',
19
- justifyContent: 'flex-start',
20
- alignItems: 'center',
21
- boxShadow: '0 0 12px rgba(0,0,0,0.6)',
22
- transition:
23
- 'opacity .35s cubic-bezier(0.550, 0.085, 0.680, 0.530), padding .2s cubic-bezier(0.550, 0.085, 0.680, 0.530)',
24
- opacity: '0',
25
- position: 'fixed',
26
- zIndex: '1',
27
- color: theme.text.secondary,
28
- },
29
- })
29
+ render: ({ children }) => {
30
30
  return <>{children}</>
31
31
  },
32
32
  })