athameui 0.0.2

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 (54) hide show
  1. package/README.md +7 -0
  2. package/components/Accordion/Accordion.tsx +81 -0
  3. package/components/Accordion/AccordionItem.tsx +65 -0
  4. package/components/Accordion/index.ts +2 -0
  5. package/components/Button/BackToTopButton.tsx +53 -0
  6. package/components/Button/Button.tsx +114 -0
  7. package/components/Button/ButtonGroup.tsx +13 -0
  8. package/components/Button/ButtonPrimitive.tsx +16 -0
  9. package/components/Button/button.variants.ts +30 -0
  10. package/components/Button/index.ts +3 -0
  11. package/components/Button/stories/Buttons.stories.tsx +81 -0
  12. package/components/Header/Header.tsx +12 -0
  13. package/components/Header/index.ts +1 -0
  14. package/components/List/List.tsx +44 -0
  15. package/components/List/ListItem.tsx +52 -0
  16. package/components/index.ts +5 -0
  17. package/dist/athameui.cjs +188 -0
  18. package/dist/athameui.cjs.map +1 -0
  19. package/dist/athameui.mjs +181 -0
  20. package/dist/athameui.mjs.map +1 -0
  21. package/dist/components/Accordion/Accordion.d.ts +15 -0
  22. package/dist/components/Accordion/Accordion.d.ts.map +1 -0
  23. package/dist/components/Accordion/AccordionItem.d.ts +15 -0
  24. package/dist/components/Accordion/AccordionItem.d.ts.map +1 -0
  25. package/dist/components/Accordion/index.d.ts +3 -0
  26. package/dist/components/Accordion/index.d.ts.map +1 -0
  27. package/dist/components/Button/BackToTopButton.d.ts +7 -0
  28. package/dist/components/Button/BackToTopButton.d.ts.map +1 -0
  29. package/dist/components/Button/Button.d.ts +48 -0
  30. package/dist/components/Button/Button.d.ts.map +1 -0
  31. package/dist/components/Button/ButtonGroup.d.ts +8 -0
  32. package/dist/components/Button/ButtonGroup.d.ts.map +1 -0
  33. package/dist/components/Button/ButtonPrimitive.d.ts +3 -0
  34. package/dist/components/Button/ButtonPrimitive.d.ts.map +1 -0
  35. package/dist/components/Button/button.variants.d.ts +29 -0
  36. package/dist/components/Button/button.variants.d.ts.map +1 -0
  37. package/dist/components/Button/index.d.ts +4 -0
  38. package/dist/components/Button/index.d.ts.map +1 -0
  39. package/dist/components/Header/Header.d.ts +7 -0
  40. package/dist/components/Header/Header.d.ts.map +1 -0
  41. package/dist/components/Header/index.d.ts +2 -0
  42. package/dist/components/Header/index.d.ts.map +1 -0
  43. package/dist/components/List/List.d.ts +18 -0
  44. package/dist/components/List/List.d.ts.map +1 -0
  45. package/dist/components/List/ListItem.d.ts +20 -0
  46. package/dist/components/List/ListItem.d.ts.map +1 -0
  47. package/dist/components/index.d.ts +4 -0
  48. package/dist/components/index.d.ts.map +1 -0
  49. package/dist/main.d.ts +2 -0
  50. package/dist/main.d.ts.map +1 -0
  51. package/dist/utils/cx.d.ts +2 -0
  52. package/dist/utils/cx.d.ts.map +1 -0
  53. package/main.ts +1 -0
  54. package/package.json +73 -0
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # athameUI 🗡️
2
+
3
+ A sharp React component library that's a cut above... sorry, not sorry.
4
+
5
+ This project is still very much a work in progress. Primarily, I'm using it for one of my other projects, so, for the time being, I'm only creating components as I need them.
6
+
7
+ But feel free to look about. You can clone it and try it out, if you feel so inclined.
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import { useState } from "react";
3
+ import { AccordionItem } from "./AccordionItem";
4
+ import type { AccordionItemType } from "./AccordionItem";
5
+ import { cx } from "../../utils/cx";
6
+
7
+ type AccordionProps = {
8
+ children?: React.ReactNode;
9
+ items: AccordionItemType[];
10
+ childrenFirst?: boolean;
11
+ expand?: "one" | "any";
12
+ className?:
13
+ | string
14
+ | {
15
+ accordion?: string | false | null | undefined;
16
+ accordionItem?: string | false | null | undefined;
17
+ }
18
+ | false
19
+ | undefined
20
+ | null;
21
+ expandToggleCallback?: (expandedItems: (string | number)[]) => void;
22
+ };
23
+
24
+ export const Accordion = ({
25
+ children,
26
+ className,
27
+ items,
28
+ childrenFirst,
29
+ expand = "one",
30
+
31
+ expandToggleCallback,
32
+ }: AccordionProps) => {
33
+ const [expandedItems, setExpandedItems] = useState<(string | number)[]>([]);
34
+ const classes = cx(
35
+ "ath-accordion",
36
+ typeof className === "object" && className !== null
37
+ ? className.accordion
38
+ : className
39
+ );
40
+
41
+ const expandToggle = (itemId: string | number) => {
42
+ console.log(
43
+ "Rendering AccordionItem:",
44
+ itemId,
45
+ "Expanded:",
46
+ expandedItems.includes(itemId)
47
+ );
48
+ if (expandedItems.includes(itemId)) {
49
+ setExpandedItems(expandedItems.filter((id) => id !== itemId));
50
+ } else {
51
+ if (expand === "one") {
52
+ setExpandedItems([itemId]);
53
+ } else if (expand === "any") {
54
+ setExpandedItems([...expandedItems, itemId]);
55
+ }
56
+ }
57
+ if (expandToggleCallback) {
58
+ expandToggleCallback(expandedItems);
59
+ }
60
+ };
61
+
62
+ return (
63
+ <div className={classes}>
64
+ {childrenFirst && children}
65
+ {items.map((item) => (
66
+ <AccordionItem
67
+ key={item.id}
68
+ item={item}
69
+ expanded={expandedItems.includes(item.id)}
70
+ onToggle={expandToggle}
71
+ className={
72
+ typeof className === "object" && className !== null
73
+ ? className.accordionItem
74
+ : undefined
75
+ }
76
+ />
77
+ ))}
78
+ {!childrenFirst && children}
79
+ </div>
80
+ );
81
+ };
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { useRef } from "react";
3
+ import { Button } from "../Button/Button";
4
+ import { cx } from "../../utils/cx";
5
+
6
+ export type AccordionItemType = {
7
+ id: string | number;
8
+ heading: string;
9
+ content: React.ReactNode;
10
+ };
11
+
12
+ export type AccordionItemProps = {
13
+ item: AccordionItemType;
14
+ expanded: boolean;
15
+ onToggle: (id: string | number) => void;
16
+ className?:
17
+ | string
18
+ | { accordionItem?: string | false | null | undefined }
19
+ | false
20
+ | undefined
21
+ | null;
22
+ };
23
+
24
+ export const AccordionItem = ({
25
+ item,
26
+ expanded,
27
+ onToggle,
28
+ className,
29
+ }: AccordionItemProps) => {
30
+ const contentRef = useRef<HTMLDivElement>(null);
31
+ const contentHeight = contentRef.current?.scrollHeight;
32
+
33
+ const classes = cx(
34
+ "ath-accordion-item",
35
+ typeof className === "object" && className !== null
36
+ ? className.accordionItem
37
+ : className
38
+ );
39
+
40
+ return (
41
+ <div className={classes}>
42
+ <Button
43
+ className="ath-accordion-item-heading"
44
+ variant="ghost"
45
+ size="full"
46
+ textAlign="left"
47
+ onClick={() => onToggle(item.id)}
48
+ dark
49
+ >
50
+ <h4>{item.heading}</h4>
51
+ </Button>
52
+ <div
53
+ className={cx(
54
+ "ath-accordion-expansion-wrapper",
55
+ expanded && "expanded"
56
+ )}
57
+ style={{ height: expanded ? contentHeight : 0 }}
58
+ >
59
+ <div ref={contentRef} className="ath-accordion-item-content">
60
+ {item.content}
61
+ </div>
62
+ </div>
63
+ </div>
64
+ );
65
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./Accordion";
2
+ export * from "./AccordionItem";
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import { useEffect, useState } from "react";
3
+ import { Button } from "./Button";
4
+ import { HugeiconsIcon } from "@hugeicons/react";
5
+ import { CircleArrowUp02Icon } from "@hugeicons/core-free-icons";
6
+ import type { ButtonProps } from "./Button";
7
+ import { cx } from "../../utils/cx";
8
+
9
+ type BackToTopButtonProps = ButtonProps & {
10
+ className?: string;
11
+ };
12
+
13
+ export const BackToTopButton = (props: BackToTopButtonProps) => {
14
+ const { className, variant, dark = false, testId, title } = props;
15
+ const [visible, setVisible] = useState(false);
16
+
17
+ useEffect(() => {
18
+ const handleScroll = () => {
19
+ const scrollPosition = window.scrollY;
20
+ if (scrollPosition < 300) {
21
+ setVisible(false);
22
+ } else {
23
+ setVisible(true);
24
+ }
25
+ };
26
+
27
+ window.addEventListener("scroll", handleScroll);
28
+ return () => {
29
+ window.removeEventListener("scroll", handleScroll);
30
+ };
31
+ }, []);
32
+
33
+ const scrollToTop = () => {
34
+ window.scrollTo({ top: 0, behavior: "smooth" });
35
+ };
36
+
37
+ if (!visible) return null;
38
+
39
+ const classes = cx("ath-back-to-top-button", className);
40
+
41
+ return (
42
+ <Button
43
+ className={classes}
44
+ data-testid={`${testId} "ath-back-to-top-button"`}
45
+ onClick={scrollToTop}
46
+ variant={variant ?? "secondary"}
47
+ title={title ?? "Back to Top"}
48
+ dark={dark}
49
+ >
50
+ <HugeiconsIcon icon={CircleArrowUp02Icon} width="50" height="50" />
51
+ </Button>
52
+ );
53
+ };
@@ -0,0 +1,114 @@
1
+ "use client";
2
+ import { ButtonPrimitive, type ButtonPrimitiveProps } from "./ButtonPrimitive";
3
+ import { cx } from "../../utils/cx";
4
+ import { buttonVariants, ButtonSize, ButtonVariant } from "./button.variants";
5
+
6
+ import type {
7
+ KeyboardEventHandler,
8
+ MouseEventHandler,
9
+ ReactElement,
10
+ } from "react";
11
+ import { forwardRef } from "react";
12
+
13
+ export type ButtonProps = Omit<ButtonPrimitiveProps, "className"> & {
14
+ children?: ReactElement | string;
15
+ className?:
16
+ | string
17
+ | { button?: string | false | null | undefined }
18
+ | false
19
+ | undefined
20
+ | null;
21
+ type?: "button" | "submit" | "reset";
22
+ variant?: ButtonVariant;
23
+ size?: ButtonSize;
24
+ dark?: boolean;
25
+ iconOnly?: boolean;
26
+ icon?: ReactElement | string;
27
+ title?: string;
28
+ disabled?: boolean;
29
+ autoFocus?: boolean;
30
+ tabIndex?: number;
31
+ testId?: string;
32
+ textAlign?: "left" | "center" | "right";
33
+
34
+ onClick?: MouseEventHandler<HTMLButtonElement>;
35
+ onKeyDown?: KeyboardEventHandler<HTMLButtonElement>;
36
+ onFocus?: (event: React.FocusEvent<HTMLButtonElement>) => void;
37
+ onBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
38
+ };
39
+
40
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
41
+ (
42
+ {
43
+ children,
44
+ className,
45
+ variant = "primary",
46
+ size = "medium",
47
+ dark = false,
48
+ disabled = false,
49
+ icon,
50
+ iconOnly,
51
+ tabIndex,
52
+ testId,
53
+ type = "button",
54
+ title,
55
+ autoFocus = false,
56
+ textAlign = "center",
57
+
58
+ onBlur,
59
+ onClick,
60
+ onFocus,
61
+ onKeyDown,
62
+ ...rest
63
+ },
64
+ ref
65
+ ) => {
66
+ const classes = cx(
67
+ "ath-button",
68
+ buttonVariants.size[size],
69
+ buttonVariants.variant[variant],
70
+ buttonVariants.textAlign[textAlign],
71
+ dark ? buttonVariants.dark : "",
72
+ typeof className === "object" && className !== null
73
+ ? className.button
74
+ : className
75
+ );
76
+
77
+ const onClickHandler: MouseEventHandler<HTMLButtonElement> = (e) => {
78
+ onClick?.(e);
79
+ };
80
+
81
+ return (
82
+ <ButtonPrimitive
83
+ ref={ref}
84
+ type={type}
85
+ title={title}
86
+ className={classes}
87
+ disabled={disabled}
88
+ autoFocus={autoFocus}
89
+ tabIndex={disabled ? -1 : tabIndex}
90
+ data-testid={testId}
91
+ onClick={onClickHandler}
92
+ onKeyDown={onKeyDown}
93
+ onFocus={onFocus}
94
+ onBlur={onBlur}
95
+ {...rest}
96
+ >
97
+ {icon ? (
98
+ <>
99
+ {typeof icon === "string" ? (
100
+ <span aria-hidden="true">{icon}</span>
101
+ ) : (
102
+ icon
103
+ )}
104
+ {!iconOnly && children}
105
+ </>
106
+ ) : (
107
+ <>{children}</>
108
+ )}
109
+ </ButtonPrimitive>
110
+ );
111
+ }
112
+ );
113
+
114
+ Button.displayName = "Button";
@@ -0,0 +1,13 @@
1
+ type ButtonGroupProps = {
2
+ children: React.ReactNode;
3
+ gap?: string;
4
+ direction?: "row" | "column";
5
+ };
6
+
7
+ export const ButtonGroup = ({
8
+ children,
9
+ gap = "2",
10
+ direction = "row",
11
+ }: ButtonGroupProps) => {
12
+ return <div className={`flex flex-${direction} gap-${gap}`}>{children}</div>;
13
+ };
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { forwardRef } from "react";
3
+
4
+ export type ButtonPrimitiveProps =
5
+ React.ButtonHTMLAttributes<HTMLButtonElement> & {};
6
+
7
+ export const ButtonPrimitive = forwardRef<
8
+ HTMLButtonElement,
9
+ ButtonPrimitiveProps
10
+ >(({ children, ...rest }, ref) => {
11
+ return (
12
+ <button ref={ref} {...rest}>
13
+ {children}
14
+ </button>
15
+ );
16
+ });
@@ -0,0 +1,30 @@
1
+ // button.variants.ts
2
+ export const buttonVariants = {
3
+ size: {
4
+ small: "ath-button-small",
5
+ medium: "ath-button-medium",
6
+ large: "ath-button-large",
7
+ full: "ath-button-full",
8
+ },
9
+ variant: {
10
+ primary: "ath-button-primary",
11
+ secondary: "ath-button-secondary",
12
+ tertiary: "ath-button-tertiary",
13
+ danger: "ath-button-danger",
14
+ warning: "ath-button-warning",
15
+ success: "ath-button-success",
16
+ outline: "ath-button-outline",
17
+ ghost: "ath-button-ghost",
18
+ },
19
+ textAlign: {
20
+ left: "ath-button-text-left",
21
+ center: "ath-button-text-center",
22
+ right: "ath-button-text-right",
23
+ },
24
+ dark: "ath-button-dark",
25
+ } as const;
26
+
27
+ export type ButtonSize = keyof typeof buttonVariants.size;
28
+ export type ButtonVariant = keyof typeof buttonVariants.variant;
29
+ export type ButtonDark = keyof typeof buttonVariants.dark;
30
+ export type ButtonTextAlign = keyof typeof buttonVariants.textAlign;
@@ -0,0 +1,3 @@
1
+ export * from "./Button";
2
+ export * from "./ButtonGroup";
3
+ export * from "./BackToTopButton";
@@ -0,0 +1,81 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Button } from "../Button";
3
+ import { HugeiconsIcon } from "@hugeicons/react";
4
+ import { SmileDizzyIcon } from "@hugeicons/core-free-icons";
5
+ import styles from "../Button.module.css";
6
+
7
+ const meta: Meta<typeof Button> = {
8
+ component: Button,
9
+ title: "Components/Button",
10
+ decorators: [
11
+ (Story, context) => {
12
+ const { dark } = context.args;
13
+ return (
14
+ <div
15
+ style={{
16
+ padding: "2rem",
17
+ backgroundColor: dark
18
+ ? "var(--color-background-dark)"
19
+ : "var(--color-background-light)",
20
+ }}
21
+ >
22
+ <Story aria-label="Button example" data-aria="Button example" />
23
+ </div>
24
+ );
25
+ },
26
+ ],
27
+ tags: ["autodocs"],
28
+ argTypes: {
29
+ children: {
30
+ description: `<code>ReactElement<any, string | JSXElementConstructor<any>> |</code> `,
31
+ type: undefined,
32
+ },
33
+ className: {
34
+ control: false,
35
+ },
36
+ iconOnly: {
37
+ control: "boolean",
38
+ },
39
+ dark: {
40
+ control: "boolean",
41
+ },
42
+ icon: {
43
+ control: "radio",
44
+ options: ["No Icon", "w/ Icon"],
45
+ mapping: {
46
+ "No Icon": "",
47
+ "w/ Icon": (
48
+ <HugeiconsIcon
49
+ className={styles.buttonSVGIcon}
50
+ icon={SmileDizzyIcon}
51
+ />
52
+ ),
53
+ },
54
+ },
55
+ type: {
56
+ control: false,
57
+
58
+ description: `
59
+ \`"button"\` \`"submit"\` \`"reset"\`
60
+
61
+ **Default:** \`"button"\`
62
+ `,
63
+ },
64
+ },
65
+ };
66
+ export default meta;
67
+
68
+ export const Default: StoryObj<typeof Button> = {
69
+ args: {
70
+ children: "Click me",
71
+ className: "",
72
+ variant: "primary",
73
+ dark: false,
74
+ size: "medium",
75
+ type: "button",
76
+ title: "Title Example",
77
+ icon: "w/ Icon",
78
+ iconOnly: true,
79
+ testId: "test-id",
80
+ },
81
+ };
@@ -0,0 +1,12 @@
1
+ import { cx } from "../../utils/cx";
2
+
3
+ type HeaderProps = {
4
+ children: React.ReactNode;
5
+ className?: string;
6
+ };
7
+
8
+ export const Header = ({ children, className }: HeaderProps) => {
9
+ const classes = cx("ath-header", className);
10
+
11
+ return <header className={classes}>{children}</header>;
12
+ };
@@ -0,0 +1 @@
1
+ export * from "./Header";
@@ -0,0 +1,44 @@
1
+ import { ReactNode } from "react";
2
+
3
+ type ListProps = {
4
+ children?: ReactNode[];
5
+ addClasses?: {
6
+ list?: string;
7
+ ul?: string;
8
+ ol?: string;
9
+ };
10
+ ariaLabel?: string;
11
+ ariaLabelledBy?: string;
12
+ ariaDescribedBy?: string;
13
+ role?: "list" | "menu" | "menubar" | "tablist" | "tree" | "grid";
14
+ dataTestId?: string;
15
+ ordered?: boolean;
16
+ };
17
+
18
+ export const List: React.FC<ListProps> = ({
19
+ addClasses,
20
+ children,
21
+ ariaLabel,
22
+ ariaLabelledBy,
23
+ ariaDescribedBy,
24
+ role = "list",
25
+ dataTestId,
26
+ ordered = false,
27
+ }) => {
28
+ const ListType = ordered ? "ol" : "ul";
29
+
30
+ return (
31
+ <ListType
32
+ className={`bn-list font-medium m-0 w-full flex flex-col list-none ps-0 ${
33
+ addClasses?.list || ""
34
+ } ${ordered ? addClasses?.ol || "" : addClasses?.ul || ""}`}
35
+ role={role}
36
+ aria-label={ariaLabel}
37
+ aria-labelledby={ariaLabelledBy}
38
+ aria-describedby={ariaDescribedBy}
39
+ data-testid={dataTestId}
40
+ >
41
+ {children}
42
+ </ListType>
43
+ );
44
+ };
@@ -0,0 +1,52 @@
1
+ type ListItemProps = {
2
+ children?: React.ReactNode;
3
+ addClasses?: {
4
+ li?: string;
5
+ };
6
+ useHover?: boolean;
7
+ role?: "listitem" | "menuitem" | "tab" | "treeitem" | "option";
8
+ ariaLabel?: string;
9
+ ariaDescribedBy?: string;
10
+ ariaSelected?: boolean;
11
+ ariaExpanded?: boolean;
12
+ ariaLevel?: number;
13
+ tabIndex?: number;
14
+ dataTestId?: string;
15
+ onClick?: (event: React.MouseEvent<HTMLLIElement>) => void;
16
+ onKeyDown?: (event: React.KeyboardEvent<HTMLLIElement>) => void;
17
+ };
18
+
19
+ export const ListItem: React.FC<ListItemProps> = ({
20
+ children,
21
+ addClasses,
22
+ role = "listitem",
23
+ ariaLabel,
24
+ ariaDescribedBy,
25
+ ariaSelected,
26
+ ariaExpanded,
27
+ ariaLevel,
28
+ tabIndex,
29
+ dataTestId,
30
+ onClick,
31
+ onKeyDown,
32
+ }) => {
33
+ return (
34
+ <li
35
+ className={`bn-list-item rounded-md border border-solid border-color-secondary-50 hover:bg-secondary-50 ${
36
+ addClasses?.li || ""
37
+ }`}
38
+ role={role}
39
+ aria-label={ariaLabel}
40
+ aria-describedby={ariaDescribedBy}
41
+ aria-selected={ariaSelected}
42
+ aria-expanded={ariaExpanded}
43
+ aria-level={ariaLevel}
44
+ tabIndex={tabIndex}
45
+ data-testid={dataTestId}
46
+ onClick={onClick}
47
+ onKeyDown={onKeyDown}
48
+ >
49
+ {children}
50
+ </li>
51
+ );
52
+ };
@@ -0,0 +1,5 @@
1
+ export * from "./Button";
2
+ export * from "./Header";
3
+ export * from "./Accordion";
4
+ // export * from "./AthameRouter";
5
+ // export * from "./Link";