@grantcodes/ui 2.0.0-beta4 → 2.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 (310) hide show
  1. package/CHANGELOG.md +111 -0
  2. package/README.md +6 -8
  3. package/custom-elements.json +4273 -0
  4. package/package.json +80 -89
  5. package/src/components/app-bar/app-bar.component.js +90 -0
  6. package/src/components/app-bar/app-bar.js +8 -0
  7. package/src/components/app-bar/app-bar.stories.js +84 -0
  8. package/src/components/app-bar/app-bar.styles.js +227 -0
  9. package/src/components/app-bar/app-bar.test.js +174 -0
  10. package/src/components/app-bar/index.js +3 -0
  11. package/src/components/avatar/avatar.component.js +78 -0
  12. package/src/components/avatar/avatar.js +18 -0
  13. package/src/components/avatar/avatar.stories.js +45 -0
  14. package/src/components/avatar/avatar.styles.js +42 -0
  15. package/src/components/avatar/avatar.test.js +85 -0
  16. package/src/components/avatar/index.js +13 -0
  17. package/src/components/badge/badge.component.js +36 -0
  18. package/src/components/badge/badge.js +8 -0
  19. package/src/components/badge/badge.stories.js +46 -0
  20. package/src/components/badge/badge.styles.js +94 -0
  21. package/src/components/badge/badge.test.js +70 -0
  22. package/src/components/badge/index.js +3 -0
  23. package/src/components/breadcrumb/breadcrumb.component.js +110 -0
  24. package/src/components/breadcrumb/breadcrumb.js +12 -0
  25. package/src/components/breadcrumb/breadcrumb.stories.js +25 -0
  26. package/src/components/breadcrumb/breadcrumb.styles.js +96 -0
  27. package/src/components/breadcrumb/breadcrumb.test.js +144 -0
  28. package/src/components/breadcrumb/index.js +3 -0
  29. package/src/components/button/button.component.js +64 -0
  30. package/src/components/button/button.js +6 -0
  31. package/src/components/button/button.stories.js +78 -0
  32. package/src/components/button/button.styles.js +97 -0
  33. package/src/components/button/button.test.js +98 -0
  34. package/src/components/button/index.js +1 -0
  35. package/src/components/button-group/button-group.component.js +27 -0
  36. package/src/components/button-group/button-group.js +6 -0
  37. package/src/components/button-group/button-group.stories.js +33 -0
  38. package/src/components/button-group/button-group.styles.js +43 -0
  39. package/src/components/button-group/button-group.test.js +57 -0
  40. package/src/components/button-group/index.js +1 -0
  41. package/src/components/card/card.component.js +17 -0
  42. package/src/components/card/card.js +6 -0
  43. package/src/components/card/card.stories.js +36 -0
  44. package/src/components/card/card.styles.js +128 -0
  45. package/src/components/card/card.test.js +59 -0
  46. package/src/components/card/index.js +1 -0
  47. package/src/components/code-preview/code-preview.component.js +53 -0
  48. package/src/components/code-preview/code-preview.js +7 -0
  49. package/src/components/code-preview/code-preview.stories.js +67 -0
  50. package/src/components/code-preview/code-preview.styles.js +18 -0
  51. package/src/components/code-preview/code-preview.test.js +118 -0
  52. package/src/components/code-preview/index.js +1 -0
  53. package/src/components/container/container.component.js +38 -0
  54. package/src/components/container/container.js +7 -0
  55. package/src/components/container/container.stories.js +59 -0
  56. package/src/components/container/container.styles.js +43 -0
  57. package/src/components/container/container.test.js +84 -0
  58. package/src/components/container/index.js +1 -0
  59. package/src/components/dialog/dialog.component.js +78 -0
  60. package/src/components/dialog/dialog.js +7 -0
  61. package/src/components/dialog/dialog.stories.js +43 -0
  62. package/src/components/dialog/dialog.styles.js +74 -0
  63. package/src/components/dialog/dialog.test.js +97 -0
  64. package/src/components/dialog/index.js +1 -0
  65. package/src/components/dropdown/dropdown.component.js +225 -0
  66. package/src/components/dropdown/dropdown.js +12 -0
  67. package/src/components/dropdown/dropdown.stories.js +107 -0
  68. package/src/components/dropdown/dropdown.styles.js +128 -0
  69. package/src/components/dropdown/dropdown.test.js +144 -0
  70. package/src/components/dropdown/index.js +3 -0
  71. package/src/components/dropzone/dropzone.component.js +141 -0
  72. package/src/components/dropzone/dropzone.js +6 -0
  73. package/src/components/dropzone/dropzone.stories.js +41 -0
  74. package/src/components/dropzone/dropzone.styles.js +64 -0
  75. package/src/components/dropzone/dropzone.test.js +112 -0
  76. package/src/components/dropzone/index.js +1 -0
  77. package/src/components/footer/footer-column.component.js +15 -0
  78. package/src/components/footer/footer-column.styles.js +51 -0
  79. package/src/components/footer/footer.component.js +38 -0
  80. package/src/components/footer/footer.js +9 -0
  81. package/src/components/footer/footer.stories.js +143 -0
  82. package/src/components/footer/footer.styles.js +90 -0
  83. package/src/components/footer/footer.test.js +107 -0
  84. package/src/components/footer/index.js +2 -0
  85. package/src/components/form-field/form-field.component.js +173 -0
  86. package/src/components/form-field/form-field.js +7 -0
  87. package/src/components/form-field/form-field.stories.js +104 -0
  88. package/src/components/form-field/form-field.styles.js +47 -0
  89. package/src/components/form-field/form-field.test.js +118 -0
  90. package/src/components/form-field/index.js +1 -0
  91. package/src/components/gallery/gallery-image.component.js +52 -0
  92. package/src/components/gallery/gallery-image.js +7 -0
  93. package/src/components/gallery/gallery.component.js +25 -0
  94. package/src/components/gallery/gallery.js +7 -0
  95. package/src/components/gallery/gallery.stories.js +60 -0
  96. package/src/components/gallery/gallery.styles.js +56 -0
  97. package/src/components/gallery/gallery.test.js +58 -0
  98. package/src/components/gallery/index.js +2 -0
  99. package/src/components/icon/icon.component.js +14 -0
  100. package/src/components/icon/icon.js +7 -0
  101. package/src/components/icon/icon.stories.js +26 -0
  102. package/src/components/icon/icon.styles.js +28 -0
  103. package/src/components/icon/icon.test.js +57 -0
  104. package/src/components/icon/index.js +1 -0
  105. package/src/components/loading/index.js +1 -0
  106. package/src/components/loading/loading.component.js +21 -0
  107. package/src/components/loading/loading.js +7 -0
  108. package/src/components/loading/loading.stories.js +25 -0
  109. package/src/components/loading/loading.styles.js +43 -0
  110. package/src/components/loading/loading.test.js +57 -0
  111. package/src/components/notice/index.js +1 -0
  112. package/src/components/notice/notice.component.js +77 -0
  113. package/src/components/notice/notice.js +7 -0
  114. package/src/components/notice/notice.stories.js +122 -0
  115. package/src/components/notice/notice.styles.js +72 -0
  116. package/src/components/notice/notice.test.js +146 -0
  117. package/src/components/pagination/index.js +1 -0
  118. package/src/components/pagination/pagination.component.js +62 -0
  119. package/src/components/pagination/pagination.js +7 -0
  120. package/src/components/pagination/pagination.stories.js +34 -0
  121. package/src/components/pagination/pagination.styles.js +19 -0
  122. package/src/components/pagination/pagination.test.js +98 -0
  123. package/src/components/sidebar/index.js +3 -0
  124. package/src/components/sidebar/sidebar.component.js +165 -0
  125. package/src/components/sidebar/sidebar.js +8 -0
  126. package/src/components/sidebar/sidebar.stories.js +155 -0
  127. package/src/components/sidebar/sidebar.styles.js +192 -0
  128. package/src/components/sidebar/sidebar.test.js +196 -0
  129. package/src/components/tabs/index.js +2 -0
  130. package/src/components/tabs/internal/tabs-button.component.js +39 -0
  131. package/src/components/tabs/internal/tabs-button.js +7 -0
  132. package/src/components/tabs/internal/tabs-item.component.js +39 -0
  133. package/src/components/tabs/tab.component.js +20 -0
  134. package/src/components/tabs/tab.js +7 -0
  135. package/src/components/tabs/tabs.component.js +130 -0
  136. package/src/components/tabs/tabs.js +7 -0
  137. package/src/components/tabs/tabs.stories.js +39 -0
  138. package/src/components/tabs/tabs.styles.js +88 -0
  139. package/src/components/tabs/tabs.test.js +148 -0
  140. package/src/components/toast/index.js +3 -0
  141. package/src/components/toast/toast.component.js +187 -0
  142. package/src/components/toast/toast.js +9 -0
  143. package/src/components/toast/toast.stories.js +169 -0
  144. package/src/components/toast/toast.styles.js +207 -0
  145. package/src/components/toast/toast.test.js +196 -0
  146. package/src/components/tooltip/index.js +1 -0
  147. package/src/components/tooltip/tooltip.component.js +70 -0
  148. package/src/components/tooltip/tooltip.js +7 -0
  149. package/src/components/tooltip/tooltip.stories.js +33 -0
  150. package/src/components/tooltip/tooltip.styles.js +78 -0
  151. package/src/components/tooltip/tooltip.test.js +150 -0
  152. package/src/css/all.css +1 -0
  153. package/src/css/base.css +31 -0
  154. package/src/css/colors.stories.js +192 -0
  155. package/src/css/elements/a.css +50 -0
  156. package/src/css/elements/forms/button.css +15 -0
  157. package/src/css/elements/forms/input.css +183 -0
  158. package/src/css/elements/forms/label.css +5 -0
  159. package/src/css/elements/media/image.css +3 -0
  160. package/src/css/elements.css +5 -0
  161. package/src/css/elements.stories.js +108 -0
  162. package/src/css/helpers.css +16 -0
  163. package/src/css/themes/grantcodes.css +3 -0
  164. package/src/css/themes/todomap.css +2 -0
  165. package/src/css/themes/wireframe.css +2 -0
  166. package/src/css/tokens.stories.js +183 -0
  167. package/src/css/typography.css +64 -0
  168. package/src/css/typography.stories.js +179 -0
  169. package/src/css/util/functions.css +16 -0
  170. package/src/css/util/index.css +2 -0
  171. package/src/css/util/mixins.css +63 -0
  172. package/src/icons.js +3 -0
  173. package/src/lib/classnames.js +61 -0
  174. package/src/lib/generate-id.js +10 -0
  175. package/src/main.js +17 -0
  176. package/src/test-utils/assert-helpers.js +150 -0
  177. package/src/test-utils/events.js +88 -0
  178. package/src/test-utils/fixture.js +77 -0
  179. package/src/test-utils/index.js +7 -0
  180. package/dist/components/avatar/avatar.component.js +0 -3
  181. package/dist/components/avatar/avatar.js +0 -1
  182. package/dist/components/avatar/avatar.react.js +0 -1
  183. package/dist/components/avatar/avatar.scss.js +0 -1
  184. package/dist/components/avatar/index.js +0 -1
  185. package/dist/components/button/button.component.js +0 -5
  186. package/dist/components/button/button.js +0 -1
  187. package/dist/components/button/button.react.js +0 -1
  188. package/dist/components/button/button.scss.js +0 -1
  189. package/dist/components/button/index.js +0 -1
  190. package/dist/components/button-group/button-group.component.js +0 -5
  191. package/dist/components/button-group/button-group.js +0 -1
  192. package/dist/components/button-group/button-group.react.js +0 -1
  193. package/dist/components/button-group/button-group.scss.js +0 -1
  194. package/dist/components/button-group/index.js +0 -1
  195. package/dist/components/card/card.component.js +0 -8
  196. package/dist/components/card/card.js +0 -1
  197. package/dist/components/card/card.react.js +0 -1
  198. package/dist/components/card/card.scss.js +0 -1
  199. package/dist/components/card/index.js +0 -1
  200. package/dist/components/code-preview/code-preview.component.js +0 -1
  201. package/dist/components/code-preview/code-preview.js +0 -1
  202. package/dist/components/code-preview/code-preview.react.js +0 -1
  203. package/dist/components/code-preview/code-preview.scss.js +0 -1
  204. package/dist/components/code-preview/index.js +0 -1
  205. package/dist/components/container/container.component.js +0 -5
  206. package/dist/components/container/container.js +0 -1
  207. package/dist/components/container/container.react.js +0 -1
  208. package/dist/components/container/container.scss.js +0 -1
  209. package/dist/components/container/index.js +0 -1
  210. package/dist/components/dialog/dialog.component.js +0 -23
  211. package/dist/components/dialog/dialog.js +0 -1
  212. package/dist/components/dialog/dialog.react.js +0 -1
  213. package/dist/components/dialog/dialog.scss.js +0 -1
  214. package/dist/components/dialog/index.js +0 -1
  215. package/dist/components/dropzone/dropzone.component.js +0 -11
  216. package/dist/components/dropzone/dropzone.js +0 -1
  217. package/dist/components/dropzone/dropzone.react.js +0 -1
  218. package/dist/components/dropzone/dropzone.scss.js +0 -1
  219. package/dist/components/dropzone/index.js +0 -1
  220. package/dist/components/form-field/form-field.component.js +0 -22
  221. package/dist/components/form-field/form-field.js +0 -1
  222. package/dist/components/form-field/form-field.react.js +0 -1
  223. package/dist/components/form-field/form-field.scss.js +0 -1
  224. package/dist/components/form-field/index.js +0 -1
  225. package/dist/components/gallery/gallery-image.component.js +0 -14
  226. package/dist/components/gallery/gallery-image.js +0 -1
  227. package/dist/components/gallery/gallery.component.js +0 -5
  228. package/dist/components/gallery/gallery.js +0 -1
  229. package/dist/components/gallery/gallery.react.js +0 -1
  230. package/dist/components/gallery/gallery.scss.js +0 -1
  231. package/dist/components/gallery/index.js +0 -1
  232. package/dist/components/icon/icon.component.js +0 -1
  233. package/dist/components/icon/icon.js +0 -1
  234. package/dist/components/icon/icon.react.js +0 -1
  235. package/dist/components/icon/icon.scss.js +0 -1
  236. package/dist/components/icon/index.js +0 -1
  237. package/dist/components/loading/index.js +0 -1
  238. package/dist/components/loading/loading.component.js +0 -5
  239. package/dist/components/loading/loading.js +0 -1
  240. package/dist/components/loading/loading.react.js +0 -1
  241. package/dist/components/loading/loading.scss.js +0 -1
  242. package/dist/components/notice/index.js +0 -1
  243. package/dist/components/notice/notice.component.js +0 -17
  244. package/dist/components/notice/notice.js +0 -1
  245. package/dist/components/notice/notice.react.js +0 -1
  246. package/dist/components/notice/notice.scss.js +0 -1
  247. package/dist/components/pagination/index.js +0 -1
  248. package/dist/components/pagination/pagination.component.js +0 -13
  249. package/dist/components/pagination/pagination.js +0 -1
  250. package/dist/components/pagination/pagination.react.js +0 -1
  251. package/dist/components/pagination/pagination.scss.js +0 -1
  252. package/dist/components/tabs/index.js +0 -1
  253. package/dist/components/tabs/tab.component.js +0 -1
  254. package/dist/components/tabs/tab.js +0 -1
  255. package/dist/components/tabs/tabs-item.component.js +0 -1
  256. package/dist/components/tabs/tabs-panel.component.js +0 -10
  257. package/dist/components/tabs/tabs-panel.js +0 -1
  258. package/dist/components/tabs/tabs-tab.component.js +0 -12
  259. package/dist/components/tabs/tabs-tab.js +0 -1
  260. package/dist/components/tabs/tabs.component.js +0 -28
  261. package/dist/components/tabs/tabs.js +0 -1
  262. package/dist/components/tabs/tabs.react.js +0 -1
  263. package/dist/components/tabs/tabs.scss.js +0 -1
  264. package/dist/components/tooltip/index.js +0 -1
  265. package/dist/components/tooltip/tooltip.component.js +0 -10
  266. package/dist/components/tooltip/tooltip.js +0 -1
  267. package/dist/components/tooltip/tooltip.react.js +0 -1
  268. package/dist/components/tooltip/tooltip.scss.js +0 -1
  269. package/dist/css/base.css +0 -1
  270. package/dist/css/themes/grantcodes.css +0 -1
  271. package/dist/fonts/greycliff-bold-oblique.woff +0 -0
  272. package/dist/fonts/greycliff-bold-oblique.woff2 +0 -0
  273. package/dist/fonts/greycliff-bold.woff +0 -0
  274. package/dist/fonts/greycliff-bold.woff2 +0 -0
  275. package/dist/fonts/greycliff-demi-bold-oblique.woff +0 -0
  276. package/dist/fonts/greycliff-demi-bold-oblique.woff2 +0 -0
  277. package/dist/fonts/greycliff-demi-bold.woff +0 -0
  278. package/dist/fonts/greycliff-demi-bold.woff2 +0 -0
  279. package/dist/fonts/greycliff-extra-bold-oblique.woff +0 -0
  280. package/dist/fonts/greycliff-extra-bold-oblique.woff2 +0 -0
  281. package/dist/fonts/greycliff-extra-bold.woff +0 -0
  282. package/dist/fonts/greycliff-extra-bold.woff2 +0 -0
  283. package/dist/fonts/greycliff-extra-light-oblique.woff +0 -0
  284. package/dist/fonts/greycliff-extra-light-oblique.woff2 +0 -0
  285. package/dist/fonts/greycliff-extra-light.woff +0 -0
  286. package/dist/fonts/greycliff-extra-light.woff2 +0 -0
  287. package/dist/fonts/greycliff-heavy-oblique.woff +0 -0
  288. package/dist/fonts/greycliff-heavy-oblique.woff2 +0 -0
  289. package/dist/fonts/greycliff-heavy.woff +0 -0
  290. package/dist/fonts/greycliff-heavy.woff2 +0 -0
  291. package/dist/fonts/greycliff-light-oblique.woff +0 -0
  292. package/dist/fonts/greycliff-light-oblique.woff2 +0 -0
  293. package/dist/fonts/greycliff-light.woff +0 -0
  294. package/dist/fonts/greycliff-light.woff2 +0 -0
  295. package/dist/fonts/greycliff-medium-oblique.woff +0 -0
  296. package/dist/fonts/greycliff-medium-oblique.woff2 +0 -0
  297. package/dist/fonts/greycliff-medium.woff +0 -0
  298. package/dist/fonts/greycliff-medium.woff2 +0 -0
  299. package/dist/fonts/greycliff-regular-oblique.woff +0 -0
  300. package/dist/fonts/greycliff-regular-oblique.woff2 +0 -0
  301. package/dist/fonts/greycliff-regular.woff +0 -0
  302. package/dist/fonts/greycliff-regular.woff2 +0 -0
  303. package/dist/fonts/greycliff-thin-oblique.woff +0 -0
  304. package/dist/fonts/greycliff-thin-oblique.woff2 +0 -0
  305. package/dist/fonts/greycliff-thin.woff +0 -0
  306. package/dist/fonts/greycliff-thin.woff2 +0 -0
  307. package/dist/icons.js +0 -1
  308. package/dist/lib/generate-id.js +0 -1
  309. package/dist/main.js +0 -1
  310. package/dist/react.js +0 -1
