@govtechsg/sgds-web-component 0.0.7 → 0.0.10

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 (263) hide show
  1. package/.github/workflows/publish-latest.yml +22 -0
  2. package/.github/workflows/publish-pr.yml +28 -0
  3. package/.husky/commit-msg +4 -0
  4. package/.husky/prepare-commit-msg +8 -0
  5. package/.storybook/main.js +16 -0
  6. package/.storybook/preview-head.html +11 -0
  7. package/.storybook/preview.js +9 -0
  8. package/.vscode/settings.json +7 -0
  9. package/CONTRIBUTING.md +56 -0
  10. package/LICENSE +20 -0
  11. package/amplify.yml +22 -0
  12. package/commitlint.config.js +1 -0
  13. package/coverage/lcov-report/base.css +224 -0
  14. package/coverage/lcov-report/block-navigation.js +87 -0
  15. package/coverage/lcov-report/button-element.scss.html +112 -0
  16. package/coverage/lcov-report/button-element.ts.html +145 -0
  17. package/coverage/lcov-report/favicon.png +0 -0
  18. package/coverage/lcov-report/index.html +116 -0
  19. package/coverage/lcov-report/prettify.css +1 -0
  20. package/coverage/lcov-report/prettify.js +2 -0
  21. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  22. package/coverage/lcov-report/sorter.js +196 -0
  23. package/coverage/lcov.info +32 -0
  24. package/index.html +430 -0
  25. package/{Button → lib/Button}/index.d.ts +0 -0
  26. package/{Button → lib/Button}/index.js +304 -39
  27. package/lib/Button/index.js.map +1 -0
  28. package/{Button → lib/Button}/package.json +0 -0
  29. package/lib/Button/sgds-button.d.ts +48 -0
  30. package/lib/Card/index.d.ts +1 -0
  31. package/lib/Card/index.js +6150 -0
  32. package/lib/Card/index.js.map +1 -0
  33. package/lib/Card/package.json +7 -0
  34. package/lib/Card/sgds-action-card.d.ts +20 -0
  35. package/lib/Checkbox/index.d.ts +1 -0
  36. package/lib/Checkbox/index.js +6366 -0
  37. package/lib/Checkbox/index.js.map +1 -0
  38. package/lib/Checkbox/package.json +7 -0
  39. package/lib/Checkbox/sgds-checkbox.d.ts +36 -0
  40. package/lib/Dropdown/index.d.ts +3 -0
  41. package/{Mainnav → lib/Dropdown}/index.js +2786 -9258
  42. package/lib/Dropdown/index.js.map +1 -0
  43. package/lib/Dropdown/package.json +7 -0
  44. package/lib/Dropdown/sgds-dropdown-item.d.ts +7 -0
  45. package/lib/Dropdown/sgds-dropdown.d.ts +7 -0
  46. package/{Footer → lib/Footer}/index.d.ts +0 -0
  47. package/{Footer → lib/Footer}/index.js +111 -95
  48. package/lib/Footer/index.js.map +1 -0
  49. package/{Footer → lib/Footer}/package.json +0 -0
  50. package/{Footer → lib/Footer}/sgds-footer.d.ts +2 -2
  51. package/lib/Input/index.d.ts +1 -0
  52. package/lib/Input/index.js +6656 -0
  53. package/lib/Input/index.js.map +1 -0
  54. package/lib/Input/package.json +7 -0
  55. package/lib/Input/sgds-input.d.ts +42 -0
  56. package/{Mainnav → lib/Mainnav}/index.d.ts +1 -0
  57. package/{index.js → lib/Mainnav/index.js} +3876 -23415
  58. package/lib/Mainnav/index.js.map +1 -0
  59. package/{Mainnav → lib/Mainnav}/package.json +0 -0
  60. package/lib/Mainnav/sgds-mainnav-dropdown.d.ts +5 -0
  61. package/lib/Mainnav/sgds-mainnav-item.d.ts +4 -0
  62. package/{Mainnav → lib/Mainnav}/sgds-mainnav.d.ts +3 -2
  63. package/{Masthead → lib/Masthead}/index.d.ts +0 -0
  64. package/{Masthead → lib/Masthead}/index.js +140 -114
  65. package/lib/Masthead/index.js.map +1 -0
  66. package/{Masthead → lib/Masthead}/package.json +0 -0
  67. package/{Masthead → lib/Masthead}/sgds-masthead.d.ts +1 -1
  68. package/lib/Modal/index.d.ts +1 -0
  69. package/lib/Modal/index.js +6432 -0
  70. package/lib/Modal/index.js.map +1 -0
  71. package/lib/Modal/package.json +7 -0
  72. package/lib/Modal/sgds-modal.d.ts +28 -0
  73. package/lib/QuantityToggle/index.d.ts +1 -0
  74. package/lib/QuantityToggle/index.js +7049 -0
  75. package/lib/QuantityToggle/index.js.map +1 -0
  76. package/lib/QuantityToggle/package.json +7 -0
  77. package/lib/QuantityToggle/sgds-quantitytoggle.d.ts +30 -0
  78. package/lib/Radio/index.d.ts +2 -0
  79. package/lib/Radio/index.js +12607 -0
  80. package/lib/Radio/index.js.map +1 -0
  81. package/lib/Radio/package.json +7 -0
  82. package/lib/Radio/sgds-radio.d.ts +31 -0
  83. package/lib/Radio/sgds-radiogroup.d.ts +41 -0
  84. package/{Sidenav → lib/Sidenav}/index.d.ts +0 -0
  85. package/{Sidenav → lib/Sidenav}/index.js +2266 -2171
  86. package/lib/Sidenav/index.js.map +1 -0
  87. package/{Sidenav → lib/Sidenav}/package.json +0 -0
  88. package/{Sidenav → lib/Sidenav}/sgds-sidenav-item.d.ts +2 -1
  89. package/lib/Sidenav/sgds-sidenav-link.d.ts +4 -0
  90. package/{Sidenav → lib/Sidenav}/sgds-sidenav.d.ts +1 -1
  91. package/lib/Tab/index.d.ts +3 -0
  92. package/lib/Tab/index.js +13557 -0
  93. package/lib/Tab/index.js.map +1 -0
  94. package/lib/Tab/package.json +7 -0
  95. package/lib/Tab/sgds-tab.d.ts +26 -0
  96. package/lib/Tab/sgds-tabgroup.d.ts +47 -0
  97. package/lib/Tab/sgds-tabpanel.d.ts +25 -0
  98. package/lib/Textarea/index.d.ts +1 -0
  99. package/lib/Textarea/index.js +6696 -0
  100. package/lib/Textarea/index.js.map +1 -0
  101. package/lib/Textarea/package.json +7 -0
  102. package/lib/Textarea/sgds-textarea.d.ts +53 -0
  103. package/lib/index.d.ts +16 -0
  104. package/lib/index.js +134580 -0
  105. package/lib/index.js.map +1 -0
  106. package/lib/umd/index.js +134587 -0
  107. package/lib/umd/index.js.map +1 -0
  108. package/lib/utils/animate.d.ts +10 -0
  109. package/lib/utils/animation-registry.d.ts +18 -0
  110. package/{utils → lib/utils}/breakpoints.d.ts +0 -0
  111. package/lib/utils/card-element.d.ts +11 -0
  112. package/lib/utils/defaultvalue.d.ts +2 -0
  113. package/lib/utils/dropdown-element.d.ts +37 -0
  114. package/lib/utils/event.d.ts +2 -0
  115. package/lib/utils/form.d.ts +38 -0
  116. package/{utils → lib/utils}/generateId.d.ts +0 -0
  117. package/lib/utils/link-element.d.ts +7 -0
  118. package/lib/utils/mergeDeep.d.ts +2 -0
  119. package/lib/utils/modal.d.ts +12 -0
  120. package/lib/utils/object.d.ts +2 -0
  121. package/lib/utils/offset.d.ts +4 -0
  122. package/lib/utils/scroll.d.ts +13 -0
  123. package/{utils → lib/utils}/sgds-element.d.ts +0 -0
  124. package/lib/utils/slot.d.ts +22 -0
  125. package/lib/utils/tabbable.d.ts +8 -0
  126. package/lib/utils/watch.d.ts +14 -0
  127. package/mocks/dropdown.d.ts +4 -0
  128. package/mocks/dropdown.ts +27 -0
  129. package/mocks/link.d.ts +3 -0
  130. package/mocks/link.ts +6 -0
  131. package/package.json +65 -10
  132. package/rollup.config.js +73 -0
  133. package/rollup.test.config.js +42 -0
  134. package/scripts/buildUtils.js +30 -0
  135. package/scripts/frankBuild.js +49 -0
  136. package/src/Button/index.ts +1 -0
  137. package/src/Button/sgds-button.scss +28 -0
  138. package/src/Button/sgds-button.ts +153 -0
  139. package/src/Card/index.ts +1 -0
  140. package/src/Card/sgds-action-card.scss +27 -0
  141. package/src/Card/sgds-action-card.ts +115 -0
  142. package/src/Checkbox/index.ts +1 -0
  143. package/src/Checkbox/sgds-checkbox.scss +4 -0
  144. package/src/Checkbox/sgds-checkbox.ts +149 -0
  145. package/src/Dropdown/index.ts +3 -0
  146. package/src/Dropdown/sgds-dropdown-item.ts +39 -0
  147. package/src/Dropdown/sgds-dropdown.scss +5 -0
  148. package/src/Dropdown/sgds-dropdown.ts +54 -0
  149. package/src/Footer/index.ts +3 -0
  150. package/src/Footer/sgds-footer.scss +5 -0
  151. package/src/Footer/sgds-footer.ts +121 -0
  152. package/src/Input/index.ts +1 -0
  153. package/src/Input/sgds-input.scss +20 -0
  154. package/src/Input/sgds-input.ts +178 -0
  155. package/src/Mainnav/index.ts +4 -0
  156. package/src/Mainnav/sgds-mainnav-dropdown.scss +13 -0
  157. package/src/Mainnav/sgds-mainnav-dropdown.ts +45 -0
  158. package/src/Mainnav/sgds-mainnav-item.scss +24 -0
  159. package/src/Mainnav/sgds-mainnav-item.ts +8 -0
  160. package/src/Mainnav/sgds-mainnav.scss +39 -0
  161. package/src/Mainnav/sgds-mainnav.ts +183 -0
  162. package/src/Masthead/index.ts +1 -0
  163. package/src/Masthead/sgds-masthead.scss +217 -0
  164. package/src/Masthead/sgds-masthead.ts +189 -0
  165. package/src/Modal/index.ts +1 -0
  166. package/src/Modal/sgds-modal.scss +128 -0
  167. package/src/Modal/sgds-modal.ts +309 -0
  168. package/src/QuantityToggle/index.ts +1 -0
  169. package/src/QuantityToggle/sgds-quantitytoggle.scss +10 -0
  170. package/src/QuantityToggle/sgds-quantitytoggle.ts +130 -0
  171. package/src/Radio/index.ts +2 -0
  172. package/src/Radio/sgds-radio.scss +5 -0
  173. package/src/Radio/sgds-radio.ts +120 -0
  174. package/src/Radio/sgds-radiogroup.scss +22 -0
  175. package/src/Radio/sgds-radiogroup.ts +221 -0
  176. package/src/Sidenav/index.ts +4 -0
  177. package/src/Sidenav/sgds-sidenav-item.scss +73 -0
  178. package/src/Sidenav/sgds-sidenav-item.ts +145 -0
  179. package/src/Sidenav/sgds-sidenav-link.scss +25 -0
  180. package/src/Sidenav/sgds-sidenav-link.ts +8 -0
  181. package/src/Sidenav/sgds-sidenav.scss +6 -0
  182. package/src/Sidenav/sgds-sidenav.ts +33 -0
  183. package/src/Tab/index.ts +3 -0
  184. package/src/Tab/sgds-tab.scss +84 -0
  185. package/src/Tab/sgds-tab.ts +87 -0
  186. package/src/Tab/sgds-tabgroup.scss +198 -0
  187. package/src/Tab/sgds-tabgroup.ts +295 -0
  188. package/src/Tab/sgds-tabpanel.scss +12 -0
  189. package/src/Tab/sgds-tabpanel.ts +55 -0
  190. package/src/Textarea/index.ts +1 -0
  191. package/src/Textarea/sgds-textarea.scss +23 -0
  192. package/src/Textarea/sgds-textarea.ts +201 -0
  193. package/src/index.ts +16 -0
  194. package/src/utils/animate.ts +69 -0
  195. package/src/utils/animation-registry.ts +71 -0
  196. package/src/utils/base.scss +14 -0
  197. package/src/utils/breakpoints.ts +5 -0
  198. package/src/utils/card-element.ts +42 -0
  199. package/src/utils/components.style.scss +531 -0
  200. package/src/utils/defaultvalue.ts +51 -0
  201. package/src/utils/dropdown-element.ts +244 -0
  202. package/src/utils/event.ts +13 -0
  203. package/src/utils/form.ts +183 -0
  204. package/src/utils/generateId.ts +4 -0
  205. package/src/utils/link-element.ts +34 -0
  206. package/src/utils/mergeDeep.ts +22 -0
  207. package/src/utils/modal.ts +64 -0
  208. package/src/utils/object.ts +2 -0
  209. package/src/utils/offset.ts +6 -0
  210. package/src/utils/scroll.ts +57 -0
  211. package/src/utils/sgds-element.ts +18 -0
  212. package/src/utils/slot.ts +102 -0
  213. package/src/utils/tabbable.ts +81 -0
  214. package/src/utils/watch.ts +62 -0
  215. package/stories/ActionCard.stories.mdx +199 -0
  216. package/stories/Button.stories.mdx +194 -0
  217. package/stories/Checkbox.stories.mdx +196 -0
  218. package/stories/Dropdown.stories.mdx +152 -0
  219. package/stories/Footer.stories.mdx +261 -0
  220. package/stories/Input.stories.mdx +236 -0
  221. package/stories/MainNav.stories.mdx +169 -0
  222. package/stories/Masthead.stories.mdx +112 -0
  223. package/stories/Modal.stories.mdx +103 -0
  224. package/stories/QuantityToggle.stories.mdx +97 -0
  225. package/stories/Radio.stories.mdx +262 -0
  226. package/stories/Sample.stories.js +29 -0
  227. package/stories/Sample.stories.mdx +33 -0
  228. package/stories/SideNav.stories.mdx +245 -0
  229. package/stories/common.js +185 -0
  230. package/stories/textarea.stories.mdx +253 -0
  231. package/test/button.element.test.ts +185 -0
  232. package/test/checkbox.test.ts +240 -0
  233. package/test/dropdown.test.ts +637 -0
  234. package/test/footer.test.ts +181 -0
  235. package/test/generateId.test.ts +18 -0
  236. package/test/input.element.test.ts +316 -0
  237. package/test/link-element.test.ts +38 -0
  238. package/test/mainnav.test.ts +313 -0
  239. package/test/masthead.test.ts +116 -0
  240. package/test/modal.test.ts +149 -0
  241. package/test/quantitytoggle.test.ts +76 -0
  242. package/test/radio.test.ts +310 -0
  243. package/test/selectable-card.test.ts +159 -0
  244. package/test/sidenav.test.ts +390 -0
  245. package/test/tab.test.ts +76 -0
  246. package/test/textarea.test.ts +126 -0
  247. package/tsconfig.json +26 -0
  248. package/tsconfig.test.json +24 -0
  249. package/typings/scss.d.ts +5 -0
  250. package/web-dev-server.config.mjs +7 -0
  251. package/web-test-runner.config.mjs +47 -0
  252. package/Button/index.js.map +0 -1
  253. package/Button/sgds-button.d.ts +0 -23
  254. package/Footer/index.js.map +0 -1
  255. package/Mainnav/index.js.map +0 -1
  256. package/Mainnav/sgds-mainnav-item.d.ts +0 -7
  257. package/Masthead/index.js.map +0 -1
  258. package/Sidenav/index.js.map +0 -1
  259. package/Sidenav/sgds-sidenav-link.d.ts +0 -7
  260. package/index.d.ts +0 -5
  261. package/index.js.map +0 -1
  262. package/umd/index.js +0 -52092
  263. package/umd/index.js.map +0 -1
