@a2zb/react 1.0.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.
Files changed (29) hide show
  1. package/dist/src/components/data-display/ArrowRow.js +23 -0
  2. package/dist/src/components/data-display/Copyable.js +26 -0
  3. package/dist/src/components/data-display/DetailFields.js +7 -0
  4. package/dist/src/components/data-display/Details.js +13 -0
  5. package/dist/src/components/data-display/index.js +4 -0
  6. package/dist/src/components/feedback/Spinner.js +2 -0
  7. package/dist/src/components/feedback/Toast.js +19 -0
  8. package/dist/src/components/feedback/index.js +2 -0
  9. package/dist/src/components/forms/Checkbox.js +4 -0
  10. package/dist/src/components/forms/Select.js +4 -0
  11. package/dist/src/components/forms/TextInput.js +28 -0
  12. package/dist/src/components/forms/index.js +3 -0
  13. package/dist/src/components/input/Checkbox.js +4 -0
  14. package/dist/src/components/input/Select.js +4 -0
  15. package/dist/src/components/input/TextInput.js +28 -0
  16. package/dist/src/components/input/index.js +3 -0
  17. package/dist/src/components/media/Gallery.js +71 -0
  18. package/dist/src/components/media/GalleryItem.js +4 -0
  19. package/dist/src/components/media/index.js +2 -0
  20. package/dist/src/components/navigation/ArrowList.js +33 -0
  21. package/dist/src/components/navigation/SelectableList.js +33 -0
  22. package/dist/src/components/navigation/Tabs.js +12 -0
  23. package/dist/src/components/navigation/index.js +2 -0
  24. package/dist/src/components/overlays/Modal.js +38 -0
  25. package/dist/src/components/overlays/Popover.js +20 -0
  26. package/dist/src/components/overlays/index.js +2 -0
  27. package/dist/src/index.js +6 -0
  28. package/dist/src/types/eth.js +1 -0
  29. package/package.json +29 -0
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from 'clsx';
3
+ import { useEffect, useRef } from 'react';
4
+ export function ArrowRow({ isSelected, onSelect, children, className, dataId, dataTestId, }) {
5
+ const ref = useRef(null);
6
+ useEffect(() => {
7
+ if (isSelected) {
8
+ ref.current?.focus();
9
+ }
10
+ }, [isSelected]);
11
+ const appliedClasses = className ??
12
+ clsx(
13
+ // default
14
+ !isSelected && 'hover:bg-white/15 bg-secondary/80',
15
+ // selected
16
+ isSelected && 'bg-accent/20');
17
+ return (_jsx("li", { ref: ref, "data-id": dataId, "data-testid": dataTestId, tabIndex: isSelected ? 0 : -1, onClick: onSelect, onKeyDown: e => {
18
+ if (e.key === 'Enter' || e.key === ' ') {
19
+ e.preventDefault();
20
+ onSelect();
21
+ }
22
+ }, className: appliedClasses, children: children }));
23
+ }
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ export function Copyable({ value, children, className = "" }) {
4
+ const [copied, setCopied] = useState(false);
5
+ async function handleCopy(e) {
6
+ e.stopPropagation();
7
+ // for safe context -> copy to clipboard
8
+ await navigator.clipboard.writeText(value);
9
+ setCopied(true);
10
+ setTimeout(() => setCopied(false), 1000);
11
+ }
12
+ return (_jsx("span", { onClick: handleCopy, tabIndex: 0, onKeyDown: (e) => {
13
+ if (e.key === "Enter" || e.key === " ") {
14
+ e.preventDefault();
15
+ handleCopy(e);
16
+ }
17
+ }, className: `
18
+ cursor-pointer
19
+ text-accent
20
+ underline underline-offset-2 decoration-dotted
21
+ hover:decoration-solid
22
+ hover:text-accent-strong
23
+ transition-colors
24
+ ${className}
25
+ `, title: "Click to copy", children: copied ? "copied" : (children ?? value) }));
26
+ }
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ function DetailRow({ label, value, className, }) {
3
+ return (_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "truncate", children: label }), _jsx("span", { className: `whitespace-nowrap ${className ?? ''}`, children: value })] }));
4
+ }
5
+ export function DetailFields({ data, fields }) {
6
+ return (_jsx(_Fragment, { children: fields.map((field, i) => (_jsx(DetailRow, { label: field.label, value: field.getValue(data), className: field.className }, i))) }));
7
+ }
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { truncateHex } from "@a2zb/lib";
3
+ import { Copyable } from "./Copyable";
4
+ import { DetailFields } from "./DetailFields";
5
+ const Badge = ({ label }) => (_jsx("span", { className: `text-xs font-semibold px-2 py-1 rounded classes`, children: label.toUpperCase() }));
6
+ // --- copyable field ---
7
+ export const HexDetailField = (value, className) => (_jsx(Copyable, { className: className, value: value, children: truncateHex(value) }));
8
+ export function Details({ item, title, detailsFields, bottomFields, }) {
9
+ if (!item) {
10
+ return _jsx("div", { className: `p-4 text-sm`, children: "select a BIPBAPBOP" });
11
+ }
12
+ return (_jsxs("div", { className: `h-full flex flex-col p-4 text-sm justify-between`, tabIndex: -1, children: [title && (_jsxs("div", { className: "flex justify-between text-start", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "text-xs", children: title.field.label }), _jsx("span", { className: "font-medium", children: title.field.getValue(item) })] }), _jsx(Badge, { label: title.badge.label })] })), _jsx("div", { className: "flex flex-col gap-4", children: _jsx(DetailFields, { data: item, fields: detailsFields }) }), bottomFields && (_jsx("div", { className: "pt-2 border-t border-white/5 flex flex-col gap-2", children: _jsx(DetailFields, { data: item, fields: bottomFields }) }))] }));
13
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./ArrowRow";
2
+ export * from "./Copyable";
3
+ export * from "./DetailFields";
4
+ export * from "./Details";
@@ -0,0 +1,2 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export const Spinner = ({ size = 16 }) => (_jsx("div", { style: { width: size, height: size }, className: "animate-spin rounded-full border-2 border-accent/80 border-t-transparent" }));
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { toast as sonnerToast } from 'sonner';
3
+ function getTitleColor(variant) {
4
+ switch (variant) {
5
+ case 'error':
6
+ return 'text-failure';
7
+ case 'success':
8
+ return 'text-success';
9
+ default:
10
+ return 'text-accent-weak';
11
+ }
12
+ }
13
+ /* so that you can call it without having to use toast.custom everytime. */
14
+ export function toast(toast) {
15
+ return sonnerToast.custom(id => (_jsx(Toast, { id: id, title: toast.title, description: toast.description, variant: toast.variant, toastAction: toast.toastAction })));
16
+ }
17
+ export function Toast({ title, description, variant, toastAction }) {
18
+ return (_jsxs("div", { className: "flex items-start gap-3 w-full md:max-w-[360px] rounded-xl border border-default bg-surface p-4 shadow-[var(--panel-shadow)]", children: [_jsx("div", { className: "mt-1 h-2 w-2 rounded-full bg-accent" }), _jsxs("div", { className: "flex-1", children: [_jsx("p", { className: `text-sm font-semibold ${getTitleColor(variant)}`, children: title }), _jsxs("p", { className: "mt-1 text-xs text-muted", children: [description, ' ', toastAction && (_jsx("button", { onClick: () => toastAction.fn(), className: "text-accent underline", children: toastAction.text }))] })] }), _jsx("button", { className: "text-xs text-muted hover:text-accent transition-colors", onClick: () => sonnerToast.dismiss(), children: "\u2715" })] }));
19
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Spinner';
2
+ export * from './Toast';
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function Checkbox({ label, checked, onChange }) {
3
+ return (_jsxs("label", { className: "flex items-center gap-3 cursor-pointer select-none relative", children: [_jsxs("div", { className: "relative w-4 h-4 flex items-center justify-center", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: e => onChange?.(e.target.checked), className: "\n peer w-4 h-4 rounded-sm \n border border-accent/50 \n bg-transparent\n cursor-pointer \n appearance-none\n transition-all\n\n hover:border-accent/80\n checked:border-accent\n " }), _jsx("span", { className: "\n absolute text-accent text-[14px] leading-none\n pointer-events-none\n opacity-0 peer-checked:opacity-100 \n transition-opacity\n ", children: "\u2713" })] }), _jsx("span", { className: "text-sm text-subtle peer-hover:text-accent transition-colors", children: label })] }));
4
+ }
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function Select({ options, value, onChange }) {
3
+ return (_jsx("select", { className: "border border-default rounded px-2 py-1 text-sm bg-surface", value: value, onChange: e => onChange?.(e.target.value), children: options.map(option => (_jsx("option", { value: option, children: option }, option))) }));
4
+ }
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ // merketplaceView is a heavy component and shouldnt rerender on every keystroke
4
+ // so internal state is added. then render only happens on submit
5
+ export function TextInput({ placeholder, value, onChange, onSubmit, ref, numeric }) {
6
+ const [internal, setInternal] = useState(value ?? '');
7
+ const [prevValue, setPrevValue] = useState(value);
8
+ // if parent changed value prop - sync internal to match
9
+ if (prevValue !== value) {
10
+ setPrevValue(value);
11
+ setInternal(value ?? '');
12
+ }
13
+ return (_jsx("input", { ref: ref, className: "px-4 py-2 w-full bg-black/10 text-muted rounded-lg border border-default", placeholder: placeholder, value: internal, ...(numeric && { inputMode: 'decimal', pattern: '[0-9.]*', 'data-numeric': true }), onChange: e => {
14
+ const value = numeric
15
+ ? e.currentTarget.value.replace(/[^0-9.]/g, '')
16
+ : e.currentTarget.value;
17
+ setInternal(value);
18
+ onChange?.(value);
19
+ }, onKeyDown: e => {
20
+ if (e.key === 'Enter') {
21
+ e.preventDefault();
22
+ onSubmit?.(internal);
23
+ }
24
+ else if (e.key === 'Escape') {
25
+ setInternal('');
26
+ }
27
+ } }));
28
+ }
@@ -0,0 +1,3 @@
1
+ export * from './Checkbox';
2
+ export * from './Select';
3
+ export * from './TextInput';
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function Checkbox({ label, checked, onChange }) {
3
+ return (_jsxs("label", { className: "flex items-center gap-3 cursor-pointer select-none relative", children: [_jsxs("div", { className: "relative w-4 h-4 flex items-center justify-center", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: e => onChange?.(e.target.checked), className: "\n peer w-4 h-4 rounded-sm \n border border-accent/50 \n bg-transparent\n cursor-pointer \n appearance-none\n transition-all\n\n hover:border-accent/80\n checked:border-accent\n " }), _jsx("span", { className: "\n absolute text-accent text-[14px] leading-none\n pointer-events-none\n opacity-0 peer-checked:opacity-100 \n transition-opacity\n ", children: "\u2713" })] }), _jsx("span", { className: "text-sm text-subtle peer-hover:text-accent transition-colors", children: label })] }));
4
+ }
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function Select({ options, value, onChange }) {
3
+ return (_jsx("select", { className: "border border-default rounded px-2 py-1 text-sm bg-surface", value: value, onChange: e => onChange?.(e.target.value), children: options.map(option => (_jsx("option", { value: option, children: option }, option))) }));
4
+ }
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ // merketplaceView is a heavy component and shouldnt rerender on every keystroke
4
+ // so internal state is added. then render only happens on submit
5
+ export function TextInput({ placeholder, value, onChange, onSubmit, ref, numeric }) {
6
+ const [internal, setInternal] = useState(value ?? '');
7
+ const [prevValue, setPrevValue] = useState(value);
8
+ // if parent changed value prop - sync internal to match
9
+ if (prevValue !== value) {
10
+ setPrevValue(value);
11
+ setInternal(value ?? '');
12
+ }
13
+ return (_jsx("input", { ref: ref, className: "px-4 py-2 w-full bg-black/10 text-muted rounded-lg border border-default", placeholder: placeholder, value: internal, ...(numeric && { inputMode: 'decimal', pattern: '[0-9.]*', 'data-numeric': true }), onChange: e => {
14
+ const value = numeric
15
+ ? e.currentTarget.value.replace(/[^0-9.]/g, '')
16
+ : e.currentTarget.value;
17
+ setInternal(value);
18
+ onChange?.(value);
19
+ }, onKeyDown: e => {
20
+ if (e.key === 'Enter') {
21
+ e.preventDefault();
22
+ onSubmit?.(internal);
23
+ }
24
+ else if (e.key === 'Escape') {
25
+ setInternal('');
26
+ }
27
+ } }));
28
+ }
@@ -0,0 +1,3 @@
1
+ export * from './Checkbox';
2
+ export * from './Select';
3
+ export * from './TextInput';
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import { ArrowRow } from "../data-display";
4
+ import { ArrowList } from "../navigation";
5
+ import clsx from "clsx";
6
+ export function Gallery({ items, galleryItem, selected, onSelect, isFresh, galleryView = "list", ref, onLoadMore, isLoading, hasMore, }) {
7
+ // load more on 'regular' scroll
8
+ useEffect(() => {
9
+ const el = ref?.current;
10
+ if (!el || !onLoadMore)
11
+ return;
12
+ const handleScroll = () => {
13
+ const distance = el.scrollHeight - (el.scrollTop + el.clientHeight);
14
+ if (distance < 100 && !isLoading && hasMore) {
15
+ onLoadMore();
16
+ }
17
+ };
18
+ el.addEventListener("scroll", handleScroll);
19
+ return () => el.removeEventListener("scroll", handleScroll);
20
+ }, [ref, onLoadMore, isLoading, hasMore]);
21
+ // load more for keyboard
22
+ useEffect(() => {
23
+ if (!selected || !onLoadMore || isLoading || !hasMore)
24
+ return;
25
+ const index = items.findIndex((i) => i.id === selected.id);
26
+ if (index === -1)
27
+ return;
28
+ if (items.length - index < 5) {
29
+ onLoadMore();
30
+ }
31
+ }, [
32
+ selected?.id,
33
+ items.length,
34
+ hasMore,
35
+ isLoading,
36
+ items,
37
+ onLoadMore,
38
+ selected,
39
+ ]);
40
+ useEffect(() => {
41
+ if (!selected || !ref?.current)
42
+ return;
43
+ const el = ref.current.querySelector(`[data-id="${selected.id}"]`);
44
+ if (!el)
45
+ return;
46
+ el.scrollIntoView({
47
+ block: "center",
48
+ inline: "center",
49
+ behavior: "smooth",
50
+ });
51
+ el.scrollTop -= 40;
52
+ }, [selected, ref]);
53
+ const galleryClasses = galleryView === "list"
54
+ ? {
55
+ arrowList: "flex flex-col gap-4",
56
+ arrowRow: "border border-default/65 rounded-xl transition",
57
+ }
58
+ : {
59
+ arrowList: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 lg-rounded",
60
+ arrowRow: "outline-none focus-visible:ring-0.5 focus-visible:ring-accent rounded-lg block",
61
+ };
62
+ return (_jsx("div", { className: "flex h-full min-h-0 gap-4", children: _jsx("div", { className: "flex min-h-0 flex-1 flex-col gap-4", children: _jsx(ArrowList, { ref: ref, items: items, getId: (c) => c.id, selectedId: selected?.id, onSelect: onSelect, className: `${galleryClasses.arrowList} min-h-0 flex-1 rounded-lg p-1`, children: ({ item, isSelected, onSelect }) => (_jsx(ArrowRow, { isSelected: isSelected, onSelect: onSelect, dataId: item.id, className: clsx(galleryClasses.arrowRow,
63
+ // default
64
+ !isSelected &&
65
+ !isFresh?.(item) &&
66
+ "hover:bg-white/15 bg-surface/75",
67
+ // fresh
68
+ isFresh?.(item) && "fresh",
69
+ // selected
70
+ isSelected && "bg-accent/25"), children: galleryItem(item) }, item.id)) }) }) }));
71
+ }
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function GalleryItem({ image, imgWidth = 250, imgHeight = 250, title, details }) {
3
+ return (_jsxs("div", { className: "card group hover:-translate-y-1 transition-transform hover:text-accent", children: [_jsx("img", { src: image, alt: title ?? '', className: "border-b border-default object-cover bg-primary", width: imgWidth, height: imgHeight, loading: "eager" }), _jsxs("div", { className: "flex flex-col", children: [title && (_jsx("span", { className: "h-[40px] grid place-items-center border-b border-default", children: title })), _jsx("div", { className: "transition-colors", children: details })] })] }));
4
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Gallery';
2
+ export * from './GalleryItem';
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function ArrowList({ items, getId, selectedId, onSelect, children, className = '', ref, }) {
3
+ const base = 'overflow-y-auto no-scrollbar';
4
+ return (_jsx("ul", { ref: ref, className: `${base} ${className}`, tabIndex: 0, onKeyDown: e => {
5
+ if (!items.length)
6
+ return;
7
+ if (e.key === 'Home') {
8
+ e.preventDefault();
9
+ onSelect(items[0]);
10
+ return;
11
+ }
12
+ if (e.key === 'End') {
13
+ e.preventDefault();
14
+ onSelect(items[items.length - 1]);
15
+ return;
16
+ }
17
+ if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp')
18
+ return;
19
+ e.preventDefault();
20
+ const index = selectedId === undefined ? 0 : items.findIndex(it => getId(it) === selectedId);
21
+ if (index === -1)
22
+ return;
23
+ let next = index;
24
+ if (e.key === 'ArrowDown')
25
+ next = Math.min(index + 1, items.length - 1);
26
+ if (e.key === 'ArrowUp')
27
+ next = Math.max(index - 1, 0);
28
+ onSelect(items[next]);
29
+ }, children: items.map(item => {
30
+ const isSelected = selectedId !== undefined && getId(item) === selectedId;
31
+ return children({ item, isSelected, onSelect: () => onSelect(item) });
32
+ }) }));
33
+ }
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function ArrowList({ items, getId, selectedId, onSelect, children, className = '', ref, }) {
3
+ const base = 'overflow-y-auto no-scrollbar';
4
+ return (_jsx("ul", { ref: ref, className: `${base} ${className}`, tabIndex: 0, onKeyDown: e => {
5
+ if (!items.length)
6
+ return;
7
+ if (e.key === 'Home') {
8
+ e.preventDefault();
9
+ onSelect(items[0]);
10
+ return;
11
+ }
12
+ if (e.key === 'End') {
13
+ e.preventDefault();
14
+ onSelect(items[items.length - 1]);
15
+ return;
16
+ }
17
+ if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp')
18
+ return;
19
+ e.preventDefault();
20
+ const index = selectedId === undefined ? 0 : items.findIndex(it => getId(it) === selectedId);
21
+ if (index === -1)
22
+ return;
23
+ let next = index;
24
+ if (e.key === 'ArrowDown')
25
+ next = Math.min(index + 1, items.length - 1);
26
+ if (e.key === 'ArrowUp')
27
+ next = Math.max(index - 1, 0);
28
+ onSelect(items[next]);
29
+ }, children: items.map(item => {
30
+ const isSelected = selectedId !== undefined && getId(item) === selectedId;
31
+ return children({ item, isSelected, onSelect: () => onSelect(item) });
32
+ }) }));
33
+ }
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function Tabs({ value, onChange, items }) {
3
+ return (_jsx("div", { className: "flex w-full border-b border-soft", children: items.map(item => {
4
+ const active = item === value;
5
+ return (_jsx("button", { onClick: () => onChange(item), className: `
6
+ flex-1 py-2 text-center border-b-2 transition-colors duration-200 cursor-pointer
7
+ ${active
8
+ ? 'border-accent/60 text-accent-weak'
9
+ : 'border-transparent text-subtle hover:border-accent/30'}
10
+ `, children: item }, item));
11
+ }) }));
12
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./ArrowList";
2
+ export * from "./Tabs";
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { FocusTrap } from "focus-trap-react";
4
+ export function Modal({ isOpen, onClose, children, escTxt = "Close", selfManagesFocus, }) {
5
+ const lastFocusedRef = useRef(null);
6
+ // close on Escape always, close on X unless focus is on a non-numeric text input
7
+ const handler = (e) => {
8
+ const target = e.target;
9
+ const isNumeric = target.dataset?.numeric !== undefined;
10
+ const typingText = !isNumeric &&
11
+ (target.tagName === "INPUT" ||
12
+ target.tagName === "TEXTAREA" ||
13
+ target.isContentEditable);
14
+ if (e.key === "Escape") {
15
+ onClose();
16
+ return;
17
+ }
18
+ if (!typingText && e.key.toLowerCase() === "x") {
19
+ onClose();
20
+ }
21
+ };
22
+ // Close on ESC key
23
+ useEffect(() => {
24
+ if (!isOpen)
25
+ return;
26
+ lastFocusedRef.current = document.activeElement;
27
+ document.addEventListener("keydown", handler);
28
+ return () => {
29
+ document.removeEventListener("keydown", handler);
30
+ lastFocusedRef.current?.focus();
31
+ };
32
+ }, [onClose, isOpen]);
33
+ if (!isOpen)
34
+ return null;
35
+ return (_jsx("div", { className: "\n fixed inset-0 z-[999] flex items-center justify-center\n bg-black/50 backdrop-blur-sm animate-fadeIn\n ", role: "dialog", onClick: onClose, children: _jsx(FocusTrap, { focusTrapOptions: {
36
+ initialFocus: selfManagesFocus ? false : "#modal-close-btn",
37
+ }, children: _jsxs("div", { className: "\n flex flex-col gap-2\n bg-primary/60 backdrop-blur-lg\n border border-default\n rounded-lg\n shadow-lg p-2", onClick: (e) => e.stopPropagation(), children: [children, _jsx("button", { id: "modal-close-btn", className: "btn btn-secondary outline-none", onClick: onClose, children: escTxt })] }) }) }));
38
+ }
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ export function Popover({ trigger, children, align = 'right' }) {
4
+ const [open, setOpen] = useState(false);
5
+ const ref = useRef(null);
6
+ useEffect(() => {
7
+ if (!open)
8
+ return;
9
+ function handleClick(e) {
10
+ if (ref.current && !ref.current.contains(e.target))
11
+ setOpen(false);
12
+ }
13
+ document.addEventListener('mousedown', handleClick);
14
+ return () => document.removeEventListener('mousedown', handleClick);
15
+ }, [open]);
16
+ return (_jsxs("div", { ref: ref, className: "relative", children: [_jsx("div", { onClick: () => setOpen(v => !v), className: "cursor-pointer flex items-center", children: trigger }), open && (_jsx("div", { className: `absolute top-full mt-1 z-50 whitespace-nowrap p-2 ${align === 'right' ? 'right-0' : 'left-0'}`, style: {
17
+ background: 'var(--bg-surface)',
18
+ border: '1px solid var(--border-soft)',
19
+ }, children: children }))] }));
20
+ }
@@ -0,0 +1,2 @@
1
+ export * from './Modal';
2
+ export * from './Popover';
@@ -0,0 +1,6 @@
1
+ export * from "./components/data-display";
2
+ export * from "./components/feedback";
3
+ export * from "./components/input";
4
+ export * from "./components/media";
5
+ export * from "./components/navigation";
6
+ export * from "./components/overlays";
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@a2zb/react",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "dependencies": {
11
+ "clsx": "^2.1.1",
12
+ "focus-trap-react": "^12.0.0",
13
+ "sonner": "^2.0.7",
14
+ "@a2zb/lib": "1.0.0"
15
+ },
16
+ "peerDependencies": {
17
+ "react": "^19",
18
+ "@a2zb/styles": "^1.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "^19.2.17",
22
+ "react": "^19.2.7",
23
+ "typescript": "^6.0.3",
24
+ "@a2zb/styles": "1.0.0"
25
+ },
26
+ "scripts": {
27
+ "build": "tsc"
28
+ }
29
+ }