@bspk/ui 1.1.13 → 1.1.15
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/dist/Checkbox.d.ts +1 -1
- package/dist/Checkbox.js +2 -2
- package/dist/Checkbox.js.map +1 -1
- package/dist/CheckboxOption.d.ts +2 -1
- package/dist/Dropdown.d.ts +3 -3
- package/dist/Dropdown.js +2 -1
- package/dist/Dropdown.js.map +1 -1
- package/dist/FormField.d.ts +5 -4
- package/dist/FormField.js +3 -3
- package/dist/FormField.js.map +1 -1
- package/dist/InlineAlert.js +1 -1
- package/dist/Link.d.ts +2 -1
- package/dist/Link.js.map +1 -1
- package/dist/ListItem.d.ts +1 -1
- package/dist/ListItem.js +2 -2
- package/dist/ListItem.js.map +1 -1
- package/dist/NumberField.d.ts +2 -1
- package/dist/NumberInput.d.ts +23 -13
- package/dist/NumberInput.js +15 -4
- package/dist/NumberInput.js.map +1 -1
- package/dist/Radio.d.ts +2 -2
- package/dist/Radio.js.map +1 -1
- package/dist/RadioOption.d.ts +2 -1
- package/dist/RadioOption.js.map +1 -1
- package/dist/SegmentedControl.d.ts +11 -7
- package/dist/SegmentedControl.js +7 -6
- package/dist/SegmentedControl.js.map +1 -1
- package/dist/StylesProviderAnywhere.js +1 -1
- package/dist/StylesProviderBetterHomesGardens.js +1 -1
- package/dist/StylesProviderCartus.js +1 -1
- package/dist/StylesProviderCentury21.js +1 -1
- package/dist/StylesProviderColdwellBanker.js +1 -1
- package/dist/StylesProviderCorcoran.js +1 -1
- package/dist/StylesProviderDenaliBoss.js +1 -1
- package/dist/StylesProviderEra.js +1 -1
- package/dist/StylesProviderSothebys.js +1 -1
- package/dist/TabGroup.d.ts +15 -6
- package/dist/TabGroup.js +13 -5
- package/dist/TabGroup.js.map +1 -1
- package/dist/TextField.d.ts +2 -1
- package/dist/TextField.js.map +1 -1
- package/dist/TextInput.d.ts +3 -3
- package/dist/TextInput.js +2 -2
- package/dist/TextInput.js.map +1 -1
- package/dist/Textarea.d.ts +20 -10
- package/dist/Textarea.js +15 -8
- package/dist/Textarea.js.map +1 -1
- package/dist/base.css +1 -1
- package/dist/form-field.css +1 -1
- package/dist/hooks/useFloatingMenu.d.ts +3 -2
- package/dist/hooks/useFloatingMenu.js +1 -0
- package/dist/hooks/useFloatingMenu.js.map +1 -1
- package/dist/hooks/{useNavOptions.d.ts → useOptionIconsInvalid.d.ts} +4 -5
- package/dist/hooks/useOptionIconsInvalid.js +21 -0
- package/dist/hooks/useOptionIconsInvalid.js.map +1 -0
- package/dist/index.d.ts +22 -13
- package/dist/index.js.map +1 -1
- package/dist/inline-alert.css +1 -1
- package/dist/number-input.css +1 -1
- package/dist/textarea.css +1 -1
- package/package.json +2 -2
- package/src/Checkbox.tsx +3 -0
- package/src/CheckboxOption.tsx +4 -4
- package/src/Dropdown.tsx +5 -2
- package/src/FormField.tsx +35 -24
- package/src/Link.tsx +28 -25
- package/src/ListItem.tsx +2 -0
- package/src/NumberField.tsx +5 -5
- package/src/NumberInput.tsx +51 -30
- package/src/Radio.tsx +3 -5
- package/src/RadioOption.tsx +4 -1
- package/src/SegmentedControl.tsx +18 -13
- package/src/TabGroup.tsx +33 -12
- package/src/TextField.tsx +19 -17
- package/src/TextInput.tsx +33 -30
- package/src/Textarea.tsx +72 -56
- package/src/base.scss +4 -3
- package/src/form-field.scss +2 -1
- package/src/hooks/useFloatingMenu.ts +4 -2
- package/src/hooks/useOptionIconsInvalid.ts +49 -0
- package/src/index.ts +24 -13
- package/src/inline-alert.scss +1 -1
- package/src/number-input.scss +6 -3
- package/src/textarea.scss +5 -2
- package/dist/hooks/useNavOptions.js +0 -43
- package/dist/hooks/useNavOptions.js.map +0 -1
- package/src/hooks/useNavOptions.ts +0 -76
package/src/TabGroup.tsx
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import './tab-group.scss';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
|
|
4
|
-
import { Badge } from './Badge';
|
|
5
|
-
import {
|
|
4
|
+
import { Badge, BadgeProps } from './Badge';
|
|
5
|
+
import { useOptionIconsInvalid } from './hooks/useOptionIconsInvalid';
|
|
6
6
|
|
|
7
7
|
import { ElementProps } from './';
|
|
8
8
|
|
|
9
|
+
export type TabGroupSize = 'large' | 'medium' | 'small';
|
|
10
|
+
|
|
11
|
+
const TAB_BADGE_SIZES: Record<TabGroupSize, BadgeProps['size']> = {
|
|
12
|
+
large: 'small',
|
|
13
|
+
medium: 'x-small',
|
|
14
|
+
small: 'x-small',
|
|
15
|
+
};
|
|
16
|
+
|
|
9
17
|
export type TabGroupOption = {
|
|
10
18
|
/**
|
|
11
19
|
* The label of the tab. This is the text that will be displayed on the tab.
|
|
@@ -19,7 +27,11 @@ export type TabGroupOption = {
|
|
|
19
27
|
* @default false
|
|
20
28
|
*/
|
|
21
29
|
disabled?: boolean;
|
|
22
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* The value of the tab sent to onChange when selected.
|
|
32
|
+
*
|
|
33
|
+
* If not provided, the label will be used as the value.
|
|
34
|
+
*/
|
|
23
35
|
value?: string;
|
|
24
36
|
/** The icon to display on the left side of the tab. */
|
|
25
37
|
icon?: ReactNode;
|
|
@@ -37,20 +49,24 @@ export type TabGroupProps = {
|
|
|
37
49
|
* @required
|
|
38
50
|
*/
|
|
39
51
|
options: TabGroupOption[];
|
|
40
|
-
/**
|
|
41
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The id of the selected tab.
|
|
54
|
+
*
|
|
55
|
+
* @required
|
|
56
|
+
*/
|
|
57
|
+
value: TabGroupOption['value'];
|
|
42
58
|
/**
|
|
43
59
|
* The function to call when the tab is clicked.
|
|
44
60
|
*
|
|
45
61
|
* @required
|
|
46
62
|
*/
|
|
47
|
-
onChange: (
|
|
63
|
+
onChange: (tabValue: string, index: number) => void;
|
|
48
64
|
/**
|
|
49
65
|
* The size of the tabs.
|
|
50
66
|
*
|
|
51
67
|
* @default medium
|
|
52
68
|
*/
|
|
53
|
-
size?:
|
|
69
|
+
size?: TabGroupSize;
|
|
54
70
|
/**
|
|
55
71
|
* When 'fill' the options will fill the width of the container. When 'hug', the options will be as wide as their
|
|
56
72
|
* content.
|
|
@@ -77,12 +93,13 @@ function TabGroup({
|
|
|
77
93
|
onChange: onTabChange,
|
|
78
94
|
value,
|
|
79
95
|
size = 'medium',
|
|
80
|
-
options,
|
|
96
|
+
options: optionsProp,
|
|
81
97
|
width = 'hug',
|
|
82
98
|
showTrail = false,
|
|
83
99
|
...containerProps
|
|
84
100
|
}: ElementProps<TabGroupProps, 'div'>) {
|
|
85
|
-
const
|
|
101
|
+
const options = Array.isArray(optionsProp) ? optionsProp : [];
|
|
102
|
+
useOptionIconsInvalid(options);
|
|
86
103
|
|
|
87
104
|
return (
|
|
88
105
|
<div
|
|
@@ -92,7 +109,7 @@ function TabGroup({
|
|
|
92
109
|
data-size={size}
|
|
93
110
|
data-width={width}
|
|
94
111
|
>
|
|
95
|
-
{
|
|
112
|
+
{options.map((item, itemIndex) => {
|
|
96
113
|
const isActive = item.value === value;
|
|
97
114
|
|
|
98
115
|
return (
|
|
@@ -100,12 +117,16 @@ function TabGroup({
|
|
|
100
117
|
data-active={isActive || undefined}
|
|
101
118
|
disabled={item.disabled || undefined}
|
|
102
119
|
key={item.value}
|
|
103
|
-
onClick={() =>
|
|
120
|
+
onClick={() => {
|
|
121
|
+
onTabChange(item.value || item.label, itemIndex);
|
|
122
|
+
}}
|
|
104
123
|
>
|
|
105
124
|
<span>
|
|
106
125
|
{(isActive && item.iconActive) || item.icon}
|
|
107
126
|
{item.label}
|
|
108
|
-
{item.badge &&
|
|
127
|
+
{item.badge && !item.disabled && !isActive && (
|
|
128
|
+
<Badge count={item.badge} size={TAB_BADGE_SIZES[size]} />
|
|
129
|
+
)}
|
|
109
130
|
</span>
|
|
110
131
|
</button>
|
|
111
132
|
);
|
package/src/TextField.tsx
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { FormFieldProps, FormField } from './FormField';
|
|
2
2
|
import { TextInputProps, TextInput } from './TextInput';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
4
|
+
import { InvalidPropsLibrary } from '.';
|
|
5
|
+
|
|
6
|
+
export type TextFieldProps = InvalidPropsLibrary &
|
|
7
|
+
Pick<
|
|
8
|
+
TextInputProps,
|
|
9
|
+
| 'autoComplete'
|
|
10
|
+
| 'disabled'
|
|
11
|
+
| 'inputRef'
|
|
12
|
+
| 'leading'
|
|
13
|
+
| 'name'
|
|
14
|
+
| 'onChange'
|
|
15
|
+
| 'placeholder'
|
|
16
|
+
| 'readOnly'
|
|
17
|
+
| 'required'
|
|
18
|
+
| 'size'
|
|
19
|
+
| 'trailing'
|
|
20
|
+
| 'type'
|
|
21
|
+
| 'value'
|
|
22
|
+
> &
|
|
21
23
|
Pick<FormFieldProps, 'controlId' | 'errorMessage' | 'helperText' | 'label' | 'labelTrailing'>;
|
|
22
24
|
|
|
23
25
|
/**
|
package/src/TextInput.tsx
CHANGED
|
@@ -4,38 +4,39 @@ import { ChangeEvent, HTMLInputAutoCompleteAttribute, HTMLInputTypeAttribute, Re
|
|
|
4
4
|
|
|
5
5
|
import { useId } from './hooks/useId';
|
|
6
6
|
|
|
7
|
-
import { ElementProps, CommonProps } from '.';
|
|
7
|
+
import { ElementProps, CommonProps, InvalidPropsLibrary } from '.';
|
|
8
8
|
|
|
9
9
|
export type TextInputProps = CommonProps<
|
|
10
|
-
'aria-label' | 'disabled' | 'id' | '
|
|
11
|
-
> &
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
10
|
+
'aria-label' | 'disabled' | 'id' | 'name' | 'readOnly' | 'required' | 'size' | 'value'
|
|
11
|
+
> &
|
|
12
|
+
InvalidPropsLibrary & {
|
|
13
|
+
/**
|
|
14
|
+
* Callback when the value of the field changes.
|
|
15
|
+
*
|
|
16
|
+
* @type (next: String, Event) => void
|
|
17
|
+
* @required
|
|
18
|
+
*/
|
|
19
|
+
onChange: (next: string, event?: ChangeEvent<HTMLInputElement>) => void;
|
|
20
|
+
/** The ref of the container. */
|
|
21
|
+
containerRef?: (node: HTMLElement | null) => void;
|
|
22
|
+
/** The ref of the input. */
|
|
23
|
+
inputRef?: (node: HTMLElement | null) => void;
|
|
24
|
+
/** The trailing element to display in the field. */
|
|
25
|
+
trailing?: ReactNode;
|
|
26
|
+
/** The leading element to display in the field. */
|
|
27
|
+
leading?: ReactNode;
|
|
28
|
+
/** The placeholder of the field. */
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
/** The type of the input. */
|
|
31
|
+
type?: Extract<HTMLInputTypeAttribute, 'number' | 'text'>;
|
|
32
|
+
/**
|
|
33
|
+
* Specifies if user agent has any permission to provide automated assistance in filling out form field values.
|
|
34
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
|
|
35
|
+
*
|
|
36
|
+
* @default off
|
|
37
|
+
*/
|
|
38
|
+
autoComplete?: HTMLInputAutoCompleteAttribute;
|
|
39
|
+
};
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* A text input that allows users to enter text, numbers or symbols in a singular line. This is the base element and is
|
|
@@ -63,6 +64,7 @@ function TextInput({
|
|
|
63
64
|
disabled,
|
|
64
65
|
autoComplete = 'off',
|
|
65
66
|
containerRef,
|
|
67
|
+
errorMessage,
|
|
66
68
|
...otherProps
|
|
67
69
|
}: ElementProps<TextInputProps, 'div'>) {
|
|
68
70
|
const id = useId(idProp);
|
|
@@ -82,6 +84,7 @@ function TextInput({
|
|
|
82
84
|
>
|
|
83
85
|
{leading && <span data-leading>{leading}</span>}
|
|
84
86
|
<input
|
|
87
|
+
aria-errormessage={errorMessage || undefined}
|
|
85
88
|
aria-invalid={invalid || undefined}
|
|
86
89
|
aria-label={ariaLabel}
|
|
87
90
|
autoComplete={autoComplete}
|
package/src/Textarea.tsx
CHANGED
|
@@ -3,62 +3,76 @@ import { ChangeEvent, CSSProperties, Ref } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import { useId } from './hooks/useId';
|
|
5
5
|
|
|
6
|
-
import { CommonProps } from './';
|
|
6
|
+
import { CommonProps, InvalidPropsLibrary } from './';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* @required
|
|
14
|
-
*/
|
|
15
|
-
onChange: (next: string, event?: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
16
|
-
/**
|
|
17
|
-
* The size of the field.
|
|
18
|
-
*
|
|
19
|
-
* @default medium
|
|
20
|
-
*/
|
|
21
|
-
size?: 'large' | 'medium' | 'small';
|
|
22
|
-
/** The value of the field. */
|
|
23
|
-
value?: string;
|
|
24
|
-
/**
|
|
25
|
-
* The textarea control name of the field.
|
|
26
|
-
*
|
|
27
|
-
* @required
|
|
28
|
-
*/
|
|
29
|
-
name: string;
|
|
30
|
-
/** The ref of the field. */
|
|
31
|
-
innerRef?: Ref<HTMLTextAreaElement>;
|
|
32
|
-
/** The placeholder of the field. */
|
|
33
|
-
placeholder?: string;
|
|
34
|
-
/**
|
|
35
|
-
* The maximum number of characters that the field will accept.
|
|
36
|
-
*
|
|
37
|
-
* @minimum 1
|
|
38
|
-
*/
|
|
39
|
-
maxLength?: number;
|
|
40
|
-
/**
|
|
41
|
-
* The minimum number of rows that the textarea should have. If set the textarea will automatically grow and shrink
|
|
42
|
-
* to fit the content.
|
|
43
|
-
*
|
|
44
|
-
* @minimum 3
|
|
45
|
-
*/
|
|
46
|
-
minRows?: number;
|
|
47
|
-
/**
|
|
48
|
-
* The maximum number of rows that the textarea should have. If set the textarea will automatically grow and shrink
|
|
49
|
-
* to fit the content.
|
|
50
|
-
*
|
|
51
|
-
* @maximum 10
|
|
52
|
-
*/
|
|
53
|
-
maxRows?: number;
|
|
54
|
-
};
|
|
8
|
+
const DEFAULT = {
|
|
9
|
+
minRows: 3,
|
|
10
|
+
maxRows: 10,
|
|
11
|
+
textSize: 'medium',
|
|
12
|
+
} as const;
|
|
55
13
|
|
|
56
|
-
|
|
57
|
-
|
|
14
|
+
export type TextareaProps = CommonProps<'aria-label' | 'disabled' | 'id' | 'readOnly' | 'required'> &
|
|
15
|
+
InvalidPropsLibrary & {
|
|
16
|
+
/**
|
|
17
|
+
* Callback when the value of the field changes.
|
|
18
|
+
*
|
|
19
|
+
* @type (next: String, Event) => void
|
|
20
|
+
* @required
|
|
21
|
+
*/
|
|
22
|
+
onChange: (next: string, event?: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
23
|
+
/**
|
|
24
|
+
* The text size of the field.
|
|
25
|
+
*
|
|
26
|
+
* @default medium
|
|
27
|
+
*/
|
|
28
|
+
textSize?: 'large' | 'medium' | 'small';
|
|
29
|
+
/**
|
|
30
|
+
* The value of the field.
|
|
31
|
+
*
|
|
32
|
+
* @type multiline
|
|
33
|
+
*/
|
|
34
|
+
value?: string;
|
|
35
|
+
/**
|
|
36
|
+
* The textarea control name of the field.
|
|
37
|
+
*
|
|
38
|
+
* @required
|
|
39
|
+
*/
|
|
40
|
+
name: string;
|
|
41
|
+
/** The ref of the field. */
|
|
42
|
+
innerRef?: Ref<HTMLTextAreaElement>;
|
|
43
|
+
/** The placeholder of the field. */
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The maximum number of characters that the field will accept.
|
|
47
|
+
*
|
|
48
|
+
* @minimum 1
|
|
49
|
+
*/
|
|
50
|
+
maxLength?: number;
|
|
51
|
+
/**
|
|
52
|
+
* The minimum number of rows that the textarea will show.
|
|
53
|
+
*
|
|
54
|
+
* @default 3
|
|
55
|
+
* @minimum 3
|
|
56
|
+
* @maximum 10
|
|
57
|
+
*/
|
|
58
|
+
minRows?: number;
|
|
59
|
+
/**
|
|
60
|
+
* The maximum number of rows that the textarea will show.
|
|
61
|
+
*
|
|
62
|
+
* @default 10
|
|
63
|
+
* @minimum 3
|
|
64
|
+
* @maximum 10
|
|
65
|
+
*/
|
|
66
|
+
maxRows?: number;
|
|
67
|
+
};
|
|
58
68
|
|
|
59
69
|
/**
|
|
60
70
|
* A component that allows users to input large amounts of text that could span multiple lines.
|
|
61
71
|
*
|
|
72
|
+
* This component gives you a textarea HTML element that automatically adjusts its height to match the length of the
|
|
73
|
+
* content within maximum and minimum rows. A character counter when a maxLength is set to show the number of characters
|
|
74
|
+
* remaining below the limit.
|
|
75
|
+
*
|
|
62
76
|
* @element
|
|
63
77
|
*
|
|
64
78
|
* @name Textarea
|
|
@@ -66,27 +80,28 @@ const MAX_ROWS = 10;
|
|
|
66
80
|
function Textarea({
|
|
67
81
|
invalid: invalidProp,
|
|
68
82
|
onChange,
|
|
69
|
-
|
|
83
|
+
textSize = DEFAULT.textSize,
|
|
70
84
|
value = '',
|
|
71
85
|
name,
|
|
72
86
|
'aria-label': ariaLabel,
|
|
73
87
|
innerRef,
|
|
74
88
|
placeholder,
|
|
75
89
|
id: idProp,
|
|
76
|
-
minRows: minRowsProp =
|
|
77
|
-
maxRows: maxRowsProp =
|
|
90
|
+
minRows: minRowsProp = DEFAULT.minRows,
|
|
91
|
+
maxRows: maxRowsProp = DEFAULT.maxRows,
|
|
92
|
+
errorMessage,
|
|
78
93
|
...otherProps
|
|
79
94
|
}: TextareaProps) {
|
|
80
95
|
const id = useId(idProp);
|
|
81
96
|
const invalid = !otherProps.readOnly && !otherProps.disabled && invalidProp;
|
|
82
97
|
// ensure minRows and maxRows are within bounds
|
|
83
|
-
const minRows = Math.min(
|
|
84
|
-
const maxRows = Math.max(
|
|
98
|
+
const minRows = Math.min(DEFAULT.maxRows, Math.max(minRowsProp, DEFAULT.minRows));
|
|
99
|
+
const maxRows = Math.max(DEFAULT.minRows, Math.min(maxRowsProp, DEFAULT.maxRows));
|
|
85
100
|
|
|
86
101
|
return (
|
|
87
102
|
<div
|
|
88
103
|
data-bspk="textarea"
|
|
89
|
-
data-size={
|
|
104
|
+
data-size={textSize}
|
|
90
105
|
style={
|
|
91
106
|
{
|
|
92
107
|
'--min-rows': minRows,
|
|
@@ -96,6 +111,7 @@ function Textarea({
|
|
|
96
111
|
>
|
|
97
112
|
<textarea
|
|
98
113
|
{...otherProps}
|
|
114
|
+
aria-errormessage={errorMessage || undefined}
|
|
99
115
|
aria-invalid={invalid || undefined}
|
|
100
116
|
aria-label={ariaLabel}
|
|
101
117
|
id={id}
|
package/src/base.scss
CHANGED
|
@@ -51,16 +51,17 @@ body {
|
|
|
51
51
|
a {
|
|
52
52
|
color: var(--foreground-link-text-default);
|
|
53
53
|
|
|
54
|
-
&:hover {
|
|
54
|
+
&:not([disabled]):hover {
|
|
55
55
|
color: var(--foreground-link-text-default-hovered);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
&:visited {
|
|
58
|
+
&:not([disabled]):visited {
|
|
59
59
|
color: var(--foreground-link-text-default-visited);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
&[disabled] {
|
|
63
63
|
pointer-events: none;
|
|
64
|
+
cursor: text;
|
|
64
65
|
color: var(--foreground-link-text-default-disabled);
|
|
65
66
|
}
|
|
66
67
|
}
|
package/src/form-field.scss
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AriaAttributes, useId, useState } from 'react';
|
|
2
2
|
|
|
3
|
-
import { CommonProps } from '..';
|
|
3
|
+
import { CommonProps, InvalidPropsLibrary } from '..';
|
|
4
4
|
import { EVENT_KEY } from '../utils/keyboard';
|
|
5
5
|
|
|
6
6
|
import { Placement, useFloating } from './useFloating';
|
|
@@ -9,7 +9,7 @@ import { useOutsideClick } from './useOutsideClick';
|
|
|
9
9
|
|
|
10
10
|
export type UseFloatingMenuProps = {
|
|
11
11
|
placement: Placement;
|
|
12
|
-
triggerProps?: CommonProps<'disabled' | '
|
|
12
|
+
triggerProps?: CommonProps<'disabled' | 'readOnly'> & InvalidPropsLibrary;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export type UseFloatingMenuReturn = {
|
|
@@ -30,6 +30,7 @@ export type UseFloatingMenuReturn = {
|
|
|
30
30
|
'aria-invalid': boolean | undefined;
|
|
31
31
|
'aria-owns': string;
|
|
32
32
|
'aria-readonly': boolean | undefined;
|
|
33
|
+
'aria-errormessage': string | undefined;
|
|
33
34
|
role: 'combobox';
|
|
34
35
|
tabIndex: number;
|
|
35
36
|
ref: (node: HTMLElement | null) => void;
|
|
@@ -75,6 +76,7 @@ export function useFloatingMenu({ placement, triggerProps }: UseFloatingMenuProp
|
|
|
75
76
|
tabIndex: -1,
|
|
76
77
|
},
|
|
77
78
|
triggerProps: {
|
|
79
|
+
'aria-errormessage': triggerProps?.errorMessage || undefined,
|
|
78
80
|
'aria-activedescendant': selectedId || undefined,
|
|
79
81
|
'aria-controls': menuId,
|
|
80
82
|
'aria-expanded': show,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import { isValidIcon } from '../utils/children';
|
|
4
|
+
import { useErrorLogger } from '../utils/errors';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A utility hook used within navigation components. Returns true if the icons are invalid.
|
|
8
|
+
*
|
|
9
|
+
* @param options [NavOption[]] The options to display. Each option has an optional leading icon.
|
|
10
|
+
* @returns {boolean} True if the icons are invalid.
|
|
11
|
+
*/
|
|
12
|
+
export function useOptionIconsInvalid<T extends NavOption>(options: T[] | undefined): boolean {
|
|
13
|
+
const { logError } = useErrorLogger();
|
|
14
|
+
|
|
15
|
+
return useMemo(() => {
|
|
16
|
+
if (!options || !Array.isArray(options)) return true;
|
|
17
|
+
|
|
18
|
+
const iconsInvalid = options.some((o) => o.icon) && !options.every((option) => isValidIcon(option.icon));
|
|
19
|
+
|
|
20
|
+
logError(
|
|
21
|
+
iconsInvalid,
|
|
22
|
+
'useNavOptions - Every option either must have a valid icon or none at all. All icons removed.',
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return iconsInvalid;
|
|
26
|
+
}, [logError, options]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type NavOption = {
|
|
30
|
+
/**
|
|
31
|
+
* The label of the option. This is the text that will be displayed on the option.
|
|
32
|
+
*
|
|
33
|
+
* @required
|
|
34
|
+
*/
|
|
35
|
+
label: string;
|
|
36
|
+
/**
|
|
37
|
+
* Determines if the element is [disabled](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled).
|
|
38
|
+
*
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
/** The value of the option. If not provided, the label will be used as the value. */
|
|
43
|
+
value?: string;
|
|
44
|
+
/** The the icon to display before the label. */
|
|
45
|
+
icon?: React.ReactNode;
|
|
46
|
+
iconActive?: React.ReactNode;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Copyright 2025 Anywhere Real Estate - CC BY 4.0 */
|
package/src/index.ts
CHANGED
|
@@ -34,7 +34,8 @@ export type CallToActionButton = {
|
|
|
34
34
|
size?: ButtonSize;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
export type ToggleControlProps<T extends HTMLElement> = CommonProps<'aria-label' | 'disabled' | '
|
|
37
|
+
export type ToggleControlProps<T extends HTMLElement> = CommonProps<'aria-label' | 'disabled' | 'name', T> &
|
|
38
|
+
InvalidPropsLibrary &
|
|
38
39
|
Required<CommonProps<'value'>> & {
|
|
39
40
|
/**
|
|
40
41
|
* Marks the control as checked.
|
|
@@ -56,6 +57,28 @@ export type CommonProps<K extends keyof CommonPropsLibrary, T extends HTMLElemen
|
|
|
56
57
|
K
|
|
57
58
|
>;
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The props that are common to input elements.
|
|
62
|
+
*
|
|
63
|
+
* If an element is invalid it must have an errorMessage.
|
|
64
|
+
*/
|
|
65
|
+
export type InvalidPropsLibrary = {
|
|
66
|
+
/**
|
|
67
|
+
* Marks the element as invalid and displays error state theme.
|
|
68
|
+
*
|
|
69
|
+
* If the errorMessage is empty the error state theme will not appear.
|
|
70
|
+
*
|
|
71
|
+
* @default false
|
|
72
|
+
*/
|
|
73
|
+
invalid?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Marks the element as invalid and displays error message.
|
|
76
|
+
*
|
|
77
|
+
* When an element is invalid it must display an error message explaining why it is invalid.
|
|
78
|
+
*/
|
|
79
|
+
errorMessage?: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
59
82
|
export type CommonPropsLibrary<T extends HTMLElement = HTMLElement> = {
|
|
60
83
|
/** The id of the element. If not provided one will be generated. */
|
|
61
84
|
id?: string;
|
|
@@ -89,12 +112,6 @@ export type CommonPropsLibrary<T extends HTMLElement = HTMLElement> = {
|
|
|
89
112
|
* @default false
|
|
90
113
|
*/
|
|
91
114
|
disabled?: boolean;
|
|
92
|
-
/**
|
|
93
|
-
* Marks the element as invalid and displays error state theme.
|
|
94
|
-
*
|
|
95
|
-
* @default false
|
|
96
|
-
*/
|
|
97
|
-
invalid?: boolean;
|
|
98
115
|
/**
|
|
99
116
|
* Determines if the element is [readonly](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly).
|
|
100
117
|
*
|
|
@@ -107,12 +124,6 @@ export type CommonPropsLibrary<T extends HTMLElement = HTMLElement> = {
|
|
|
107
124
|
* @required
|
|
108
125
|
*/
|
|
109
126
|
name: string;
|
|
110
|
-
/**
|
|
111
|
-
* Marks the element as invalid and displays error message.
|
|
112
|
-
*
|
|
113
|
-
* When an element is invalid it must display an error message explaining why it is invalid.
|
|
114
|
-
*/
|
|
115
|
-
errorMessage?: string;
|
|
116
127
|
/**
|
|
117
128
|
* The value of the control.
|
|
118
129
|
*
|
package/src/inline-alert.scss
CHANGED
package/src/number-input.scss
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[data-bspk='number-input'] {
|
|
2
|
-
// medium
|
|
2
|
+
// medium is the default size
|
|
3
3
|
--font: var(--body-base);
|
|
4
4
|
--height: var(--spacing-sizing-10);
|
|
5
5
|
--svg-width: var(--spacing-sizing-05);
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
border: 1px solid var(--stroke-neutral-base);
|
|
14
14
|
border-radius: var(--radius-small);
|
|
15
15
|
background: var(--surface-neutral-t1-base);
|
|
16
|
+
max-width: 280px;
|
|
16
17
|
|
|
17
18
|
&:focus-within {
|
|
18
19
|
border-color: var(--stroke-brand-primary);
|
|
19
|
-
outline: 1px solid var(--stroke-brand-primary);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
[data-divider] {
|
|
@@ -29,11 +29,13 @@
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
button {
|
|
32
|
-
|
|
32
|
+
width: var(--height);
|
|
33
|
+
height: var(--height);
|
|
33
34
|
background: none;
|
|
34
35
|
border: none;
|
|
35
36
|
cursor: pointer;
|
|
36
37
|
font: var(--font);
|
|
38
|
+
flex-shrink: 0;
|
|
37
39
|
|
|
38
40
|
svg {
|
|
39
41
|
width: var(--svg-width);
|
|
@@ -59,6 +61,7 @@
|
|
|
59
61
|
padding: 0 var(--spacing-sizing-03);
|
|
60
62
|
background: transparent;
|
|
61
63
|
border: none;
|
|
64
|
+
flex-grow: 1;
|
|
62
65
|
|
|
63
66
|
&:focus {
|
|
64
67
|
outline: none;
|
package/src/textarea.scss
CHANGED
|
@@ -43,7 +43,11 @@
|
|
|
43
43
|
textarea {
|
|
44
44
|
--border-color: var(--stroke-neutral-base);
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
&::placeholder {
|
|
47
|
+
color: var(--foreground-neutral-on-surface-variant-03);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
resize: none;
|
|
47
51
|
color: var(--foreground-neutral-on-surface);
|
|
48
52
|
background-color: var(--surface-neutral-t1-base);
|
|
49
53
|
border-radius: var(--radius-small);
|
|
@@ -71,7 +75,6 @@
|
|
|
71
75
|
|
|
72
76
|
linear-gradient(var(--interactions-disabled-opacity), var(--interactions-disabled-opacity)),
|
|
73
77
|
linear-gradient(var(--surface-neutral-t1-base), var(--surface-neutral-t1-base));
|
|
74
|
-
color: var(--foreground-neutral-on-surface-variant-02);
|
|
75
78
|
cursor: not-allowed;
|
|
76
79
|
}
|
|
77
80
|
|