athameui 0.0.5 → 0.0.6-c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/components/Avatar/Avatar.tsx +51 -0
  2. package/components/Avatar/index.ts +1 -0
  3. package/components/Button/Button.tsx +6 -18
  4. package/components/Button/button.variants.ts +0 -9
  5. package/components/Button/index.ts +0 -1
  6. package/components/{Button → ButtonGroup}/ButtonGroup.tsx +3 -13
  7. package/components/ButtonGroup/ButtonGroupProps.ts +12 -0
  8. package/components/ButtonGroup/buttonGroup.variants.ts +8 -0
  9. package/components/ButtonGroup/index.ts +1 -0
  10. package/components/FormElements/FormInput.tsx +7 -1
  11. package/components/List/List.tsx +12 -32
  12. package/components/Modal/Modal.tsx +206 -0
  13. package/components/Modal/index.ts +1 -0
  14. package/components/Modals/Modal.tsx +206 -0
  15. package/components/index.ts +7 -6
  16. package/dist/athameui.cjs +393 -235
  17. package/dist/athameui.cjs.map +1 -1
  18. package/dist/athameui.mjs +393 -237
  19. package/dist/athameui.mjs.map +1 -1
  20. package/dist/components/Avatar/Avatar.d.ts +17 -0
  21. package/dist/components/Avatar/Avatar.d.ts.map +1 -0
  22. package/dist/components/Avatar/index.d.ts +2 -0
  23. package/dist/components/Avatar/index.d.ts.map +1 -0
  24. package/dist/components/Button/Button.d.ts +2 -4
  25. package/dist/components/Button/Button.d.ts.map +1 -1
  26. package/dist/components/Button/button.variants.d.ts +0 -7
  27. package/dist/components/Button/button.variants.d.ts.map +1 -1
  28. package/dist/components/Button/index.d.ts +0 -1
  29. package/dist/components/Button/index.d.ts.map +1 -1
  30. package/dist/components/ButtonGroup/ButtonGroup.d.ts +3 -0
  31. package/dist/components/ButtonGroup/ButtonGroup.d.ts.map +1 -0
  32. package/dist/components/{Button/ButtonGroup.d.ts → ButtonGroup/ButtonGroupProps.d.ts} +2 -4
  33. package/dist/components/ButtonGroup/ButtonGroupProps.d.ts.map +1 -0
  34. package/dist/components/ButtonGroup/buttonGroup.variants.d.ts +8 -0
  35. package/dist/components/ButtonGroup/buttonGroup.variants.d.ts.map +1 -0
  36. package/dist/components/ButtonGroup/index.d.ts +2 -0
  37. package/dist/components/ButtonGroup/index.d.ts.map +1 -0
  38. package/dist/components/FormElements/FormInput.d.ts +2 -1
  39. package/dist/components/FormElements/FormInput.d.ts.map +1 -1
  40. package/dist/components/List/List.d.ts +8 -8
  41. package/dist/components/List/List.d.ts.map +1 -1
  42. package/dist/components/Modal/Modal.d.ts +37 -0
  43. package/dist/components/Modal/Modal.d.ts.map +1 -0
  44. package/dist/components/Modal/index.d.ts +2 -0
  45. package/dist/components/Modal/index.d.ts.map +1 -0
  46. package/dist/components/Modals/Modal.d.ts +37 -0
  47. package/dist/components/Modals/Modal.d.ts.map +1 -0
  48. package/dist/components/index.d.ts +7 -4
  49. package/dist/components/index.d.ts.map +1 -1
  50. package/dist/styles/components/avatar.css +22 -0
  51. package/dist/styles/components/button-group.css +16 -0
  52. package/dist/styles/components/button.css +0 -14
  53. package/dist/styles/components/form-elements.css +1 -1
  54. package/dist/styles/components/modals.css +60 -0
  55. package/dist/styles/theme.css +7 -4
  56. package/package.json +1 -1
  57. package/styles/components/avatar.css +22 -0
  58. package/styles/components/button-group.css +16 -0
  59. package/styles/components/button.css +0 -14
  60. package/styles/components/form-elements.css +1 -1
  61. package/styles/components/modals.css +60 -0
  62. package/styles/theme.css +7 -4
  63. package/dist/components/Button/ButtonGroup.d.ts.map +0 -1
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { CSSObject } from "@emotion/react";
3
+ import { cx } from "../../utils/cx";
4
+
5
+ type AvatarProps = {
6
+ displayName: string;
7
+ imageUrl?: string;
8
+ className?: {
9
+ avatar?: string;
10
+ initials?: string;
11
+ // image?: string;
12
+ };
13
+ size?: number;
14
+ sx?: {
15
+ avatar?: CSSObject;
16
+ initials?: CSSObject;
17
+ // image?: CSSObject;
18
+ };
19
+ };
20
+
21
+ // TODO: Add images later
22
+ export const Avatar = ({
23
+ className,
24
+ displayName = "Display Name",
25
+ sx,
26
+ size = 40,
27
+ }: AvatarProps) => {
28
+ const initials = displayName.split(" ").map((n) => n[0].toUpperCase());
29
+ const avatarClasses = cx(
30
+ ".ath-avatar",
31
+ `width-${size}px`,
32
+ `height-${size}px`,
33
+ `line-height-${size}px`,
34
+ className?.avatar,
35
+ );
36
+ const initialsClasses = cx(
37
+ ".ath-avatar-initials",
38
+ `font-size-${size / 2}px`,
39
+ className?.initials,
40
+ );
41
+ // const imageClasses = cx(".ath-avatar-image", className?.image);
42
+
43
+ return (
44
+ <div css={sx?.avatar} className={avatarClasses}>
45
+ <div css={sx?.initials} className={initialsClasses}>
46
+ {initials[0]}
47
+ {initials[1]}
48
+ </div>
49
+ </div>
50
+ );
51
+ };
@@ -0,0 +1 @@
1
+ export * from './Avatar';
@@ -14,14 +14,14 @@ import {
14
14
  import { CSSObject } from "@emotion/react";
15
15
 
16
16
  export type ButtonProps = {
17
- children?: ReactElement | string;
17
+ children?: React.ReactNode | string;
18
18
  className?: {
19
19
  button?: string | false | null | undefined;
20
20
  icon?: string | false | null | undefined;
21
21
  };
22
22
  dark?: boolean;
23
23
  disabled?: boolean;
24
- icon?: React.ComponentType<{ className?: string }> | string;
24
+ icon?: ReactElement | string;
25
25
  size?: ButtonSize;
26
26
  sx?: CSSObject | { button?: CSSObject; icon?: CSSObject };
27
27
  title?: string;
@@ -43,7 +43,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
43
43
  className,
44
44
  dark = false,
45
45
  disabled = false,
46
- icon: Icon,
46
+ icon,
47
47
  size = "medium",
48
48
  sx,
49
49
  tabIndex,
@@ -59,7 +59,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
59
59
  },
60
60
  ref,
61
61
  ) => {
62
- const isIconOnly = Icon && !children;
62
+ const isIconOnly = icon && !children;
63
63
 
64
64
  const sharedClasses = cx(
65
65
  "ath-button",
@@ -77,14 +77,6 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
77
77
  : className,
78
78
  );
79
79
 
80
- const iconClasses = cx(
81
- sharedClasses,
82
- "ath-button-icon",
83
- typeof className === "object" && className !== null
84
- ? className.icon
85
- : undefined,
86
- );
87
-
88
80
  const onClickHandler: MouseEventHandler<HTMLButtonElement> = (e) => {
89
81
  onClick?.(e);
90
82
  };
@@ -115,13 +107,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
115
107
  onBlur={onBlurHandler}
116
108
  {...rest}
117
109
  >
118
- {Icon ? (
110
+ {icon ? (
119
111
  <>
120
- <Icon
121
- className={iconClasses}
122
- css={sx?.icon ? sx.icon : undefined}
123
- />
124
-
112
+ {icon}
125
113
  {!isIconOnly && children}
126
114
  </>
127
115
  ) : (
@@ -20,19 +20,10 @@ export const buttonVariants = {
20
20
  outline: "ath-button-outline",
21
21
  ghost: "ath-button-ghost",
22
22
  },
23
-
24
23
  dark: "ath-button-dark",
25
24
  } as const;
26
25
 
27
- export const ButtonGroupVariants = {
28
- direction: {
29
- row: "ath-button-group-direction-row",
30
- column: "ath-button-group-direction-column",
31
- },
32
- } as const;
33
-
34
26
  export type ButtonSize = keyof typeof buttonVariants.size;
35
27
  export type ButtonVariant = keyof typeof buttonVariants.variant;
36
28
  export type ButtonDark = typeof buttonVariants.dark;
37
29
  export type ButtonTextPosition = keyof typeof buttonVariants.textPosition;
38
- export type ButtonGroupDirection = keyof typeof ButtonGroupVariants.direction;
@@ -1,3 +1,2 @@
1
1
  export * from "./Button";
2
- export * from "./ButtonGroup";
3
2
  export * from "./ScrollToTopButton";
@@ -1,16 +1,6 @@
1
- import { CSSObject } from "@emotion/react";
2
1
  import { cx } from "../../utils";
3
-
4
- type ButtonGroupProps = {
5
- children: React.ReactNode;
6
- className?: {
7
- buttonGroup?: string | false | null | undefined;
8
- };
9
- direction?: "row" | "column";
10
- sx?: {
11
- buttonGroup?: CSSObject | string;
12
- };
13
- };
2
+ import { ButtonGroupVariants } from "./buttonGroup.variants";
3
+ import type { ButtonGroupProps } from "./ButtonGroupProps";
14
4
 
15
5
  export const ButtonGroup = ({
16
6
  children,
@@ -20,7 +10,7 @@ export const ButtonGroup = ({
20
10
  }: ButtonGroupProps) => {
21
11
  const buttonGroupClasses = cx(
22
12
  "ath-button-group",
23
- `ath-button-group-direction-${direction}`,
13
+ ButtonGroupVariants.direction[direction],
24
14
  className?.buttonGroup ? className.buttonGroup : undefined,
25
15
  );
26
16
 
@@ -0,0 +1,12 @@
1
+ import { CSSObject } from "@emotion/react";
2
+
3
+ export type ButtonGroupProps = {
4
+ children: React.ReactNode;
5
+ className?: {
6
+ buttonGroup?: string | false | null | undefined;
7
+ };
8
+ direction?: "row" | "column";
9
+ sx?: {
10
+ buttonGroup?: CSSObject | string;
11
+ };
12
+ };
@@ -0,0 +1,8 @@
1
+ export const ButtonGroupVariants = {
2
+ direction: {
3
+ row: "ath-button-group-direction-row",
4
+ column: "ath-button-group-direction-column",
5
+ },
6
+ } as const;
7
+
8
+ export type ButtonGroupDirection = keyof typeof ButtonGroupVariants.direction;
@@ -0,0 +1 @@
1
+ export * from "./ButtonGroup";
@@ -4,6 +4,7 @@ import { cx } from "../../utils/cx";
4
4
  import { FormInputLabel } from "./FormInputLabel";
5
5
 
6
6
  export type FormInputProps = {
7
+ autoComplete?: string;
7
8
  autoFocus?: boolean;
8
9
  autoResize?: boolean;
9
10
  className?: {
@@ -48,7 +49,9 @@ export type FormInputProps = {
48
49
  value?: string | number;
49
50
 
50
51
  onChange: (
51
- e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
52
+ e:
53
+ | React.ChangeEvent<HTMLInputElement>
54
+ | React.ChangeEvent<HTMLTextAreaElement>,
52
55
  ) => void;
53
56
  onKeyDown?: React.KeyboardEventHandler<
54
57
  HTMLInputElement | HTMLTextAreaElement
@@ -56,6 +59,7 @@ export type FormInputProps = {
56
59
  };
57
60
 
58
61
  export const FormInput: React.FC<FormInputProps> = ({
62
+ autoComplete,
59
63
  autoFocus = false,
60
64
  autoResize = false,
61
65
  className,
@@ -159,6 +163,7 @@ export const FormInput: React.FC<FormInputProps> = ({
159
163
  const inputElement =
160
164
  type === "textarea" ? (
161
165
  <textarea
166
+ autoComplete={autoComplete}
162
167
  autoFocus={autoFocus}
163
168
  ref={textAreaRef}
164
169
  name={name}
@@ -175,6 +180,7 @@ export const FormInput: React.FC<FormInputProps> = ({
175
180
  />
176
181
  ) : (
177
182
  <input
183
+ autoComplete={autoComplete}
178
184
  autoFocus={autoFocus}
179
185
  name={name}
180
186
  type={type}
@@ -1,52 +1,32 @@
1
1
  import type { ReactElement } from "react";
2
+ import { CSSObject } from "@emotion/react";
2
3
  import { cx } from "../../utils/cx";
3
4
 
4
5
  type ListProps = {
5
6
  children?: ReactElement[] | string[];
6
- className?:
7
- | string
8
- | {
9
- list?: string | false | null | undefined;
10
- }
11
- | false
12
- | undefined
13
- | null;
14
- ariaLabel?: string;
15
- ariaLabelledBy?: string;
16
- ariaDescribedBy?: string;
17
- role?: "list" | "menu" | "menubar" | "tablist" | "tree" | "grid";
18
- dataTestId?: string;
7
+ className?: {
8
+ list?: string | false | null | undefined;
9
+ };
19
10
  ordered?: boolean;
11
+ role?: "list" | "menu" | "menubar" | "tablist" | "tree" | "grid";
12
+ sx?: {
13
+ list?: CSSObject;
14
+ };
20
15
  };
21
16
 
22
17
  export const List = ({
23
18
  className,
24
19
  children,
25
- ariaLabel,
26
- ariaLabelledBy,
27
- ariaDescribedBy,
28
- role = "list",
29
- dataTestId,
30
20
  ordered = false,
21
+ role = "list",
22
+ sx,
31
23
  }: ListProps) => {
32
24
  const ListType = ordered ? "ol" : "ul";
33
25
 
34
- const classes = cx(
35
- "ath-list",
36
- typeof className === "object" && className !== null
37
- ? className.list
38
- : className,
39
- );
26
+ const classes = cx("ath-list", className?.list);
40
27
 
41
28
  return (
42
- <ListType
43
- className={classes}
44
- role={role}
45
- aria-label={ariaLabel}
46
- aria-labelledby={ariaLabelledBy}
47
- aria-describedby={ariaDescribedBy}
48
- data-testid={dataTestId}
49
- >
29
+ <ListType className={classes} role={role} css={sx?.list}>
50
30
  {children}
51
31
  </ListType>
52
32
  );
@@ -0,0 +1,206 @@
1
+ import React from "react";
2
+ import { CSSObject } from "@emotion/react";
3
+ import { HugeiconsIcon } from "@hugeicons/react";
4
+ import { CancelCircleIcon } from "@hugeicons/core-free-icons";
5
+ import { Button } from "../Button/Button";
6
+ import { cx } from "../../main";
7
+
8
+ export type ModalProps = {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ children: React.ReactNode;
12
+ title?: string;
13
+ maxWidth?: string;
14
+ showCloseButton?: boolean;
15
+ dataTestId?: string;
16
+ className?: {
17
+ backdrop?: string;
18
+ modal?: string;
19
+ header?: string;
20
+ title?: string;
21
+ content?: string;
22
+ closeButton?: string;
23
+ };
24
+ closeOnBackdropClick?: boolean;
25
+ closeOnEscape?: boolean;
26
+ trapFocus?: boolean;
27
+ initialFocusRef?: React.RefObject<HTMLElement>;
28
+ finalFocusRef?: React.RefObject<HTMLElement>;
29
+ ariaLabel?: string;
30
+ ariaLabelledBy?: string;
31
+ ariaDescribedBy?: string;
32
+ sx?: {
33
+ backdrop?: CSSObject;
34
+ modal?: CSSObject;
35
+ header?: CSSObject;
36
+ title?: CSSObject;
37
+ content?: CSSObject;
38
+ closeButton?: CSSObject;
39
+ };
40
+ };
41
+
42
+ export const Modal: React.FC<ModalProps> = ({
43
+ isOpen,
44
+ children,
45
+ title,
46
+ showCloseButton = true,
47
+ dataTestId,
48
+ className,
49
+ closeOnBackdropClick = true,
50
+ closeOnEscape = true,
51
+ trapFocus = true,
52
+ initialFocusRef,
53
+ finalFocusRef,
54
+ sx,
55
+
56
+ onClose,
57
+ ...rest
58
+ }) => {
59
+ const [previouslyFocusedElement, setPreviouslyFocusedElement] =
60
+ React.useState<HTMLElement | null>(null);
61
+ const modalRef = React.useRef<HTMLDivElement>(null);
62
+
63
+ const backdropClasses = cx("ath-modal-backdrop", className?.backdrop);
64
+ const modalClasses = cx("ath-modal", className?.modal);
65
+ const headerClasses = cx("ath-modal-header", className?.header);
66
+ const titleClasses = cx("ath-modal-title", className?.title);
67
+ const contentClasses = cx("ath-modal-content", className?.content);
68
+ const closeButtonClasses = cx(
69
+ "ath-modal-close-button",
70
+ className?.closeButton,
71
+ );
72
+
73
+ const handleBackdropClick = (e: React.MouseEvent) => {
74
+ if (closeOnBackdropClick && e.target === e.currentTarget) {
75
+ onClose();
76
+ }
77
+ };
78
+
79
+ // Focus trap implementation
80
+ const handleKeyDown = (e: React.KeyboardEvent) => {
81
+ if (!trapFocus || e.key !== "Tab") return;
82
+
83
+ const modalElement = modalRef.current;
84
+ if (!modalElement) return;
85
+
86
+ const focusableElements = modalElement.querySelectorAll(
87
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])',
88
+ );
89
+ const firstElement = focusableElements[0] as HTMLElement;
90
+ const lastElement = focusableElements[
91
+ focusableElements.length - 1
92
+ ] as HTMLElement;
93
+
94
+ if (e.shiftKey) {
95
+ if (document.activeElement === firstElement) {
96
+ e.preventDefault();
97
+ lastElement?.focus();
98
+ }
99
+ } else {
100
+ if (document.activeElement === lastElement) {
101
+ e.preventDefault();
102
+ firstElement?.focus();
103
+ }
104
+ }
105
+ };
106
+
107
+ React.useEffect(() => {
108
+ if (isOpen) {
109
+ setPreviouslyFocusedElement(document.activeElement as HTMLElement);
110
+
111
+ document.body.style.overflow = "hidden";
112
+
113
+ setTimeout(() => {
114
+ if (initialFocusRef?.current) {
115
+ initialFocusRef.current.focus();
116
+ } else {
117
+ const modalElement = modalRef.current;
118
+ if (modalElement) {
119
+ const firstFocusable = modalElement.querySelector(
120
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
121
+ ) as HTMLElement;
122
+ if (firstFocusable) {
123
+ firstFocusable.focus();
124
+ }
125
+ }
126
+ }
127
+ }, 0);
128
+ } else {
129
+ document.body.style.overflow = "";
130
+
131
+ if (finalFocusRef?.current) {
132
+ finalFocusRef.current.focus();
133
+ } else if (previouslyFocusedElement) {
134
+ previouslyFocusedElement.focus();
135
+ }
136
+ }
137
+
138
+ const handleEscape = (e: KeyboardEvent) => {
139
+ if (closeOnEscape && e.key === "Escape") {
140
+ e.preventDefault();
141
+ onClose();
142
+ }
143
+ };
144
+
145
+ if (isOpen) {
146
+ document.addEventListener("keydown", handleEscape);
147
+ }
148
+
149
+ return () => {
150
+ document.removeEventListener("keydown", handleEscape);
151
+ document.body.style.overflow = "";
152
+ };
153
+ }, [
154
+ isOpen,
155
+ onClose,
156
+ closeOnEscape,
157
+ initialFocusRef,
158
+ finalFocusRef,
159
+ previouslyFocusedElement,
160
+ ]);
161
+
162
+ if (!isOpen) return null;
163
+
164
+ const titleId = title ? `${dataTestId || "modal"}-title` : undefined;
165
+
166
+ return (
167
+ <div
168
+ className={backdropClasses}
169
+ onClick={handleBackdropClick}
170
+ onKeyDown={handleKeyDown}
171
+ css={sx?.backdrop}
172
+ >
173
+ <div
174
+ ref={modalRef}
175
+ css={sx?.modal}
176
+ className={modalClasses}
177
+ role="dialog"
178
+ {...rest}
179
+ >
180
+ {(title || showCloseButton) && (
181
+ <div css={sx?.header} className={headerClasses}>
182
+ {title && (
183
+ <h1 css={sx?.title} className={titleClasses} id={titleId}>
184
+ {title}
185
+ </h1>
186
+ )}
187
+ {showCloseButton && (
188
+ <Button
189
+ className={{ button: closeButtonClasses }}
190
+ sx={{ button: sx?.closeButton }}
191
+ onClick={onClose}
192
+ aria-label="Close modal"
193
+ variant="ghost"
194
+ size="medium"
195
+ icon={<HugeiconsIcon icon={CancelCircleIcon} size={24} />}
196
+ />
197
+ )}
198
+ </div>
199
+ )}
200
+ <div css={sx?.content} className={contentClasses}>
201
+ {children}
202
+ </div>
203
+ </div>
204
+ </div>
205
+ );
206
+ };
@@ -0,0 +1 @@
1
+ export * from "./Modal";