@kosmeo-ui/react 0.1.1 → 0.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.
- package/dist/components/buttons/button/index.d.ts +2 -0
- package/dist/components/buttons/button/index.d.ts.map +1 -1
- package/dist/components/buttons/button/index.js +2 -2
- package/dist/components/buttons/button/index.js.map +1 -1
- package/dist/components/buttons/button/styles.css +6 -4
- package/dist/components/buttons/rowActions/styles.css +7 -0
- package/dist/components/data-display/addressBlock/styles.css +2 -4
- package/dist/components/data-display/dataTable/styles.css +15 -0
- package/dist/components/data-display/infiniteScroll/filters/searchFilter.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/filters/searchFilter.js +4 -35
- package/dist/components/data-display/infiniteScroll/filters/searchFilter.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/filters/styles.css +4 -0
- package/dist/components/data-display/infiniteScroll/header/deleteButton.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/header/deleteButton.js +1 -1
- package/dist/components/data-display/infiniteScroll/header/deleteButton.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/header/index.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/header/index.js +1 -1
- package/dist/components/data-display/infiniteScroll/header/index.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/table/index.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/table/index.js +2 -2
- package/dist/components/data-display/infiniteScroll/table/index.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/index.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/index.js +1 -6
- package/dist/components/data-display/infiniteScroll/tags/index.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/listFilterTag.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/listFilterTag.js +3 -3
- package/dist/components/data-display/infiniteScroll/tags/listFilterTag.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/rangeFilterTag.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/rangeFilterTag.js +3 -13
- package/dist/components/data-display/infiniteScroll/tags/rangeFilterTag.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/selectFilterTag.d.ts.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/selectFilterTag.js +3 -5
- package/dist/components/data-display/infiniteScroll/tags/selectFilterTag.js.map +1 -1
- package/dist/components/data-display/infiniteScroll/tags/styles.css +1 -29
- package/dist/components/data-display/recoveryCodeGrid/styles.css +1 -2
- package/dist/components/data-display/statCard/styles.css +1 -1
- package/dist/components/data-display/table/styles.css +12 -1
- package/dist/components/data-display/tagCard/index.d.ts.map +1 -1
- package/dist/components/data-display/tagCard/index.js +1 -1
- package/dist/components/data-display/tagCard/index.js.map +1 -1
- package/dist/components/feedback/badge/styles.css +8 -6
- package/dist/components/feedback/emptyState/styles.css +2 -4
- package/dist/components/feedback/progressBar/index.d.ts.map +1 -1
- package/dist/components/feedback/progressBar/index.js +7 -2
- package/dist/components/feedback/progressBar/index.js.map +1 -1
- package/dist/components/feedback/progressBar/styles.css +15 -6
- package/dist/components/feedback/spinner/styles.css +0 -13
- package/dist/components/forms/fieldShell/styles.css +1 -1
- package/dist/components/forms/formActions/styles.css +1 -1
- package/dist/components/forms/infoRow/styles.css +1 -2
- package/dist/components/forms/requiredAsterisk/styles.css +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/inputs/checkbox/index.d.ts +6 -1
- package/dist/components/inputs/checkbox/index.d.ts.map +1 -1
- package/dist/components/inputs/checkbox/index.js +9 -2
- package/dist/components/inputs/checkbox/index.js.map +1 -1
- package/dist/components/inputs/checkbox/styles.css +8 -0
- package/dist/components/inputs/colorPicker/styles.css +1 -1
- package/dist/components/inputs/dateInput/index.d.ts +3 -3
- package/dist/components/inputs/dateInput/index.d.ts.map +1 -1
- package/dist/components/inputs/dateInput/index.js +13 -3
- package/dist/components/inputs/dateInput/index.js.map +1 -1
- package/dist/components/inputs/dropdown/index.d.ts +2 -0
- package/dist/components/inputs/dropdown/index.d.ts.map +1 -1
- package/dist/components/inputs/dropdown/index.js +1 -1
- package/dist/components/inputs/dropdown/index.js.map +1 -1
- package/dist/components/inputs/dropdownArrowButton/index.d.ts.map +1 -1
- package/dist/components/inputs/dropdownArrowButton/index.js +1 -1
- package/dist/components/inputs/dropdownArrowButton/index.js.map +1 -1
- package/dist/components/inputs/dropdownArrowButton/styles.css +1 -1
- package/dist/components/inputs/input/index.d.ts +5 -1
- package/dist/components/inputs/input/index.d.ts.map +1 -1
- package/dist/components/inputs/input/index.js +25 -4
- package/dist/components/inputs/input/index.js.map +1 -1
- package/dist/components/inputs/input/styles.css +46 -2
- package/dist/components/inputs/multiSelect/index.d.ts.map +1 -1
- package/dist/components/inputs/multiSelect/index.js +1 -1
- package/dist/components/inputs/multiSelect/index.js.map +1 -1
- package/dist/components/inputs/multiSelect/styles.css +1 -1
- package/dist/components/inputs/phoneInput/index.d.ts.map +1 -1
- package/dist/components/inputs/phoneInput/index.js +1 -1
- package/dist/components/inputs/phoneInput/index.js.map +1 -1
- package/dist/components/inputs/richTextEditor/index.d.ts.map +1 -1
- package/dist/components/inputs/richTextEditor/index.js +14 -3
- package/dist/components/inputs/richTextEditor/index.js.map +1 -1
- package/dist/components/layout/borderedSection/index.d.ts +3 -1
- package/dist/components/layout/borderedSection/index.d.ts.map +1 -1
- package/dist/components/layout/borderedSection/index.js +2 -2
- package/dist/components/layout/borderedSection/index.js.map +1 -1
- package/dist/components/layout/card/styles.css +22 -0
- package/dist/components/layout/sectionHeader/styles.css +1 -2
- package/dist/components/navigation/pagination/index.d.ts +23 -0
- package/dist/components/navigation/pagination/index.d.ts.map +1 -0
- package/dist/components/navigation/pagination/index.js +29 -0
- package/dist/components/navigation/pagination/index.js.map +1 -0
- package/dist/components/navigation/pagination/styles.css +113 -0
- package/dist/components/navigation/pagination/utils.d.ts +9 -0
- package/dist/components/navigation/pagination/utils.d.ts.map +1 -0
- package/dist/components/navigation/pagination/utils.js +25 -0
- package/dist/components/navigation/pagination/utils.js.map +1 -0
- package/dist/components/navigation/sidebar/styles.css +9 -1
- package/dist/components/navigation/tabButton/styles.css +6 -6
- package/dist/components/navigation/tabs/styles.css +2 -2
- package/dist/components/overlays/confirmationModal/index.d.ts +5 -0
- package/dist/components/overlays/confirmationModal/index.d.ts.map +1 -1
- package/dist/components/overlays/confirmationModal/index.js +23 -4
- package/dist/components/overlays/confirmationModal/index.js.map +1 -1
- package/dist/components/overlays/confirmationModal/styles.css +3 -0
- package/dist/components/overlays/modal/index.d.ts +2 -0
- package/dist/components/overlays/modal/index.d.ts.map +1 -1
- package/dist/components/overlays/modal/index.js +13 -4
- package/dist/components/overlays/modal/index.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/usePaginated.d.ts +43 -0
- package/dist/hooks/usePaginated.d.ts.map +1 -0
- package/dist/hooks/usePaginated.js +60 -0
- package/dist/hooks/usePaginated.js.map +1 -0
- package/dist/styles.css +37 -14
- package/package.json +1 -1
- package/src/components/buttons/button/index.tsx +14 -5
- package/src/components/buttons/button/styles.css +6 -4
- package/src/components/buttons/rowActions/styles.css +7 -0
- package/src/components/data-display/addressBlock/styles.css +2 -4
- package/src/components/data-display/dataTable/styles.css +15 -0
- package/src/components/data-display/infiniteScroll/filters/searchFilter.tsx +16 -65
- package/src/components/data-display/infiniteScroll/filters/styles.css +4 -0
- package/src/components/data-display/infiniteScroll/header/deleteButton.tsx +1 -0
- package/src/components/data-display/infiniteScroll/header/index.tsx +4 -0
- package/src/components/data-display/infiniteScroll/table/index.tsx +2 -0
- package/src/components/data-display/infiniteScroll/tags/index.tsx +1 -11
- package/src/components/data-display/infiniteScroll/tags/listFilterTag.tsx +12 -16
- package/src/components/data-display/infiniteScroll/tags/rangeFilterTag.tsx +4 -22
- package/src/components/data-display/infiniteScroll/tags/selectFilterTag.tsx +4 -11
- package/src/components/data-display/infiniteScroll/tags/styles.css +1 -29
- package/src/components/data-display/recoveryCodeGrid/styles.css +1 -2
- package/src/components/data-display/statCard/styles.css +1 -1
- package/src/components/data-display/table/styles.css +12 -1
- package/src/components/data-display/tagCard/index.tsx +5 -3
- package/src/components/feedback/badge/styles.css +8 -6
- package/src/components/feedback/emptyState/styles.css +2 -4
- package/src/components/feedback/progressBar/index.tsx +14 -8
- package/src/components/feedback/progressBar/styles.css +15 -6
- package/src/components/feedback/spinner/styles.css +0 -13
- package/src/components/forms/fieldShell/styles.css +1 -1
- package/src/components/forms/formActions/styles.css +1 -1
- package/src/components/forms/infoRow/styles.css +1 -2
- package/src/components/forms/requiredAsterisk/styles.css +1 -1
- package/src/components/index.ts +2 -0
- package/src/components/inputs/checkbox/index.tsx +24 -4
- package/src/components/inputs/checkbox/styles.css +8 -0
- package/src/components/inputs/colorPicker/styles.css +1 -1
- package/src/components/inputs/dateInput/index.tsx +13 -4
- package/src/components/inputs/dropdown/index.tsx +4 -0
- package/src/components/inputs/dropdownArrowButton/index.tsx +1 -0
- package/src/components/inputs/dropdownArrowButton/styles.css +1 -1
- package/src/components/inputs/input/index.tsx +47 -4
- package/src/components/inputs/input/styles.css +46 -2
- package/src/components/inputs/multiSelect/index.tsx +1 -0
- package/src/components/inputs/multiSelect/styles.css +1 -1
- package/src/components/inputs/phoneInput/index.tsx +6 -2
- package/src/components/inputs/richTextEditor/index.tsx +27 -3
- package/src/components/layout/borderedSection/index.tsx +4 -1
- package/src/components/layout/card/styles.css +22 -0
- package/src/components/layout/sectionHeader/styles.css +1 -2
- package/src/components/navigation/pagination/index.tsx +134 -0
- package/src/components/navigation/pagination/styles.css +113 -0
- package/src/components/navigation/pagination/utils.ts +32 -0
- package/src/components/navigation/sidebar/styles.css +9 -1
- package/src/components/navigation/tabButton/styles.css +6 -6
- package/src/components/navigation/tabs/styles.css +2 -2
- package/src/components/overlays/confirmationModal/index.tsx +37 -1
- package/src/components/overlays/confirmationModal/styles.css +3 -0
- package/src/components/overlays/modal/index.tsx +23 -4
- package/src/hooks/index.ts +4 -0
- package/src/hooks/usePaginated.ts +123 -0
- package/src/styles.css +37 -14
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.d.ts +0 -10
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.d.ts.map +0 -1
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.js +0 -22
- package/dist/components/data-display/infiniteScroll/tags/searchFilterTag.js.map +0 -1
- package/src/components/data-display/infiniteScroll/tags/searchFilterTag.tsx +0 -41
|
@@ -88,7 +88,7 @@ button.k-badge {
|
|
|
88
88
|
|
|
89
89
|
.k-badge-warning {
|
|
90
90
|
background-color: var(--warning-color);
|
|
91
|
-
color: var(--
|
|
91
|
+
color: var(--warning-color-contrast);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
.k-badge-success {
|
|
@@ -107,13 +107,15 @@ button.k-badge {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/* Subtle variants - transparent background with colored text */
|
|
110
|
+
/* Subtle text blends the variant color 60/40 with --text-color: the full
|
|
111
|
+
variant color on its own 15% tint falls under 4.5:1 for most variants. */
|
|
110
112
|
.k-badge-subtle.k-badge-primary {
|
|
111
113
|
background-color: color-mix(
|
|
112
114
|
in srgb,
|
|
113
115
|
var(--primary-color) 15%,
|
|
114
116
|
var(--background-color)
|
|
115
117
|
);
|
|
116
|
-
color: var(--primary-color);
|
|
118
|
+
color: color-mix(in srgb, var(--primary-color) 60%, var(--text-color));
|
|
117
119
|
}
|
|
118
120
|
|
|
119
121
|
.k-badge-subtle.k-badge-secondary {
|
|
@@ -122,7 +124,7 @@ button.k-badge {
|
|
|
122
124
|
var(--secondary-color) 15%,
|
|
123
125
|
var(--background-color)
|
|
124
126
|
);
|
|
125
|
-
color: var(--secondary-color);
|
|
127
|
+
color: color-mix(in srgb, var(--secondary-color) 60%, var(--text-color));
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
.k-badge-subtle.k-badge-light {
|
|
@@ -140,7 +142,7 @@ button.k-badge {
|
|
|
140
142
|
var(--danger-color) 15%,
|
|
141
143
|
var(--background-color)
|
|
142
144
|
);
|
|
143
|
-
color: var(--danger-color);
|
|
145
|
+
color: color-mix(in srgb, var(--danger-color) 60%, var(--text-color));
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
.k-badge-subtle.k-badge-warning {
|
|
@@ -149,7 +151,7 @@ button.k-badge {
|
|
|
149
151
|
var(--warning-color) 20%,
|
|
150
152
|
var(--background-color)
|
|
151
153
|
);
|
|
152
|
-
color: var(--warning-color);
|
|
154
|
+
color: var(--warning-color-text);
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
.k-badge-subtle.k-badge-success {
|
|
@@ -158,5 +160,5 @@ button.k-badge {
|
|
|
158
160
|
var(--success-color) 15%,
|
|
159
161
|
var(--background-color)
|
|
160
162
|
);
|
|
161
|
-
color: var(--success-color);
|
|
163
|
+
color: color-mix(in srgb, var(--success-color) 60%, var(--text-color));
|
|
162
164
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
.k-empty-state-inline {
|
|
2
2
|
font-size: 0.875rem;
|
|
3
|
-
color: var(--text-color);
|
|
4
|
-
opacity: 0.65;
|
|
3
|
+
color: var(--text-color-secondary);
|
|
5
4
|
font-style: italic;
|
|
6
5
|
padding: 0.5rem 0;
|
|
7
6
|
}
|
|
@@ -14,8 +13,7 @@
|
|
|
14
13
|
gap: 0.75rem;
|
|
15
14
|
padding: 2rem 1rem;
|
|
16
15
|
text-align: center;
|
|
17
|
-
color: var(--text-color);
|
|
18
|
-
opacity: 0.75;
|
|
16
|
+
color: var(--text-color-secondary);
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
.k-empty-state-icon {
|
|
@@ -17,16 +17,22 @@ const ProgressBar = (props: ProgressBarProps) => {
|
|
|
17
17
|
const fillBorderRadius =
|
|
18
18
|
clampedProgress === 100 ? "0.25rem" : "0.25rem 0 0 0.25rem";
|
|
19
19
|
|
|
20
|
+
// The percentage sits beside the bar, not on the fill: no single text color
|
|
21
|
+
// reaches 4.5:1 on every variant's fill in both themes.
|
|
20
22
|
return (
|
|
21
|
-
<div className=
|
|
22
|
-
<div
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
<div className="k-progress-bar">
|
|
24
|
+
<div className="k-progress-bar-container">
|
|
25
|
+
<div
|
|
26
|
+
className={`k-progress-bar-fill k-progress-bar-fill-${variant}`}
|
|
27
|
+
style={{
|
|
28
|
+
width: `${clampedProgress}%`,
|
|
29
|
+
borderRadius: fillBorderRadius,
|
|
30
|
+
}}
|
|
31
|
+
/>
|
|
29
32
|
</div>
|
|
33
|
+
{showPercentage && (
|
|
34
|
+
<span className="k-progress-bar-text">{clampedProgress}%</span>
|
|
35
|
+
)}
|
|
30
36
|
</div>
|
|
31
37
|
);
|
|
32
38
|
};
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
.k-progress-bar
|
|
1
|
+
.k-progress-bar {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
gap: 0.5rem;
|
|
2
5
|
width: 100%;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.k-progress-bar-container {
|
|
9
|
+
flex: 1;
|
|
10
|
+
min-width: 0;
|
|
3
11
|
height: 1.5625rem;
|
|
4
12
|
background-color: var(--background-color-dark);
|
|
5
13
|
border-radius: 0.25rem;
|
|
@@ -10,16 +18,17 @@
|
|
|
10
18
|
.k-progress-bar-fill {
|
|
11
19
|
height: 100%;
|
|
12
20
|
background-color: var(--success-color);
|
|
13
|
-
display: flex;
|
|
14
|
-
align-items: center;
|
|
15
|
-
justify-content: center;
|
|
16
|
-
color: var(--text-color-light);
|
|
17
|
-
font-weight: 600;
|
|
18
21
|
transition: width 0.5s ease-in-out;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
.k-progress-bar-text {
|
|
25
|
+
flex-shrink: 0;
|
|
26
|
+
min-width: 2.75rem;
|
|
27
|
+
text-align: right;
|
|
22
28
|
font-size: var(--text-size-sm);
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
font-variant-numeric: tabular-nums;
|
|
31
|
+
color: var(--text-color);
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
.k-progress-bar-fill-primary {
|
|
@@ -49,16 +49,3 @@
|
|
|
49
49
|
.k-spinner-warning {
|
|
50
50
|
border-left-color: var(--warning-color);
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
/* Visually hidden class for accessibility text */
|
|
54
|
-
.k-sr-only {
|
|
55
|
-
position: absolute;
|
|
56
|
-
width: 0.0625rem;
|
|
57
|
-
height: 0.0625rem;
|
|
58
|
-
padding: 0;
|
|
59
|
-
margin: -0.0625rem;
|
|
60
|
-
overflow: hidden;
|
|
61
|
-
clip: rect(0, 0, 0, 0);
|
|
62
|
-
white-space: nowrap;
|
|
63
|
-
border: 0;
|
|
64
|
-
}
|
package/src/components/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ export { default as RadioGroup } from "./inputs/radioGroup/index.js";
|
|
|
39
39
|
export { default as ListRow } from "./data-display/listRow/index.js";
|
|
40
40
|
export { default as TagCard } from "./data-display/tagCard/index.js";
|
|
41
41
|
export { default as Stepper } from "./navigation/stepper/index.js";
|
|
42
|
+
export { default as Pagination } from "./navigation/pagination/index.js";
|
|
42
43
|
export { default as Typeahead } from "./inputs/typeahead/index.js";
|
|
43
44
|
export { default as AuthPage } from "./layout/authPage/index.js";
|
|
44
45
|
export { default as Checkbox } from "./inputs/checkbox/index.js";
|
|
@@ -120,6 +121,7 @@ export type {
|
|
|
120
121
|
AddressBlockProps,
|
|
121
122
|
AddressBlockAddress,
|
|
122
123
|
} from "./data-display/addressBlock/index.js";
|
|
124
|
+
export type { PaginationProps } from "./navigation/pagination/index.js";
|
|
123
125
|
export type {
|
|
124
126
|
StepperProps,
|
|
125
127
|
StepperStep,
|
|
@@ -7,22 +7,39 @@ interface CheckboxProps {
|
|
|
7
7
|
label?: string | React.ReactNode;
|
|
8
8
|
labelLocation?: "top" | "right" | "bottom" | "left";
|
|
9
9
|
checked: boolean;
|
|
10
|
-
|
|
10
|
+
/** **_Optional_** only when `readOnly`, which never calls it */
|
|
11
|
+
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
|
11
12
|
onClick?: (event: React.MouseEvent<HTMLInputElement>) => void;
|
|
12
13
|
/** **_Optional_** whether the field is required (shows red asterisk and enables validation) */
|
|
13
14
|
required?: boolean;
|
|
14
15
|
/** **_Optional_** if the checkbox is disabled */
|
|
15
16
|
disabled?: boolean;
|
|
17
|
+
/** **_Optional_** if the checkbox is read-only: unlike `disabled`, it stays focusable and is announced as read-only */
|
|
18
|
+
readOnly?: boolean;
|
|
16
19
|
/** **_Optional_** ID of the field. If one is not provided, it will be randomly generated */
|
|
17
20
|
id?: string;
|
|
21
|
+
/** **_Optional_** accessible name when there's no visible `label` (e.g. a row-select checkbox) */
|
|
22
|
+
"aria-label"?: string;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
const Checkbox = (props: CheckboxProps) => {
|
|
21
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
labelLocation = "top",
|
|
28
|
+
disabled = false,
|
|
29
|
+
required = false,
|
|
30
|
+
readOnly = false,
|
|
31
|
+
} = props;
|
|
22
32
|
|
|
23
33
|
const inputId =
|
|
24
34
|
props.id || `checkbox-${Math.random().toString(36).substring(2, 9)}`;
|
|
25
35
|
|
|
36
|
+
// Browsers ignore `readonly` on checkboxes, so a click (or Space) is
|
|
37
|
+
// cancelled here instead; aria-readonly tells assistive tech why.
|
|
38
|
+
const handleClick = (event: React.MouseEvent<HTMLInputElement>) => {
|
|
39
|
+
if (readOnly) event.preventDefault();
|
|
40
|
+
props.onClick?.(event);
|
|
41
|
+
};
|
|
42
|
+
|
|
26
43
|
return (
|
|
27
44
|
<div className={`k-checkbox-container k-${labelLocation}`}>
|
|
28
45
|
{props.label && (
|
|
@@ -42,8 +59,11 @@ const Checkbox = (props: CheckboxProps) => {
|
|
|
42
59
|
type="checkbox"
|
|
43
60
|
required={required}
|
|
44
61
|
checked={props.checked}
|
|
45
|
-
onClick={
|
|
46
|
-
onChange={props.onChange}
|
|
62
|
+
onClick={handleClick}
|
|
63
|
+
onChange={readOnly ? undefined : props.onChange}
|
|
64
|
+
readOnly={readOnly}
|
|
65
|
+
aria-readonly={readOnly || undefined}
|
|
66
|
+
aria-label={props["aria-label"]}
|
|
47
67
|
className="k-checkbox"
|
|
48
68
|
disabled={disabled}
|
|
49
69
|
/>
|
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
color: var(--text-color);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
.k-checkbox[aria-readonly="true"] {
|
|
40
|
+
cursor: default;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.k-checkbox-label:has(+ .k-checkbox[aria-readonly="true"]) {
|
|
44
|
+
color: var(--text-color-secondary);
|
|
45
|
+
}
|
|
46
|
+
|
|
39
47
|
.k-checkbox:disabled {
|
|
40
48
|
cursor: not-allowed;
|
|
41
49
|
opacity: 0.6;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import FieldShell from "../../forms/fieldShell/index.js";
|
|
4
|
-
import { ChangeEvent } from "react";
|
|
4
|
+
import { ChangeEvent, useId } from "react";
|
|
5
5
|
import "./styles.css";
|
|
6
6
|
import {
|
|
7
7
|
dateInputToIso,
|
|
@@ -50,9 +50,12 @@ export const DateInput = ({
|
|
|
50
50
|
disabled,
|
|
51
51
|
min,
|
|
52
52
|
max,
|
|
53
|
-
id,
|
|
53
|
+
id: idProp,
|
|
54
54
|
name,
|
|
55
55
|
}: DateInputProps) => {
|
|
56
|
+
// Without an id the label isn't linked to the input.
|
|
57
|
+
const fallbackId = useId();
|
|
58
|
+
const id = idProp ?? fallbackId;
|
|
56
59
|
const handle = (e: ChangeEvent<HTMLInputElement>) =>
|
|
57
60
|
onChange(dateInputToIso(e.target.value));
|
|
58
61
|
return (
|
|
@@ -89,9 +92,12 @@ export const DateTimeInput = ({
|
|
|
89
92
|
disabled,
|
|
90
93
|
min,
|
|
91
94
|
max,
|
|
92
|
-
id,
|
|
95
|
+
id: idProp,
|
|
93
96
|
name,
|
|
94
97
|
}: DateTimeInputProps) => {
|
|
98
|
+
// Without an id the label isn't linked to the input.
|
|
99
|
+
const fallbackId = useId();
|
|
100
|
+
const id = idProp ?? fallbackId;
|
|
95
101
|
const handle = (e: ChangeEvent<HTMLInputElement>) =>
|
|
96
102
|
onChange(dateTimeLocalToIso(e.target.value));
|
|
97
103
|
return (
|
|
@@ -128,9 +134,12 @@ export const TimeInput = ({
|
|
|
128
134
|
disabled,
|
|
129
135
|
min,
|
|
130
136
|
max,
|
|
131
|
-
id,
|
|
137
|
+
id: idProp,
|
|
132
138
|
name,
|
|
133
139
|
}: TimeInputProps) => {
|
|
140
|
+
// Without an id the label isn't linked to the input.
|
|
141
|
+
const fallbackId = useId();
|
|
142
|
+
const id = idProp ?? fallbackId;
|
|
134
143
|
const handle = (e: ChangeEvent<HTMLInputElement>) =>
|
|
135
144
|
onChange(e.target.value || null);
|
|
136
145
|
return (
|
|
@@ -23,6 +23,8 @@ export interface DropdownProps extends PropsWithSize, PropsWithVariant {
|
|
|
23
23
|
isOpen?: boolean;
|
|
24
24
|
/** Controlled mode: callback when open state changes */
|
|
25
25
|
onOpenChange?: (isOpen: boolean) => void;
|
|
26
|
+
/** Accessible name for the trigger. Required when it shows only an `icon`. */
|
|
27
|
+
"aria-label"?: string;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const Dropdown = (props: DropdownProps) => {
|
|
@@ -63,6 +65,7 @@ const Dropdown = (props: DropdownProps) => {
|
|
|
63
65
|
variant={props.variant}
|
|
64
66
|
icon={props.icon}
|
|
65
67
|
onClick={handleTriggerClick}
|
|
68
|
+
aria-label={props["aria-label"]}
|
|
66
69
|
>
|
|
67
70
|
{props.label && label}
|
|
68
71
|
</IconButton>
|
|
@@ -71,6 +74,7 @@ const Dropdown = (props: DropdownProps) => {
|
|
|
71
74
|
size={props.size}
|
|
72
75
|
variant={props.variant}
|
|
73
76
|
onClick={handleTriggerClick}
|
|
77
|
+
aria-label={props["aria-label"]}
|
|
74
78
|
>
|
|
75
79
|
{props.label && label}
|
|
76
80
|
</Button>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
|
|
3
|
+
import { faEye, faEyeSlash, faXmark } from "@fortawesome/free-solid-svg-icons";
|
|
4
4
|
import DropdownArrowButton from "../dropdownArrowButton/index.js";
|
|
5
|
+
import { ReactNode, useCallback, useRef, useState } from "react";
|
|
5
6
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
7
|
import FieldShell from "../../forms/fieldShell/index.js";
|
|
7
8
|
import { PropsWithSize } from "../../types.js";
|
|
8
|
-
import { ReactNode, useState } from "react";
|
|
9
9
|
import "./styles.css";
|
|
10
10
|
|
|
11
11
|
interface InputProps extends PropsWithSize {
|
|
@@ -27,6 +27,8 @@ interface InputProps extends PropsWithSize {
|
|
|
27
27
|
|
|
28
28
|
/** **_Optional_** if the field is disabled */
|
|
29
29
|
disabled?: boolean;
|
|
30
|
+
/** **_Optional_** if the field is read-only: unlike `disabled`, it stays focusable and is submitted */
|
|
31
|
+
readOnly?: boolean;
|
|
30
32
|
/** **_Optional_** if the field is a dropdown */
|
|
31
33
|
dropDown?: boolean;
|
|
32
34
|
/** **_Optional_** whether or not the drop down options are open */
|
|
@@ -73,6 +75,8 @@ interface InputProps extends PropsWithSize {
|
|
|
73
75
|
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
|
|
74
76
|
/** **_Optional_** function that is called when the input is clicked */
|
|
75
77
|
onClick?: React.MouseEventHandler<HTMLInputElement>;
|
|
78
|
+
/** **_Optional_** shows an × button at the end of a non-empty field; clear the value here. Sits at the right edge, so don't combine with `suffix` or `type="password"` */
|
|
79
|
+
onClear?: () => void;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
const Input = (props: InputProps) => {
|
|
@@ -86,6 +90,31 @@ const Input = (props: InputProps) => {
|
|
|
86
90
|
suffix,
|
|
87
91
|
} = props;
|
|
88
92
|
const [showPassword, setShowPassword] = useState(false);
|
|
93
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
94
|
+
const { ref: forwardedRef } = props;
|
|
95
|
+
|
|
96
|
+
// Keep our own handle on the input (the clear button refocuses it) while
|
|
97
|
+
// still honouring a caller's `ref`.
|
|
98
|
+
const setRefs = useCallback(
|
|
99
|
+
(el: HTMLInputElement | null) => {
|
|
100
|
+
inputRef.current = el;
|
|
101
|
+
if (typeof forwardedRef === "function") forwardedRef(el);
|
|
102
|
+
else if (forwardedRef) forwardedRef.current = el;
|
|
103
|
+
},
|
|
104
|
+
[forwardedRef]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const showClear =
|
|
108
|
+
!!props.onClear &&
|
|
109
|
+
String(props.value ?? "") !== "" &&
|
|
110
|
+
!disabled &&
|
|
111
|
+
!props.readOnly;
|
|
112
|
+
|
|
113
|
+
const handleClear = () => {
|
|
114
|
+
props.onClear?.();
|
|
115
|
+
// The button unmounts once the field is empty; keep focus in the field.
|
|
116
|
+
inputRef.current?.focus();
|
|
117
|
+
};
|
|
89
118
|
|
|
90
119
|
const inputId =
|
|
91
120
|
props.id || `input-${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -102,6 +131,7 @@ const Input = (props: InputProps) => {
|
|
|
102
131
|
"k-input",
|
|
103
132
|
`k-input-${size}`,
|
|
104
133
|
type === "password" && "k-input-password",
|
|
134
|
+
props.onClear && "k-input-clearable",
|
|
105
135
|
hasAddon && "k-input-with-addon",
|
|
106
136
|
prefix !== undefined && "k-input-with-prefix",
|
|
107
137
|
suffix !== undefined && "k-input-with-suffix",
|
|
@@ -110,9 +140,11 @@ const Input = (props: InputProps) => {
|
|
|
110
140
|
.join(" ");
|
|
111
141
|
|
|
112
142
|
const renderAddon = (content: ReactNode, side: "prefix" | "suffix") => (
|
|
143
|
+
// Not aria-hidden: addons can be interactive (PhoneInput's country
|
|
144
|
+
// picker), and text like "$" or "USD" is useful context. Icons hide
|
|
145
|
+
// themselves.
|
|
113
146
|
<span
|
|
114
147
|
className={`k-input-addon k-input-addon-${side} k-input-addon-${size}`}
|
|
115
|
-
aria-hidden="true"
|
|
116
148
|
>
|
|
117
149
|
{content}
|
|
118
150
|
</span>
|
|
@@ -140,7 +172,7 @@ const Input = (props: InputProps) => {
|
|
|
140
172
|
>
|
|
141
173
|
{prefix !== undefined && renderAddon(prefix, "prefix")}
|
|
142
174
|
<input
|
|
143
|
-
ref={
|
|
175
|
+
ref={setRefs}
|
|
144
176
|
id={inputId}
|
|
145
177
|
name={props.name}
|
|
146
178
|
type={inputType}
|
|
@@ -152,6 +184,7 @@ const Input = (props: InputProps) => {
|
|
|
152
184
|
autoFocus={props.autoFocus}
|
|
153
185
|
autoComplete={props.autoComplete}
|
|
154
186
|
disabled={disabled}
|
|
187
|
+
readOnly={props.readOnly}
|
|
155
188
|
onChange={props.onChange}
|
|
156
189
|
onFocus={props.onFocus}
|
|
157
190
|
onBlur={props.onBlur}
|
|
@@ -159,6 +192,16 @@ const Input = (props: InputProps) => {
|
|
|
159
192
|
onKeyDown={props.onKeyDown}
|
|
160
193
|
className={inputClasses}
|
|
161
194
|
/>
|
|
195
|
+
{showClear && (
|
|
196
|
+
<button
|
|
197
|
+
type="button"
|
|
198
|
+
onClick={handleClear}
|
|
199
|
+
className="k-input-clear-button"
|
|
200
|
+
aria-label="Clear"
|
|
201
|
+
>
|
|
202
|
+
<FontAwesomeIcon icon={faXmark} />
|
|
203
|
+
</button>
|
|
204
|
+
)}
|
|
162
205
|
{suffix !== undefined && renderAddon(suffix, "suffix")}
|
|
163
206
|
{props.dropDown && (
|
|
164
207
|
<DropdownArrowButton
|
|
@@ -51,6 +51,13 @@
|
|
|
51
51
|
box-shadow: 0 0 0 0.1875rem var(--border-shadow-color-active);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/* No opacity, unlike disabled: a read-only value keeps full text contrast. */
|
|
55
|
+
.k-input[readonly]:not(:disabled) {
|
|
56
|
+
background-color: var(--background-color-dark);
|
|
57
|
+
color: var(--text-color-secondary);
|
|
58
|
+
cursor: default;
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
.k-input:disabled {
|
|
55
62
|
background-color: var(--bg-color-disabled);
|
|
56
63
|
color: var(--text-color-disabled);
|
|
@@ -58,9 +65,46 @@
|
|
|
58
65
|
opacity: 0.6;
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
.k-input-clearable {
|
|
69
|
+
padding-right: 2.25rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.k-input-clear-button {
|
|
73
|
+
position: absolute;
|
|
74
|
+
right: 0.5rem;
|
|
75
|
+
top: 50%;
|
|
76
|
+
transform: translateY(-50%);
|
|
77
|
+
display: flex;
|
|
78
|
+
align-items: center;
|
|
79
|
+
justify-content: center;
|
|
80
|
+
width: 1.5rem;
|
|
81
|
+
height: 1.5rem;
|
|
82
|
+
padding: 0;
|
|
83
|
+
border: none;
|
|
84
|
+
border-radius: 50%;
|
|
85
|
+
background: none;
|
|
86
|
+
color: var(--text-color-secondary);
|
|
87
|
+
cursor: pointer;
|
|
88
|
+
transition: color 0.2s ease-in-out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.k-input-clear-button:hover {
|
|
92
|
+
color: var(--primary-color-text);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.k-input-clear-button:focus-visible {
|
|
96
|
+
outline: none;
|
|
97
|
+
box-shadow: 0 0 0 0.125rem var(--border-shadow-color-active);
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
.k-password-toggle-button {
|
|
62
101
|
position: absolute;
|
|
63
|
-
right: 0.
|
|
102
|
+
right: 0.5rem;
|
|
103
|
+
top: 50%;
|
|
104
|
+
transform: translateY(-50%);
|
|
105
|
+
/* 24px hit area (WCAG 2.5.8) around the small eye icon. */
|
|
106
|
+
width: 1.5rem;
|
|
107
|
+
height: 1.5rem;
|
|
64
108
|
background: none;
|
|
65
109
|
border: none;
|
|
66
110
|
cursor: pointer;
|
|
@@ -73,7 +117,7 @@
|
|
|
73
117
|
}
|
|
74
118
|
|
|
75
119
|
.k-password-toggle-button:hover {
|
|
76
|
-
color: var(--primary-color);
|
|
120
|
+
color: var(--primary-color-text);
|
|
77
121
|
}
|
|
78
122
|
|
|
79
123
|
.k-password-toggle-button:focus {
|
|
@@ -179,6 +179,7 @@ const MultiSelect = <T,>(props: MultiSelectProps<T>) => {
|
|
|
179
179
|
<button
|
|
180
180
|
type="button"
|
|
181
181
|
className="k-multiselect-selected-item-remove"
|
|
182
|
+
aria-label={`Remove ${String(v[props.labelKey])}`}
|
|
182
183
|
onClick={(e) => {
|
|
183
184
|
e.stopPropagation();
|
|
184
185
|
handleOptionToggle(v);
|
|
@@ -169,10 +169,14 @@ const PhoneInput = (props: PhoneInputProps) => {
|
|
|
169
169
|
className="k-phone-input-picker-trigger"
|
|
170
170
|
onClick={() => !disabled && setPickerOpen((o) => !o)}
|
|
171
171
|
disabled={disabled}
|
|
172
|
-
|
|
172
|
+
// Starts with the visible "+1" so voice-control users can say what
|
|
173
|
+
// they see (WCAG 2.5.3); the flag is decorative next to the name.
|
|
174
|
+
aria-label={`+${getCountryCallingCode(country)} ${countryName(country)}, change country`}
|
|
173
175
|
aria-expanded={pickerOpen}
|
|
174
176
|
>
|
|
175
|
-
<span className="k-phone-input-flag">
|
|
177
|
+
<span className="k-phone-input-flag" aria-hidden="true">
|
|
178
|
+
{flagEmoji(country)}
|
|
179
|
+
</span>
|
|
176
180
|
<span className="k-phone-input-dial">
|
|
177
181
|
+{getCountryCallingCode(country)}
|
|
178
182
|
</span>
|