athameui 0.0.13 → 0.0.14
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/components/Accordion/Accordion.tsx +23 -23
- package/components/Accordion/AccordionItem.tsx +7 -1
- package/components/Avatar/Avatar.tsx +6 -1
- package/components/Button/Button.tsx +11 -0
- package/components/Button/ScrollToTopButton.tsx +2 -1
- package/components/Carousel/Carousel.tsx +95 -3
- package/components/Checkbox/Checkbox.tsx +51 -0
- package/components/Checkbox/index.ts +1 -0
- package/components/FormField/FormField.tsx +24 -0
- package/components/FormTextInput/FormTextInput.tsx +11 -0
- package/components/Modal/Modal.tsx +7 -0
- package/components/SlideOut/SlideOut.tsx +31 -2
- package/components/index.ts +1 -0
- package/dist/athameui.cjs +166 -19
- package/dist/athameui.cjs.map +1 -1
- package/dist/athameui.mjs +167 -21
- package/dist/athameui.mjs.map +1 -1
- package/dist/components/Accordion/AccordionItem.d.ts.map +1 -1
- package/dist/components/Avatar/Avatar.d.ts.map +1 -1
- package/dist/components/Button/Button.d.ts +5 -0
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Button/ScrollToTopButton.d.ts.map +1 -1
- package/dist/components/Carousel/Carousel.d.ts +4 -1
- package/dist/components/Carousel/Carousel.d.ts.map +1 -1
- package/dist/components/Checkbox/Checkbox.d.ts +14 -0
- package/dist/components/Checkbox/Checkbox.d.ts.map +1 -0
- package/dist/components/Checkbox/index.d.ts +2 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -0
- package/dist/components/FormField/FormField.d.ts +3 -0
- package/dist/components/FormField/FormField.d.ts.map +1 -1
- package/dist/components/FormTextInput/FormTextInput.d.ts +6 -1
- package/dist/components/FormTextInput/FormTextInput.d.ts.map +1 -1
- package/dist/components/Modal/Modal.d.ts.map +1 -1
- package/dist/components/SlideOut/SlideOut.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/styles/components/carousel.css +22 -2
- package/dist/styles/components/checkbox.css +60 -0
- package/dist/styles/theme.css +1 -0
- package/package.json +1 -1
- package/styles/components/carousel.css +22 -2
- package/styles/components/checkbox.css +60 -0
- package/styles/theme.css +1 -0
|
@@ -80,29 +80,29 @@ export const Accordion = ({
|
|
|
80
80
|
<div className={accordionClasses} css={sx?.accordion}>
|
|
81
81
|
{childrenFirst && children}
|
|
82
82
|
<List>
|
|
83
|
-
{items.map((item) =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
)
|
|
83
|
+
{items.map((item) =>
|
|
84
|
+
CustomAccordionItem ? (
|
|
85
|
+
<CustomAccordionItem
|
|
86
|
+
key={item.id}
|
|
87
|
+
expanded={expandedItems.includes(item.id)}
|
|
88
|
+
onToggle={expandToggle}
|
|
89
|
+
{...item}
|
|
90
|
+
/>
|
|
91
|
+
) : (
|
|
92
|
+
<AccordionItem
|
|
93
|
+
key={item.id}
|
|
94
|
+
sx={sx}
|
|
95
|
+
item={item}
|
|
96
|
+
expanded={expandedItems.includes(item.id)}
|
|
97
|
+
onToggle={expandToggle}
|
|
98
|
+
className={
|
|
99
|
+
className?.accordionItem
|
|
100
|
+
? { ...className.accordionItem }
|
|
101
|
+
: undefined
|
|
102
|
+
}
|
|
103
|
+
/>
|
|
104
|
+
),
|
|
105
|
+
)}
|
|
106
106
|
</List>
|
|
107
107
|
{!childrenFirst && children}
|
|
108
108
|
</div>
|
|
@@ -103,14 +103,20 @@ export const AccordionItem = ({
|
|
|
103
103
|
textPosition={headingTextPosition}
|
|
104
104
|
onClick={onToggleHandler}
|
|
105
105
|
disabled={disabled}
|
|
106
|
+
aria-expanded={expanded}
|
|
107
|
+
aria-controls={`accordion-content-${item.id}`}
|
|
106
108
|
sx={sx?.accordionHeading}
|
|
107
109
|
dark
|
|
108
110
|
>
|
|
109
111
|
<div className={titleClasses}>{item.heading}</div>
|
|
110
112
|
</Button>
|
|
111
113
|
<div
|
|
114
|
+
id={`accordion-content-${item.id}`}
|
|
112
115
|
className={expansionClasses}
|
|
113
|
-
style={{
|
|
116
|
+
style={{
|
|
117
|
+
height: expanded ? contentHeight : 0,
|
|
118
|
+
visibility: expanded ? "visible" : "hidden",
|
|
119
|
+
}}
|
|
114
120
|
>
|
|
115
121
|
<div ref={contentRef} className={contentWrapperClasses}>
|
|
116
122
|
{item.content}
|
|
@@ -34,7 +34,12 @@ export const Avatar = ({
|
|
|
34
34
|
// const imageClasses = cx(".ath-avatar-image", className?.image);
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
|
-
<div
|
|
37
|
+
<div
|
|
38
|
+
css={sx?.avatar}
|
|
39
|
+
className={avatarClasses}
|
|
40
|
+
role="img"
|
|
41
|
+
aria-label={displayName}
|
|
42
|
+
>
|
|
38
43
|
<div css={sx?.initials} className={initialsClasses}>
|
|
39
44
|
{initials[0]}
|
|
40
45
|
{initials[1]}
|
|
@@ -30,11 +30,16 @@ export type ButtonProps = {
|
|
|
30
30
|
type?: "button" | "submit" | "reset";
|
|
31
31
|
variant?: ButtonVariant;
|
|
32
32
|
wrap?: boolean;
|
|
33
|
+
"aria-controls"?: string;
|
|
34
|
+
"aria-expanded"?: boolean;
|
|
35
|
+
"aria-label"?: string;
|
|
36
|
+
"aria-pressed"?: boolean;
|
|
33
37
|
|
|
34
38
|
onBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
35
39
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
36
40
|
onFocus?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
37
41
|
onKeyDown?: KeyboardEventHandler<HTMLButtonElement>;
|
|
42
|
+
onMouseDown?: MouseEventHandler<HTMLButtonElement>;
|
|
38
43
|
onMouseEnter?: MouseEventHandler<HTMLButtonElement>;
|
|
39
44
|
onMouseLeave?: MouseEventHandler<HTMLButtonElement>;
|
|
40
45
|
};
|
|
@@ -59,6 +64,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
59
64
|
onClick,
|
|
60
65
|
onFocus,
|
|
61
66
|
onKeyDown,
|
|
67
|
+
onMouseDown,
|
|
62
68
|
onMouseEnter,
|
|
63
69
|
onMouseLeave,
|
|
64
70
|
...other
|
|
@@ -100,6 +106,10 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
100
106
|
onBlur?.(e);
|
|
101
107
|
};
|
|
102
108
|
|
|
109
|
+
const onMouseDownHandler: MouseEventHandler<HTMLButtonElement> = (e) => {
|
|
110
|
+
onMouseDown?.(e);
|
|
111
|
+
};
|
|
112
|
+
|
|
103
113
|
const onMouseEnterHandler: MouseEventHandler<HTMLButtonElement> = (e) => {
|
|
104
114
|
onMouseEnter?.(e);
|
|
105
115
|
};
|
|
@@ -122,6 +132,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
122
132
|
onBlur={onBlurHandler}
|
|
123
133
|
onMouseEnter={onMouseEnterHandler}
|
|
124
134
|
onMouseLeave={onMouseLeaveHandler}
|
|
135
|
+
onMouseDown={onMouseDownHandler}
|
|
125
136
|
{...other}
|
|
126
137
|
>
|
|
127
138
|
{icon ? (
|
|
@@ -50,7 +50,8 @@ export const ScrollToTopButton = ({
|
|
|
50
50
|
data-testid="ath-scroll-to-top-button"
|
|
51
51
|
onClick={scrollToTop}
|
|
52
52
|
variant={variant ?? "secondary"}
|
|
53
|
-
title={title ?? "Back to
|
|
53
|
+
title={title ?? "Back to top"}
|
|
54
|
+
aria-label="Back to top"
|
|
54
55
|
dark={dark}
|
|
55
56
|
>
|
|
56
57
|
<HugeiconsIcon icon={CircleArrowUp02Icon} width="50" height="50" />
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Children,
|
|
3
|
+
isValidElement,
|
|
3
4
|
useState,
|
|
4
5
|
useRef,
|
|
5
6
|
useLayoutEffect,
|
|
7
|
+
type DragEvent,
|
|
6
8
|
type ReactNode,
|
|
7
9
|
} from "react";
|
|
8
10
|
import { CSSObject } from "@emotion/react";
|
|
@@ -10,7 +12,11 @@ import { cx } from "../../utils/cx";
|
|
|
10
12
|
import { Button } from "../Button/Button";
|
|
11
13
|
import { FormLabel } from "../FormLabel/FormLabel";
|
|
12
14
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
13
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
ArrowLeftIcon,
|
|
17
|
+
ArrowRightIcon,
|
|
18
|
+
Delete02Icon,
|
|
19
|
+
} from "@hugeicons/core-free-icons";
|
|
14
20
|
|
|
15
21
|
export type CarouselProps = {
|
|
16
22
|
children?: ReactNode;
|
|
@@ -19,6 +25,9 @@ export type CarouselProps = {
|
|
|
19
25
|
};
|
|
20
26
|
emptyMessage?: string;
|
|
21
27
|
label?: string;
|
|
28
|
+
onRemove?: (index: number) => void;
|
|
29
|
+
onReorder?: (from: number, to: number) => void;
|
|
30
|
+
removeLabel?: string | ((index: number) => string);
|
|
22
31
|
sx?: {
|
|
23
32
|
carousel?: CSSObject;
|
|
24
33
|
};
|
|
@@ -29,14 +38,55 @@ export const Carousel = ({
|
|
|
29
38
|
className,
|
|
30
39
|
emptyMessage = "Nothing here yet",
|
|
31
40
|
label,
|
|
41
|
+
onRemove,
|
|
42
|
+
onReorder,
|
|
43
|
+
removeLabel = "Remove item",
|
|
32
44
|
sx,
|
|
33
45
|
}: CarouselProps) => {
|
|
34
46
|
const [isHovered, setIsHovered] = useState(false);
|
|
35
47
|
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
|
36
48
|
const [showRightArrow, setShowRightArrow] = useState(false);
|
|
49
|
+
const [dropIndex, setDropIndex] = useState<number | null>(null);
|
|
37
50
|
const carouselRef = useRef<HTMLDivElement>(null);
|
|
51
|
+
const dragIndex = useRef<number | null>(null);
|
|
38
52
|
const carouselClasses = cx("ath-carousel", className?.carousel);
|
|
39
|
-
const
|
|
53
|
+
const items = Children.toArray(children);
|
|
54
|
+
const itemCount = items.length;
|
|
55
|
+
const interactive = Boolean(onRemove || onReorder);
|
|
56
|
+
|
|
57
|
+
const finishDrag = () => {
|
|
58
|
+
dragIndex.current = null;
|
|
59
|
+
setDropIndex(null);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleDragStart = (index: number) => (event: DragEvent<HTMLDivElement>) => {
|
|
63
|
+
const target = event.target;
|
|
64
|
+
if (
|
|
65
|
+
target instanceof Element &&
|
|
66
|
+
target.closest(".ath-carousel-remove, .ath-carousel-remove-button")
|
|
67
|
+
) {
|
|
68
|
+
event.preventDefault();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
dragIndex.current = index;
|
|
72
|
+
event.dataTransfer.effectAllowed = "move";
|
|
73
|
+
event.dataTransfer.setData("text/plain", String(index));
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const handleDragOver = (index: number) => (event: DragEvent<HTMLDivElement>) => {
|
|
77
|
+
if (!onReorder) return;
|
|
78
|
+
event.preventDefault();
|
|
79
|
+
event.dataTransfer.dropEffect = "move";
|
|
80
|
+
if (dropIndex !== index) setDropIndex(index);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const handleDrop = (index: number) => (event: DragEvent<HTMLDivElement>) => {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
const from = dragIndex.current;
|
|
86
|
+
finishDrag();
|
|
87
|
+
if (from == null || !onReorder) return;
|
|
88
|
+
onReorder(from, index);
|
|
89
|
+
};
|
|
40
90
|
|
|
41
91
|
useLayoutEffect(() => {
|
|
42
92
|
const el = carouselRef.current;
|
|
@@ -93,12 +143,53 @@ export const Carousel = ({
|
|
|
93
143
|
<Button
|
|
94
144
|
className={{ button: "ath-carousel-arrow ath-carousel-left-arrow" }}
|
|
95
145
|
onClick={() => scrollByPage(-1)}
|
|
146
|
+
aria-label={
|
|
147
|
+
label ? `Scroll ${label} backward` : "Scroll backward"
|
|
148
|
+
}
|
|
96
149
|
icon={<HugeiconsIcon icon={ArrowLeftIcon} size={36} />}
|
|
97
150
|
/>
|
|
98
151
|
)}
|
|
99
152
|
<div className={carouselClasses} css={sx?.carousel} ref={carouselRef}>
|
|
100
153
|
{itemCount > 0 ? (
|
|
101
|
-
|
|
154
|
+
interactive ? (
|
|
155
|
+
items.map((child, index) => (
|
|
156
|
+
<div
|
|
157
|
+
key={isValidElement(child) ? (child.key ?? index) : index}
|
|
158
|
+
className={cx(
|
|
159
|
+
"ath-carousel-slot",
|
|
160
|
+
onReorder && dropIndex === index && "ath-carousel-slot-target",
|
|
161
|
+
)}
|
|
162
|
+
draggable={Boolean(onReorder)}
|
|
163
|
+
onDragStart={onReorder ? handleDragStart(index) : undefined}
|
|
164
|
+
onDragOver={onReorder ? handleDragOver(index) : undefined}
|
|
165
|
+
onDrop={onReorder ? handleDrop(index) : undefined}
|
|
166
|
+
onDragEnd={onReorder ? finishDrag : undefined}
|
|
167
|
+
>
|
|
168
|
+
{onRemove && (
|
|
169
|
+
<Button
|
|
170
|
+
variant="ghost"
|
|
171
|
+
size="small"
|
|
172
|
+
type="button"
|
|
173
|
+
className={{ button: "ath-carousel-remove-button" }}
|
|
174
|
+
icon={<HugeiconsIcon icon={Delete02Icon} size={16} />}
|
|
175
|
+
aria-label={
|
|
176
|
+
typeof removeLabel === "function"
|
|
177
|
+
? removeLabel(index)
|
|
178
|
+
: removeLabel
|
|
179
|
+
}
|
|
180
|
+
onMouseDown={(event) => event.stopPropagation()}
|
|
181
|
+
onClick={(event) => {
|
|
182
|
+
event.stopPropagation();
|
|
183
|
+
onRemove(index);
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
)}
|
|
187
|
+
{child}
|
|
188
|
+
</div>
|
|
189
|
+
))
|
|
190
|
+
) : (
|
|
191
|
+
children
|
|
192
|
+
)
|
|
102
193
|
) : (
|
|
103
194
|
<div className="ath-carousel-empty">{emptyMessage}</div>
|
|
104
195
|
)}
|
|
@@ -109,6 +200,7 @@ export const Carousel = ({
|
|
|
109
200
|
button: "ath-carousel-arrow ath-carousel-right-arrow",
|
|
110
201
|
}}
|
|
111
202
|
onClick={() => scrollByPage(1)}
|
|
203
|
+
aria-label={label ? `Scroll ${label} forward` : "Scroll forward"}
|
|
112
204
|
icon={<HugeiconsIcon icon={ArrowRightIcon} size={36} />}
|
|
113
205
|
/>
|
|
114
206
|
)}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { CSSObject } from "@emotion/react";
|
|
3
|
+
import { cx } from "../../utils/cx";
|
|
4
|
+
|
|
5
|
+
export type CheckboxProps = {
|
|
6
|
+
checked: boolean;
|
|
7
|
+
className?: string;
|
|
8
|
+
dark?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
id?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
sx?: CSSObject;
|
|
14
|
+
onChange: (checked: boolean) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const Checkbox = ({
|
|
18
|
+
checked,
|
|
19
|
+
className,
|
|
20
|
+
dark = false,
|
|
21
|
+
disabled = false,
|
|
22
|
+
id,
|
|
23
|
+
label,
|
|
24
|
+
name,
|
|
25
|
+
sx,
|
|
26
|
+
onChange,
|
|
27
|
+
}: CheckboxProps) => {
|
|
28
|
+
return (
|
|
29
|
+
<label
|
|
30
|
+
className={cx(
|
|
31
|
+
"ath-checkbox",
|
|
32
|
+
dark && "ath-checkbox-dark",
|
|
33
|
+
disabled && "ath-checkbox-disabled",
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
css={sx}
|
|
37
|
+
htmlFor={id}
|
|
38
|
+
>
|
|
39
|
+
<input
|
|
40
|
+
id={id}
|
|
41
|
+
className="ath-checkbox-input"
|
|
42
|
+
type="checkbox"
|
|
43
|
+
name={name}
|
|
44
|
+
checked={checked}
|
|
45
|
+
disabled={disabled}
|
|
46
|
+
onChange={(event) => onChange(event.target.checked)}
|
|
47
|
+
/>
|
|
48
|
+
{label && <span className="ath-checkbox-label">{label}</span>}
|
|
49
|
+
</label>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Checkbox";
|
|
@@ -23,6 +23,7 @@ export type FormFieldProps = {
|
|
|
23
23
|
error?: string;
|
|
24
24
|
helperText?: string | ReactNode;
|
|
25
25
|
id?: string;
|
|
26
|
+
inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
|
|
26
27
|
label?: string | ReactNode;
|
|
27
28
|
labelPosition?: "left" | "right" | "above" | "below";
|
|
28
29
|
name?: string;
|
|
@@ -57,6 +58,8 @@ export type FormFieldProps = {
|
|
|
57
58
|
| React.ChangeEvent<HTMLInputElement>
|
|
58
59
|
| React.ChangeEvent<HTMLTextAreaElement>,
|
|
59
60
|
) => void;
|
|
61
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
|
|
62
|
+
onFocus?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
|
|
60
63
|
onKeyDown?: React.KeyboardEventHandler<
|
|
61
64
|
HTMLInputElement | HTMLTextAreaElement
|
|
62
65
|
>;
|
|
@@ -72,6 +75,7 @@ export const FormField: React.FC<FormFieldProps> = ({
|
|
|
72
75
|
error,
|
|
73
76
|
helperText,
|
|
74
77
|
id,
|
|
78
|
+
inputMode,
|
|
75
79
|
label,
|
|
76
80
|
labelPosition = "above",
|
|
77
81
|
name,
|
|
@@ -85,6 +89,8 @@ export const FormField: React.FC<FormFieldProps> = ({
|
|
|
85
89
|
value,
|
|
86
90
|
|
|
87
91
|
onChange,
|
|
92
|
+
onBlur,
|
|
93
|
+
onFocus,
|
|
88
94
|
onKeyDown,
|
|
89
95
|
...other
|
|
90
96
|
}) => {
|
|
@@ -167,16 +173,29 @@ export const FormField: React.FC<FormFieldProps> = ({
|
|
|
167
173
|
/>
|
|
168
174
|
) : null;
|
|
169
175
|
|
|
176
|
+
const describedBy =
|
|
177
|
+
[
|
|
178
|
+
error && name ? `${name}-error` : "",
|
|
179
|
+
helperText && name ? `${name}-helper` : "",
|
|
180
|
+
]
|
|
181
|
+
.filter(Boolean)
|
|
182
|
+
.join(" ") || undefined;
|
|
183
|
+
|
|
170
184
|
const inputElement =
|
|
171
185
|
type === "textarea" ? (
|
|
172
186
|
<textarea
|
|
173
187
|
autoComplete={autoComplete}
|
|
174
188
|
autoFocus={autoFocus}
|
|
175
189
|
ref={textAreaRef}
|
|
190
|
+
id={id}
|
|
176
191
|
name={name}
|
|
192
|
+
aria-invalid={error ? true : undefined}
|
|
193
|
+
aria-describedby={describedBy}
|
|
177
194
|
onChange={handleTextAreaChange}
|
|
178
195
|
disabled={disabled}
|
|
179
196
|
readOnly={readOnly}
|
|
197
|
+
onBlur={onBlur}
|
|
198
|
+
onFocus={onFocus}
|
|
180
199
|
onKeyDown={onKeyDown}
|
|
181
200
|
tabIndex={disabled ? -1 : tabIndex}
|
|
182
201
|
className={textAreaClasses}
|
|
@@ -190,10 +209,13 @@ export const FormField: React.FC<FormFieldProps> = ({
|
|
|
190
209
|
autoComplete={autoComplete}
|
|
191
210
|
autoFocus={autoFocus}
|
|
192
211
|
name={name}
|
|
212
|
+
inputMode={inputMode}
|
|
193
213
|
type={type}
|
|
194
214
|
onChange={handleInputChange}
|
|
195
215
|
disabled={disabled}
|
|
196
216
|
readOnly={readOnly}
|
|
217
|
+
onBlur={onBlur}
|
|
218
|
+
onFocus={onFocus}
|
|
197
219
|
onKeyDown={onKeyDown}
|
|
198
220
|
tabIndex={disabled ? -1 : tabIndex}
|
|
199
221
|
className={{ input: inputClasses }}
|
|
@@ -202,6 +224,8 @@ export const FormField: React.FC<FormFieldProps> = ({
|
|
|
202
224
|
placeholder={placeholder}
|
|
203
225
|
size={size}
|
|
204
226
|
id={id}
|
|
227
|
+
aria-invalid={error ? true : undefined}
|
|
228
|
+
aria-describedby={describedBy}
|
|
205
229
|
{...other}
|
|
206
230
|
/>
|
|
207
231
|
);
|
|
@@ -2,6 +2,8 @@ import { CSSObject } from "@emotion/react";
|
|
|
2
2
|
import { cx } from "../../utils/cx";
|
|
3
3
|
|
|
4
4
|
type FormTextInputProps = {
|
|
5
|
+
"aria-describedby"?: string;
|
|
6
|
+
"aria-invalid"?: boolean;
|
|
5
7
|
autoComplete?: string;
|
|
6
8
|
autoFocus?: boolean;
|
|
7
9
|
className?: {
|
|
@@ -11,6 +13,7 @@ type FormTextInputProps = {
|
|
|
11
13
|
disabled?: boolean;
|
|
12
14
|
error?: string;
|
|
13
15
|
id?: string;
|
|
16
|
+
inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
|
|
14
17
|
name?: string;
|
|
15
18
|
placeholder?: string;
|
|
16
19
|
readOnly?: boolean;
|
|
@@ -31,6 +34,8 @@ type FormTextInputProps = {
|
|
|
31
34
|
value?: string | number;
|
|
32
35
|
|
|
33
36
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
37
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
38
|
+
onFocus?: React.FocusEventHandler<HTMLInputElement>;
|
|
34
39
|
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
|
|
35
40
|
};
|
|
36
41
|
|
|
@@ -42,6 +47,7 @@ export const FormTextInput = ({
|
|
|
42
47
|
error,
|
|
43
48
|
disabled,
|
|
44
49
|
id,
|
|
50
|
+
inputMode,
|
|
45
51
|
name,
|
|
46
52
|
placeholder,
|
|
47
53
|
readOnly,
|
|
@@ -52,6 +58,8 @@ export const FormTextInput = ({
|
|
|
52
58
|
value,
|
|
53
59
|
|
|
54
60
|
onChange,
|
|
61
|
+
onBlur,
|
|
62
|
+
onFocus,
|
|
55
63
|
onKeyDown,
|
|
56
64
|
...other
|
|
57
65
|
}: FormTextInputProps) => {
|
|
@@ -79,12 +87,15 @@ export const FormTextInput = ({
|
|
|
79
87
|
css={sx?.input}
|
|
80
88
|
disabled={disabled}
|
|
81
89
|
name={name}
|
|
90
|
+
inputMode={inputMode}
|
|
82
91
|
placeholder={placeholder}
|
|
83
92
|
readOnly={readOnly}
|
|
84
93
|
tabIndex={disabled ? -1 : tabIndex}
|
|
85
94
|
type={type}
|
|
86
95
|
value={value}
|
|
87
96
|
onChange={onChangeHandler}
|
|
97
|
+
onBlur={onBlur}
|
|
98
|
+
onFocus={onFocus}
|
|
88
99
|
onKeyDown={onKeyDown}
|
|
89
100
|
id={id}
|
|
90
101
|
{...other}
|
|
@@ -52,6 +52,9 @@ export const Modal: React.FC<ModalProps> = ({
|
|
|
52
52
|
trapFocus = true,
|
|
53
53
|
initialFocusRef,
|
|
54
54
|
finalFocusRef,
|
|
55
|
+
ariaLabel,
|
|
56
|
+
ariaLabelledBy,
|
|
57
|
+
ariaDescribedBy,
|
|
55
58
|
sx,
|
|
56
59
|
|
|
57
60
|
onClose,
|
|
@@ -176,6 +179,10 @@ export const Modal: React.FC<ModalProps> = ({
|
|
|
176
179
|
css={sx?.modal}
|
|
177
180
|
className={modalClasses}
|
|
178
181
|
role="dialog"
|
|
182
|
+
aria-modal="true"
|
|
183
|
+
aria-labelledby={ariaLabelledBy || titleId}
|
|
184
|
+
aria-label={ariaLabelledBy || titleId ? undefined : ariaLabel}
|
|
185
|
+
aria-describedby={ariaDescribedBy}
|
|
179
186
|
{...other}
|
|
180
187
|
>
|
|
181
188
|
{(title || showCloseButton) && (
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CSSObject } from "@emotion/react";
|
|
2
2
|
import { HugeiconsIcon } from "@hugeicons/react";
|
|
3
3
|
import { CancelCircleIcon } from "@hugeicons/core-free-icons";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
4
5
|
import { cx } from "../../utils/cx";
|
|
5
6
|
import { Button } from "../Button/Button";
|
|
6
7
|
|
|
@@ -49,11 +50,39 @@ export const SlideOut = ({
|
|
|
49
50
|
"ath-slide-out-close-button",
|
|
50
51
|
className?.closeButton,
|
|
51
52
|
);
|
|
53
|
+
const slideOutRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const element = slideOutRef.current;
|
|
57
|
+
if (!element) return;
|
|
58
|
+
if (isOpen) element.removeAttribute("inert");
|
|
59
|
+
else element.setAttribute("inert", "");
|
|
60
|
+
}, [isOpen]);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!isOpen) return;
|
|
64
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
65
|
+
if (event.key === "Escape") onClose();
|
|
66
|
+
};
|
|
67
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
68
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
69
|
+
}, [isOpen, onClose]);
|
|
52
70
|
|
|
53
71
|
return (
|
|
54
|
-
<div
|
|
72
|
+
<div
|
|
73
|
+
ref={slideOutRef}
|
|
74
|
+
className={slideOutClasses}
|
|
75
|
+
css={sx?.slideOut}
|
|
76
|
+
data-testid="slide-out"
|
|
77
|
+
>
|
|
55
78
|
<div className={overlayClasses} css={sx?.overlay} onClick={onClose} />
|
|
56
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
className={panelClasses}
|
|
81
|
+
css={sx?.panel}
|
|
82
|
+
role="dialog"
|
|
83
|
+
aria-modal="true"
|
|
84
|
+
aria-label="Menu"
|
|
85
|
+
>
|
|
57
86
|
<Button
|
|
58
87
|
className={{ button: closeButtonClasses }}
|
|
59
88
|
sx={{ button: sx?.closeButton }}
|
package/components/index.ts
CHANGED