@grantcodes/ui 2.0.0-beta4 → 2.0.2

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 +125 -0
  2. package/README.md +6 -8
  3. package/custom-elements.json +4273 -0
  4. package/package.json +79 -88
  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,70 @@
1
+ import { describe, it, afterEach } from "node:test";
2
+ import { strict as assert } from "node:assert";
3
+ import { fixture, cleanup } from "../../test-utils/index.js";
4
+ import "./badge.js";
5
+
6
+ describe("Badge 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-badge");
15
+ assert.ok(element, "Element should be created");
16
+ assert.ok(element.shadowRoot, "Element should have shadow root");
17
+ });
18
+
19
+ it("should have neutral variant by default", async () => {
20
+ element = await fixture("grantcodes-badge");
21
+ assert.strictEqual(element.variant, "neutral", "Default variant should be neutral");
22
+ });
23
+
24
+ // it("should have outline style by default", async () => {
25
+ // element = await fixture("grantcodes-badge");
26
+ // assert.strictEqual(element.style, "outline", "Default style should be outline");
27
+ // });
28
+
29
+ // it("should render badge with correct style class", async () => {
30
+ // element = await fixture("grantcodes-badge", {
31
+ // style: "outline",
32
+ // });
33
+
34
+ // const badge = element.shadowRoot.querySelector(".badge--outline");
35
+ // assert.ok(badge, "Badge should have outline style class");
36
+ // });
37
+
38
+ it("should render slotted content", async () => {
39
+ element = await fixture("grantcodes-badge");
40
+ element.textContent = "Badge Text";
41
+
42
+ await element.updateComplete;
43
+
44
+ assert.strictEqual(element.textContent, "Badge Text", "Slotted content should be rendered");
45
+ });
46
+
47
+ it("should support all variant types", async () => {
48
+ const variants = ["primary", "success", "warning", "error", "info", "neutral"];
49
+
50
+ for (const variant of variants) {
51
+ element = await fixture("grantcodes-badge", { variant });
52
+ const badge = element.shadowRoot.querySelector(`.badge--${variant}`);
53
+ assert.ok(badge, `Badge should have ${variant} variant class`);
54
+ cleanup(element);
55
+ }
56
+ });
57
+
58
+ // it("should support all style types", async () => {
59
+ // const styles = ["outline", "soft"];
60
+
61
+ // for (const style of styles) {
62
+ // element = await fixture("grantcodes-badge", { style });
63
+ // const badge = element.shadowRoot.querySelector(`.badge--${style}`);
64
+ // assert.ok(badge, `Badge should have ${style} style class`);
65
+ // cleanup(element);
66
+ // }
67
+ // });
68
+ });
69
+
70
+
@@ -0,0 +1,3 @@
1
+ export * from "./badge.js";
2
+
3
+
@@ -0,0 +1,110 @@
1
+ import { LitElement } from "lit";
2
+ import { html } from "lit/static-html.js";
3
+ import { breadcrumbStyles } from "./breadcrumb.styles.js";
4
+
5
+ export class GrantCodesBreadcrumb extends LitElement {
6
+ static styles = [breadcrumbStyles];
7
+
8
+ static properties = {
9
+ separator: { type: String },
10
+ };
11
+
12
+ constructor() {
13
+ super();
14
+
15
+ /**
16
+ * Custom separator between breadcrumb items
17
+ * @type {string}
18
+ */
19
+ this.separator = "/";
20
+ }
21
+
22
+ firstUpdated() {
23
+ const value = this.separator ?? "/";
24
+ this.style.setProperty("--component-breadcrumb-separator", `'${value}'`);
25
+
26
+ // Track slot content changes to update separators
27
+ const slot = this.renderRoot.querySelector('slot:not([name])');
28
+ if (slot) {
29
+ slot.addEventListener("slotchange", () => this._updateSeparators());
30
+ this._updateSeparators();
31
+ }
32
+ }
33
+
34
+ updated(changed) {
35
+ if (changed.has("separator")) {
36
+ // Reflect separator into CSS custom property for pseudo-element content
37
+ const value = this.separator ?? "/";
38
+ this.style.setProperty("--component-breadcrumb-separator", `'${value}'`);
39
+ }
40
+
41
+ // Recalculate separators if light DOM may have changed
42
+ if (changed.size > 0) {
43
+ this._updateSeparators();
44
+ }
45
+ }
46
+
47
+ _updateSeparators() {
48
+ const slot = this.renderRoot.querySelector('slot:not([name])');
49
+ if (!slot) return;
50
+ const items = slot.assignedElements({ flatten: true }).filter((el) =>
51
+ el.tagName === "GRANTCODES-BREADCRUMB-ITEM",
52
+ );
53
+ items.forEach((el, index) => {
54
+ if (index < items.length - 1) {
55
+ el.setAttribute("data-has-separator", "");
56
+ } else {
57
+ el.removeAttribute("data-has-separator");
58
+ }
59
+ });
60
+ }
61
+
62
+ render() {
63
+ return html`
64
+ <nav aria-label="Breadcrumb" class="breadcrumb">
65
+ <ol class="breadcrumb__list">
66
+ <slot></slot>
67
+ </ol>
68
+ </nav>
69
+ `;
70
+ }
71
+ }
72
+
73
+ export class GrantCodesBreadcrumbItem extends LitElement {
74
+ static styles = [breadcrumbStyles];
75
+
76
+ static properties = {
77
+ href: { type: String },
78
+ current: { type: Boolean },
79
+ };
80
+
81
+ constructor() {
82
+ super();
83
+
84
+ /**
85
+ * URL for the breadcrumb item
86
+ * @type {string}
87
+ */
88
+ this.href = "";
89
+
90
+ /**
91
+ * Whether this is the current page
92
+ * @type {boolean}
93
+ */
94
+ this.current = false;
95
+ }
96
+
97
+ render() {
98
+ const content = this.href && !this.current
99
+ ? html`<a href="${this.href}" class="breadcrumb__link"><slot></slot></a>`
100
+ : html`<span class="breadcrumb__text"><slot></slot></span>`;
101
+
102
+ return html`
103
+ <li class="breadcrumb__item" ?data-current=${this.current}>
104
+ ${content}
105
+ </li>
106
+ `;
107
+ }
108
+ }
109
+
110
+
@@ -0,0 +1,12 @@
1
+ import {
2
+ GrantCodesBreadcrumb,
3
+ GrantCodesBreadcrumbItem,
4
+ } from "./breadcrumb.component.js";
5
+
6
+ export * from "./breadcrumb.component.js";
7
+ export default GrantCodesBreadcrumb;
8
+
9
+ customElements.define("grantcodes-breadcrumb", GrantCodesBreadcrumb);
10
+ customElements.define("grantcodes-breadcrumb-item", GrantCodesBreadcrumbItem);
11
+
12
+
@@ -0,0 +1,25 @@
1
+ import { html } from "lit";
2
+ import "./breadcrumb.js";
3
+
4
+ const meta = {
5
+ title: "Components/Breadcrumb",
6
+ component: "grantcodes-breadcrumb",
7
+ };
8
+
9
+ export default meta;
10
+
11
+ /**
12
+ * Basic breadcrumb navigation showing the current page hierarchy
13
+ */
14
+ export const Breadcrumb = {
15
+ render: () => html`
16
+ <grantcodes-breadcrumb>
17
+ <grantcodes-breadcrumb-item href="/">Home</grantcodes-breadcrumb-item>
18
+ <grantcodes-breadcrumb-item href="/products">Products</grantcodes-breadcrumb-item>
19
+ <grantcodes-breadcrumb-item href="/products/electronics">
20
+ Electronics
21
+ </grantcodes-breadcrumb-item>
22
+ <grantcodes-breadcrumb-item current>Laptops</grantcodes-breadcrumb-item>
23
+ </grantcodes-breadcrumb>
24
+ `,
25
+ };
@@ -0,0 +1,96 @@
1
+ import { css } from "lit";
2
+
3
+ export const breadcrumbStyles = css`
4
+ *,
5
+ *::before,
6
+ *::after {
7
+ box-sizing: border-box;
8
+ }
9
+
10
+ :host {
11
+ display: block;
12
+ }
13
+
14
+ .breadcrumb {
15
+ display: block;
16
+ }
17
+
18
+ .breadcrumb__list {
19
+ display: flex;
20
+ flex-wrap: wrap;
21
+ align-items: center;
22
+ gap: 0.5rem;
23
+ margin: 0;
24
+ padding: 0;
25
+ list-style: none;
26
+ }
27
+
28
+ .breadcrumb__item {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 0.5rem;
32
+ font-size: var(--g-typography-font-size-14);
33
+ color: var(--g-color-brand-purple-700);
34
+ }
35
+
36
+ /* Separator using ::after pseudo-element; driven by parent via data-has-separator */
37
+ :host([data-has-separator]) .breadcrumb__item::after {
38
+ content: var(--component-breadcrumb-separator, "/");
39
+ display: inline-block;
40
+ color: var(--g-color-brand-purple-400);
41
+ font-weight: 300;
42
+ user-select: none;
43
+ pointer-events: none;
44
+ }
45
+
46
+ /* Current page styling */
47
+ .breadcrumb__item[data-current] {
48
+ font-weight: 600;
49
+ color: var(--color-base-primary-900);
50
+ }
51
+
52
+ .breadcrumb__item[data-current]::after {
53
+ display: none;
54
+ }
55
+
56
+ .breadcrumb__link {
57
+ color: var(--g-color-brand-purple-600);
58
+ text-decoration: none;
59
+ transition: color 0.2s ease;
60
+ border-radius: var(--g-theme-border-radius-md, 0.25rem);
61
+ padding-inline: 0.25em;
62
+ padding-block: 0.125em;
63
+ margin-inline: -0.25em;
64
+ margin-block: -0.125em;
65
+ }
66
+
67
+ .breadcrumb__link:hover {
68
+ color: var(--g-color-brand-purple-700);
69
+ text-decoration: underline;
70
+ }
71
+
72
+ .breadcrumb__link:focus-visible {
73
+ outline: 2px solid var(--component-focus-ring-color, rgba(106, 91, 197, 0.4));
74
+ outline-offset: 2px;
75
+ }
76
+
77
+ .breadcrumb__text {
78
+ display: inline-block;
79
+ }
80
+
81
+ /* Responsive: Show only last few items on small screens */
82
+ @media (max-width: 640px) {
83
+ .breadcrumb__item:not(:last-child):not(:nth-last-child(2)) {
84
+ display: none;
85
+ }
86
+
87
+ .breadcrumb__item:nth-last-child(2)::before {
88
+ content: "...";
89
+ display: inline-block;
90
+ margin-inline-end: 0.5rem;
91
+ color: var(--g-color-brand-purple-400);
92
+ }
93
+ }
94
+ `;
95
+
96
+
@@ -0,0 +1,144 @@
1
+ import { describe, it, afterEach } from "node:test";
2
+ import { strict as assert } from "node:assert";
3
+ import { fixture, cleanup } from "../../test-utils/index.js";
4
+ import "./breadcrumb.js";
5
+
6
+ describe("Breadcrumb Component", () => {
7
+ let element;
8
+
9
+ afterEach(() => {
10
+ cleanup(element);
11
+ });
12
+
13
+ it("should render breadcrumb container", async () => {
14
+ element = await fixture("grantcodes-breadcrumb");
15
+ assert.ok(element, "Element should be created");
16
+ assert.ok(element.shadowRoot, "Element should have shadow root");
17
+ });
18
+
19
+ it("should render nav element with aria-label", async () => {
20
+ element = await fixture("grantcodes-breadcrumb");
21
+ const nav = element.shadowRoot.querySelector("nav");
22
+ assert.ok(nav, "Nav element should exist");
23
+ assert.strictEqual(
24
+ nav.getAttribute("aria-label"),
25
+ "Breadcrumb",
26
+ "Should have breadcrumb aria-label",
27
+ );
28
+ });
29
+
30
+ it("should render ordered list", async () => {
31
+ element = await fixture("grantcodes-breadcrumb");
32
+ const list = element.shadowRoot.querySelector("ol");
33
+ assert.ok(list, "Ordered list should exist");
34
+ assert.ok(
35
+ list.classList.contains("breadcrumb__list"),
36
+ "Should have breadcrumb__list class",
37
+ );
38
+ });
39
+
40
+ it("should have default separator", async () => {
41
+ element = await fixture("grantcodes-breadcrumb");
42
+ assert.strictEqual(element.separator, "/", "Default separator should be /");
43
+ });
44
+
45
+ it("should have slot for breadcrumb items", async () => {
46
+ element = await fixture("grantcodes-breadcrumb");
47
+ const slot = element.shadowRoot.querySelector("slot");
48
+ assert.ok(slot, "Slot should exist for breadcrumb items");
49
+ });
50
+ });
51
+
52
+ describe("Breadcrumb Item Component", () => {
53
+ let element;
54
+
55
+ afterEach(() => {
56
+ cleanup(element);
57
+ });
58
+
59
+ it("should render breadcrumb item", async () => {
60
+ element = await fixture("grantcodes-breadcrumb-item");
61
+ assert.ok(element, "Element should be created");
62
+ assert.ok(element.shadowRoot, "Element should have shadow root");
63
+ });
64
+
65
+ it("should render as link when href is provided", async () => {
66
+ element = await fixture("grantcodes-breadcrumb-item", {
67
+ href: "/test",
68
+ });
69
+
70
+ const link = element.shadowRoot.querySelector(".breadcrumb__link");
71
+ assert.ok(link, "Link should exist");
72
+ assert.strictEqual(link.getAttribute("href"), "/test", "Href should be set");
73
+ });
74
+
75
+ it("should render as span when no href", async () => {
76
+ element = await fixture("grantcodes-breadcrumb-item");
77
+
78
+ const span = element.shadowRoot.querySelector(".breadcrumb__text");
79
+ assert.ok(span, "Span should exist");
80
+ });
81
+
82
+ it("should render as span when current", async () => {
83
+ element = await fixture("grantcodes-breadcrumb-item", {
84
+ href: "/test",
85
+ current: true,
86
+ });
87
+
88
+ const span = element.shadowRoot.querySelector(".breadcrumb__text");
89
+ const link = element.shadowRoot.querySelector(".breadcrumb__link");
90
+ assert.ok(span, "Span should exist for current page");
91
+ assert.ok(!link, "Link should not exist for current page");
92
+ });
93
+
94
+ it("should have current attribute when current is true", async () => {
95
+ element = await fixture("grantcodes-breadcrumb-item", {
96
+ current: true,
97
+ });
98
+
99
+ const item = element.shadowRoot.querySelector(".breadcrumb__item");
100
+ assert.ok(
101
+ item.hasAttribute("data-current"),
102
+ "Should have data-current attribute",
103
+ );
104
+ });
105
+
106
+ it("should not have current attribute when current is false", async () => {
107
+ element = await fixture("grantcodes-breadcrumb-item", {
108
+ current: false,
109
+ });
110
+
111
+ const item = element.shadowRoot.querySelector(".breadcrumb__item");
112
+ assert.ok(
113
+ !item.hasAttribute("data-current"),
114
+ "Should not have data-current attribute",
115
+ );
116
+ });
117
+
118
+ it("should render list item", async () => {
119
+ element = await fixture("grantcodes-breadcrumb-item");
120
+ const item = element.shadowRoot.querySelector("li");
121
+ assert.ok(item, "List item should exist");
122
+ });
123
+
124
+ it("should render slotted content", async () => {
125
+ element = await fixture("grantcodes-breadcrumb-item");
126
+ element.textContent = "Home";
127
+
128
+ await element.updateComplete;
129
+
130
+ assert.strictEqual(element.textContent, "Home", "Slotted content should be rendered");
131
+ });
132
+
133
+ it("should not be current by default", async () => {
134
+ element = await fixture("grantcodes-breadcrumb-item");
135
+ assert.strictEqual(element.current, false, "Should not be current by default");
136
+ });
137
+
138
+ it("should have empty href by default", async () => {
139
+ element = await fixture("grantcodes-breadcrumb-item");
140
+ assert.strictEqual(element.href, "", "Href should be empty by default");
141
+ });
142
+ });
143
+
144
+
@@ -0,0 +1,3 @@
1
+ export * from "./breadcrumb.js";
2
+
3
+
@@ -0,0 +1,64 @@
1
+ import { LitElement } from "lit";
2
+ import { html } from "lit/static-html.js";
3
+ import { literal } from "lit/static-html.js";
4
+ import { buttonStyles } from "./button.styles.js";
5
+
6
+ export class GrantCodesButton extends LitElement {
7
+ // Styles are scoped to this element: they won't conflict with styles
8
+ // on the main page or in other components. Styling API can be exposed
9
+ // via CSS custom properties.
10
+ static styles = [buttonStyles];
11
+
12
+ static properties = {
13
+ href: { type: String },
14
+ type: { type: String },
15
+ name: { type: String },
16
+ value: { type: String },
17
+ disabled: { type: Boolean, reflect: true },
18
+ };
19
+
20
+ constructor() {
21
+ super();
22
+
23
+ this.href = "";
24
+ this.type = "button";
25
+ this.name = "";
26
+ this.value = "";
27
+ this.disabled = false;
28
+ }
29
+
30
+ // The render() method is called any time reactive properties change.
31
+ // Return HTML in a string template literal tagged with the `html`
32
+ // tag function to describe the component's internal DOM.
33
+ // Expressions can set attribute values, property values, event handlers,
34
+ // and child nodes/text.
35
+ /** @returns {import('lit').TemplateResult} */
36
+ render() {
37
+ const isLink = !!this.href;
38
+ const tag = isLink ? literal`a` : literal`button`;
39
+
40
+ if (isLink) {
41
+ return html`
42
+ <${tag}
43
+ class="button"
44
+ href=${this.href}
45
+ ?disabled=${this.disabled}
46
+ >
47
+ <span><slot></slot></span>
48
+ </${tag}>
49
+ `;
50
+ }
51
+
52
+ return html`
53
+ <${tag}
54
+ class="button"
55
+ type=${this.type}
56
+ name=${this.name ?? ""}
57
+ value=${this.value ?? ""}
58
+ ?disabled=${this.disabled}
59
+ >
60
+ <span><slot></slot></span>
61
+ </${tag}>
62
+ `;
63
+ }
64
+ }
@@ -0,0 +1,6 @@
1
+ import { GrantCodesButton } from "./button.component.js";
2
+
3
+ export * from "./button.component.js";
4
+ export default GrantCodesButton;
5
+
6
+ customElements.define("grantcodes-button", GrantCodesButton);
@@ -0,0 +1,78 @@
1
+ import { getStorybookHelpers } from "@wc-toolkit/storybook-helpers";
2
+ import { html } from "lit/static-html.js";
3
+ import "./button.js";
4
+ const { events, args, argTypes, template } =
5
+ getStorybookHelpers("grantcodes-button");
6
+
7
+ const meta = {
8
+ title: "Components/Button",
9
+ component: "grantcodes-button",
10
+ args,
11
+ argTypes,
12
+ render: (args) => template(args, html`Button label`),
13
+ parameters: {
14
+ actions: {
15
+ handles: events,
16
+ },
17
+ },
18
+ };
19
+
20
+ export default meta;
21
+
22
+ /**
23
+ * Default button with standard appearance.
24
+ */
25
+ export const Button = {};
26
+
27
+ /**
28
+ * Disabled button that cannot be interacted with.
29
+ * The button will not respond to clicks when disabled.
30
+ */
31
+ export const Disabled = {
32
+ args: {
33
+ disabled: true,
34
+ },
35
+ };
36
+
37
+ /**
38
+ * Button rendered as a link (anchor tag).
39
+ * Useful for navigation actions that look like buttons.
40
+ */
41
+ export const AsLink = {
42
+ args: {
43
+ href: "https://example.com",
44
+ },
45
+ render: (args) => template(args, html`Link Button`),
46
+ };
47
+
48
+ /**
49
+ * Different button types for forms.
50
+ * - button: Standard button (default)
51
+ * - submit: Submits a form
52
+ * - reset: Resets form fields
53
+ */
54
+ export const ButtonTypes = {
55
+ render: () => html`
56
+ <div style="display: flex; gap: 1rem;">
57
+ <grantcodes-button type="button">Button</grantcodes-button>
58
+ <grantcodes-button type="submit">Submit</grantcodes-button>
59
+ <grantcodes-button type="reset">Reset</grantcodes-button>
60
+ </div>
61
+ `,
62
+ };
63
+
64
+ /**
65
+ * Various button states for different contexts.
66
+ */
67
+ export const AllStates = {
68
+ render: () => html`
69
+ <div style="display: flex; flex-direction: column; gap: 1rem; max-width: 300px;">
70
+ <grantcodes-button>Normal Button</grantcodes-button>
71
+ <grantcodes-button disabled>Disabled Button</grantcodes-button>
72
+ <grantcodes-button href="#">Link Button</grantcodes-button>
73
+ <grantcodes-button>
74
+ <strong>With HTML Content</strong>
75
+ </grantcodes-button>
76
+ </div>
77
+ `,
78
+ };