@sats-group/ui-lib 74.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/.nvmrc +1 -0
  2. package/README.md +35 -0
  3. package/catalog-info.yaml +14 -0
  4. package/eslint.config.mjs +94 -0
  5. package/fonts/Inter-BoldItalic.woff +0 -0
  6. package/fonts/Inter-BoldItalic.woff2 +0 -0
  7. package/fonts/Inter-ExtraBold.woff +0 -0
  8. package/fonts/Inter-ExtraBold.woff2 +0 -0
  9. package/fonts/Inter-Italic.woff +0 -0
  10. package/fonts/Inter-Italic.woff2 +0 -0
  11. package/fonts/Inter-Regular.woff +0 -0
  12. package/fonts/Inter-Regular.woff2 +0 -0
  13. package/fonts/Inter-SemiBold.woff +0 -0
  14. package/fonts/Inter-SemiBold.woff2 +0 -0
  15. package/fonts/LICENSE.txt +92 -0
  16. package/fonts/SATSHeadline-Bold.woff +0 -0
  17. package/fonts/SATSHeadline-BoldItalic.woff +0 -0
  18. package/fonts/SATSHeadline-RegularItalic.woff +0 -0
  19. package/fonts/SATSHeadline-SemiBoldItalic.woff +0 -0
  20. package/logos/e-avatar.svg +3 -0
  21. package/logos/elixia-letter.svg +3 -0
  22. package/logos/elixia-small.svg +8 -0
  23. package/logos/elixia.svg +8 -0
  24. package/logos/s-avatar.svg +3 -0
  25. package/logos/sats-letter.svg +3 -0
  26. package/logos/sats-small.svg +3 -0
  27. package/logos/sats.svg +4 -0
  28. package/package.json +58 -0
  29. package/react/add-bem-modifiers.ts +51 -0
  30. package/react/badge/badge.scss +53 -0
  31. package/react/badge/badge.tsx +28 -0
  32. package/react/badge/badge.types.ts +34 -0
  33. package/react/badge/index.ts +2 -0
  34. package/react/banner/banner.scss +118 -0
  35. package/react/banner/banner.tsx +92 -0
  36. package/react/banner/banner.types.ts +10 -0
  37. package/react/banner/index.ts +2 -0
  38. package/react/bomb/bomb.scss +33 -0
  39. package/react/bomb/bomb.tsx +19 -0
  40. package/react/bomb/bomb.types.ts +1 -0
  41. package/react/bomb/index.ts +2 -0
  42. package/react/button/button.tsx +19 -0
  43. package/react/button/button.types.ts +3 -0
  44. package/react/button/index.ts +2 -0
  45. package/react/checkbox/checkbox.scss +218 -0
  46. package/react/checkbox/checkbox.tsx +176 -0
  47. package/react/checkbox/checkbox.types.ts +19 -0
  48. package/react/checkbox/index.ts +2 -0
  49. package/react/chip/chip.scss +46 -0
  50. package/react/chip/chip.tsx +37 -0
  51. package/react/chip/chip.types.ts +18 -0
  52. package/react/chip/index.ts +2 -0
  53. package/react/chip/remove.tsx +14 -0
  54. package/react/chip-selected/chip-selected.scss +47 -0
  55. package/react/chip-selected/chip-selected.tsx +102 -0
  56. package/react/chip-selected/chip-selected.types.ts +11 -0
  57. package/react/chip-selected/index.ts +2 -0
  58. package/react/confirmation/confirmation.scss +60 -0
  59. package/react/confirmation/confirmation.tsx +85 -0
  60. package/react/confirmation/confirmation.types.ts +24 -0
  61. package/react/confirmation/index.ts +2 -0
  62. package/react/context-menu/context-menu.scss +183 -0
  63. package/react/context-menu/context-menu.tsx +200 -0
  64. package/react/context-menu/context-menu.types.ts +71 -0
  65. package/react/context-menu/index.ts +2 -0
  66. package/react/cropped-image/cropped-image.scss +48 -0
  67. package/react/cropped-image/cropped-image.tsx +36 -0
  68. package/react/cropped-image/cropped-image.types.ts +26 -0
  69. package/react/cropped-image/index.ts +2 -0
  70. package/react/dropdown-list/dropdown-list.scss +170 -0
  71. package/react/dropdown-list/dropdown-list.tsx +116 -0
  72. package/react/dropdown-list/dropdown-list.types.ts +17 -0
  73. package/react/dropdown-list/index.ts +2 -0
  74. package/react/expander/expander.scss +115 -0
  75. package/react/expander/expander.tsx +167 -0
  76. package/react/expander/expander.types.ts +26 -0
  77. package/react/expander/index.ts +2 -0
  78. package/react/filter/filter.scss +94 -0
  79. package/react/filter/filter.tsx +99 -0
  80. package/react/filter/filter.types.ts +8 -0
  81. package/react/filter/index.ts +2 -0
  82. package/react/filter-wrapper/filter-wrapper.scss +46 -0
  83. package/react/filter-wrapper/filter-wrapper.tsx +24 -0
  84. package/react/filter-wrapper/filter-wrapper.types.ts +10 -0
  85. package/react/filter-wrapper/index.ts +2 -0
  86. package/react/flag/flag.scss +26 -0
  87. package/react/flag/flag.tsx +27 -0
  88. package/react/flag/flag.types.ts +17 -0
  89. package/react/flag/index.ts +2 -0
  90. package/react/form-content/checkbox-category.tsx +183 -0
  91. package/react/form-content/form-content.checkbox-list.tsx +126 -0
  92. package/react/form-content/form-content.checkbox-list.types.ts +36 -0
  93. package/react/form-content/form-content.radio-list.tsx +58 -0
  94. package/react/form-content/form-content.range.tsx +20 -0
  95. package/react/form-content/form-content.range.types.ts +14 -0
  96. package/react/form-content/form-content.scss +234 -0
  97. package/react/form-content/form-content.search.tsx +47 -0
  98. package/react/form-content/form-content.tsx +95 -0
  99. package/react/form-content/form-content.types.ts +55 -0
  100. package/react/form-content/index.ts +2 -0
  101. package/react/form-content/types/index.d.ts +1 -0
  102. package/react/hidden-input/hidden-input.tsx +9 -0
  103. package/react/hidden-input/hidden-input.types.ts +6 -0
  104. package/react/hidden-input/index.ts +2 -0
  105. package/react/hooks/focus-previous-element.ts +30 -0
  106. package/react/hooks/is-running-on-client.ts +1 -0
  107. package/react/hooks/use-click-outside.ts +23 -0
  108. package/react/hooks/use-escape.ts +18 -0
  109. package/react/hooks/use-event.ts +29 -0
  110. package/react/hooks/use-is-mounted.ts +11 -0
  111. package/react/hooks/use-toggle.ts +19 -0
  112. package/react/icons/16/close.tsx +12 -0
  113. package/react/icons/18/close.tsx +18 -0
  114. package/react/icons/24/arrow-down.tsx +14 -0
  115. package/react/icons/24/arrow-right.tsx +14 -0
  116. package/react/icons/24/arrow-up.tsx +14 -0
  117. package/react/icons/24/close.tsx +12 -0
  118. package/react/icons/24/remove.tsx +12 -0
  119. package/react/icons/24/search.tsx +10 -0
  120. package/react/icons/icons.md +3 -0
  121. package/react/indexed-access-type.ts +1 -0
  122. package/react/link/index.ts +2 -0
  123. package/react/link/link.scss +44 -0
  124. package/react/link/link.tsx +62 -0
  125. package/react/link/link.types.ts +37 -0
  126. package/react/link-button/index.ts +2 -0
  127. package/react/link-button/link-button.tsx +17 -0
  128. package/react/link-button/link-button.types.ts +5 -0
  129. package/react/link-card/index.ts +2 -0
  130. package/react/link-card/link-card.scss +37 -0
  131. package/react/link-card/link-card.tsx +24 -0
  132. package/react/link-card/link-card.types.ts +5 -0
  133. package/react/logos/e-avatar.tsx +12 -0
  134. package/react/logos/elixia-letter.tsx +12 -0
  135. package/react/logos/elixia-small.tsx +12 -0
  136. package/react/logos/elixia.tsx +12 -0
  137. package/react/logos/index.ts +8 -0
  138. package/react/logos/s-avatar.tsx +12 -0
  139. package/react/logos/sats-letter.tsx +12 -0
  140. package/react/logos/sats-small.tsx +12 -0
  141. package/react/logos/sats.tsx +12 -0
  142. package/react/message/hook/use-message.ts +22 -0
  143. package/react/message/index.ts +2 -0
  144. package/react/message/message.scss +92 -0
  145. package/react/message/message.tsx +60 -0
  146. package/react/message/message.types.ts +39 -0
  147. package/react/message/publish.ts +19 -0
  148. package/react/message-field/index.ts +2 -0
  149. package/react/message-field/message-field.scss +21 -0
  150. package/react/message-field/message-field.tsx +70 -0
  151. package/react/message-field/message-field.types.ts +24 -0
  152. package/react/modal/index.ts +2 -0
  153. package/react/modal/modal.scss +162 -0
  154. package/react/modal/modal.tsx +130 -0
  155. package/react/modal/modal.types.ts +36 -0
  156. package/react/modal/tab-trapper.tsx +68 -0
  157. package/react/progress-bar/index.ts +2 -0
  158. package/react/progress-bar/progress-bar.scss +71 -0
  159. package/react/progress-bar/progress-bar.tsx +81 -0
  160. package/react/progress-bar/progress-bar.types.ts +35 -0
  161. package/react/radio/index.ts +2 -0
  162. package/react/radio/radio.scss +142 -0
  163. package/react/radio/radio.tsx +87 -0
  164. package/react/radio/radio.types.ts +15 -0
  165. package/react/scale-bar/index.ts +2 -0
  166. package/react/scale-bar/scale-bar.scss +22 -0
  167. package/react/scale-bar/scale-bar.tsx +29 -0
  168. package/react/scale-bar/scale-bar.types.ts +4 -0
  169. package/react/search/index.ts +2 -0
  170. package/react/search/search.scss +207 -0
  171. package/react/search/search.tsx +255 -0
  172. package/react/search/search.types.ts +43 -0
  173. package/react/select/chevron-down.tsx +24 -0
  174. package/react/select/index.ts +2 -0
  175. package/react/select/select.scss +135 -0
  176. package/react/select/select.tsx +105 -0
  177. package/react/select/select.types.ts +19 -0
  178. package/react/select-option/README.md +3 -0
  179. package/react/select-option/index.ts +2 -0
  180. package/react/select-option/select-option.tsx +16 -0
  181. package/react/select-option/select-option.types.ts +8 -0
  182. package/react/tag/index.ts +2 -0
  183. package/react/tag/tag.scss +107 -0
  184. package/react/tag/tag.tsx +26 -0
  185. package/react/tag/tag.types.ts +30 -0
  186. package/react/text/index.ts +2 -0
  187. package/react/text/text.scss +109 -0
  188. package/react/text/text.tsx +40 -0
  189. package/react/text/text.types.ts +29 -0
  190. package/react/text-area/index.ts +2 -0
  191. package/react/text-area/text-area.scss +180 -0
  192. package/react/text-area/text-area.tsx +153 -0
  193. package/react/text-area/text-area.types.ts +24 -0
  194. package/react/text-input/index.ts +2 -0
  195. package/react/text-input/text-input.scss +233 -0
  196. package/react/text-input/text-input.tsx +106 -0
  197. package/react/text-input/text-input.types.ts +19 -0
  198. package/react/toggle/index.ts +2 -0
  199. package/react/toggle/toggle.scss +69 -0
  200. package/react/toggle/toggle.tsx +83 -0
  201. package/react/toggle/toggle.types.ts +11 -0
  202. package/react/toolbox/index.ts +2 -0
  203. package/react/toolbox/toolbox.scss +68 -0
  204. package/react/toolbox/toolbox.tsx +43 -0
  205. package/react/toolbox/toolbox.types.ts +39 -0
  206. package/react/ts/debounce.ts +12 -0
  207. package/react/types.ts +38 -0
  208. package/react/use-input-validation.ts +47 -0
  209. package/react/use-input-validation.types.ts +12 -0
  210. package/react/visually-button/index.ts +2 -0
  211. package/react/visually-button/visually-button.scss +470 -0
  212. package/react/visually-button/visually-button.tsx +130 -0
  213. package/react/visually-button/visually-button.types.ts +71 -0
  214. package/react/visually-hidden/index.ts +2 -0
  215. package/react/visually-hidden/visually-hidden.scss +6 -0
  216. package/react/visually-hidden/visually-hidden.tsx +10 -0
  217. package/tokens/corner-radius.scss +5 -0
  218. package/tokens/dark.scss +392 -0
  219. package/tokens/darkmode.scss +131 -0
  220. package/tokens/elevation.scss +57 -0
  221. package/tokens/font-faces.scss +62 -0
  222. package/tokens/font-names.scss +2 -0
  223. package/tokens/font-sizes.scss +95 -0
  224. package/tokens/light.scss +392 -0
  225. package/tokens/lightmode.scss +131 -0
  226. package/tokens/primitives.scss +137 -0
  227. package/tokens/spacing.scss +12 -0
