@govtechsg/sgds-web-component 0.0.8 → 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 +2785 -9262
  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} +3870 -23414
  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 +2 -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 -52097
  263. package/umd/index.js.map +0 -1
@@ -0,0 +1,309 @@
1
+ import { html } from 'lit';
2
+ import { customElement, property, query } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import { ifDefined } from 'lit/directives/if-defined.js';
5
+ import { animateTo, stopAnimations } from '../utils/animate';
6
+ import { waitForEvent } from '../utils/event';
7
+ import Modal from '../utils/modal';
8
+ import { lockBodyScrolling, unlockBodyScrolling } from '../utils/scroll';
9
+ import SgdsElement from "../utils/sgds-element";
10
+ import { HasSlotController } from '../utils/slot';
11
+ import { watch } from '../utils/watch';
12
+ import { getAnimation, setDefaultAnimation } from '../utils/animation-registry';
13
+ import styles from "./sgds-modal.scss";
14
+
15
+
16
+ @customElement('sgds-modal')
17
+ export class SgdsModal extends SgdsElement {
18
+
19
+ static styles = styles;
20
+
21
+ @query('.modal') dialog: HTMLElement;
22
+ @query('.modal-panel') panel: HTMLElement;
23
+ @query('.modal-overlay') overlay: HTMLElement;
24
+
25
+ private readonly hasSlotController = new HasSlotController(this, 'footer');
26
+ private modal: Modal;
27
+ private originalTrigger: HTMLElement | null;
28
+
29
+ @property({ type: Boolean, reflect: true }) open = false;
30
+ // @property({ type: Boolean, reflect: true }) centeredAlignVariant = false;
31
+
32
+ @property({ reflect: true }) title = '';
33
+ @property({ reflect: true }) titleIcon = '';
34
+ @property({ attribute: 'no-header', type: Boolean, reflect: true }) noHeader = false;
35
+
36
+ connectedCallback() {
37
+ super.connectedCallback();
38
+ this.handleDocumentKeyDown = this.handleDocumentKeyDown.bind(this);
39
+ this.modal = new Modal(this);
40
+ }
41
+
42
+ firstUpdated() {
43
+ this.dialog.hidden = !this.open;
44
+
45
+ if (this.open) {
46
+ this.addOpenListeners();
47
+ this.modal.activate();
48
+ lockBodyScrolling(this);
49
+ }
50
+ }
51
+
52
+ disconnectedCallback() {
53
+ super.disconnectedCallback();
54
+ unlockBodyScrolling(this);
55
+ }
56
+
57
+ /** Shows the dialog. */
58
+ async show() {
59
+ if (this.open) {
60
+ return undefined;
61
+ }
62
+
63
+ this.open = true;
64
+ return waitForEvent(this, 'sgds-after-show');
65
+ }
66
+
67
+ /** Hides the dialog */
68
+ async hide() {
69
+ if (!this.open) {
70
+ return undefined;
71
+ }
72
+
73
+ this.open = false;
74
+ return waitForEvent(this, 'sgds-after-hide');
75
+ }
76
+
77
+ private requestClose(source: 'close-button' | 'keyboard' | 'overlay') {
78
+ const slRequestClose = this.emit('sgds-request-close', {
79
+ cancelable: true,
80
+ detail: { source }
81
+ });
82
+
83
+ if (slRequestClose.defaultPrevented) {
84
+ const animation = getAnimation(this, 'modal.denyClose');
85
+ animateTo(this.panel, animation.keyframes);
86
+ return;
87
+ }
88
+
89
+ this.hide();
90
+ }
91
+
92
+ addOpenListeners() {
93
+ document.addEventListener('keydown', this.handleDocumentKeyDown);
94
+ }
95
+
96
+ removeOpenListeners() {
97
+ document.removeEventListener('keydown', this.handleDocumentKeyDown);
98
+ }
99
+
100
+ handleDocumentKeyDown(event: KeyboardEvent) {
101
+ if (this.open && event.key === 'Escape') {
102
+ event.stopPropagation();
103
+ this.requestClose('keyboard');
104
+ }
105
+ }
106
+
107
+ @watch('open', { waitUntilFirstUpdate: true })
108
+ async handleOpenChange() {
109
+ if (this.open) {
110
+ // Show
111
+ this.emit('sgds-show');
112
+ this.addOpenListeners();
113
+ this.originalTrigger = document.activeElement as HTMLElement;
114
+ this.modal.activate();
115
+
116
+ lockBodyScrolling(this);
117
+
118
+ // When the dialog is shown, Safari will attempt to set focus on whatever element has autofocus. This can cause
119
+ // the dialogs's animation to jitter (if it starts offscreen), so we'll temporarily remove the attribute, call
120
+ // `focus({ preventScroll: true })` ourselves, and add the attribute back afterwards.
121
+ //
122
+ // Related: https://github.com/shoelace-style/shoelace/issues/693
123
+ //
124
+ const autoFocusTarget = this.querySelector('[autofocus]');
125
+ if (autoFocusTarget) {
126
+ autoFocusTarget.removeAttribute('autofocus');
127
+ }
128
+
129
+ await Promise.all([stopAnimations(this.dialog), stopAnimations(this.overlay)]);
130
+ this.dialog.hidden = false;
131
+
132
+ // Set initial focus
133
+ requestAnimationFrame(() => {
134
+ const slInitialFocus = this.emit('sgds-initial-focus', { cancelable: true });
135
+
136
+ if (!slInitialFocus.defaultPrevented) {
137
+ // Set focus to the autofocus target and restore the attribute
138
+ if (autoFocusTarget) {
139
+ (autoFocusTarget as HTMLInputElement).focus({ preventScroll: true });
140
+ } else {
141
+ this.panel.focus({ preventScroll: true });
142
+ }
143
+ }
144
+
145
+ // Restore the autofocus attribute
146
+ if (autoFocusTarget) {
147
+ autoFocusTarget.setAttribute('autofocus', '');
148
+ }
149
+ });
150
+
151
+ const panelAnimation = getAnimation(this, 'modal.show');
152
+ const overlayAnimation = getAnimation(this, 'modal.overlay.show');
153
+ await Promise.all([
154
+ animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),
155
+ animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)
156
+ ]);
157
+
158
+ this.emit('sgds-after-show');
159
+ } else {
160
+ // Hide
161
+ this.emit('sgds-hide');
162
+ this.removeOpenListeners();
163
+ this.modal.deactivate();
164
+
165
+ await Promise.all([stopAnimations(this.dialog), stopAnimations(this.overlay)]);
166
+ const panelAnimation = getAnimation(this, 'modal.hide');
167
+ const overlayAnimation = getAnimation(this, 'modal.overlay.hide');
168
+
169
+ // Animate the overlay and the panel at the same time. Because animation durations might be different, we need to
170
+ // hide each one individually when the animation finishes, otherwise the first one that finishes will reappear
171
+ // unexpectedly. We'll unhide them after all animations have completed.
172
+ await Promise.all([
173
+ animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options).then(() => {
174
+ this.overlay.hidden = true;
175
+ }),
176
+ animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options).then(() => {
177
+ this.panel.hidden = true;
178
+ })
179
+ ]);
180
+
181
+ this.dialog.hidden = true;
182
+
183
+ // Now that the dialog is hidden, restore the overlay and panel for next time
184
+ this.overlay.hidden = false;
185
+ this.panel.hidden = false;
186
+
187
+ unlockBodyScrolling(this);
188
+
189
+ // Restore focus to the original trigger
190
+ const trigger = this.originalTrigger;
191
+ if (typeof trigger?.focus === 'function') {
192
+ setTimeout(() => trigger.focus());
193
+ }
194
+
195
+ this.emit('sgds-after-hide');
196
+ }
197
+ }
198
+
199
+ render() {
200
+ // if label is defined
201
+ const withLabelIcon = html`
202
+ <sl-icon name=${this.titleIcon} class="pe-2 flex-shrink-0"></sl-icon>`
203
+
204
+ return html`
205
+ <div
206
+ part="base"
207
+ class=${classMap({
208
+ modal: true,
209
+ 'modal--open': this.open,
210
+ 'modal--has-footer': this.hasSlotController.test('footer')
211
+ })}
212
+ >
213
+ <div part="overlay" class="modal-overlay" @click=${() => this.requestClose('overlay')} tabindex="-1"></div>
214
+
215
+ <div
216
+ part="panel"
217
+ class="modal-panel"
218
+ role="dialog"
219
+ aria-modal="true"
220
+ aria-hidden=${this.open ? 'false' : 'true'}
221
+ aria-label=${ifDefined(this.noHeader ? this.title : undefined)}
222
+ aria-labelledby=${ifDefined(!this.noHeader ? 'title' : undefined)}
223
+ tabindex="0"
224
+ >
225
+ ${!this.noHeader
226
+ ? html`
227
+ <h3 part="header"
228
+ class=${classMap({
229
+ 'modal-header' : true,
230
+ // centered: this.centeredAlignVariant,
231
+ })}
232
+ >
233
+ <div
234
+ part="title"
235
+ class=${classMap({
236
+ 'modal-title d-flex align-items-center': true,
237
+ // centered : this.centeredAlignVariant,
238
+ })}
239
+ id="title"
240
+ >
241
+ ${this.titleIcon ? withLabelIcon : null}
242
+ ${this.title.length > 0 ? this.title : String.fromCharCode(65279)}
243
+
244
+ </div>
245
+ <sgds-button
246
+ part="close-button"
247
+ variant="icon"
248
+ exportparts="base:close-button__base"
249
+ class=${classMap({
250
+ 'modal-close': true,
251
+ // 'centered': this.centeredAlignVariant,
252
+ })}
253
+ @click="${() => this.requestClose('close-button')}"
254
+ ><sl-icon name="x-lg"></sl-icon></sgds-button>
255
+ </h3>
256
+ `
257
+ : ''}
258
+
259
+ <div part="body" class="modal-body">
260
+ <slot></slot>
261
+ </div>
262
+
263
+ <footer
264
+ part="footer"
265
+ class=${classMap({
266
+ 'modal-footer': true,
267
+ // centered: this.centeredAlignVariant,
268
+ })}
269
+ >
270
+ <slot name="footer"></slot>
271
+ </footer>
272
+ </div>
273
+ </div>
274
+ `;
275
+ }
276
+ }
277
+
278
+ setDefaultAnimation('modal.show', {
279
+ keyframes: [
280
+ { opacity: 0, transform: 'scale(0.8)' },
281
+ { opacity: 1, transform: 'scale(1)' }
282
+ ],
283
+ options: { duration: 250, easing: 'ease' }
284
+ });
285
+
286
+ setDefaultAnimation('modal.hide', {
287
+ keyframes: [
288
+ { opacity: 1, transform: 'scale(1)' },
289
+ { opacity: 0, transform: 'scale(0.8)' }
290
+ ],
291
+ options: { duration: 250, easing: 'ease' }
292
+ });
293
+
294
+ setDefaultAnimation('modal.denyClose', {
295
+ keyframes: [{ transform: 'scale(1)' }, { transform: 'scale(1.02)' }, { transform: 'scale(1)' }],
296
+ options: { duration: 250 }
297
+ });
298
+
299
+ setDefaultAnimation('modal.overlay.show', {
300
+ keyframes: [{ opacity: 0 }, { opacity: 1 }],
301
+ options: { duration: 250 }
302
+ });
303
+
304
+ setDefaultAnimation('modal.overlay.hide', {
305
+ keyframes: [{ opacity: 1 }, { opacity: 0 }],
306
+ options: { duration: 250 }
307
+ });
308
+
309
+ export default SgdsModal;
@@ -0,0 +1 @@
1
+ export { SgdsQuantityToggle } from "./sgds-quantitytoggle";
@@ -0,0 +1,10 @@
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
+ @import "~@govtechsg/sgds/sass/buttons";
10
+
@@ -0,0 +1,130 @@
1
+ import { customElement, property,query, state } from "lit/decorators.js";
2
+ import { html } from 'lit/static-html.js';
3
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
+ import SgdsElement from "../utils/sgds-element";
5
+ import { defaultValue } from "../utils/defaultvalue";
6
+ import genId from "../utils/generateId";
7
+ import { live } from 'lit/directives/live.js';
8
+ import { watch } from "../utils/watch";
9
+ import {SgdsButton} from "../Button";
10
+ import {classMap} from 'lit/directives/class-map.js';
11
+ import styles from "./sgds-quantitytoggle.scss";
12
+
13
+ @customElement("sgds-quantitytoggle")
14
+ export class SgdsQuantityToggle extends SgdsElement {
15
+ @query('input.form-control') input: HTMLInputElement;
16
+ @query('sgds-button.button-group_button-first') leftBtn: SgdsButton;
17
+ @query('sgds-button.button-group_button-last') lastBtn: SgdsButton;
18
+ static styles = styles;
19
+
20
+
21
+ @property({ reflect: true, type: String}) quantToggleId = genId("quantToggle", "toggle");
22
+
23
+ @property({reflect: true}) name : string;
24
+ /** The input's minimum value. */
25
+ @property() min: number | string;
26
+ /** The input's maximum value. */
27
+ @property() max: number | string;
28
+
29
+ @property() size : 'sm' | 'lg' | "default" = "sm" ;
30
+
31
+ @property ({reflect: true}) count : number | string;
32
+
33
+ @property({ type: Boolean, reflect: true }) disabled = false;
34
+
35
+ @property({ reflect: true }) quantityToggleClasses? : string;
36
+
37
+ /**
38
+ * Specifies the granularity that the value must adhere to, or the special value `any` which means no stepping is
39
+ * implied, allowing any numeric value.
40
+ */
41
+ @property() step = 1 ;
42
+
43
+ /** 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. */
44
+ @defaultValue()
45
+ defaultValue = '';
46
+
47
+ handleChange(event: string){
48
+ this.emit(event);
49
+ this.count = this.input.value;
50
+ }
51
+ onPlus(event:MouseEvent){
52
+ if (this.disabled) {
53
+ event.preventDefault();
54
+ event.stopPropagation();
55
+ return;
56
+ }
57
+ this.count = parseInt(this.input.value) + parseInt(this.input.step);
58
+ };
59
+ onMinus(event:MouseEvent){
60
+ if (this.disabled) {
61
+ event.preventDefault();
62
+ event.stopPropagation();
63
+ return;
64
+ }
65
+ if (this.count < this.step) {
66
+ this.count = 0;
67
+ }
68
+ else {
69
+ this.count = parseInt(this.input.value) - parseInt(this.input.step);
70
+ }
71
+
72
+ };
73
+
74
+
75
+
76
+ @watch('count', { waitUntilFirstUpdate: true })
77
+
78
+ render() {
79
+ return html`
80
+ <div
81
+ part="base"
82
+ class="${classMap(
83
+ {
84
+ "sgds" : true,
85
+ 'disabled': this.disabled,
86
+ "input-group" : true,
87
+ [`${this.quantityToggleClasses}`]: this.quantityToggleClasses
88
+ })}"
89
+ variant="quantity-toggle"
90
+ id=${this.quantToggleId}
91
+ size=${this.size}
92
+ >
93
+ <sgds-button
94
+ part="button"
95
+ variant="primary"
96
+ class="button-group_button-first"
97
+ size=${this.size}
98
+ @click=${this.onMinus}
99
+ ?disabled=${this.disabled}
100
+ >
101
+ <sl-icon name="dash-lg"></sl-icon>
102
+ </sgds-button>
103
+ <input
104
+ type="number"
105
+ class="form-control ${"form-control-" + this.size} text-center"
106
+ name=${ifDefined(this.name)}
107
+ step=${ifDefined(this.step as number)}
108
+ min=${ifDefined(this.min)}
109
+ max=${ifDefined(this.max)}
110
+ .value=${live(this.count as string)}
111
+ @change=${()=> this.handleChange('sgds-change')}
112
+ @input=${()=> this.handleChange('sgds-input')}
113
+ ?disabled=${this.disabled}
114
+ >
115
+ <sgds-button
116
+ part="button"
117
+ variant="primary"
118
+ class="button-group_button-last"
119
+ size=${this.size}
120
+ @click=${this.onPlus}
121
+ ?disabled=${this.disabled}
122
+ >
123
+ <sl-icon name="plus-lg"></sl-icon>
124
+ </sgds-button>
125
+ </div>
126
+ `;
127
+ }
128
+ }
129
+
130
+ export default SgdsQuantityToggle;
@@ -0,0 +1,2 @@
1
+ export { SgdsRadio } from "./sgds-radio";
2
+ export { SgdsRadioGroup } from "./sgds-radiogroup";
@@ -0,0 +1,5 @@
1
+ @import "../utils/base.scss";
2
+ @import "~@govtechsg/sgds/sass/forms/form-check";
3
+ @import "~@govtechsg/sgds/sass/forms/labels";
4
+
5
+
@@ -0,0 +1,120 @@
1
+ import { html } from "lit";
2
+ import { customElement, property, state } from "lit/decorators.js";
3
+ import { classMap } from "lit/directives/class-map.js";
4
+ import { watch } from "../utils/watch";
5
+ import styles from "./sgds-radio.scss";
6
+ import SgdsElement from "../utils/sgds-element";
7
+ import { ifDefined } from "lit/directives/if-defined.js";
8
+ import { live } from "lit/directives/live.js";
9
+ import genId from "../utils/generateId";
10
+
11
+
12
+ @customElement("sgds-radio")
13
+ export class SgdsRadio extends SgdsElement {
14
+ static styles = styles;
15
+
16
+ @state() checked = false;
17
+ @state() protected hasFocus = false;
18
+
19
+ /** The radio's value attribute. */
20
+ @property() value: string;
21
+
22
+ /** For id/for pair of the HTML form control. */
23
+ @property({ type: String, reflect: true }) radioId = genId("radio");
24
+
25
+ /** Disables the radio. */
26
+ @property({ type: Boolean, reflect: true }) disabled = false;
27
+
28
+ /** Aligns the radios horizontally */
29
+ @property({ type: Boolean, reflect: true }) isInline = false;
30
+
31
+ /** For aria-label */
32
+ @property({ type: String, reflect: true }) ariaLabel = "";
33
+
34
+ connectedCallback(): void {
35
+ super.connectedCallback();
36
+ this.setInitialAttributes();
37
+ this.addEventListeners();
38
+ }
39
+
40
+ @watch("checked")
41
+ handleCheckedChange() {
42
+ this.setAttribute("aria-checked", this.checked ? "true" : "false");
43
+ this.setAttribute("tabindex", this.checked ? "0" : "-1");
44
+ }
45
+
46
+ @watch("disabled", { waitUntilFirstUpdate: true })
47
+ handleDisabledChange() {
48
+ this.setAttribute("aria-disabled", this.disabled ? "true" : "false");
49
+ }
50
+
51
+ private handleBlur() {
52
+ this.hasFocus = false;
53
+ this.emit('sgds-blur');
54
+ }
55
+
56
+ private handleClick() {
57
+ if (!this.disabled) {
58
+ this.checked = true;
59
+ }
60
+ }
61
+
62
+ private handleFocus() {
63
+ this.hasFocus = true;
64
+ this.emit('sgds-focus');
65
+ }
66
+
67
+ private addEventListeners() {
68
+ this.addEventListener('blur', () => this.handleBlur());
69
+ this.addEventListener('click', () => this.handleClick());
70
+ this.addEventListener('focus', () => this.handleFocus());
71
+ }
72
+
73
+ private setInitialAttributes() {
74
+ this.setAttribute("role", "radio");
75
+ this.setAttribute("tabindex", "-1");
76
+ this.setAttribute("aria-disabled", this.disabled ? "true" : "false");
77
+ }
78
+
79
+ render() {
80
+ return html`
81
+ <div
82
+ part="base"
83
+ class=${classMap({
84
+ "form-check": true,
85
+ "form-check-inline": this.isInline,
86
+ })}
87
+ >
88
+ <input
89
+ part="control"
90
+ class=${classMap({
91
+ "form-check-input": true,
92
+ })}
93
+ type="radio"
94
+ id=${ifDefined(this.radioId)}
95
+ value=${ifDefined(this.value)}
96
+ .checked=${live(this.checked)}
97
+ ?disabled=${this.disabled}
98
+ aria-disabled=${this.disabled ? "true" : "false"}
99
+ aria-checked=${this.checked ? "true" : "false"}
100
+ @click=${this.handleClick}
101
+ />
102
+ <label
103
+ part="label"
104
+ for="${ifDefined(this.radioId)}"
105
+ aria-label=${ifDefined(this.ariaLabel)}
106
+ class="form-check-label"
107
+ ><slot></slot
108
+ ></label>
109
+ </div>
110
+ `;
111
+ }
112
+ }
113
+
114
+ declare global {
115
+ interface HTMLElementTagNameMap {
116
+ 'sgds-radio': SgdsRadio;
117
+ }
118
+ }
119
+
120
+ export default SgdsRadio;
@@ -0,0 +1,22 @@
1
+ @import "../utils/base.scss";
2
+ @import "~@govtechsg/sgds/sass/forms/form-check";
3
+ @import "~@govtechsg/sgds/sass/forms/labels";
4
+ @import "~@govtechsg/sgds/sass/forms/validation";
5
+ @import "~@govtechsg/sgds/sass/forms/form-text";
6
+ @import "~@govtechsg/sgds/sass/forms/form-control";
7
+ @import "~@govtechsg/sgds/sass/forms/form-control-group";
8
+ @import "~@govtechsg/sgds/sass/forms/input-group";
9
+
10
+ .visually-hidden {
11
+ position: absolute;
12
+ width: 1px;
13
+ height: 1px;
14
+ padding: 0;
15
+ margin: -1px;
16
+ overflow: hidden;
17
+ clip: rect(0, 0, 0, 0);
18
+ white-space: nowrap;
19
+ border: 0;
20
+ }
21
+
22
+