@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,198 @@
1
+ @import '../utils/components.style';
2
+ @import '../utils/base.scss';
3
+ :host {
4
+ --indicator-color: #0f71bb;
5
+ --track-color: transparent;
6
+ --track-width: 2px;
7
+ display: block;
8
+
9
+ }
10
+ .tab-group {
11
+ display: flex;
12
+ border-radius: 0;
13
+ }
14
+ .tab-group__tabs {
15
+ display: flex;
16
+ position: relative;
17
+ }
18
+ .tab-group__indicator {
19
+ position: absolute;
20
+ // transition: var(--sl-transition-fast) translate ease, var(--sl-transition-fast) width ease;
21
+ }
22
+ .tab-group--has-scroll-controls .tab-group__nav-container {
23
+ position: relative;
24
+ padding: 0 var(--sl-spacing-x-large);
25
+ }
26
+ .tab-group__body {
27
+ display: block;
28
+ overflow: auto;
29
+ }
30
+ .tab-group__scroll-button {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ position: absolute;
35
+ top: 0;
36
+ bottom: 0;
37
+ width: var(--sl-spacing-x-large);
38
+ }
39
+ .tab-group__scroll-button--start {
40
+ left: 0;
41
+ }
42
+ .tab-group__scroll-button--end {
43
+ right: 0;
44
+ }
45
+ .tab-group--rtl .tab-group__scroll-button--start {
46
+ left: auto;
47
+ right: 0;
48
+ }
49
+ .tab-group--rtl .tab-group__scroll-button--end {
50
+ left: 0;
51
+ right: auto;
52
+ }
53
+ /*
54
+ * Top
55
+ */
56
+ .tab-group--top {
57
+ flex-direction: column;
58
+
59
+ }
60
+ .tab-group--top .tab-group__nav-container {
61
+ order: 1;
62
+ }
63
+ .tab-group--top .tab-group__nav {
64
+ display: flex;
65
+ overflow-x: auto;
66
+ /* Hide scrollbar in Firefox */
67
+ scrollbar-width: none;
68
+ }
69
+ /* Hide scrollbar in Chrome/Safari */
70
+ .tab-group--top .tab-group__nav::-webkit-scrollbar {
71
+ width: 0;
72
+ height: 0;
73
+ }
74
+ .tab-group--top .tab-group__tabs {
75
+ flex: 1 1 auto;
76
+ position: relative;
77
+ flex-direction: row;
78
+ border-bottom: solid $nav-tabs-border-width transparent;
79
+ gap: 1rem;
80
+
81
+ }
82
+ .tab-group--top .tab-group__indicator {
83
+ bottom: calc(-1 * #{$nav-tabs-border-width});
84
+ // border-bottom: solid $nav-tabs-border-width $blue-500;
85
+ }
86
+ .tab-group--top .tab-group__body {
87
+ order: 2;
88
+ }
89
+ .tab-group--top ::slotted(sl-tab-panel) {
90
+ --padding: var(--sl-spacing-medium) 0;
91
+ }
92
+ /*
93
+ * Bottom
94
+ */
95
+ .tab-group--bottom {
96
+ flex-direction: column;
97
+ }
98
+ .tab-group--bottom .tab-group__nav-container {
99
+ order: 2;
100
+ }
101
+ .tab-group--bottom .tab-group__nav {
102
+ display: flex;
103
+ overflow-x: auto;
104
+ /* Hide scrollbar in Firefox */
105
+ scrollbar-width: none;
106
+ }
107
+ /* Hide scrollbar in Chrome/Safari */
108
+ .tab-group--bottom .tab-group__nav::-webkit-scrollbar {
109
+ width: 0;
110
+ height: 0;
111
+ }
112
+ .tab-group--bottom .tab-group__tabs {
113
+ flex: 1 1 auto;
114
+ position: relative;
115
+ flex-direction: row;
116
+ border-top: solid $nav-tabs-border-width transparent;
117
+ }
118
+ .tab-group--bottom .tab-group__indicator {
119
+ top: calc(-1 * #{$nav-tabs-border-width});
120
+ border-top: solid $nav-tabs-border-width $blue-500;
121
+ }
122
+ .tab-group--bottom .tab-group__body {
123
+ order: 1;
124
+ }
125
+ .tab-group--bottom ::slotted(sl-tab-panel) {
126
+ --padding: var(--sl-spacing-medium) 0;
127
+ }
128
+ /*
129
+ * Start
130
+ */
131
+ .tab-group--start {
132
+ flex-direction: row;
133
+ }
134
+ .tab-group--start .tab-group__nav-container {
135
+ order: 1;
136
+ }
137
+ .tab-group--start .tab-group__tabs {
138
+ flex: 0 0 auto;
139
+ flex-direction: column;
140
+ border-inline-end: solid $nav-tabs-border-width transparent;
141
+ }
142
+ .tab-group--start .tab-group__indicator {
143
+ right: calc(-1 * #{$nav-tabs-border-width});
144
+ border-right: solid $nav-tabs-border-width $blue-500;
145
+ }
146
+ .tab-group--start.tab-group--rtl .tab-group__indicator {
147
+ right: auto;
148
+ left: calc(-1 * #{$nav-tabs-border-width});
149
+ }
150
+ .tab-group--start .tab-group__body {
151
+ flex: 1 1 auto;
152
+ order: 2;
153
+ }
154
+ .tab-group--start ::slotted(sl-tab-panel) {
155
+ --padding: 0 var(--sl-spacing-medium);
156
+ }
157
+ /*
158
+ * End
159
+ */
160
+ .tab-group--end {
161
+ flex-direction: row;
162
+ }
163
+ .tab-group--end .tab-group__nav-container {
164
+ order: 2;
165
+ }
166
+ .tab-group--end .tab-group__tabs {
167
+ flex: 0 0 auto;
168
+ flex-direction: column;
169
+ border-left: solid $nav-tabs-border-width transparent;
170
+ }
171
+ .tab-group--end .tab-group__indicator {
172
+ left: calc(-1 * #{$nav-tabs-border-width});
173
+ border-inline-start: solid $nav-tabs-border-width $blue-500;
174
+ }
175
+ .tab-group--end.tab-group--rtl .tab-group__indicator {
176
+ right: calc(-1 * #{$nav-tabs-border-width});
177
+ left: auto;
178
+ }
179
+ .tab-group--end .tab-group__body {
180
+ flex: 1 1 auto;
181
+ order: 1;
182
+ }
183
+ .tab-group--end ::slotted(sl-tab-panel) {
184
+ --padding: 0 var(--sl-spacing-medium);
185
+ }
186
+
187
+ .tab-group--top{
188
+ &.tab-group--basic-toggle .tab-group__tabs {
189
+ gap:0;
190
+ ::slotted(sgds-tab){
191
+ margin-right: -2px;
192
+
193
+ }
194
+ }
195
+ }
196
+
197
+
198
+
@@ -0,0 +1,295 @@
1
+ import { html } from 'lit';
2
+ import { customElement, property, query, state } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import { scrollIntoView } from '../utils/scroll';
5
+ import SgdsElement from "../utils/sgds-element";
6
+ import { watch } from "../utils/watch";
7
+ import styles from "./sgds-tabgroup.scss";
8
+ import { SgdsTabPanel } from './sgds-tabpanel';
9
+ import { SgdsTab } from './sgds-tab';
10
+
11
+ @customElement('sgds-tab-group')
12
+ export class SgdsTabGroup extends SgdsElement {
13
+ static styles = styles;
14
+
15
+ @query('.tab-group') tabGroup: HTMLElement;
16
+ @query('.tab-group__body') body: HTMLSlotElement;
17
+ @query('.tab-group__nav') nav: HTMLElement;
18
+
19
+ private activeTab?: SgdsTab;
20
+ private mutationObserver: MutationObserver;
21
+ private resizeObserver: ResizeObserver;
22
+ private tabs: SgdsTab[] = [];
23
+ private panels: SgdsTabPanel[] = [];
24
+
25
+ @state() private hasScrollControls = false;
26
+
27
+ /** The placement of the tabs. */
28
+ @property() placement: 'top' | 'bottom' | 'start' | 'end' = 'top';
29
+
30
+ @property({reflect: true}) TabVariant: 'basic-toggle' | 'info-toggle';
31
+ /**
32
+ * When set to auto, navigating tabs with the arrow keys will instantly show the corresponding tab panel. When set to
33
+ * manual, the tab will receive focus but will not show until the user presses spacebar or enter.
34
+ */
35
+ @property() activation: 'auto' | 'manual' = 'auto';
36
+
37
+ /** Disables the scroll arrows that appear when tabs overflow. */
38
+ @property({ attribute: 'no-scroll-controls', type: Boolean }) noScrollControls = false;
39
+
40
+ connectedCallback() {
41
+ super.connectedCallback();
42
+
43
+ this.resizeObserver = new ResizeObserver(() => {
44
+ this.updateScrollControls();
45
+ });
46
+
47
+ this.mutationObserver = new MutationObserver(mutations => {
48
+ // Update aria labels when the DOM changes
49
+ if (mutations.some(m => !['aria-labelledby', 'aria-controls'].includes(m.attributeName!))) {
50
+ setTimeout(() => this.setAriaLabels());
51
+ }
52
+
53
+ // Sync tabs when disabled states change
54
+ if (mutations.some(m => m.attributeName === 'disabled')) {
55
+ this.syncTabsAndPanels();
56
+ }
57
+ });
58
+
59
+ this.updateComplete.then(() => {
60
+ this.syncTabsAndPanels();
61
+ this.mutationObserver.observe(this, { attributes: true, childList: true, subtree: true });
62
+ this.resizeObserver.observe(this.nav);
63
+
64
+ // Set initial tab state when the tabs first become visible
65
+ const intersectionObserver = new IntersectionObserver((entries, observer) => {
66
+ if (entries[0].intersectionRatio > 0) {
67
+ this.setAriaLabels();
68
+ this.setTabVariant();
69
+ this.setActiveTab(this.getActiveTab() ?? this.tabs[0], { emitEvents: false });
70
+ observer.unobserve(entries[0].target);
71
+ }
72
+ });
73
+ intersectionObserver.observe(this.tabGroup);
74
+ });
75
+ }
76
+
77
+ disconnectedCallback() {
78
+ this.mutationObserver.disconnect();
79
+ this.resizeObserver.unobserve(this.nav);
80
+ }
81
+
82
+ /** Shows the specified tab panel. */
83
+ show(panel: string) {
84
+ const tab = this.tabs.find(el => el.panel === panel);
85
+
86
+ if (tab) {
87
+ this.setActiveTab(tab, { scrollBehavior: 'smooth' });
88
+ }
89
+ }
90
+
91
+ getAllTabs(options: { includeDisabled: boolean } = { includeDisabled: true }) {
92
+ const slot = this.shadowRoot!.querySelector<HTMLSlotElement>('slot[name="nav"]')!;
93
+
94
+ return [...(slot.assignedElements() as SgdsTab[])].filter(el => {
95
+ return options.includeDisabled
96
+ ? el.tagName.toLowerCase() === 'sgds-tab'
97
+ : el.tagName.toLowerCase() === 'sgds-tab' && !el.disabled;
98
+ });
99
+ }
100
+
101
+ getAllPanels() {
102
+ return [...this.body.assignedElements()].filter(el => el.tagName.toLowerCase() === 'sgds-tab-panel') as [SgdsTabPanel];
103
+ }
104
+
105
+ getActiveTab() {
106
+ return this.tabs.find(el => el.active);
107
+ }
108
+
109
+ handleClick(event: MouseEvent) {
110
+ const target = event.target as HTMLElement;
111
+ const tab = target.closest('sgds-tab') as SgdsTab;
112
+ const tabGroup = tab?.closest('sgds-tab-group');
113
+
114
+ // Ensure the target tab is in this tab group
115
+ if (tabGroup !== this) {
116
+ return;
117
+ }
118
+
119
+ if (tab !== null) {
120
+ this.setActiveTab(tab, { scrollBehavior: 'smooth' });
121
+ }
122
+ }
123
+
124
+ handleKeyDown(event: KeyboardEvent) {
125
+ const target = event.target as HTMLElement;
126
+ const tab = target.closest('sgds-tab') as SgdsTab;
127
+ const tabGroup = tab?.closest('sgds-tab-group');
128
+
129
+ // Ensure the target tab is in this tab group
130
+ if (tabGroup !== this) {
131
+ return;
132
+ }
133
+
134
+ // Activate a tab
135
+ if (['Enter', ' '].includes(event.key)) {
136
+ if (tab !== null) {
137
+ this.setActiveTab(tab, { scrollBehavior: 'smooth' });
138
+ event.preventDefault();
139
+ }
140
+ }
141
+
142
+ // Move focus left or right
143
+ if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
144
+ const activeEl = this.tabs.find(t => t.matches(':focus'));
145
+
146
+
147
+ if (activeEl?.tagName.toLowerCase() === 'sgds-tab') {
148
+ let index = this.tabs.indexOf(activeEl);
149
+
150
+ if (event.key === 'Home') {
151
+ index = 0;
152
+ } else if (event.key === 'End') {
153
+ index = this.tabs.length - 1;
154
+ } else if (
155
+ ['top', 'bottom'].includes(this.placement) ||
156
+ (['start', 'end'].includes(this.placement) && event.key === 'ArrowUp')
157
+ ) {
158
+ index--;
159
+ } else if (
160
+ ['top', 'bottom'].includes(this.placement) ||
161
+ (['start', 'end'].includes(this.placement) && event.key === 'ArrowDown')
162
+ ) {
163
+ index++;
164
+ }
165
+
166
+ if (index < 0) {
167
+ index = this.tabs.length - 1;
168
+ }
169
+
170
+ if (index > this.tabs.length - 1) {
171
+ index = 0;
172
+ }
173
+
174
+ this.tabs[index].focus({ preventScroll: true });
175
+
176
+ if (this.activation === 'auto') {
177
+ this.setActiveTab(this.tabs[index], { scrollBehavior: 'smooth' });
178
+ }
179
+
180
+ if (['top', 'bottom'].includes(this.placement)) {
181
+ scrollIntoView(this.tabs[index], this.nav, 'horizontal');
182
+ }
183
+
184
+ event.preventDefault();
185
+ }
186
+ }
187
+ }
188
+
189
+ handleScrollToStart() {
190
+ this.nav.scroll({
191
+ left: this.nav.scrollLeft - this.nav.clientWidth,
192
+ behavior: 'smooth'
193
+ });
194
+ }
195
+
196
+ handleScrollToEnd() {
197
+ this.nav.scroll({
198
+ left: this.nav.scrollLeft + this.nav.clientWidth,
199
+ behavior: 'smooth'
200
+ });
201
+ }
202
+
203
+ @watch('noScrollControls', { waitUntilFirstUpdate: true })
204
+ updateScrollControls() {
205
+ if (this.noScrollControls) {
206
+ this.hasScrollControls = false;
207
+ } else {
208
+ this.hasScrollControls =
209
+ ['top', 'bottom'].includes(this.placement) && this.nav.scrollWidth > this.nav.clientWidth;
210
+ }
211
+ }
212
+
213
+ setActiveTab(tab: SgdsTab, options?: { emitEvents?: boolean; scrollBehavior?: 'auto' | 'smooth' }) {
214
+ options = {
215
+ emitEvents: true,
216
+ scrollBehavior: 'auto',
217
+ ...options
218
+ };
219
+
220
+ if (tab !== this.activeTab && !tab.disabled) {
221
+ const previousTab = this.activeTab;
222
+ this.activeTab = tab;
223
+
224
+ // Sync active tab and panel
225
+ this.tabs.map(el => (el.active = el === this.activeTab));
226
+ this.panels.map(el => (el.active = el.name === this.activeTab?.panel));
227
+
228
+
229
+ if (['top', 'bottom'].includes(this.placement)) {
230
+ scrollIntoView(this.activeTab, this.nav, 'horizontal', options.scrollBehavior);
231
+ }
232
+
233
+ // Emit events
234
+ if (options.emitEvents) {
235
+ if (previousTab) {
236
+ this.emit('sgds-tab-hide', { detail: { name: previousTab.panel } });
237
+ }
238
+
239
+ this.emit('sgds-tab-show', { detail: { name: this.activeTab.panel } });
240
+ }
241
+ }
242
+ }
243
+
244
+ setAriaLabels() {
245
+ // Link each tab with its corresponding panel
246
+ this.tabs.forEach(tab => {
247
+ const panel = this.panels.find(el => el.name === tab.panel);
248
+ if (panel) {
249
+ tab.setAttribute('aria-controls', panel.getAttribute('id')!);
250
+ panel.setAttribute('aria-labelledby', tab.getAttribute('id')!);
251
+ }
252
+ });
253
+ }
254
+
255
+ setTabVariant() {
256
+ // Link each tab with its corresponding panel
257
+ this.tabs.forEach(tab => {
258
+ tab.setAttribute('variant', this.TabVariant);
259
+ });
260
+ }
261
+
262
+ // This stores tabs and panels so we can refer to a cache instead of calling querySelectorAll() multiple times.
263
+ syncTabsAndPanels() {
264
+ this.tabs = this.getAllTabs({ includeDisabled: false });
265
+ this.panels = this.getAllPanels();
266
+ }
267
+
268
+ render() {
269
+ return html`
270
+ <div
271
+ part="base"
272
+ class=${classMap({
273
+ 'tab-group': true,
274
+ 'tab-group--top': this.placement === 'top',
275
+ 'tab-group--bottom': this.placement === 'bottom',
276
+ 'tab-group--start': this.placement === 'start',
277
+ 'tab-group--end': this.placement === 'end',
278
+ 'tab-group--basic-toggle': this.TabVariant === 'basic-toggle',
279
+ 'tab-group--info-toggle': this.TabVariant === 'info-toggle',
280
+ })}
281
+ @click=${this.handleClick}
282
+ @keydown=${this.handleKeyDown}
283
+ >
284
+ <div class="tab-group__nav-container" part="nav">
285
+ <div class="tab-group__nav">
286
+ <div part="tabs" class="tab-group__tabs" role="tablist">
287
+ <slot name="nav" @slotchange=${this.syncTabsAndPanels}></slot>
288
+ </div>
289
+ </div>
290
+ </div>
291
+ <slot part="body" class="tab-group__body" @slotchange=${this.syncTabsAndPanels}></slot>
292
+ </div>
293
+ `;
294
+ }
295
+ }
@@ -0,0 +1,12 @@
1
+ @import '../utils/components.style';
2
+ :host {
3
+ --padding: 1rem;
4
+ display: block;
5
+ }
6
+ .tab-panel {
7
+ display: block;
8
+ padding: var(--padding) 0;
9
+ }
10
+ .tab-panel:not(.tab-panel--active) {
11
+ display: none;
12
+ }
@@ -0,0 +1,55 @@
1
+ import { html } from 'lit';
2
+ import { customElement, property } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import SgdsElement from "../utils/sgds-element";
5
+ import { watch } from "../utils/watch";
6
+ import styles from "./sgds-tabpanel.scss";
7
+ let id = 0;
8
+
9
+ /**
10
+ * @summary Tab panels are used inside [tab groups](/components/tab-group) to display tabbed content.
11
+ *
12
+ * @since 2.0
13
+ * @status stable
14
+ *
15
+ * @slot - The tab panel's content.
16
+ *
17
+ * @csspart base - The component's base wrapper.
18
+ *
19
+ * @cssproperty --padding - The tab panel's padding.
20
+ */
21
+ @customElement('sgds-tab-panel')
22
+ export class SgdsTabPanel extends SgdsElement {
23
+ static styles = styles;
24
+ private readonly attrId = ++id;
25
+ private readonly componentId = `sgds-tab-panel-${this.attrId}`;
26
+
27
+ /** The tab panel's name. */
28
+ @property({ reflect: true }) name = '';
29
+
30
+ /** When true, the tab panel will be shown. */
31
+ @property({ type: Boolean, reflect: true }) active = false;
32
+
33
+ connectedCallback() {
34
+ super.connectedCallback();
35
+ this.id = this.id.length > 0 ? this.id : this.componentId;
36
+ this.setAttribute('role', 'tabpanel');
37
+ }
38
+
39
+ @watch('active')
40
+ handleActiveChange() {
41
+ this.setAttribute('aria-hidden', this.active ? 'false' : 'true');
42
+ }
43
+
44
+ render() {
45
+ return html`
46
+ <slot
47
+ part="base"
48
+ class=${classMap({
49
+ 'tab-panel': true,
50
+ 'tab-panel--active': this.active
51
+ })}
52
+ ></slot>
53
+ `;
54
+ }
55
+ }
@@ -0,0 +1 @@
1
+ export { SgdsTextArea } from "./sgds-textarea";
@@ -0,0 +1,23 @@
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
+ & > .text-area-label-wrapper{
13
+ line-height: var(--sgds-body-line-height);
14
+ }
15
+
16
+ .textarea-resize-none{
17
+ resize: none;
18
+ }
19
+ .textarea-resize-vertical {
20
+ resize: vertical;
21
+ }
22
+ }
23
+