@@ -0,0 +1,196 @@
1
+ import { describe, it, afterEach } from "node:test";
2
+ import { strict as assert } from "node:assert";
3
+ import { fixture, cleanup, click } from "../../test-utils/index.js";
4
+ import "./sidebar.js";
5
+
6
+ describe("Sidebar Component", () => {
7
+ let element;
8
+
9
+ afterEach(() => {
10
+ cleanup(element);
11
+ });
12
+
13
+ it("should render with default properties", async () => {
14
+ element = await fixture("grantcodes-sidebar");
15
+ assert.ok(element, "Element should be created");
16
+ assert.ok(element.shadowRoot, "Element should have shadow root");
17
+ });
18
+
19
+ it("should have left position by default", async () => {
20
+ element = await fixture("grantcodes-sidebar");
21
+ assert.strictEqual(element.position, "left", "Should have left position by default");
22
+ });
23
+
24
+ it("should not be collapsed by default", async () => {
25
+ element = await fixture("grantcodes-sidebar");
26
+ assert.strictEqual(element.collapsed, false, "Should not be collapsed by default");
27
+ });
28
+
29
+ it("should be collapsible by default", async () => {
30
+ element = await fixture("grantcodes-sidebar");
31
+ assert.strictEqual(element.collapsible, true, "Should be collapsible by default");
32
+ });
33
+
34
+ it("should have default width", async () => {
35
+ element = await fixture("grantcodes-sidebar");
36
+ assert.strictEqual(element.width, "280px", "Should have default width");
37
+ });
38
+
39
+ it("should render aside element", async () => {
40
+ element = await fixture("grantcodes-sidebar");
41
+ const aside = element.shadowRoot.querySelector("aside");
42
+ assert.ok(aside, "Aside element should exist");
43
+ });
44
+
45
+ it("should apply left position class", async () => {
46
+ element = await fixture("grantcodes-sidebar", {
47
+ position: "left",
48
+ });
49
+
50
+ const sidebar = element.shadowRoot.querySelector(".sidebar--left");
51
+ assert.ok(sidebar, "Should have left position class");
52
+ });
53
+
54
+ it("should apply right position class", async () => {
55
+ element = await fixture("grantcodes-sidebar", {
56
+ position: "right",
57
+ });
58
+
59
+ const sidebar = element.shadowRoot.querySelector(".sidebar--right");
60
+ assert.ok(sidebar, "Should have right position class");
61
+ });
62
+
63
+ it("should apply collapsed class when collapsed", async () => {
64
+ element = await fixture("grantcodes-sidebar", {
65
+ collapsed: true,
66
+ });
67
+
68
+ const sidebar = element.shadowRoot.querySelector(".sidebar--collapsed");
69
+ assert.ok(sidebar, "Should have collapsed class");
70
+ });
71
+
72
+ it("should render toggle button when collapsible", async () => {
73
+ element = await fixture("grantcodes-sidebar", {
74
+ collapsible: true,
75
+ });
76
+
77
+ const toggle = element.shadowRoot.querySelector(".sidebar__toggle");
78
+ assert.ok(toggle, "Toggle button should exist");
79
+ });
80
+
81
+ it("should not render toggle button when not collapsible", async () => {
82
+ element = await fixture("grantcodes-sidebar", {
83
+ collapsible: false,
84
+ });
85
+
86
+ const toggle = element.shadowRoot.querySelector(".sidebar__toggle");
87
+ assert.ok(!toggle, "Toggle button should not exist");
88
+ });
89
+
90
+ it("should toggle collapsed state when button is clicked", async () => {
91
+ element = await fixture("grantcodes-sidebar", {
92
+ collapsible: true,
93
+ });
94
+
95
+ const toggle = element.shadowRoot.querySelector(".sidebar__toggle");
96
+
97
+ assert.strictEqual(element.collapsed, false, "Should start not collapsed");
98
+
99
+ click(toggle);
100
+ await element.updateComplete;
101
+
102
+ assert.strictEqual(element.collapsed, true, "Should be collapsed after click");
103
+ });
104
+
105
+ it("should emit toggle event when collapsed state changes", async () => {
106
+ element = await fixture("grantcodes-sidebar", {
107
+ collapsible: true,
108
+ });
109
+
110
+ let toggledState = null;
111
+ element.addEventListener("toggle", (e) => {
112
+ toggledState = e.detail.collapsed;
113
+ });
114
+
115
+ const toggle = element.shadowRoot.querySelector(".sidebar__toggle");
116
+ click(toggle);
117
+
118
+ await element.updateComplete;
119
+
120
+ assert.strictEqual(toggledState, true, "Toggle event should fire with collapsed state");
121
+ });
122
+
123
+ it("should apply custom width", async () => {
124
+ element = await fixture("grantcodes-sidebar", {
125
+ width: "350px",
126
+ });
127
+
128
+ const aside = element.shadowRoot.querySelector("aside");
129
+ const style = aside.getAttribute("style");
130
+ assert.ok(style.includes("--sidebar-width: 350px"), "Should set custom width");
131
+ });
132
+
133
+ it("should have mobile toggle button", async () => {
134
+ element = await fixture("grantcodes-sidebar");
135
+ const mobileToggle = element.shadowRoot.querySelector(".sidebar__mobile-toggle");
136
+ assert.ok(mobileToggle, "Mobile toggle button should exist");
137
+ });
138
+
139
+ it("should have content container", async () => {
140
+ element = await fixture("grantcodes-sidebar");
141
+ const content = element.shadowRoot.querySelector(".sidebar__content");
142
+ assert.ok(content, "Content container should exist");
143
+ });
144
+
145
+ it("should have slot for content", async () => {
146
+ element = await fixture("grantcodes-sidebar");
147
+ const slot = element.shadowRoot.querySelector("slot");
148
+ assert.ok(slot, "Slot should exist for content");
149
+ });
150
+
151
+ it("should toggle mobile drawer when mobile toggle is clicked", async () => {
152
+ element = await fixture("grantcodes-sidebar");
153
+ const mobileToggle = element.shadowRoot.querySelector(".sidebar__mobile-toggle");
154
+
155
+ assert.strictEqual(element._drawerOpen, false, "Drawer should start closed");
156
+
157
+ click(mobileToggle);
158
+ await element.updateComplete;
159
+
160
+ assert.strictEqual(element._drawerOpen, true, "Drawer should be open after click");
161
+ });
162
+
163
+ it("should emit drawer-toggle event", async () => {
164
+ element = await fixture("grantcodes-sidebar");
165
+
166
+ let drawerState = null;
167
+ element.addEventListener("drawer-toggle", (e) => {
168
+ drawerState = e.detail.open;
169
+ });
170
+
171
+ const mobileToggle = element.shadowRoot.querySelector(".sidebar__mobile-toggle");
172
+ click(mobileToggle);
173
+
174
+ await element.updateComplete;
175
+
176
+ assert.strictEqual(drawerState, true, "Drawer toggle event should fire");
177
+ });
178
+
179
+ it("should render overlay when drawer is open", async () => {
180
+ element = await fixture("grantcodes-sidebar");
181
+ element._drawerOpen = true;
182
+
183
+ await element.updateComplete;
184
+
185
+ const overlay = element.shadowRoot.querySelector(".sidebar__overlay");
186
+ assert.ok(overlay, "Overlay should exist when drawer is open");
187
+ });
188
+
189
+ it("should not render overlay when drawer is closed", async () => {
190
+ element = await fixture("grantcodes-sidebar");
191
+ const overlay = element.shadowRoot.querySelector(".sidebar__overlay");
192
+ assert.ok(!overlay, "Overlay should not exist when drawer is closed");
193
+ });
194
+ });
195
+
196
+
@@ -0,0 +1,2 @@
1
+ export * from "./tabs";
2
+ export * from "./tab";
@@ -0,0 +1,39 @@
1
+ import { LitElement } from "lit";
2
+ import { html } from "lit/static-html.js";
3
+ import { ifDefined } from "lit/directives/if-defined.js";
4
+ import { GrantCodesTabsItem } from "./tabs-item.component.js";
5
+ import { tabsStyles } from "../tabs.styles.js";
6
+
7
+ export class GrantCodesTabsButton extends GrantCodesTabsItem {
8
+ static shadowRootOptions = {
9
+ ...LitElement.shadowRootOptions,
10
+ delegatesFocus: true,
11
+ };
12
+
13
+ static styles = [tabsStyles];
14
+
15
+ render() {
16
+ console.log("GrantCodesTabsButton", this, {
17
+ active: this.active,
18
+ label: this.label,
19
+ index: this.index,
20
+ containerId: this.containerId,
21
+ buttonId: this.buttonId,
22
+ panelId: this.panelId,
23
+ content: this.content,
24
+ });
25
+ return html`
26
+ <button
27
+ id="${this.buttonId}"
28
+ type="button"
29
+ role="tab"
30
+ aria-selected=${this.active}
31
+ aria-controls="${this.panelId}"
32
+ tabindex=${ifDefined(this.active ? undefined : "-1")}
33
+ class="tabs__tab ${this.active ? "is-active" : ""}"
34
+ >
35
+ <span>${this.label}</span>
36
+ </button>
37
+ `;
38
+ }
39
+ }
@@ -0,0 +1,7 @@
1
+ import { GrantCodesTabsButton } from "./tabs-button.component.js";
2
+
3
+ export * from "./tabs-button.component.js";
4
+ export default GrantCodesTabsButton;
5
+
6
+ customElements.define("grantcodes-tabs-button", GrantCodesTabsButton);
7
+
@@ -0,0 +1,39 @@
1
+ import { LitElement } from "lit";
2
+ import { html } from "lit/static-html.js";
3
+
4
+ export class GrantCodesTabsItem extends LitElement {
5
+ static properties = {
6
+ active: { type: Boolean },
7
+ label: { type: String },
8
+ index: { type: Number },
9
+ containerId: { type: String },
10
+ buttonId: { type: String, reflect: true },
11
+ panelId: { type: String },
12
+ content: { type: String },
13
+ };
14
+
15
+ constructor() {
16
+ super();
17
+
18
+ this.active = false;
19
+ this.label = "";
20
+ this.index = -1;
21
+ this.containerId = "";
22
+ }
23
+
24
+ get buttonId() {
25
+ return `${this.containerId}-button-${this.index.toString()}`;
26
+ }
27
+
28
+ get panelId() {
29
+ return `${this.containerId}-panel-${this.index.toString()}`;
30
+ }
31
+
32
+ get content() {
33
+ return this.label;
34
+ }
35
+
36
+ render() {
37
+ return html``;
38
+ }
39
+ }
@@ -0,0 +1,20 @@
1
+ import { GrantCodesTabsItem } from "./internal/tabs-item.component.js";
2
+ import { html } from "lit/static-html.js";
3
+ import { tabsStyles } from "./tabs.styles.js";
4
+
5
+ export class GrantCodesTab extends GrantCodesTabsItem {
6
+ static styles = [tabsStyles];
7
+
8
+ render() {
9
+ return html`
10
+ <div
11
+ id="${this.panelId}"
12
+ role="tabpanel"
13
+ aria-labelledby="${this.buttonId}"
14
+ class="tabs__panel ${this.active ? "is-active" : ""}"
15
+ >
16
+ <slot></slot>
17
+ </div>
18
+ `;
19
+ }
20
+ }
@@ -0,0 +1,7 @@
1
+ import { GrantCodesTab } from "./tab.component.js";
2
+
3
+ export * from "./tab.component.js";
4
+ export default GrantCodesTab;
5
+
6
+ customElements.define("grantcodes-tab", GrantCodesTab);
7
+
@@ -0,0 +1,130 @@
1
+ import { LitElement } from "lit";
2
+ import { html } from "lit/static-html.js";
3
+ import { ifDefined } from "lit/directives/if-defined.js";
4
+ import { tabsStyles } from "./tabs.styles.js";
5
+ import { GrantCodesTab } from "./tab.component.js";
6
+ import { GrantCodesTabsButton } from "./internal/tabs-button.component.js";
7
+ import "./internal/tabs-button.js";
8
+ import { generateId } from "../../lib/generate-id.js";
9
+
10
+ export class GrantCodesTabs extends LitElement {
11
+ // Styles are scoped to this element: they won't conflict with styles
12
+ // on the main page or in other components. Styling API can be exposed
13
+ // via CSS custom properties.
14
+ static styles = [tabsStyles];
15
+
16
+ static properties = {
17
+ label: { type: String },
18
+ _focusedTabIndex: { type: Number, state: true },
19
+ };
20
+
21
+ constructor() {
22
+ super();
23
+
24
+ this.label = "";
25
+ this._focusedTabIndex = -1;
26
+
27
+ if (!this.id) {
28
+ this.id = generateId("tabs");
29
+ }
30
+ }
31
+
32
+ /** @type {GrantCodesTab[]} */
33
+ tabs = [];
34
+
35
+ /** @type {GrantCodesTabsButton[]} */
36
+ tabButtons = [];
37
+
38
+ get activeTab() {
39
+ return this.tabs.find((tab) => tab.active) || null;
40
+ }
41
+
42
+ set activeTab(tab) {
43
+ // Ignore setting activeTab to null. As long as there are children, one tab
44
+ // must be selected.
45
+ this.tabs.forEach((t, i) => {
46
+ if (t === tab) {
47
+ t.active = true;
48
+ this._focusedTabIndex = i;
49
+ } else {
50
+ t.active = false;
51
+ }
52
+ });
53
+ this.requestUpdate();
54
+ }
55
+
56
+ initializeTabs() {
57
+ this.tabs.forEach((tab, i) => {
58
+ tab.index = i;
59
+ tab.containerId = this.id;
60
+ });
61
+
62
+ // If no tab is active, default to the first tab.
63
+ if (this.activeTab == null) {
64
+ this._focusedTabIndex = 0;
65
+ this.activeTab = this.tabs[0];
66
+ }
67
+ }
68
+
69
+ firstUpdated() {
70
+ const slot = this.renderRoot.querySelector("slot");
71
+ this.tabs = slot
72
+ ? slot.assignedElements().filter((el) => el.tagName === "GRANTCODES-TAB")
73
+ : [];
74
+ this.tabButtons = Array.from(
75
+ this.renderRoot.querySelectorAll("grantcodes-tabs-button"),
76
+ );
77
+ this.initializeTabs();
78
+ }
79
+
80
+ handleTabKeyDown(e) {
81
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
82
+ e.preventDefault();
83
+ const tabs = this.tabs;
84
+ const nextIndex =
85
+ e.key === "ArrowRight"
86
+ ? (this._focusedTabIndex + 1) % tabs.length
87
+ : (this._focusedTabIndex - 1 + tabs.length) % tabs.length;
88
+
89
+ // Focus the next tab button.
90
+ const tabButton = this.tabButtons[nextIndex];
91
+ if (tabButton) {
92
+ tabButton.focus();
93
+ this._focusedTabIndex = nextIndex;
94
+ }
95
+ }
96
+ }
97
+
98
+ renderTabButtons() {
99
+ return this.tabs.map(
100
+ (tab, i) => html`
101
+ <grantcodes-tabs-button
102
+ index=${i + 1}
103
+ label="${tab.label}"
104
+ containerId="${this.id}"
105
+ active=${tab.active}
106
+ @click=${() => {
107
+ this.activeTab = tab;
108
+ }}
109
+ @keydown=${this.handleTabKeyDown}
110
+ ></grantcodes-tabs-button>
111
+ `,
112
+ );
113
+ }
114
+
115
+ render() {
116
+ return html`
117
+ <div class="tabs" id="${this.id}">
118
+ <div role="tablist" class="tabs__tablist" aria-label=${ifDefined(this.label)}>
119
+ <div class="tabs__tablist__inner">
120
+ ${this.renderTabButtons()}
121
+ </div>
122
+ </div>
123
+
124
+ <div class="tabs__panels">
125
+ <slot></slot>
126
+ </div>
127
+ </div>
128
+ `;
129
+ }
130
+ }
@@ -0,0 +1,7 @@
1
+ import { GrantCodesTabs } from "./tabs.component.js";
2
+
3
+ export * from "./tabs.component.js";
4
+ export default GrantCodesTabs;
5
+
6
+ customElements.define("grantcodes-tabs", GrantCodesTabs);
7
+
@@ -0,0 +1,39 @@
1
+ import { html } from "lit/static-html.js";
2
+ import { getStorybookHelpers } from "@wc-toolkit/storybook-helpers";
3
+ const { events, args, argTypes, template } =
4
+ getStorybookHelpers("grantcodes-tabs");
5
+ import "./tabs.js";
6
+ import "./tab.js";
7
+
8
+ const meta = {
9
+ title: "Components/Tabs",
10
+ component: "grantcodes-tabs",
11
+ args: {
12
+ ...args,
13
+ },
14
+ argTypes,
15
+ render: (args) =>
16
+ template(
17
+ args,
18
+ html`
19
+ <grantcodes-tab label="Tab 1">
20
+ <p>This is the content of the first tab.</p>
21
+ </grantcodes-tab>
22
+ <grantcodes-tab label="Tab 2">
23
+ <p>This is the content of the second tab.</p>
24
+ </grantcodes-tab>
25
+ <grantcodes-tab label="Tab 3">
26
+ <p>This is the content of the third tab.</p>
27
+ </grantcodes-tab>
28
+ `,
29
+ ),
30
+ parameters: {
31
+ actions: {
32
+ handles: events,
33
+ },
34
+ },
35
+ };
36
+
37
+ export default meta;
38
+
39
+ export const Tabs = {};
@@ -0,0 +1,88 @@
1
+ import { css } from "lit";
2
+
3
+ export const tabsStyles = css`
4
+ *,
5
+ *::before,
6
+ *::after {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ .tabs {
11
+ --border-width: 3px;
12
+ display: block;
13
+ }
14
+
15
+ .tabs__tablist {
16
+ overflow: auto;
17
+ contain: none;
18
+ }
19
+
20
+ .tabs__tablist__inner {
21
+ position: relative;
22
+ display: flex;
23
+ flex-direction: row;
24
+ flex-wrap: nowrap;
25
+ width: fit-content;
26
+ min-width: 100%;
27
+ background-image: linear-gradient(
28
+ var(--g-theme-color-border-default, var(--g-color-brand-purple-100)),
29
+ var(--g-theme-color-border-default, var(--g-color-brand-purple-100))
30
+ );
31
+ background-size: auto var(--border-width);
32
+ background-position: bottom;
33
+ background-repeat: repeat-x;
34
+ }
35
+
36
+ .tabs__tab {
37
+ padding: 0.6em 1.2em;
38
+ line-height: 1;
39
+ font-size: 1rem;
40
+ border: none;
41
+ border-block-start: var(--border-width) solid transparent;
42
+ border-block-end: var(--border-width) solid var(--g-theme-color-border-default, var(--g-color-brand-purple-100));
43
+ background-color: transparent;
44
+ color: var(--g-theme-color-content-default);
45
+ opacity: 0.7;
46
+ white-space: nowrap;
47
+ cursor: pointer;
48
+ transition: opacity 0.2s, border-color 0.2s, background-color 0.2s;
49
+ --component-focus-ring-offset: calc(var(--component-focus-ring-width) * -1);
50
+ }
51
+
52
+ @media (min-width: 40em) {
53
+ .tabs__tab {
54
+ padding-inline: 1rem;
55
+ }
56
+ }
57
+
58
+ .tabs__tab:hover,
59
+ .tabs__tab:focus-visible {
60
+ opacity: 1;
61
+ border-block-end-color: var(--g-theme-color-border-default, var(--g-color-brand-purple-200));
62
+ }
63
+
64
+ .tabs__tab.is-active {
65
+ border-block-end-color: var(--g-theme-color-border-brand, var(--g-color-brand-purple-500));
66
+ background-color: var(--g-theme-color-background-subtle);
67
+ color: var(--g-theme-color-content-default);
68
+ z-index: 1;
69
+ position: relative;
70
+ opacity: 1;
71
+ }
72
+
73
+ .tabs__tab:focus-visible {
74
+ outline: 2px solid var(--component-focus-ring-color, rgba(106, 91, 197, 0.4));
75
+ outline-offset: 2px;
76
+ }
77
+
78
+ .tabs__panel {
79
+ margin-block-start: 1rem;
80
+ outline-color: var(--g-theme-color-border-brand, var(--g-color-brand-purple-500));
81
+ outline-offset: 1rem;
82
+ display: none;
83
+ }
84
+
85
+ .tabs__panel.is-active {
86
+ display: block;
87
+ }
88
+ `;