package/react/types.ts ADDED
@@ -0,0 +1,38 @@
1
+ import {
2
+ AnchorHTMLAttributes,
3
+ ButtonHTMLAttributes,
4
+ ImgHTMLAttributes,
5
+ InputHTMLAttributes,
6
+ OptionHTMLAttributes,
7
+ SelectHTMLAttributes,
8
+ TextareaHTMLAttributes,
9
+ FormHTMLAttributes,
10
+ } from 'react';
11
+
12
+ /** Removes any generics from type, resulting in only the raw type:
13
+ ```ts
14
+ // In editors, this type will sometimes show up as `NonNullable<{ a: string | null }>`
15
+ type X = NonNullable<{
16
+ a: string | null
17
+ }>;
18
+ // In editors, this type should show up as `{ a: string }`
19
+ type Y = Flatten<X>;
20
+ ```
21
+ */
22
+ // eslint-disable-next-line
23
+ export type Flatten<T> = T extends infer T
24
+ ? { [key in keyof T]: T[key] }
25
+ : never;
26
+
27
+ /** Extracts the property value types from an object */
28
+ export type ObjectValues<T> = Flatten<T[keyof T]>;
29
+
30
+ // NOTE: The below types include all of the supported props for "html" react components (like `button` or `a`).
31
+ export type ButtonHtmlProps = ButtonHTMLAttributes<HTMLButtonElement>;
32
+ export type LinkHtmlProps = AnchorHTMLAttributes<HTMLAnchorElement>;
33
+ export type InputHtmlProps = InputHTMLAttributes<HTMLInputElement>;
34
+ export type ImageHtmlProps = ImgHTMLAttributes<HTMLImageElement>;
35
+ export type SelectHtmlProps = SelectHTMLAttributes<HTMLSelectElement>;
36
+ export type OptionHtmlProps = OptionHTMLAttributes<HTMLOptionElement>;
37
+ export type TextAreaHtmlProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
38
+ export type FormHtmlProps = FormHTMLAttributes<HTMLFormElement>;
@@ -0,0 +1,47 @@
1
+ import { useRef, useState } from 'react';
2
+
3
+ import { Messages } from './use-input-validation.types';
4
+
5
+ type SupportedElement =
6
+ | HTMLInputElement
7
+ | HTMLSelectElement
8
+ | HTMLTextAreaElement;
9
+
10
+ const getCustomMessage = (validity: ValidityState, messages: Messages) => {
11
+ if (validity.badInput) return messages.badInput;
12
+ if (validity.customError) return messages.customError;
13
+ if (validity.patternMismatch) return messages.patternMismatch;
14
+ if (validity.rangeOverflow) return messages.rangeOverflow;
15
+ if (validity.rangeUnderflow) return messages.rangeUnderflow;
16
+ if (validity.stepMismatch) return messages.stepMismatch;
17
+ if (validity.tooLong) return messages.tooLong;
18
+ if (validity.tooShort) return messages.tooShort;
19
+ if (validity.typeMismatch) return messages.typeMismatch;
20
+ if (validity.valueMissing) return messages.valueMissing;
21
+ };
22
+
23
+ export default (customMessages: Messages = {}) => {
24
+ const [error, setError] = useState<string | undefined>();
25
+ const validationEnabled = useRef(false);
26
+
27
+ const onInvalid = (e: React.FormEvent<SupportedElement>) => {
28
+ validationEnabled.current = true;
29
+ const message = getCustomMessage(
30
+ (e.target as HTMLInputElement).validity,
31
+ customMessages,
32
+ );
33
+ setError(message || (e.target as HTMLInputElement)?.validationMessage);
34
+ };
35
+
36
+ const onChange = (e: React.ChangeEvent<SupportedElement>) => {
37
+ if (!validationEnabled.current) return;
38
+ const message = getCustomMessage(e.target.validity, customMessages);
39
+ setError(message || e.target?.validationMessage || undefined);
40
+ };
41
+
42
+ return [onChange, onInvalid, error] as [
43
+ typeof onChange,
44
+ typeof onInvalid,
45
+ typeof error,
46
+ ];
47
+ };
@@ -0,0 +1,12 @@
1
+ export type Messages = {
2
+ badInput?: string;
3
+ customError?: string;
4
+ patternMismatch?: string;
5
+ rangeOverflow?: string;
6
+ rangeUnderflow?: string;
7
+ stepMismatch?: string;
8
+ tooLong?: string;
9
+ tooShort?: string;
10
+ typeMismatch?: string;
11
+ valueMissing?: string;
12
+ };
@@ -0,0 +1,2 @@
1
+ import VisuallyButton from './visually-button';
2
+ export default VisuallyButton;
@@ -0,0 +1,470 @@
1
+ @use '../../tokens/light';
2
+ @use '../../tokens/spacing';
3
+ @use '../../tokens/corner-radius';
4
+
5
+ .visually-button {
6
+ $borderThickness: 1px;
7
+ border-radius: corner-radius.$s;
8
+ border: $borderThickness solid;
9
+ box-sizing: border-box;
10
+ cursor: pointer;
11
+ display: inline-flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ text-decoration: none;
15
+ position: relative;
16
+ background: transparent;
17
+ text-align: center;
18
+
19
+ &--wide {
20
+ display: block;
21
+ width: 100%;
22
+
23
+ &.visually-button--icon {
24
+ width: inherit;
25
+ }
26
+ }
27
+
28
+ &--size-large {
29
+ padding: (spacing.$m - 6.5px) (spacing.$m - $borderThickness); // NOTE: Subtracting border width and text size to get the right padding
30
+
31
+ &.visually-button--icon {
32
+ padding: (
33
+ spacing.$m - 1px
34
+ ); //NOTE: Hack to get the pixel size of only icon to be the same as other "large" buttons.
35
+ }
36
+ }
37
+
38
+ &--size-basic {
39
+ padding: (spacing.$s - 3.5px) (spacing.$m - $borderThickness); // NOTE: Subtracting border width and text size to get the right padding
40
+
41
+ &.visually-button--icon {
42
+ padding: spacing.$s;
43
+ }
44
+ }
45
+
46
+ &--size-small {
47
+ padding: (spacing.$xs - 3.5px) (spacing.$s - $borderThickness); // NOTE: Subtracting border width and text size to get the right padding
48
+
49
+ &.visually-button--icon {
50
+ padding: (
51
+ spacing.$xs - 1.5px
52
+ ); //NOTE: Hack to get the pixel size of only icon to be the same as other "small" buttons.
53
+ }
54
+ }
55
+
56
+ &--icon-text {
57
+ display: flex;
58
+ gap: spacing.$xs;
59
+ justify-content: center;
60
+ align-items: center;
61
+
62
+ &--stacked {
63
+ flex-direction: column;
64
+ }
65
+ }
66
+
67
+ &[disabled] {
68
+ background-color: light.$buttons-primary-disabled;
69
+ color: light.$on-buttons-on-primary-disabled;
70
+ border-color: light.$buttons-primary-disabled;
71
+ cursor: not-allowed;
72
+
73
+ &.visually-button--variant-tertiary-destructive,
74
+ &.visually-button--variant-fixed-tertiary,
75
+ &.visually-button--variant-tertiary {
76
+ background-color: transparent;
77
+ border-color: transparent;
78
+ text-decoration: none;
79
+ }
80
+
81
+ &:not(&.visually-button--icon) {
82
+ &.visually-button--variant-tertiary-destructive,
83
+ &.visually-button--variant-fixed-tertiary,
84
+ &.visually-button--variant-tertiary {
85
+ padding-right: 0;
86
+ padding-left: 0;
87
+ }
88
+ }
89
+ }
90
+
91
+ &--variant-complete,
92
+ &--variant-cta {
93
+ border-color: light.$buttons-cta-default;
94
+ background: light.$buttons-cta-default;
95
+ color: light.$on-buttons-on-cta-default;
96
+
97
+ @media (hover: hover) {
98
+ &:hover:not(&:disabled) {
99
+ background: light.$buttons-cta-hover;
100
+ border-color: light.$buttons-cta-hover;
101
+ }
102
+ }
103
+
104
+ &[disabled] {
105
+ &.visually-button--theme-spinner {
106
+ background: light.$buttons-cta-default;
107
+ color: transparent;
108
+
109
+ .visually-button__spinner {
110
+ display: flex;
111
+ border-color: light.$buttons-cta-default;
112
+ color: light.$on-buttons-on-cta-default;
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ &--variant-cta-secondary,
119
+ &--variant-secondary {
120
+ color: light.$buttons-secondary-default;
121
+ border-color: light.$buttons-secondary-default;
122
+
123
+ @media (hover: hover) {
124
+ &:hover:not(&:disabled) {
125
+ color: light.$buttons-secondary-default;
126
+ border-color: light.$buttons-secondary-default;
127
+ background-color: light.$buttons-secondary-hover;
128
+ }
129
+ }
130
+
131
+ &[disabled] {
132
+ &.visually-button--theme-spinner {
133
+ background: transparent;
134
+ color: transparent;
135
+ border-color: light.$buttons-secondary-default !important; //NOTE: This overrites the default "disabled" color for spinner style.
136
+
137
+ .visually-button__spinner {
138
+ display: flex;
139
+ color: light.$buttons-secondary-default;
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ &--variant-secondary-white,
146
+ &--variant-cta-secondary-white {
147
+ color: light.$buttons-clean-secondary-outline;
148
+ border-color: light.$buttons-clean-secondary-outline;
149
+
150
+ @media (hover: hover) {
151
+ &:hover:not(&:disabled) {
152
+ background: light.$buttons-clean-secondary-hover;
153
+ }
154
+ }
155
+
156
+ &[disabled] {
157
+ &.visually-button--theme-spinner {
158
+ background: transparent;
159
+ color: transparent;
160
+ border-color: light.$buttons-clean-secondary-outline;
161
+
162
+ .visually-button__spinner {
163
+ display: flex;
164
+ color: light.$buttons-clean-secondary-outline;
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ &--variant-secondary-destructive {
171
+ color: light.$buttons-destructive-outlined-default;
172
+ border-color: light.$buttons-destructive-outlined-default;
173
+
174
+ @media (hover: hover) {
175
+ &:hover:not(&:disabled) {
176
+ background: light.$buttons-destructive-outlined-hover;
177
+ }
178
+ }
179
+
180
+ &[disabled] {
181
+ &.visually-button--theme-spinner {
182
+ background: transparent;
183
+ color: transparent;
184
+ border-color: light.$buttons-destructive-outlined-default;
185
+
186
+ .visually-button__spinner {
187
+ display: flex;
188
+ color: light.$buttons-destructive-outlined-default;
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ &--variant-primary {
195
+ border-color: light.$buttons-primary-default;
196
+ background: light.$buttons-primary-default;
197
+ color: light.$on-buttons-on-primary-default;
198
+
199
+ @media (hover: hover) {
200
+ &:hover:not(&:disabled) {
201
+ border-color: light.$buttons-primary-hover;
202
+ background: light.$buttons-primary-hover;
203
+ }
204
+ }
205
+
206
+ &[disabled] {
207
+ &.visually-button--theme-spinner {
208
+ background: light.$buttons-primary-default;
209
+ color: transparent;
210
+
211
+ .visually-button__spinner {
212
+ display: flex;
213
+ color: light.$on-buttons-on-primary-default;
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ &--variant-primary-white {
220
+ border-color: light.$buttons-clean-default;
221
+ background: light.$buttons-clean-default;
222
+ color: light.$on-buttons-on-clean-default;
223
+
224
+ @media (hover: hover) {
225
+ &:hover:not(&:disabled) {
226
+ background: light.$buttons-clean-hover;
227
+ border-color: light.$buttons-clean-hover;
228
+ }
229
+ }
230
+
231
+ &[disabled] {
232
+ &.visually-button--theme-spinner {
233
+ background: light.$buttons-clean-default;
234
+ color: light.$on-buttons-on-clean-default;
235
+
236
+ .visually-button__spinner {
237
+ display: flex;
238
+ color: light.$on-buttons-on-clean-default;
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ &--variant-primary-destructive {
245
+ border-color: light.$buttons-destructive-default;
246
+ background: light.$buttons-destructive-default;
247
+ color: light.$on-buttons-on-destructive-default;
248
+
249
+ @media (hover: hover) {
250
+ &:hover:not(&:disabled) {
251
+ background: light.$buttons-destructive-hover;
252
+ border-color: light.$buttons-destructive-hover;
253
+ }
254
+ }
255
+
256
+ &[disabled] {
257
+ &.visually-button--theme-spinner {
258
+ background: light.$buttons-destructive-default;
259
+ color: light.$on-buttons-on-destructive-default;
260
+
261
+ .visually-button__spinner {
262
+ display: flex;
263
+ color: light.$on-buttons-on-destructive-default;
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ &--variant-tertiary-destructive,
270
+ &--variant-fixed-tertiary,
271
+ &--variant-tertiary {
272
+ border: none;
273
+ padding-right: 0;
274
+ padding-left: 0;
275
+ color: light.$on-buttons-on-link-default;
276
+
277
+ @media (hover: hover) {
278
+ &.visually-button--icon {
279
+ &:hover:not(&:disabled) {
280
+ background-color: light.$buttons-link-hover;
281
+ }
282
+ }
283
+
284
+ &:hover:not(&:disabled) {
285
+ color: light.$on-buttons-on-link-hover;
286
+ text-decoration: underline;
287
+ text-decoration-thickness: 2px;
288
+ }
289
+ }
290
+
291
+ &[disabled] {
292
+ &.visually-button--theme-spinner {
293
+ color: transparent;
294
+
295
+ .visually-button__spinner {
296
+ display: flex;
297
+ color: light.$on-buttons-on-link-default;
298
+ }
299
+ }
300
+ }
301
+ }
302
+
303
+ &--variant-tertiary-destructive {
304
+ color: light.$on-buttons-on-destructive-outlined-default;
305
+
306
+ @media (hover: hover) {
307
+ &.visually-button--icon {
308
+ &:hover:not(&:disabled) {
309
+ background-color: light.$buttons-destructive-outlined-hover;
310
+ color: light.$on-buttons-on-destructive-outlined-default;
311
+ }
312
+ }
313
+
314
+ &:hover:not(&:disabled) {
315
+ color: light.$buttons-destructive-hover;
316
+ text-decoration: underline;
317
+ text-decoration-thickness: 2px;
318
+ }
319
+ }
320
+
321
+ &[disabled] {
322
+ &.visually-button--theme-spinner {
323
+ .visually-button__spinner {
324
+ display: flex;
325
+ color: light.$on-buttons-on-destructive-outlined-default;
326
+ }
327
+ }
328
+ }
329
+ }
330
+
331
+ &--variant-fixed-tertiary {
332
+ color: light.$on-buttons-on-fixed-link-default;
333
+
334
+ @media (hover: hover) {
335
+ &.visually-button--icon {
336
+ &:hover:not(&:disabled) {
337
+ background-color: light.$buttons-fixed-link-hover;
338
+ }
339
+ }
340
+
341
+ &:hover:not(&:disabled) {
342
+ color: light.$on-buttons-on-fixed-link-hover;
343
+ text-decoration: underline;
344
+ text-decoration-thickness: 2px;
345
+ }
346
+ }
347
+
348
+ &[disabled] {
349
+ &.visually-button--theme-spinner {
350
+ .visually-button__spinner {
351
+ display: flex;
352
+ color: light.$on-buttons-on-fixed-link-default;
353
+ }
354
+ }
355
+ }
356
+ }
357
+
358
+ &--variant-waitlist {
359
+ border-color: light.$buttons-waiting-list-default;
360
+ background: light.$buttons-waiting-list-default;
361
+ color: light.$on-buttons-on-waiting-list-default;
362
+
363
+ @media (hover: hover) {
364
+ &:hover:not(&:disabled) {
365
+ background: light.$buttons-waiting-list-hover;
366
+ border-color: light.$buttons-waiting-list-hover;
367
+ }
368
+ }
369
+
370
+ &[disabled] {
371
+ &.visually-button--theme-spinner {
372
+ background: light.$buttons-waiting-list-default;
373
+ color: transparent;
374
+
375
+ .visually-button__spinner {
376
+ display: flex;
377
+ color: light.$on-buttons-on-waiting-list-default;
378
+ }
379
+ }
380
+ }
381
+ }
382
+
383
+ &--variant-waitlist-secondary {
384
+ background: transparent;
385
+ color: light.$on-buttons-on-waiting-list-outlined-default;
386
+ border-color: light.$buttons-waiting-list-outlined-default;
387
+
388
+ @media (hover: hover) {
389
+ &:hover:not(&:disabled) {
390
+ color: light.$on-buttons-on-waiting-list-outlined-hover;
391
+ border-color: light.$buttons-waiting-list-outlined-hover;
392
+ background-color: light.$buttons-waiting-list-outlined-hover;
393
+ }
394
+ }
395
+
396
+ &[disabled] {
397
+ &.visually-button--theme-spinner {
398
+ background: transparent;
399
+ color: transparent;
400
+ border-color: light.$buttons-waiting-list-outlined-default !important; //NOTE: This overrides the default "disabled" color for spinner style.
401
+
402
+ .visually-button__spinner {
403
+ display: flex;
404
+ color: light.$on-buttons-on-waiting-list-outlined-default;
405
+ }
406
+ }
407
+ }
408
+ }
409
+
410
+ &__icon {
411
+ display: block;
412
+ height: 19px;
413
+ width: 19px;
414
+ position: relative;
415
+
416
+ > * {
417
+ display: block;
418
+ left: 50%;
419
+ position: absolute;
420
+ top: 50%;
421
+ transform: translate(-50%, -50%);
422
+ }
423
+
424
+ &[disabled] {
425
+ &.visually-button--spinner {
426
+ .visually-button__spinner {
427
+ display: flex;
428
+ }
429
+ }
430
+ }
431
+ }
432
+
433
+ &__text {
434
+ text-align: center;
435
+ text-decoration: none;
436
+ }
437
+
438
+ &__spinner {
439
+ display: none;
440
+ justify-content: center;
441
+ position: absolute;
442
+ top: 50%;
443
+ left: 50%;
444
+ transform: translate(-50%, -50%);
445
+ height: 24px;
446
+ width: 24px;
447
+
448
+ @keyframes spin {
449
+ 0% {
450
+ transform: rotate(0deg);
451
+ }
452
+
453
+ 100% {
454
+ transform: rotate(360deg);
455
+ }
456
+ }
457
+
458
+ &-icon {
459
+ animation: spin 2s linear infinite;
460
+
461
+ z-index: 2;
462
+ height: 100%;
463
+ width: 100%;
464
+ position: sticky;
465
+ left: 0;
466
+ top: 50%;
467
+ margin: 0 auto;
468
+ }
469
+ }
470
+ }
@@ -0,0 +1,130 @@
1
+ import cn from 'classnames';
2
+ import * as React from 'react';
3
+
4
+ import Text from '../text';
5
+ import type { ValueOf } from '../indexed-access-type';
6
+ import { themes as textThemes, sizes as textSizes } from '../text/text.types';
7
+
8
+ import {
9
+ sizes,
10
+ themes,
11
+ variants,
12
+ VisuallyButton as Props,
13
+ } from './visually-button.types';
14
+
15
+ const italicMap: Partial<Record<ValueOf<typeof variants>, true>> = {
16
+ cta: true,
17
+ 'cta-secondary': true,
18
+ 'cta-secondary-white': true,
19
+ };
20
+
21
+ const sizeMap: Record<keyof typeof sizes, keyof typeof textSizes> = {
22
+ small: textSizes.small,
23
+ basic: textSizes.basic,
24
+ large: textSizes.large,
25
+ };
26
+
27
+ const themeMap: Record<ValueOf<typeof variants>, keyof typeof textThemes> = {
28
+ complete: textThemes.emphasis,
29
+ cta: textThemes.headline,
30
+ 'cta-secondary': textThemes.headline,
31
+ 'cta-secondary-white': textThemes.headline,
32
+ primary: textThemes.emphasis,
33
+ 'primary-destructive': textThemes.emphasis,
34
+ 'primary-white': textThemes.emphasis,
35
+ secondary: textThemes.emphasis,
36
+ 'secondary-white': textThemes.emphasis,
37
+ 'secondary-destructive': textThemes.emphasis,
38
+ tertiary: textThemes.emphasis,
39
+ 'tertiary-destructive': textThemes.emphasis,
40
+ 'fixed-tertiary': textThemes.emphasis,
41
+ waitlist: textThemes.emphasis,
42
+ 'waitlist-secondary': textThemes.emphasis,
43
+ };
44
+
45
+ const VisuallyButton: React.FunctionComponent<Props> & {
46
+ sizes: typeof sizes;
47
+ variants: typeof variants;
48
+ themes: typeof themes;
49
+ } = ({
50
+ ariaLabel,
51
+ className,
52
+ elementName = 'button',
53
+ leadingIcon,
54
+ trailingIcon,
55
+ hasStackedIcon,
56
+ size = sizes.basic,
57
+ testId,
58
+ text,
59
+ theme = themes.normal,
60
+ variant = variants.primary,
61
+ wide,
62
+ ...rest
63
+ }) =>
64
+ React.createElement(
65
+ elementName,
66
+ {
67
+ 'aria-label': leadingIcon && ariaLabel ? ariaLabel : undefined,
68
+ className: cn(
69
+ 'visually-button',
70
+ {
71
+ 'visually-button--icon': leadingIcon && !text,
72
+ 'visually-button--icon-text': (leadingIcon || trailingIcon) && text,
73
+ 'visually-button--icon-text--stacked': hasStackedIcon,
74
+ [`visually-button--size-${size}`]: size,
75
+ [`visually-button--variant-${variant}`]: variant,
76
+ 'visually-button--wide': wide,
77
+ [`visually-button--theme-${theme}`]: theme
78
+ ? themes[theme]
79
+ : undefined,
80
+ },
81
+ className,
82
+ ),
83
+ 'data-testid': testId,
84
+ ...rest,
85
+ },
86
+ <React.Fragment>
87
+ {leadingIcon ? (
88
+ <div className="visually-button__icon">{leadingIcon}</div>
89
+ ) : null}
90
+ <Text
91
+ className="visually-button__text"
92
+ italic={italicMap[variant]}
93
+ size={sizeMap[size]}
94
+ theme={themeMap[variant]}
95
+ elementName="span"
96
+ >
97
+ {text}
98
+ </Text>
99
+ {trailingIcon ? (
100
+ <div className="visually-button__icon">{trailingIcon}</div>
101
+ ) : null}
102
+ {theme === 'spinner' && (
103
+ <div className="visually-button__spinner">
104
+ <svg
105
+ className="visually-button__spinner-icon"
106
+ width="24px"
107
+ height="24px"
108
+ viewBox="0 0 24 24"
109
+ fill="none"
110
+ stroke="currentColor"
111
+ strokeWidth="2"
112
+ xmlns="http://www.w3.org/2000/svg"
113
+ >
114
+ <circle opacity="0.1" cx="12" cy="12" r="10" />
115
+ <path
116
+ d="M12 2C13.9778 2 15.9112 2.58649 17.5557 3.6853C19.2002 4.78412 20.4819 6.3459 21.2388 8.17316C21.9957 10.0004 22.1937 12.0111 21.8079 13.9509C21.422 15.8907 20.4696 17.6725 19.0711 19.0711C17.6725 20.4696 15.8907 21.422 13.9509 21.8079C12.0111 22.1937 10.0004 21.9957 8.17317 21.2388C6.3459 20.4819 4.78412 19.2002 3.6853 17.5557C2.58649 15.9112 2 13.9778 2 12"
117
+ strokeLinecap="round"
118
+ strokeLinejoin="round"
119
+ />
120
+ </svg>
121
+ </div>
122
+ )}
123
+ </React.Fragment>,
124
+ );
125
+
126
+ VisuallyButton.sizes = sizes;
127
+ VisuallyButton.variants = variants;
128
+ VisuallyButton.themes = themes;
129
+
130
+ export default VisuallyButton;