@@ -0,0 +1,121 @@
1
+ import { html } from "lit";
2
+ import { customElement, property } from "lit/decorators.js";
3
+ import styles from "./sgds-footer.scss";
4
+ import SgdsElement from "../utils/sgds-element";
5
+
6
+ export type Links = {
7
+ href: string;
8
+ label: string;
9
+ }
10
+ export interface ColumnLinks {
11
+ title: string;
12
+ links: Links[]
13
+ }
14
+ @customElement("sgds-footer")
15
+ export class SgdsFooter extends SgdsElement {
16
+ static styles = styles
17
+ @property()
18
+ title = ``;
19
+ @property()
20
+ description = ``;
21
+ @property({
22
+ type: Array,
23
+ })
24
+ links: ColumnLinks[] = [];
25
+
26
+ @property({ type: String })
27
+ lastUpdatedDate = "";
28
+
29
+ //href
30
+ @property({ type: String })
31
+ contactHref = "#";
32
+ @property({ type: String })
33
+ feedbackHref = "#";
34
+ @property({ type: String })
35
+ vulnerabilityHref = "#";
36
+ @property({ type: String })
37
+ privacyHref = "#";
38
+ @property({ type: String })
39
+ termsOfUseHref = "#";
40
+
41
+ render() {
42
+ return html`
43
+ <footer class="sgds footer">
44
+ <section class="footer-top" part="footer-top">
45
+ <div class="container-fluid">
46
+ <div class="row footer-header">
47
+ <div class="col col-lg-6 col-md-12">
48
+ <div class="title">${this.title}</div>
49
+ <div class="description">${this.description}</div>
50
+ </div>
51
+ </div>
52
+ <div class="row footer-items">
53
+ ${this.links.map(
54
+ (item: ColumnLinks) =>
55
+ html`
56
+ <div class="col-xxl-2 col-md-4 mb-3">
57
+ <div class="title">${item.title}</div>
58
+ <ul class="links">
59
+ ${item.links.map(
60
+ (link: Links) =>
61
+ html`
62
+ <li><a href="${link.href}">${link.label}</a></li>
63
+ `
64
+ )}
65
+ </ul>
66
+ </div>
67
+ `
68
+ )}
69
+ </div>
70
+ <div class="row footer-contact-links">
71
+ <div class="col">
72
+ <div class="d-flex justify-content-lg-end">
73
+ <ul>
74
+ <li><a href="${this.contactHref}">Contact</a></li>
75
+ <li><a href="${this.feedbackHref}">Feedback</a></li>
76
+ <li>
77
+ <a
78
+ href="https://www.reach.gov.sg/"
79
+ target="_blank"
80
+ rel="noopener noreferrer"
81
+ >Reach.gov.sg</a>
82
+ </li>
83
+ </ul>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ </section>
89
+ <section class="footer-bottom" part="footer-bottom">
90
+ <div class="container-fluid">
91
+ <div class="row footer-mandatory-links">
92
+ <div class="col">
93
+ <ul>
94
+ <li>
95
+ <a
96
+ href="${this.vulnerabilityHref}"
97
+ target="_blank"
98
+ rel="noopener noreferrer"
99
+ >Report Vulnerability</a>
100
+ </li>
101
+ <li><a href="${this.privacyHref}">Privacy Statement</a></li>
102
+ <li><a href="${this.termsOfUseHref}">Terms of use</a></li>
103
+ </ul>
104
+ </div>
105
+ </div>
106
+ <div class="row footer-copyrights">
107
+ <div class="col">
108
+ <div class="d-flex justify-content-lg-end text-end">
109
+ © ${new Date().getFullYear()} Government of Singapore<br />
110
+ Last Updated ${this.lastUpdatedDate}
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ </section>
116
+ </footer>
117
+ `;
118
+ }
119
+ }
120
+
121
+ export default SgdsFooter;
@@ -0,0 +1 @@
1
+ export { SgdsInput } from "./sgds-input";
@@ -0,0 +1,20 @@
1
+
2
+ @import '../utils/base.scss';
3
+ @import "~@govtechsg/sgds/sass/forms/labels";
4
+ @import "~@govtechsg/sgds/sass/forms/form-text";
5
+ @import "~@govtechsg/sgds/sass/forms/form-control";
6
+ @import "~@govtechsg/sgds/sass/forms/form-control-group";
7
+ @import "~@govtechsg/sgds/sass/forms/input-group";
8
+ @import "~@govtechsg/sgds/sass/forms/validation";
9
+
10
+ :host{
11
+ --sgds-body-line-height: 2rem;
12
+ }
13
+
14
+ .form{
15
+ &-label,
16
+ &-text
17
+ {
18
+ line-height: var(--sgds-body-line-height);
19
+ }
20
+ }
@@ -0,0 +1,178 @@
1
+
2
+ import { customElement, property,query, state } from "lit/decorators.js";
3
+ import { html } from 'lit/static-html.js';
4
+ import { ifDefined } from 'lit/directives/if-defined.js';
5
+ import {classMap} from 'lit/directives/class-map.js';
6
+ import { live } from 'lit/directives/live.js';
7
+ import styles from "./sgds-input.scss";
8
+ import SgdsElement from "../utils/sgds-element";
9
+ import { defaultValue } from "../utils/defaultvalue";
10
+ import { FormSubmitController } from "../utils/form";
11
+ import genId from "../utils/generateId";
12
+ import { watch } from "../utils/watch";
13
+ @customElement("sgds-input")
14
+ export class SgdsInput extends SgdsElement {
15
+ @query('input.form-control') input: HTMLInputElement;
16
+
17
+ @state() private hasFocus = false;
18
+
19
+ private readonly formSubmitController = new FormSubmitController(this);
20
+
21
+ static styles = styles;
22
+ @property({ reflect: true }) type:
23
+ | 'date'
24
+ | 'datetime-local'
25
+ | 'email'
26
+ | 'number'
27
+ | 'password'
28
+ | 'search'
29
+ | 'tel'
30
+ | 'text'
31
+ | 'time'
32
+ | 'url' = 'text';
33
+ @property({ reflect: true }) label : string;
34
+ @property({ reflect: true}) hintText : string;
35
+ @property({ type:String, reflect: true }) inputId = genId("input", this.type);
36
+ @property({ reflect: true }) name : string;
37
+ @property({ reflect: true }) inputClasses : string;
38
+ @property({ reflect: true }) iconName: string;
39
+
40
+ @property({ type: String, reflect: true }) value = '';
41
+ @property({ type: String, reflect: true}) minlength;
42
+ @property({ type: String, reflect: true}) maxlength;
43
+
44
+ @property({ type: String, reflect: true }) placeholder = "Placeholder";
45
+
46
+ @property({ type: String }) pattern;
47
+ @property({ type: String, reflect: true }) invalidFeedback = "default feedback";
48
+
49
+ @property({ type: Boolean, reflect: true }) autofocus = false;
50
+ @property({ type: Boolean, reflect: true }) disabled = false;
51
+ @property({ type: Boolean, reflect: true }) required = false;
52
+ /** Makes the input readonly. */
53
+ @property({ type: Boolean, reflect: true }) readonly = false;
54
+
55
+ @property({ type: Boolean, reflect: true }) invalid = false;
56
+ @property({ type: Boolean, reflect: true }) valid = false;
57
+
58
+ /** Gets or sets the default value used to reset this element. The initial value corresponds to the one originally specified in the HTML that created this element. */
59
+ @defaultValue()
60
+ defaultValue = '';
61
+
62
+ /** Sets focus on the input. */
63
+ focus(options?: FocusOptions) {
64
+ this.input.focus(options);
65
+ }
66
+
67
+ /** Checks for validity and shows the browser's validation message if the control is invalid. */
68
+ reportValidity() {
69
+ return this.input.reportValidity();
70
+ }
71
+
72
+ handleInvalid() {
73
+ this.invalid = true;
74
+ }
75
+
76
+ handleChange(event: string){
77
+ this.value = this.input.value;
78
+ this.emit(event);
79
+ }
80
+
81
+ handleFocus() {
82
+ this.hasFocus = true;
83
+ this.emit('sgds-focus');
84
+ }
85
+
86
+ handleBlur() {
87
+ this.hasFocus = false;
88
+ this.emit('sgds-blur');
89
+ }
90
+
91
+ handleKeyDown(event: KeyboardEvent) {
92
+ const hasModifier = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
93
+
94
+ // Pressing enter when focused on an input should submit the form like a native input, but we wait a tick before
95
+ // submitting to allow users to cancel the keydown event if they need to
96
+ if (event.key === 'Enter' && !hasModifier) {
97
+ setTimeout(() => {
98
+ if (!event.defaultPrevented) {
99
+ this.formSubmitController.submit();
100
+ }
101
+ });
102
+ }
103
+ }
104
+
105
+ @watch('disabled', { waitUntilFirstUpdate: true })
106
+ handleDisabledChange() {
107
+ // Disabled form controls are always valid, so we need to recheck validity when the state changes
108
+ this.input.disabled = this.disabled;
109
+ this.invalid = !this.input.checkValidity();
110
+ }
111
+
112
+ @watch('value', { waitUntilFirstUpdate: true })
113
+ handleValueChange() {
114
+ this.invalid = !this.input.checkValidity();
115
+ this.valid = this.input.checkValidity()
116
+ }
117
+
118
+ render() {
119
+ const input = html`
120
+ <input type="text"
121
+ class="form-control
122
+ ${classMap(
123
+ {
124
+ 'is-invalid' : this.invalid,
125
+ 'is-valid' : this.valid,
126
+ [`${this.inputClasses}`]: this.inputClasses
127
+ })}
128
+ "
129
+ type=${this.type}
130
+ id=${this.inputId}
131
+ name=${ifDefined(this.name)}
132
+ placeholder=${ifDefined(this.placeholder)}
133
+ aria-invalid=${this.invalid ? 'true' : 'false'}
134
+ pattern=${ifDefined(this.pattern)}
135
+ ?autofocus=${this.autofocus}
136
+ ?disabled=${this.disabled}
137
+ ?readonly=${this.readonly}
138
+ ?required=${this.required}
139
+ .value=${live(this.value)}
140
+ minlength=${ifDefined(this.minlength)}
141
+ maxlength=${ifDefined(this.maxlength)}
142
+ @input=${()=> this.handleChange('sgds-input')}
143
+ @change=${()=> this.handleChange('sgds-change')}
144
+ @keydown=${this.handleKeyDown}
145
+ @invalid=${this.handleInvalid}
146
+ @focus=${this.handleFocus}
147
+ @blur=${this.handleBlur}
148
+ >
149
+ <div id="${this.inputId}-invalid" class="invalid-feedback">${this.invalidFeedback}</div>
150
+ `
151
+ // if iconName is defined
152
+ const inputWithIcon = html`
153
+ <div class="sgds form-control-group ${this.inputClasses}">
154
+ <span class="form-control-icon">
155
+ <sl-icon name=${this.iconName}></sl-icon>
156
+ </span>
157
+ ${input}
158
+ </div>
159
+ `
160
+ // if hintText is defined
161
+ const withHintText = html`
162
+ <small id="${ifDefined(this.inputId)}Help" class="text-muted form-text">${this.hintText}</small>
163
+ `
164
+
165
+ // if label is defined
166
+ const withLabel = html`
167
+ <label for=${ifDefined(this.inputId)} class="form-label">${this.label}</label>
168
+ `
169
+
170
+ return html`
171
+ ${this.label ? withLabel : undefined }
172
+ ${this.hintText ? withHintText : undefined }
173
+ ${this.iconName ? inputWithIcon : input}
174
+ `;
175
+ }
176
+ }
177
+
178
+ export default SgdsInput;
@@ -0,0 +1,4 @@
1
+ export { SgdsMainnav } from './sgds-mainnav'
2
+ export type { MainnavExpandSize } from './sgds-mainnav'
3
+ export { SgdsMainnavItem } from './sgds-mainnav-item'
4
+ export { SgdsMainnavDropdown } from './sgds-mainnav-dropdown'
@@ -0,0 +1,13 @@
1
+ @import "./sgds-mainnav-item.scss";
2
+ @import "../Dropdown/sgds-dropdown.scss";
3
+
4
+ .nav-link {
5
+ gap: 1rem;
6
+ }
7
+ .dropdown-menu {
8
+ margin-top: 0;
9
+ border: 1px solid rgba($black, .1);
10
+ border-radius: 0 0 5px 5px;
11
+ background-color: $white;
12
+ box-shadow: 0 .5rem 1rem rgba($black, .15);
13
+ }
@@ -0,0 +1,45 @@
1
+ import { html } from "lit";
2
+ import { customElement} from "lit/decorators.js";
3
+ import { ref } from "lit/directives/ref.js";
4
+ import styles from "./sgds-mainnav-dropdown.scss";
5
+ import { DropdownElement } from "../utils/dropdown-element";
6
+
7
+ @customElement("sgds-mainnav-dropdown")
8
+ export class SgdsMainnavDropdown extends DropdownElement {
9
+ static styles = styles;
10
+
11
+ render() {
12
+ return html`
13
+ <li class="nav-item">
14
+ <a
15
+ class="nav-link"
16
+ ?disabled=${this.disabled}
17
+ aria-expanded="${this.menuIsOpen}"
18
+ ${ref(this.myDropdown)}
19
+ @click=${() => this._onClickButton()}
20
+ id=${this.togglerId}
21
+ tabindex=${this.disabled ? '-1' : '0'}
22
+ role="button"
23
+ >
24
+ ${this.togglerText}
25
+ <svg
26
+ xmlns="http://www.w3.org/2000/svg"
27
+ width="16"
28
+ height="16"
29
+ fill="currentColor"
30
+ class="bi bi-chevron-down"
31
+ viewBox="0 0 16 16"
32
+ >
33
+ <path
34
+ fill-rule="evenodd"
35
+ d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"
36
+ />
37
+ </svg>
38
+ </a>
39
+ <ul class="dropdown-menu" role="menu" part="menu">
40
+ <slot @click=${this._handleSelectSlot}></slot>
41
+ </ul>
42
+ </li>
43
+ `;
44
+ }
45
+ }
@@ -0,0 +1,24 @@
1
+ @import "../utils/base.scss";
2
+ @import "~@govtechsg/sgds/sass/navbar";
3
+
4
+ :host {
5
+ --mainnav-item-theme-color: #5925DC;
6
+ }
7
+
8
+ li {
9
+ height: 100%;
10
+ }
11
+
12
+ a.nav-link {
13
+ display: flex;
14
+ color: #344054;
15
+ border-bottom: $navbar-border-bottom-height solid transparent;
16
+ min-height: 100%;
17
+ align-items: center;
18
+
19
+ &.active,
20
+ &:hover {
21
+ color: var(--mainnav-item-theme-color);
22
+ border-color: var(--mainnav-item-theme-color);
23
+ }
24
+ }
@@ -0,0 +1,8 @@
1
+
2
+ import { customElement } from "lit/decorators.js";
3
+ import styles from "./sgds-mainnav-item.scss";
4
+ import LinkElement from "../utils/link-element";
5
+ @customElement("sgds-mainnav-item")
6
+ export class SgdsMainnavItem extends LinkElement {
7
+ static styles = styles;
8
+ }
@@ -0,0 +1,39 @@
1
+ @import "../utils/base.scss";
2
+ @import "~@govtechsg/sgds/sass/navbar";
3
+ @import "~@govtechsg/sgds/sass/transitions";
4
+ @import "~@govtechsg/sgds/sass/offcanvas";
5
+ .navbar-nav {
6
+ display: flex;
7
+ align-self: center;
8
+ gap: 1rem;
9
+ padding-left: 1rem;
10
+ padding-right: 1rem;
11
+ height: 100%;
12
+ width: 100%;
13
+ }
14
+
15
+ .navbar-toggler {
16
+ border: none;
17
+ }
18
+ .navbar-toggler:focus {
19
+ box-shadow: none;
20
+ }
21
+
22
+ slot[name="non-collapsible"] {
23
+ display: flex;
24
+ gap: 1rem;
25
+ align-items: center;
26
+ }
27
+
28
+ .slot-end {
29
+ display: flex;
30
+ margin-left: auto;
31
+ align-items: stretch;
32
+ gap: 1rem;
33
+
34
+ }
35
+
36
+ // there is no wildcard selector for element tag names in css :/
37
+ .slot-end::slotted(:not([name$="-mainnav-item"])){
38
+ align-self: center;
39
+ }
@@ -0,0 +1,183 @@
1
+ import { html, PropertyValueMap } from "lit";
2
+ import { customElement, property, queryAssignedElements, state } from "lit/decorators.js";
3
+ import styles from "./sgds-mainnav.scss";
4
+ import { Collapse, Offcanvas } from "bootstrap";
5
+ import { ref, createRef, Ref } from "lit/directives/ref.js";
6
+ import genId from "../utils/generateId";
7
+ import SgdsElement from "../utils/sgds-element";
8
+ import {
9
+ SM_BREAKPOINT,
10
+ MD_BREAKPOINT,
11
+ LG_BREAKPOINT,
12
+ XL_BREAKPOINT,
13
+ XXL_BREAKPOINT,
14
+ } from "../utils/breakpoints";
15
+ import { classMap } from "lit/directives/class-map.js";
16
+
17
+ export type MainnavExpandSize =
18
+ | "sm"
19
+ | "md"
20
+ | "lg"
21
+ | "xl"
22
+ | "xxl"
23
+ | "always"
24
+ | "never";
25
+
26
+ const SIZES = {
27
+ sm: SM_BREAKPOINT,
28
+ md: MD_BREAKPOINT,
29
+ lg: LG_BREAKPOINT,
30
+ xl: XL_BREAKPOINT,
31
+ XXL: XXL_BREAKPOINT,
32
+ never: Infinity,
33
+ always: -1,
34
+ };
35
+ @customElement("sgds-mainnav")
36
+ export class SgdsMainnav extends SgdsElement {
37
+ static styles = styles;
38
+
39
+ constructor() {
40
+ super();
41
+ window.addEventListener("resize", () => {
42
+ const newBreakpointReachedValue =
43
+ window.innerWidth < SIZES[this.expand.toString()];
44
+ if (newBreakpointReachedValue !== this.breakpointReached) {
45
+ this.requestUpdate();
46
+ }
47
+ });
48
+ }
49
+
50
+ private myCollapse: Ref<HTMLElement> = createRef();
51
+ private bsCollapse: Collapse = null;
52
+ // TODO: stylings and slots are incomplete for offcanvas mode
53
+ private myOffcanvas: Ref<HTMLElement> = createRef();
54
+ private bsOffcanvas: Offcanvas = null;
55
+
56
+ private _onClickButton() {
57
+ if (this.mode === "offcanvas") {
58
+ return this.bsOffcanvas.toggle();
59
+ }
60
+ this.bsCollapse.toggle();
61
+ }
62
+
63
+ @property({ type: String })
64
+ brandHref = "";
65
+
66
+ @property({ type: String })
67
+ collapseId = genId("mainnav", "collapse");
68
+
69
+ @property()
70
+ expand: MainnavExpandSize = "lg";
71
+
72
+ @property({ type: String })
73
+ mode: "offcanvas" | "default" = "default";
74
+
75
+ @state()
76
+ breakpointReached: Boolean = false;
77
+
78
+ @state()
79
+ expanded: Boolean = false;
80
+
81
+ firstUpdated() {
82
+
83
+ if (this.mode === "default") {
84
+ this.bsCollapse = new Collapse(this.myCollapse.value, {
85
+ toggle: false,
86
+ });
87
+ this.myCollapse.value.addEventListener("show.bs.collapse", () => {
88
+ this.expanded = true;
89
+ });
90
+ this.myCollapse.value.addEventListener("shown.bs.collapse", () => {
91
+ this.expanded = true;
92
+ });
93
+ this.myCollapse.value.addEventListener("hide.bs.collapse", () => {
94
+ this.expanded = false;
95
+ });
96
+ this.myCollapse.value.addEventListener("hidden.bs.collapse", () => {
97
+ this.expanded = false;
98
+ });
99
+ }
100
+
101
+ if (this.mode === "offcanvas") {
102
+ this.bsOffcanvas = new Offcanvas(this.myOffcanvas.value);
103
+ //add esc keyboard event for bsOffcanvas
104
+ this.addEventListener("keydown", (ev) => {
105
+ if (ev.key === "Escape") {
106
+ this.bsOffcanvas.hide();
107
+ }
108
+ });
109
+ }
110
+
111
+ }
112
+ // assigning name attribute to elements added in slot="end", to use wildcard css selector to assign styles only to *-mainnav-item
113
+ _handleSlotChange(e: Event){
114
+ const childElements = (e.target as HTMLSlotElement).assignedElements({flatten: true})
115
+ childElements.forEach(e => e.setAttribute('name', e.tagName.toLowerCase()))
116
+ }
117
+
118
+ render() {
119
+ this.breakpointReached = window.innerWidth < SIZES[this.expand.toString()];
120
+ const collapseClass = "collapse navbar-collapse order-4";
121
+ const offcanvasClass = "offcanvas offcanvas-start order-4";
122
+ return html`
123
+ <nav
124
+ class="sgds navbar navbar-light
125
+ ${this._expandClass()}"
126
+ >
127
+ <a class="navbar-brand me-auto order-1" href=${this.brandHref}>
128
+ <slot name="brand"></slot>
129
+ </a>
130
+ <slot
131
+ name="non-collapsible"
132
+ class="${this.breakpointReached ? "order-2" : "order-5"}"
133
+ ></slot>
134
+ <button
135
+ class="navbar-toggler order-3"
136
+ type="button"
137
+ @click=${() => this._onClickButton()}
138
+ aria-controls="${this.collapseId}"
139
+ aria-expanded="${this.expanded}"
140
+ aria-label="Toggle navigation"
141
+ >
142
+ <svg
143
+ xmlns="http://www.w3.org/2000/svg"
144
+ width="30"
145
+ height="30"
146
+ fill="currentColor"
147
+ class="bi bi-list"
148
+ viewBox="0 0 16 16"
149
+ >
150
+ <path
151
+ fill-rule="evenodd"
152
+ d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"
153
+ />
154
+ </svg>
155
+ </button>
156
+ <div
157
+ class=${this.mode === "default" ? collapseClass : offcanvasClass}
158
+ ${this.mode === "default"
159
+ ? ref(this.myCollapse)
160
+ : ref(this.myOffcanvas)}
161
+ id=${this.collapseId}
162
+ >
163
+ <ul class="navbar-nav">
164
+ <slot></slot>
165
+ <slot name="end" class=${classMap({"slot-end": !this.breakpointReached})} @slotchange=${this._handleSlotChange}></slot>
166
+ </ul>
167
+ </div>
168
+ </nav>
169
+ `;
170
+ }
171
+ _expandClass() {
172
+ switch (this.expand) {
173
+ case "always":
174
+ return "navbar-expand";
175
+ case "never":
176
+ break;
177
+ default:
178
+ return `navbar-expand-${this.expand}`;
179
+ }
180
+ }
181
+ }
182
+
183
+ export default SgdsMainnav;
@@ -0,0 +1 @@
1
+ export {SgdsMasthead} from './sgds-masthead';