@sats-group/ui-lib 77.1.0 → 79.0.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.
- package/eslint.config.mjs +0 -3
- package/package.json +2 -2
- package/react/button/button.tsx +14 -10
- package/react/button/button.types.ts +2 -2
- package/react/components.ts +2 -0
- package/react/context-menu/context-menu.tsx +3 -2
- package/react/div-button/div-button.tsx +15 -0
- package/react/div-button/div-button.types.ts +3 -0
- package/react/div-button/index.ts +2 -0
- package/react/form-content/form-content.tsx +4 -3
- package/react/link-button/link-button.types.ts +2 -4
- package/react/message-field/message-field.tsx +3 -3
- package/react/modal/modal.tsx +2 -1
- package/react/search/search.scss +5 -0
- package/react/search/search.tsx +48 -36
- package/react/search/search.types.ts +8 -7
- package/react/types.ts +2 -0
- package/react/visually-button/maps.ts +36 -0
- package/react/visually-button/visually-button.scss +16 -20
- package/react/visually-button/visually-button.tsx +75 -94
- package/react/visually-button/visually-button.types.ts +22 -34
package/eslint.config.mjs
CHANGED
|
@@ -19,13 +19,10 @@ export default [
|
|
|
19
19
|
{
|
|
20
20
|
ignores: [
|
|
21
21
|
'**/.vscode',
|
|
22
|
-
'**/site/build',
|
|
23
22
|
'**/dist',
|
|
24
23
|
'**/docs',
|
|
25
24
|
'**/node_modules',
|
|
26
25
|
'**/test-results',
|
|
27
|
-
'**/site/pages/colors/dark-docs.tsx',
|
|
28
|
-
'**/site/pages/colors/light-docs.tsx',
|
|
29
26
|
],
|
|
30
27
|
},
|
|
31
28
|
...compat.extends(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sats-group/ui-lib",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "79.0.0",
|
|
4
4
|
"description": "SATS web user interface library",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^18 || ^20",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"author": "developer@sats.no",
|
|
25
25
|
"license": "UNLICENSED",
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@sats-group/icons": "^
|
|
27
|
+
"@sats-group/icons": "^10.0.0",
|
|
28
28
|
"classnames": "^2.5.1",
|
|
29
29
|
"react": "^18.0.0 || ^19.0.0",
|
|
30
30
|
"react-dom": "^18.0.0 || ^19.0.0"
|
package/react/button/button.tsx
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
import { Button as Props } from './button.types';
|
|
3
|
+
import type { Button as Props } from './button.types';
|
|
4
4
|
|
|
5
5
|
import VisuallyButton from '../visually-button';
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} = ({ type = 'button', ...rest }) => (
|
|
12
|
-
<VisuallyButton elementName="button" type={type} {...rest} />
|
|
7
|
+
const ButtonRef = React.forwardRef<HTMLButtonElement, Props>(
|
|
8
|
+
({ type = 'button', ...rest }, ref) => (
|
|
9
|
+
<VisuallyButton elementName="button" ref={ref} type={type} {...rest} />
|
|
10
|
+
),
|
|
13
11
|
);
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Button
|
|
13
|
+
ButtonRef.displayName = 'Button';
|
|
14
|
+
|
|
15
|
+
const Button: typeof ButtonRef & {
|
|
16
|
+
sizes: typeof VisuallyButton.sizes;
|
|
17
|
+
variants: typeof VisuallyButton.variants;
|
|
18
|
+
} = Object.assign(ButtonRef, {
|
|
19
|
+
sizes: VisuallyButton.sizes,
|
|
20
|
+
variants: VisuallyButton.variants,
|
|
21
|
+
});
|
|
18
22
|
|
|
19
23
|
export default Button;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { VisuallyButtonButton } from '../visually-button/visually-button.types';
|
|
1
|
+
import type { VisuallyButtonButton } from '../visually-button/visually-button.types';
|
|
2
2
|
|
|
3
|
-
export type Button = VisuallyButtonButton
|
|
3
|
+
export type Button = Omit<VisuallyButtonButton, 'elementName'>;
|
package/react/components.ts
CHANGED
|
@@ -10,6 +10,7 @@ export const names = [
|
|
|
10
10
|
'confirmation',
|
|
11
11
|
'context-menu',
|
|
12
12
|
'cropped-image',
|
|
13
|
+
'div-button',
|
|
13
14
|
'dropdown-list',
|
|
14
15
|
'expander',
|
|
15
16
|
'filter',
|
|
@@ -51,6 +52,7 @@ export const selectors = [
|
|
|
51
52
|
'.confirmation',
|
|
52
53
|
'.context-menu',
|
|
53
54
|
'.cropped-image',
|
|
55
|
+
'.div-button',
|
|
54
56
|
'.dropdown-list',
|
|
55
57
|
'.expander',
|
|
56
58
|
'.filter',
|
|
@@ -90,10 +90,11 @@ const ContextMenu: React.FunctionComponent<Props> & {
|
|
|
90
90
|
<div>
|
|
91
91
|
<Button
|
|
92
92
|
{...close}
|
|
93
|
-
|
|
94
|
-
size={Button.sizes.small}
|
|
93
|
+
iconOnly
|
|
95
94
|
leadingIcon={<Close />}
|
|
96
95
|
onClick={onClose}
|
|
96
|
+
size={Button.sizes.small}
|
|
97
|
+
variant={Button.variants.tertiary}
|
|
97
98
|
/>
|
|
98
99
|
</div>
|
|
99
100
|
</div>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import VisuallyButton from '../visually-button/visually-button';
|
|
4
|
+
|
|
5
|
+
import { DivButton as Props } from './div-button.types';
|
|
6
|
+
|
|
7
|
+
const DivButton: React.FunctionComponent<Props> & {
|
|
8
|
+
sizes: typeof VisuallyButton.sizes;
|
|
9
|
+
variants: typeof VisuallyButton.variants;
|
|
10
|
+
} = props => <VisuallyButton elementName="div" {...props} />;
|
|
11
|
+
|
|
12
|
+
DivButton.sizes = VisuallyButton.sizes;
|
|
13
|
+
DivButton.variants = VisuallyButton.variants;
|
|
14
|
+
|
|
15
|
+
export default DivButton;
|
|
@@ -57,11 +57,12 @@ const FormContent: React.FC<Props> = ({
|
|
|
57
57
|
</Text>
|
|
58
58
|
<div className="form-content__modal-close-small">
|
|
59
59
|
<Button
|
|
60
|
-
|
|
60
|
+
iconOnly
|
|
61
|
+
leadingIcon={<Close />}
|
|
61
62
|
onClick={close}
|
|
62
63
|
size={Button.sizes.small}
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
text={closeLabel}
|
|
65
|
+
variant={Button.variants.secondary}
|
|
65
66
|
/>
|
|
66
67
|
</div>
|
|
67
68
|
</div>
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { VisuallyButtonLink } from '../visually-button/visually-button.types';
|
|
1
|
+
import type { VisuallyButtonLink } from '../visually-button/visually-button.types';
|
|
2
2
|
|
|
3
|
-
export type LinkButton = VisuallyButtonLink
|
|
4
|
-
href: string;
|
|
5
|
-
};
|
|
3
|
+
export type LinkButton = Omit<VisuallyButtonLink, 'elementName'>;
|
|
@@ -53,12 +53,12 @@ const MessageField: React.FunctionComponent<Props> & {
|
|
|
53
53
|
<div>
|
|
54
54
|
<Button
|
|
55
55
|
{...send}
|
|
56
|
-
leadingIcon={<Send />}
|
|
57
56
|
disabled={!hasTyped || isSubmitting}
|
|
58
|
-
|
|
59
|
-
variant={Button.variants.complete}
|
|
57
|
+
leadingIcon={<Send />}
|
|
60
58
|
onClick={onClickFunc}
|
|
61
59
|
size={size === sizes.small ? Button.sizes.small : Button.sizes.basic}
|
|
60
|
+
spinning={isSubmitting}
|
|
61
|
+
variant={Button.variants.complete}
|
|
62
62
|
/>
|
|
63
63
|
</div>
|
|
64
64
|
</div>
|
package/react/modal/modal.tsx
CHANGED
|
@@ -61,11 +61,12 @@ const Modal: React.FC<React.PropsWithChildren<Props>> & {
|
|
|
61
61
|
})}
|
|
62
62
|
>
|
|
63
63
|
<Button
|
|
64
|
-
ariaLabel={closeLabel}
|
|
65
64
|
leadingIcon={<Close />}
|
|
66
65
|
onClick={onClose}
|
|
67
66
|
size={Button.sizes.small}
|
|
67
|
+
text={closeLabel}
|
|
68
68
|
variant={Button.variants.secondary}
|
|
69
|
+
iconOnly
|
|
69
70
|
/>
|
|
70
71
|
</div>
|
|
71
72
|
{title || explanation ? (
|
package/react/search/search.scss
CHANGED
package/react/search/search.tsx
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
1
2
|
import React, { useRef, useState } from 'react';
|
|
2
3
|
|
|
3
|
-
import
|
|
4
|
-
import SearchIcon from '../icons/24/search';
|
|
5
|
-
import CloseIcon from '../icons/18/close';
|
|
6
|
-
import useInputValidation from '../use-input-validation';
|
|
4
|
+
import Button from '../button';
|
|
7
5
|
import useClickOutside from '../hooks/use-click-outside';
|
|
8
6
|
import { useIsMounted } from '../hooks/use-is-mounted';
|
|
7
|
+
import CloseIcon from '../icons/18/close';
|
|
8
|
+
import SearchIcon from '../icons/24/search';
|
|
9
|
+
import Text from '../text';
|
|
10
|
+
import useInputValidation from '../use-input-validation';
|
|
9
11
|
|
|
10
12
|
import {
|
|
11
13
|
Search as Props,
|
|
@@ -14,8 +16,6 @@ import {
|
|
|
14
16
|
variants,
|
|
15
17
|
expandDirections,
|
|
16
18
|
} from './search.types';
|
|
17
|
-
import classNames from 'classnames';
|
|
18
|
-
import Button from '../button';
|
|
19
19
|
|
|
20
20
|
const RefSearch = React.forwardRef<HTMLInputElement, Props>(
|
|
21
21
|
(
|
|
@@ -28,6 +28,7 @@ const RefSearch = React.forwardRef<HTMLInputElement, Props>(
|
|
|
28
28
|
isLabelVisible = true,
|
|
29
29
|
name,
|
|
30
30
|
onChangeFunc = () => {},
|
|
31
|
+
onClear = () => {},
|
|
31
32
|
expandable,
|
|
32
33
|
placeholder,
|
|
33
34
|
required,
|
|
@@ -41,7 +42,7 @@ const RefSearch = React.forwardRef<HTMLInputElement, Props>(
|
|
|
41
42
|
},
|
|
42
43
|
ref,
|
|
43
44
|
) => {
|
|
44
|
-
const [validationOnChange,
|
|
45
|
+
const [validationOnChange, handleInvalid, error] =
|
|
45
46
|
useInputValidation(customErrorMessages);
|
|
46
47
|
const searchArea = useRef<HTMLDivElement>(null);
|
|
47
48
|
const [expand, setExpand] = useState(false);
|
|
@@ -51,7 +52,25 @@ const RefSearch = React.forwardRef<HTMLInputElement, Props>(
|
|
|
51
52
|
|
|
52
53
|
const [inputValue, setInputValue] = useState(value);
|
|
53
54
|
|
|
54
|
-
const
|
|
55
|
+
const setValue = (value: string) => {
|
|
56
|
+
onChangeFunc(value);
|
|
57
|
+
setInputValue(value);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const handleChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
|
61
|
+
setValue(e.target.value);
|
|
62
|
+
validationOnChange(e);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const handleClear = () => {
|
|
66
|
+
setValue('');
|
|
67
|
+
onClear();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleClose = () => {
|
|
71
|
+
handleClear();
|
|
72
|
+
setExpand(false);
|
|
73
|
+
};
|
|
55
74
|
|
|
56
75
|
return (
|
|
57
76
|
<div
|
|
@@ -106,14 +125,10 @@ const RefSearch = React.forwardRef<HTMLInputElement, Props>(
|
|
|
106
125
|
})}
|
|
107
126
|
disabled={disabled}
|
|
108
127
|
name={name}
|
|
109
|
-
onChange={
|
|
110
|
-
onChangeFunc(e.target.value);
|
|
111
|
-
validationOnChange(e);
|
|
112
|
-
onMessageChange(e.target.value);
|
|
113
|
-
}}
|
|
128
|
+
onChange={handleChange}
|
|
114
129
|
onClick={() => setExpand(true)}
|
|
115
130
|
value={inputValue}
|
|
116
|
-
onInvalid={
|
|
131
|
+
onInvalid={handleInvalid}
|
|
117
132
|
placeholder={placeholder}
|
|
118
133
|
ref={ref}
|
|
119
134
|
required={required}
|
|
@@ -125,27 +140,27 @@ const RefSearch = React.forwardRef<HTMLInputElement, Props>(
|
|
|
125
140
|
}
|
|
126
141
|
/>
|
|
127
142
|
</label>
|
|
128
|
-
|
|
143
|
+
<div
|
|
144
|
+
className={classNames('search__clear-button', {
|
|
145
|
+
'search__clear-button--visible': inputValue,
|
|
146
|
+
})}
|
|
147
|
+
>
|
|
129
148
|
<Button
|
|
130
149
|
{...clear}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
150
|
+
onClick={handleClear}
|
|
151
|
+
size={
|
|
152
|
+
inputSize === inputSizes.small
|
|
153
|
+
? Button.sizes.small
|
|
154
|
+
: Button.sizes.basic
|
|
155
|
+
}
|
|
136
156
|
type="button"
|
|
137
157
|
variant={
|
|
138
158
|
variant === variants.fixed
|
|
139
159
|
? Button.variants.fixedTertiary
|
|
140
160
|
: Button.variants.tertiary
|
|
141
161
|
}
|
|
142
|
-
size={
|
|
143
|
-
inputSize === inputSizes.small
|
|
144
|
-
? Button.sizes.small
|
|
145
|
-
: Button.sizes.basic
|
|
146
|
-
}
|
|
147
162
|
/>
|
|
148
|
-
|
|
163
|
+
</div>
|
|
149
164
|
{button ? (
|
|
150
165
|
<Button
|
|
151
166
|
{...button}
|
|
@@ -167,22 +182,19 @@ const RefSearch = React.forwardRef<HTMLInputElement, Props>(
|
|
|
167
182
|
<Button
|
|
168
183
|
{...expandable.close}
|
|
169
184
|
className="search__close-button"
|
|
170
|
-
|
|
171
|
-
variant === variants.fixed
|
|
172
|
-
? Button.variants.fixedTertiary
|
|
173
|
-
: Button.variants.tertiary
|
|
174
|
-
}
|
|
185
|
+
onClick={handleClose}
|
|
175
186
|
leadingIcon={<CloseIcon />}
|
|
176
|
-
onClick={() => {
|
|
177
|
-
setExpand(false);
|
|
178
|
-
onChangeFunc('');
|
|
179
|
-
setInputValue('');
|
|
180
|
-
}}
|
|
181
187
|
size={
|
|
182
188
|
inputSize === inputSizes.small
|
|
183
189
|
? Button.sizes.small
|
|
184
190
|
: Button.sizes.basic
|
|
185
191
|
}
|
|
192
|
+
type="button"
|
|
193
|
+
variant={
|
|
194
|
+
variant === variants.fixed
|
|
195
|
+
? Button.variants.fixedTertiary
|
|
196
|
+
: Button.variants.tertiary
|
|
197
|
+
}
|
|
186
198
|
/>
|
|
187
199
|
) : null}
|
|
188
200
|
</div>
|
|
@@ -27,17 +27,18 @@ export type Search = {
|
|
|
27
27
|
button?: Button;
|
|
28
28
|
clear: Button;
|
|
29
29
|
customErrorMessages?: Messages;
|
|
30
|
-
inputSize?: ObjectValues<typeof inputSizes>; //NOTE: This is not named `size` due to InputHTMLProps having a `size` attribute that will clash
|
|
31
|
-
label: string;
|
|
32
|
-
isLabelVisible?: boolean;
|
|
33
|
-
name: string;
|
|
34
|
-
onChangeFunc?: (arg: string) => void;
|
|
35
30
|
expandable?: {
|
|
36
31
|
close: Button;
|
|
37
32
|
direction?: ObjectValues<typeof expandDirections>;
|
|
38
33
|
};
|
|
34
|
+
helpIcon?: ReactElement;
|
|
35
|
+
helpText?: string;
|
|
36
|
+
inputSize?: ObjectValues<typeof inputSizes>; //NOTE: This is not named `size` due to InputHTMLProps having a `size` attribute that will clash
|
|
37
|
+
isLabelVisible?: boolean;
|
|
38
|
+
label: string;
|
|
39
|
+
name: string;
|
|
40
|
+
onChangeFunc?: (arg: string) => void;
|
|
41
|
+
onClear?: () => void;
|
|
39
42
|
theme?: ObjectValues<typeof themes>;
|
|
40
43
|
variant?: ObjectValues<typeof variants>;
|
|
41
|
-
helpText?: string;
|
|
42
|
-
helpIcon?: ReactElement;
|
|
43
44
|
} & InputHtmlProps;
|
package/react/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AnchorHTMLAttributes,
|
|
3
3
|
ButtonHTMLAttributes,
|
|
4
|
+
HTMLAttributes,
|
|
4
5
|
ImgHTMLAttributes,
|
|
5
6
|
InputHTMLAttributes,
|
|
6
7
|
OptionHTMLAttributes,
|
|
@@ -29,6 +30,7 @@ export type ObjectValues<T> = Flatten<T[keyof T]>;
|
|
|
29
30
|
|
|
30
31
|
// NOTE: The below types include all of the supported props for "html" react components (like `button` or `a`).
|
|
31
32
|
export type ButtonHtmlProps = ButtonHTMLAttributes<HTMLButtonElement>;
|
|
33
|
+
export type DivHtmlProps = HTMLAttributes<HTMLDivElement>;
|
|
32
34
|
export type LinkHtmlProps = AnchorHTMLAttributes<HTMLAnchorElement>;
|
|
33
35
|
export type InputHtmlProps = InputHTMLAttributes<HTMLInputElement>;
|
|
34
36
|
export type ImageHtmlProps = ImgHTMLAttributes<HTMLImageElement>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ValueOf } from '../indexed-access-type';
|
|
2
|
+
import { sizes as textSizes, themes as textThemes } from '../text/text.types';
|
|
3
|
+
import { variants, sizes } from './visually-button.types';
|
|
4
|
+
|
|
5
|
+
export const italicMap: Partial<Record<ValueOf<typeof variants>, true>> = {
|
|
6
|
+
cta: true,
|
|
7
|
+
'cta-secondary': true,
|
|
8
|
+
'cta-secondary-white': true,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const sizeMap: Record<keyof typeof sizes, keyof typeof textSizes> = {
|
|
12
|
+
small: textSizes.small,
|
|
13
|
+
basic: textSizes.basic,
|
|
14
|
+
large: textSizes.large,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const themeMap: Record<
|
|
18
|
+
ValueOf<typeof variants>,
|
|
19
|
+
keyof typeof textThemes
|
|
20
|
+
> = {
|
|
21
|
+
complete: textThemes.emphasis,
|
|
22
|
+
cta: textThemes.headline,
|
|
23
|
+
'cta-secondary': textThemes.headline,
|
|
24
|
+
'cta-secondary-white': textThemes.headline,
|
|
25
|
+
primary: textThemes.emphasis,
|
|
26
|
+
'primary-destructive': textThemes.emphasis,
|
|
27
|
+
'primary-white': textThemes.emphasis,
|
|
28
|
+
secondary: textThemes.emphasis,
|
|
29
|
+
'secondary-white': textThemes.emphasis,
|
|
30
|
+
'secondary-destructive': textThemes.emphasis,
|
|
31
|
+
tertiary: textThemes.emphasis,
|
|
32
|
+
'tertiary-destructive': textThemes.emphasis,
|
|
33
|
+
'fixed-tertiary': textThemes.emphasis,
|
|
34
|
+
waitlist: textThemes.emphasis,
|
|
35
|
+
'waitlist-secondary': textThemes.emphasis,
|
|
36
|
+
};
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
&[disabled] {
|
|
105
|
-
&.visually-button--
|
|
105
|
+
&.visually-button--spinning {
|
|
106
106
|
background: light.$buttons-cta-default;
|
|
107
107
|
color: transparent;
|
|
108
108
|
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
&[disabled] {
|
|
132
|
-
&.visually-button--
|
|
132
|
+
&.visually-button--spinning {
|
|
133
133
|
background: transparent;
|
|
134
134
|
color: transparent;
|
|
135
135
|
border-color: light.$buttons-secondary-default !important; //NOTE: This overrites the default "disabled" color for spinner style.
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
&[disabled] {
|
|
157
|
-
&.visually-button--
|
|
157
|
+
&.visually-button--spinning {
|
|
158
158
|
background: transparent;
|
|
159
159
|
color: transparent;
|
|
160
160
|
border-color: light.$buttons-clean-secondary-outline;
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
&[disabled] {
|
|
181
|
-
&.visually-button--
|
|
181
|
+
&.visually-button--spinning {
|
|
182
182
|
background: transparent;
|
|
183
183
|
color: transparent;
|
|
184
184
|
border-color: light.$buttons-destructive-outlined-default;
|
|
@@ -204,7 +204,7 @@
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
&[disabled] {
|
|
207
|
-
&.visually-button--
|
|
207
|
+
&.visually-button--spinning {
|
|
208
208
|
background: light.$buttons-primary-default;
|
|
209
209
|
color: transparent;
|
|
210
210
|
|
|
@@ -229,7 +229,7 @@
|
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
&[disabled] {
|
|
232
|
-
&.visually-button--
|
|
232
|
+
&.visually-button--spinning {
|
|
233
233
|
background: light.$buttons-clean-default;
|
|
234
234
|
color: light.$on-buttons-on-clean-default;
|
|
235
235
|
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
&[disabled] {
|
|
257
|
-
&.visually-button--
|
|
257
|
+
&.visually-button--spinning {
|
|
258
258
|
background: light.$buttons-destructive-default;
|
|
259
259
|
color: light.$on-buttons-on-destructive-default;
|
|
260
260
|
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
&[disabled] {
|
|
292
|
-
&.visually-button--
|
|
292
|
+
&.visually-button--spinning {
|
|
293
293
|
color: transparent;
|
|
294
294
|
|
|
295
295
|
.visually-button__spinner {
|
|
@@ -319,7 +319,7 @@
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
&[disabled] {
|
|
322
|
-
&.visually-button--
|
|
322
|
+
&.visually-button--spinning {
|
|
323
323
|
.visually-button__spinner {
|
|
324
324
|
display: flex;
|
|
325
325
|
color: light.$on-buttons-on-destructive-outlined-default;
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
&[disabled] {
|
|
349
|
-
&.visually-button--
|
|
349
|
+
&.visually-button--spinning {
|
|
350
350
|
.visually-button__spinner {
|
|
351
351
|
display: flex;
|
|
352
352
|
color: light.$on-buttons-on-fixed-link-default;
|
|
@@ -368,7 +368,7 @@
|
|
|
368
368
|
}
|
|
369
369
|
|
|
370
370
|
&[disabled] {
|
|
371
|
-
&.visually-button--
|
|
371
|
+
&.visually-button--spinning {
|
|
372
372
|
background: light.$buttons-waiting-list-default;
|
|
373
373
|
color: transparent;
|
|
374
374
|
|
|
@@ -394,7 +394,7 @@
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
&[disabled] {
|
|
397
|
-
&.visually-button--
|
|
397
|
+
&.visually-button--spinning {
|
|
398
398
|
background: transparent;
|
|
399
399
|
color: transparent;
|
|
400
400
|
border-color: light.$buttons-waiting-list-outlined-default !important; //NOTE: This overrides the default "disabled" color for spinner style.
|
|
@@ -408,21 +408,17 @@
|
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
&__icon {
|
|
411
|
-
display:
|
|
411
|
+
display: grid;
|
|
412
412
|
height: 19px;
|
|
413
|
+
place-items: center;
|
|
413
414
|
width: 19px;
|
|
414
|
-
position: relative;
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
svg {
|
|
417
417
|
display: block;
|
|
418
|
-
left: 50%;
|
|
419
|
-
position: absolute;
|
|
420
|
-
top: 50%;
|
|
421
|
-
transform: translate(-50%, -50%);
|
|
422
418
|
}
|
|
423
419
|
|
|
424
420
|
&[disabled] {
|
|
425
|
-
&.visually-button--
|
|
421
|
+
&.visually-button--spinning {
|
|
426
422
|
.visually-button__spinner {
|
|
427
423
|
display: flex;
|
|
428
424
|
}
|
|
@@ -2,104 +2,78 @@ import cn from 'classnames';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
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
5
|
|
|
6
|
+
import { italicMap, sizeMap, themeMap } from './maps';
|
|
8
7
|
import {
|
|
9
8
|
sizes,
|
|
10
|
-
themes,
|
|
11
9
|
variants,
|
|
12
|
-
VisuallyButton as Props,
|
|
10
|
+
type VisuallyButton as Props,
|
|
13
11
|
} from './visually-button.types';
|
|
14
12
|
|
|
15
|
-
const
|
|
16
|
-
|
|
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,
|
|
13
|
+
const VisuallyButtonRef = React.forwardRef<HTMLElement, Props>(
|
|
14
|
+
(
|
|
66
15
|
{
|
|
67
|
-
|
|
68
|
-
className
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
},
|
|
81
|
-
className,
|
|
82
|
-
),
|
|
83
|
-
'data-testid': testId,
|
|
84
|
-
...rest,
|
|
16
|
+
iconOnly,
|
|
17
|
+
className,
|
|
18
|
+
elementName,
|
|
19
|
+
leadingIcon,
|
|
20
|
+
trailingIcon,
|
|
21
|
+
hasStackedIcon,
|
|
22
|
+
size = sizes.basic,
|
|
23
|
+
spinning,
|
|
24
|
+
testId,
|
|
25
|
+
text,
|
|
26
|
+
variant = variants.primary,
|
|
27
|
+
wide,
|
|
28
|
+
...rest
|
|
85
29
|
},
|
|
86
|
-
|
|
87
|
-
|
|
30
|
+
ref,
|
|
31
|
+
) => {
|
|
32
|
+
const hasIcon = Boolean(leadingIcon || trailingIcon);
|
|
33
|
+
const renderIconOnly = Boolean(iconOnly && hasIcon);
|
|
34
|
+
const role = renderIconOnly && elementName === 'div' ? 'img' : undefined;
|
|
35
|
+
|
|
36
|
+
return React.createElement(
|
|
37
|
+
elementName,
|
|
38
|
+
{
|
|
39
|
+
'aria-label': renderIconOnly ? text : undefined,
|
|
40
|
+
className: cn(
|
|
41
|
+
'visually-button',
|
|
42
|
+
{
|
|
43
|
+
'visually-button--icon': renderIconOnly,
|
|
44
|
+
'visually-button--icon-text':
|
|
45
|
+
(leadingIcon || trailingIcon) && !iconOnly,
|
|
46
|
+
'visually-button--icon-text--stacked': hasStackedIcon,
|
|
47
|
+
[`visually-button--size-${size}`]: size,
|
|
48
|
+
'visually-button--spinning': spinning,
|
|
49
|
+
[`visually-button--variant-${variant}`]: variant,
|
|
50
|
+
'visually-button--wide': wide,
|
|
51
|
+
},
|
|
52
|
+
className,
|
|
53
|
+
),
|
|
54
|
+
'data-testid': testId,
|
|
55
|
+
role,
|
|
56
|
+
...rest,
|
|
57
|
+
ref,
|
|
58
|
+
},
|
|
59
|
+
leadingIcon ? (
|
|
88
60
|
<div className="visually-button__icon">{leadingIcon}</div>
|
|
89
|
-
) : null
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
61
|
+
) : null,
|
|
62
|
+
renderIconOnly ? null : (
|
|
63
|
+
<Text
|
|
64
|
+
className="visually-button__text"
|
|
65
|
+
italic={italicMap[variant]}
|
|
66
|
+
size={sizeMap[size]}
|
|
67
|
+
theme={themeMap[variant]}
|
|
68
|
+
elementName="span"
|
|
69
|
+
>
|
|
70
|
+
{text}
|
|
71
|
+
</Text>
|
|
72
|
+
),
|
|
73
|
+
trailingIcon ? (
|
|
100
74
|
<div className="visually-button__icon">{trailingIcon}</div>
|
|
101
|
-
) : null
|
|
102
|
-
|
|
75
|
+
) : null,
|
|
76
|
+
spinning ? (
|
|
103
77
|
<div className="visually-button__spinner">
|
|
104
78
|
<svg
|
|
105
79
|
className="visually-button__spinner-icon"
|
|
@@ -119,12 +93,19 @@ const VisuallyButton: React.FunctionComponent<Props> & {
|
|
|
119
93
|
/>
|
|
120
94
|
</svg>
|
|
121
95
|
</div>
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
|
|
96
|
+
) : null,
|
|
97
|
+
);
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
VisuallyButtonRef.displayName = 'VisuallyButton';
|
|
125
102
|
|
|
126
|
-
VisuallyButton
|
|
127
|
-
|
|
128
|
-
|
|
103
|
+
const VisuallyButton: typeof VisuallyButtonRef & {
|
|
104
|
+
sizes: typeof sizes;
|
|
105
|
+
variants: typeof variants;
|
|
106
|
+
} = Object.assign(VisuallyButtonRef, {
|
|
107
|
+
sizes,
|
|
108
|
+
variants,
|
|
109
|
+
});
|
|
129
110
|
|
|
130
111
|
export default VisuallyButton;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { ReactElement } from 'react';
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type {
|
|
4
|
+
ButtonHtmlProps,
|
|
5
|
+
DivHtmlProps,
|
|
6
|
+
LinkHtmlProps,
|
|
7
|
+
ObjectValues,
|
|
8
|
+
} from '../types';
|
|
4
9
|
|
|
5
10
|
export const sizes = {
|
|
6
11
|
small: 'small',
|
|
@@ -26,46 +31,29 @@ export const variants = {
|
|
|
26
31
|
waitlistSecondary: 'waitlist-secondary',
|
|
27
32
|
} as const;
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
spinner: 'spinner',
|
|
31
|
-
normal: 'normal',
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
type TextProp = {
|
|
35
|
-
text: string;
|
|
36
|
-
ariaLabel?: never;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type AriaLabelProp = {
|
|
40
|
-
ariaLabel: string;
|
|
41
|
-
text?: never;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
type BasicProps = {
|
|
34
|
+
type CommonProps<ElementName extends string> = {
|
|
45
35
|
className?: string;
|
|
46
|
-
elementName
|
|
47
|
-
leadingIcon?: ReactElement;
|
|
48
|
-
trailingIcon?: ReactElement;
|
|
36
|
+
elementName: ElementName;
|
|
49
37
|
hasStackedIcon?: boolean;
|
|
38
|
+
iconOnly?: boolean;
|
|
39
|
+
leadingIcon?: ReactElement;
|
|
50
40
|
size?: ObjectValues<typeof sizes>;
|
|
41
|
+
spinning?: boolean;
|
|
51
42
|
testId?: string;
|
|
52
|
-
|
|
43
|
+
text: string;
|
|
44
|
+
trailingIcon?: ReactElement;
|
|
53
45
|
variant?: ObjectValues<typeof variants>;
|
|
54
46
|
wide?: boolean;
|
|
55
47
|
};
|
|
56
48
|
|
|
57
|
-
type
|
|
49
|
+
type Props<ElementProps, ElementName extends string> = ElementProps &
|
|
50
|
+
CommonProps<ElementName>;
|
|
58
51
|
|
|
59
|
-
export type VisuallyButtonButton =
|
|
60
|
-
|
|
61
|
-
export type VisuallyButtonLink =
|
|
62
|
-
|
|
63
|
-
// NOTE: If the element isn't `a` or `button` we don't restrict the type of the rest props
|
|
64
|
-
type VisuallyButtonOther = CommonProps & {
|
|
65
|
-
[key: string]: unknown;
|
|
66
|
-
};
|
|
52
|
+
export type VisuallyButtonButton = Props<ButtonHtmlProps, 'button'>;
|
|
53
|
+
export type VisuallyButtonDiv = Props<DivHtmlProps, 'div'>;
|
|
54
|
+
export type VisuallyButtonLink = Props<LinkHtmlProps, 'a'> & { href: string };
|
|
67
55
|
|
|
68
56
|
export type VisuallyButton =
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
57
|
+
| VisuallyButtonButton
|
|
58
|
+
| VisuallyButtonDiv
|
|
59
|
+
| VisuallyButtonLink;
|