@holmdigital/components 1.1.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 (43) hide show
  1. package/README.md +46 -0
  2. package/dist/Button/Button.js +117 -0
  3. package/dist/Button/Button.mjs +6 -0
  4. package/dist/Checkbox/Checkbox.js +82 -0
  5. package/dist/Checkbox/Checkbox.mjs +6 -0
  6. package/dist/Dialog/Dialog.js +129 -0
  7. package/dist/Dialog/Dialog.mjs +6 -0
  8. package/dist/FormField/FormField.js +110 -0
  9. package/dist/FormField/FormField.mjs +6 -0
  10. package/dist/Heading/Heading.js +48 -0
  11. package/dist/Heading/Heading.mjs +6 -0
  12. package/dist/Modal/Modal.js +146 -0
  13. package/dist/Modal/Modal.mjs +7 -0
  14. package/dist/NavigationMenu/NavigationMenu.js +141 -0
  15. package/dist/NavigationMenu/NavigationMenu.mjs +6 -0
  16. package/dist/RadioGroup/RadioGroup.js +103 -0
  17. package/dist/RadioGroup/RadioGroup.mjs +6 -0
  18. package/dist/Select/Select.js +157 -0
  19. package/dist/Select/Select.mjs +12 -0
  20. package/dist/SkipLink/SkipLink.js +59 -0
  21. package/dist/SkipLink/SkipLink.mjs +6 -0
  22. package/dist/Switch/Switch.js +82 -0
  23. package/dist/Switch/Switch.mjs +6 -0
  24. package/dist/Toast/Toast.js +123 -0
  25. package/dist/Toast/Toast.mjs +8 -0
  26. package/dist/Tooltip/Tooltip.js +121 -0
  27. package/dist/Tooltip/Tooltip.mjs +12 -0
  28. package/dist/chunk-2MJRKHPL.mjs +98 -0
  29. package/dist/chunk-5RKBS475.mjs +58 -0
  30. package/dist/chunk-C5M6C7KT.mjs +84 -0
  31. package/dist/chunk-GK4BYT56.mjs +117 -0
  32. package/dist/chunk-HALLFO25.mjs +22 -0
  33. package/dist/chunk-LZ42XDDI.mjs +105 -0
  34. package/dist/chunk-MKKQLWGK.mjs +35 -0
  35. package/dist/chunk-NDYRGXQ6.mjs +93 -0
  36. package/dist/chunk-NOE5QKC2.mjs +58 -0
  37. package/dist/chunk-PLT5CAFO.mjs +86 -0
  38. package/dist/chunk-V2JYAFB7.mjs +130 -0
  39. package/dist/chunk-W4ZHBRFT.mjs +14 -0
  40. package/dist/chunk-YMSNGQN6.mjs +79 -0
  41. package/dist/index.js +1256 -0
  42. package/dist/index.mjs +308 -0
  43. package/package.json +113 -0
@@ -0,0 +1,117 @@
1
+ // src/NavigationMenu/NavigationMenu.tsx
2
+ import { useState, useRef, useEffect, forwardRef } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ var NavigationMenu = forwardRef(
5
+ ({ items, className, "aria-label": ariaLabel = "Main Navigation" }, ref) => {
6
+ return /* @__PURE__ */ jsx(
7
+ "nav",
8
+ {
9
+ ref,
10
+ className: `flex items-center ${className || ""}`,
11
+ "aria-label": ariaLabel,
12
+ children: /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-2 m-0 p-0 list-none", children: items.map((item, index) => /* @__PURE__ */ jsx(MenuItem, { item }, index)) })
13
+ }
14
+ );
15
+ }
16
+ );
17
+ NavigationMenu.displayName = "NavigationMenu";
18
+ var MenuItem = ({ item }) => {
19
+ const [isOpen, setIsOpen] = useState(false);
20
+ const containerRef = useRef(null);
21
+ const timeoutRef = useRef();
22
+ const hasChildren = item.children && item.children.length > 0;
23
+ useEffect(() => {
24
+ const handleClickOutside = (event) => {
25
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
26
+ setIsOpen(false);
27
+ }
28
+ };
29
+ if (isOpen) {
30
+ document.addEventListener("click", handleClickOutside);
31
+ }
32
+ return () => document.removeEventListener("click", handleClickOutside);
33
+ }, [isOpen]);
34
+ const handleKeyDown = (e) => {
35
+ if (e.key === "Escape" && isOpen) {
36
+ setIsOpen(false);
37
+ const trigger = containerRef.current?.querySelector("button");
38
+ trigger?.focus();
39
+ }
40
+ };
41
+ const handleMouseEnter = () => {
42
+ clearTimeout(timeoutRef.current);
43
+ setIsOpen(true);
44
+ };
45
+ const handleMouseLeave = () => {
46
+ timeoutRef.current = setTimeout(() => setIsOpen(false), 200);
47
+ };
48
+ return /* @__PURE__ */ jsx(
49
+ "li",
50
+ {
51
+ ref: containerRef,
52
+ className: "relative group",
53
+ onKeyDown: handleKeyDown,
54
+ onMouseEnter: hasChildren ? handleMouseEnter : void 0,
55
+ onMouseLeave: hasChildren ? handleMouseLeave : void 0,
56
+ children: hasChildren ? /* @__PURE__ */ jsxs(Fragment, { children: [
57
+ /* @__PURE__ */ jsxs(
58
+ "button",
59
+ {
60
+ onClick: () => setIsOpen(!isOpen),
61
+ "aria-expanded": isOpen,
62
+ "aria-haspopup": "true",
63
+ className: `
64
+ flex items-center gap-1 px-4 py-2 rounded-md
65
+ text-slate-700 font-medium hover:bg-slate-100 focus:bg-slate-100
66
+ transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500
67
+ `,
68
+ children: [
69
+ item.label,
70
+ /* @__PURE__ */ jsx(
71
+ "svg",
72
+ {
73
+ className: `w-4 h-4 transition-transform ${isOpen ? "rotate-180" : ""}`,
74
+ fill: "none",
75
+ viewBox: "0 0 24 24",
76
+ stroke: "currentColor",
77
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" })
78
+ }
79
+ )
80
+ ]
81
+ }
82
+ ),
83
+ /* @__PURE__ */ jsx(
84
+ "ul",
85
+ {
86
+ className: `
87
+ absolute top-full left-0 mt-1 min-w-[200px]
88
+ bg-white border border-slate-200 rounded-lg shadow-xl
89
+ py-2 z-50
90
+ transform origin-top transition-all duration-200
91
+ ${isOpen ? "opacity-100 scale-100 visible" : "opacity-0 scale-95 invisible"}
92
+ `,
93
+ children: item.children?.map((child, idx) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
94
+ "a",
95
+ {
96
+ href: child.href,
97
+ className: "block px-4 py-2 text-slate-700 hover:bg-slate-50 hover:text-primary-600 focus:bg-slate-50 focus:text-primary-600 focus:outline-none",
98
+ children: child.label
99
+ }
100
+ ) }, idx))
101
+ }
102
+ )
103
+ ] }) : /* @__PURE__ */ jsx(
104
+ "a",
105
+ {
106
+ href: item.href,
107
+ className: "block px-4 py-2 text-slate-700 font-medium rounded-md hover:bg-slate-100 focus:bg-slate-100 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500",
108
+ children: item.label
109
+ }
110
+ )
111
+ }
112
+ );
113
+ };
114
+
115
+ export {
116
+ NavigationMenu
117
+ };
@@ -0,0 +1,22 @@
1
+ import {
2
+ Dialog
3
+ } from "./chunk-LZ42XDDI.mjs";
4
+
5
+ // src/Modal/Modal.tsx
6
+ import { forwardRef } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var Modal = forwardRef((props, ref) => {
9
+ return /* @__PURE__ */ jsx(
10
+ Dialog,
11
+ {
12
+ ref,
13
+ ...props,
14
+ className: `max-w-2xl ${props.className || ""}`
15
+ }
16
+ );
17
+ });
18
+ Modal.displayName = "Modal";
19
+
20
+ export {
21
+ Modal
22
+ };
@@ -0,0 +1,105 @@
1
+ // src/Dialog/Dialog.tsx
2
+ import { useRef, useEffect, forwardRef, useImperativeHandle } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var CloseIcon = () => /* @__PURE__ */ jsxs(
5
+ "svg",
6
+ {
7
+ xmlns: "http://www.w3.org/2000/svg",
8
+ width: "24",
9
+ height: "24",
10
+ viewBox: "0 0 24 24",
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ strokeWidth: "2",
14
+ strokeLinecap: "round",
15
+ strokeLinejoin: "round",
16
+ className: "h-5 w-5",
17
+ children: [
18
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
19
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
20
+ ]
21
+ }
22
+ );
23
+ var Dialog = forwardRef(
24
+ ({ isOpen, onClose, title, children, variant = "default", description, className, ...props }, ref) => {
25
+ const dialogRef = useRef(null);
26
+ useImperativeHandle(ref, () => dialogRef.current);
27
+ useEffect(() => {
28
+ const dialog = dialogRef.current;
29
+ if (!dialog) return;
30
+ if (isOpen) {
31
+ if (!dialog.open) {
32
+ dialog.showModal();
33
+ document.body.style.overflow = "hidden";
34
+ }
35
+ } else {
36
+ if (dialog.open) {
37
+ dialog.close();
38
+ document.body.style.overflow = "";
39
+ }
40
+ }
41
+ }, [isOpen]);
42
+ useEffect(() => {
43
+ const dialog = dialogRef.current;
44
+ if (!dialog) return;
45
+ const handleClose = () => {
46
+ onClose();
47
+ document.body.style.overflow = "";
48
+ };
49
+ dialog.addEventListener("close", handleClose);
50
+ const handleBackdropClick = (e) => {
51
+ const rect = dialog.getBoundingClientRect();
52
+ const isInDialog = rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width;
53
+ if (!isInDialog) {
54
+ dialog.close();
55
+ }
56
+ };
57
+ dialog.addEventListener("click", handleBackdropClick);
58
+ return () => {
59
+ dialog.removeEventListener("close", handleClose);
60
+ dialog.removeEventListener("click", handleBackdropClick);
61
+ };
62
+ }, [onClose]);
63
+ return /* @__PURE__ */ jsxs(
64
+ "dialog",
65
+ {
66
+ ref: dialogRef,
67
+ className: `
68
+ backdrop:bg-slate-900/50 backdrop:backdrop-blur-sm
69
+ open:animate-in open:fade-in-0 open:zoom-in-95
70
+ bg-white rounded-xl shadow-2xl ring-1 ring-slate-900/5
71
+ w-full max-w-lg p-0
72
+ ${className || ""}
73
+ `,
74
+ "aria-labelledby": "dialog-title",
75
+ "aria-describedby": description ? "dialog-desc" : void 0,
76
+ ...props,
77
+ children: [
78
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-slate-100", children: [
79
+ /* @__PURE__ */ jsx("h2", { id: "dialog-title", className: `text-lg font-semibold ${variant === "alert" ? "text-red-600" : "text-slate-900"}`, children: title }),
80
+ /* @__PURE__ */ jsx(
81
+ "button",
82
+ {
83
+ onClick: () => {
84
+ dialogRef.current?.close();
85
+ },
86
+ className: "p-1 rounded-md text-slate-400 hover:text-slate-500 hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-primary-500 transition-colors",
87
+ "aria-label": "Close dialog",
88
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
89
+ }
90
+ )
91
+ ] }),
92
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
93
+ description && /* @__PURE__ */ jsx("p", { id: "dialog-desc", className: "text-sm text-slate-500 mb-4", children: description }),
94
+ children
95
+ ] })
96
+ ]
97
+ }
98
+ );
99
+ }
100
+ );
101
+ Dialog.displayName = "Dialog";
102
+
103
+ export {
104
+ Dialog
105
+ };
@@ -0,0 +1,35 @@
1
+ // src/SkipLink/SkipLink.tsx
2
+ import { forwardRef } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var SkipLink = forwardRef(
5
+ ({ targetId = "main", className, style, children, ...props }, ref) => {
6
+ return /* @__PURE__ */ jsx(
7
+ "a",
8
+ {
9
+ ref,
10
+ href: `#${targetId}`,
11
+ className: `
12
+ fixed top-4 left-4 z-50
13
+ px-4 py-3
14
+ bg-white text-slate-900 font-medium
15
+ rounded-md shadow-lg ring-2 ring-slate-900
16
+ transition-transform duration-200
17
+ -translate-y-[150%] focus:translate-y-0
18
+ ${className || ""}
19
+ `,
20
+ style: {
21
+ // Ensure it stays on top of everything
22
+ zIndex: 9999,
23
+ ...style
24
+ },
25
+ ...props,
26
+ children: children || "Hoppa till huvudinneh\xE5ll"
27
+ }
28
+ );
29
+ }
30
+ );
31
+ SkipLink.displayName = "SkipLink";
32
+
33
+ export {
34
+ SkipLink
35
+ };
@@ -0,0 +1,93 @@
1
+ // src/Button/Button.tsx
2
+ import { forwardRef } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var Button = forwardRef(
5
+ ({
6
+ children,
7
+ variant = "primary",
8
+ size = "medium",
9
+ isLoading,
10
+ disabled,
11
+ className = "",
12
+ ...props
13
+ }, ref) => {
14
+ const baseStyles = {
15
+ display: "inline-flex",
16
+ alignItems: "center",
17
+ justifyContent: "center",
18
+ borderRadius: "4px",
19
+ border: "none",
20
+ cursor: disabled || isLoading ? "not-allowed" : "pointer",
21
+ fontFamily: "inherit",
22
+ fontWeight: "600",
23
+ transition: "all 0.2s ease",
24
+ // Garantera synlig fokusindikator (WCAG 2.4.7)
25
+ outlineOffset: "2px"
26
+ };
27
+ const variants = {
28
+ primary: {
29
+ background: "#0056b3",
30
+ // AA Large, AAA Normal mot vit text
31
+ color: "#ffffff"
32
+ },
33
+ secondary: {
34
+ background: "#f8f9fa",
35
+ color: "#212529",
36
+ border: "1px solid #dee2e6"
37
+ },
38
+ danger: {
39
+ background: "#dc3545",
40
+ color: "#ffffff"
41
+ },
42
+ ghost: {
43
+ background: "transparent",
44
+ color: "#0056b3"
45
+ }
46
+ };
47
+ const sizes = {
48
+ small: {
49
+ padding: "0.25rem 0.5rem",
50
+ fontSize: "0.875rem",
51
+ minHeight: "32px"
52
+ // OBS: Kan bryta mot 44px om inte hanteras med margin
53
+ },
54
+ medium: {
55
+ padding: "0.5rem 1rem",
56
+ fontSize: "1rem",
57
+ minHeight: "44px"
58
+ // Touch target safe
59
+ },
60
+ large: {
61
+ padding: "0.75rem 1.5rem",
62
+ fontSize: "1.25rem",
63
+ minHeight: "56px"
64
+ }
65
+ };
66
+ const style = {
67
+ ...baseStyles,
68
+ ...variants[variant],
69
+ ...sizes[size],
70
+ opacity: disabled || isLoading ? 0.65 : 1
71
+ };
72
+ return /* @__PURE__ */ jsxs(
73
+ "button",
74
+ {
75
+ ref,
76
+ style,
77
+ disabled: disabled || isLoading,
78
+ "aria-busy": isLoading,
79
+ tabIndex: props.tabIndex,
80
+ ...props,
81
+ children: [
82
+ isLoading ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { marginRight: "8px" }, children: "\u23F3" }) : null,
83
+ children
84
+ ]
85
+ }
86
+ );
87
+ }
88
+ );
89
+ Button.displayName = "Button";
90
+
91
+ export {
92
+ Button
93
+ };
@@ -0,0 +1,58 @@
1
+ // src/Checkbox/Checkbox.tsx
2
+ import { forwardRef } from "react";
3
+ import { Check } from "lucide-react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ var Checkbox = forwardRef(
6
+ ({ className = "", checked, onCheckedChange, onChange, label, disabled, id, ...props }, ref) => {
7
+ const handleChange = (e) => {
8
+ onCheckedChange?.(e.target.checked);
9
+ onChange?.(e);
10
+ };
11
+ const generatedId = id || `checkbox-${Math.random().toString(36).substr(2, 9)}`;
12
+ return /* @__PURE__ */ jsxs("div", { className: `flex items-start ${className}`, children: [
13
+ /* @__PURE__ */ jsx("div", { className: "flex items-center h-5", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
14
+ /* @__PURE__ */ jsx(
15
+ "input",
16
+ {
17
+ id: generatedId,
18
+ type: "checkbox",
19
+ ref,
20
+ className: "peer sr-only",
21
+ checked,
22
+ onChange: handleChange,
23
+ disabled,
24
+ ...props
25
+ }
26
+ ),
27
+ /* @__PURE__ */ jsx(
28
+ "div",
29
+ {
30
+ className: `
31
+ h-5 w-5 rounded border border-slate-300 bg-white shadow-sm transition-all
32
+ peer-focus:ring-2 peer-focus:ring-primary-500 peer-focus:ring-offset-2
33
+ peer-checked:bg-primary-600 peer-checked:border-primary-600
34
+ peer-disabled:cursor-not-allowed peer-disabled:opacity-50
35
+ hover:border-primary-400
36
+ flex items-center justify-center
37
+ `,
38
+ "aria-hidden": "true",
39
+ children: /* @__PURE__ */ jsx(Check, { className: `h-3.5 w-3.5 text-white transition-opacity ${checked ? "opacity-100" : "opacity-0"}`, strokeWidth: 3 })
40
+ }
41
+ )
42
+ ] }) }),
43
+ /* @__PURE__ */ jsx(
44
+ "label",
45
+ {
46
+ htmlFor: generatedId,
47
+ className: `ml-3 text-sm font-medium ${disabled ? "text-slate-400" : "text-slate-700"} cursor-pointer select-none`,
48
+ children: label
49
+ }
50
+ )
51
+ ] });
52
+ }
53
+ );
54
+ Checkbox.displayName = "Checkbox";
55
+
56
+ export {
57
+ Checkbox
58
+ };
@@ -0,0 +1,86 @@
1
+ // src/FormField/FormField.tsx
2
+ import { forwardRef, useId } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ var FormField = forwardRef(
5
+ ({
6
+ label,
7
+ error,
8
+ helpText,
9
+ required,
10
+ id,
11
+ className = "",
12
+ style,
13
+ ...props
14
+ }, ref) => {
15
+ const generatedId = useId();
16
+ const inputId = id || `input-${generatedId}`;
17
+ const helpTextId = `help-${generatedId}`;
18
+ const errorId = `error-${generatedId}`;
19
+ const describedBy = [
20
+ helpText ? helpTextId : null,
21
+ error ? errorId : null
22
+ ].filter(Boolean).join(" ");
23
+ const containerStyle = {
24
+ display: "flex",
25
+ flexDirection: "column",
26
+ marginBottom: "1rem",
27
+ fontFamily: "system-ui, sans-serif",
28
+ ...style
29
+ };
30
+ const labelStyle = {
31
+ marginBottom: "0.5rem",
32
+ fontWeight: "600",
33
+ color: "#333"
34
+ };
35
+ const inputStyle = {
36
+ padding: "0.5rem",
37
+ borderRadius: "4px",
38
+ border: error ? "2px solid #dc3545" : "1px solid #ced4da",
39
+ fontSize: "1rem",
40
+ minHeight: "44px"
41
+ // Touch target
42
+ };
43
+ const errorStyle = {
44
+ color: "#dc3545",
45
+ fontSize: "0.875rem",
46
+ marginTop: "0.25rem",
47
+ display: "flex",
48
+ alignItems: "center"
49
+ };
50
+ const helpStyle = {
51
+ color: "#6c757d",
52
+ fontSize: "0.875rem",
53
+ marginTop: "0.25rem"
54
+ };
55
+ return /* @__PURE__ */ jsxs("div", { style: containerStyle, className, children: [
56
+ /* @__PURE__ */ jsxs("label", { htmlFor: inputId, style: labelStyle, children: [
57
+ label,
58
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { color: "#dc3545", marginLeft: "4px" }, children: "*" }),
59
+ required && /* @__PURE__ */ jsx("span", { className: "sr-only", children: " (obligatoriskt)" })
60
+ ] }),
61
+ /* @__PURE__ */ jsx(
62
+ "input",
63
+ {
64
+ ref,
65
+ id: inputId,
66
+ "aria-invalid": !!error,
67
+ "aria-describedby": describedBy || void 0,
68
+ "aria-required": required,
69
+ required,
70
+ style: inputStyle,
71
+ ...props
72
+ }
73
+ ),
74
+ error && /* @__PURE__ */ jsxs("div", { id: errorId, style: errorStyle, role: "alert", children: [
75
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { marginRight: "4px" }, children: "\u26A0\uFE0F" }),
76
+ error
77
+ ] }),
78
+ helpText && /* @__PURE__ */ jsx("div", { id: helpTextId, style: helpStyle, children: helpText })
79
+ ] });
80
+ }
81
+ );
82
+ FormField.displayName = "FormField";
83
+
84
+ export {
85
+ FormField
86
+ };
@@ -0,0 +1,130 @@
1
+ // src/Select/Select.tsx
2
+ import { useState, useRef, useEffect, createContext, useContext } from "react";
3
+ import { ChevronDown, Check } from "lucide-react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ var SelectContext = createContext(void 0);
6
+ var Select = ({ value, onChange, children }) => {
7
+ const [isOpen, setIsOpen] = useState(false);
8
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
9
+ const optionsRef = useRef([]);
10
+ const containerRef = useRef(null);
11
+ useEffect(() => {
12
+ const handleClickOutside = (event) => {
13
+ if (containerRef.current && !containerRef.current.contains(event.target)) {
14
+ setIsOpen(false);
15
+ }
16
+ };
17
+ document.addEventListener("mousedown", handleClickOutside);
18
+ return () => document.removeEventListener("mousedown", handleClickOutside);
19
+ }, []);
20
+ const handleKeyDown = (e) => {
21
+ if (!isOpen) {
22
+ if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
23
+ e.preventDefault();
24
+ setIsOpen(true);
25
+ setHighlightedIndex(0);
26
+ }
27
+ return;
28
+ }
29
+ switch (e.key) {
30
+ case "ArrowDown":
31
+ e.preventDefault();
32
+ setHighlightedIndex((prev) => Math.min(prev + 1, optionsRef.current.length - 1));
33
+ break;
34
+ case "ArrowUp":
35
+ e.preventDefault();
36
+ setHighlightedIndex((prev) => Math.max(prev - 1, 0));
37
+ break;
38
+ case "Enter":
39
+ case " ":
40
+ e.preventDefault();
41
+ if (highlightedIndex >= 0 && highlightedIndex < optionsRef.current.length) {
42
+ const option = optionsRef.current[highlightedIndex];
43
+ if (option) {
44
+ option.click();
45
+ }
46
+ }
47
+ break;
48
+ case "Escape":
49
+ e.preventDefault();
50
+ setIsOpen(false);
51
+ break;
52
+ }
53
+ };
54
+ return /* @__PURE__ */ jsx(SelectContext.Provider, { value: { value, onChange, isOpen, setIsOpen, highlightedIndex, setHighlightedIndex, optionsRef }, children: /* @__PURE__ */ jsx(
55
+ "div",
56
+ {
57
+ ref: containerRef,
58
+ className: "relative inline-block text-left w-full",
59
+ onKeyDown: handleKeyDown,
60
+ children
61
+ }
62
+ ) });
63
+ };
64
+ var SelectTrigger = ({ children, className = "", placeholder = "Select..." }) => {
65
+ const context = useContext(SelectContext);
66
+ if (!context) throw new Error("SelectTrigger must be used within Select");
67
+ return /* @__PURE__ */ jsxs(
68
+ "button",
69
+ {
70
+ type: "button",
71
+ onClick: () => context.setIsOpen(!context.isOpen),
72
+ "aria-haspopup": "listbox",
73
+ "aria-expanded": context.isOpen,
74
+ className: `flex items-center justify-between w-full px-4 py-2 text-sm bg-white border border-slate-300 rounded-md focus:outline-none focus:ring-2 focus:ring-slate-900 focus:border-transparent ${className}`,
75
+ children: [
76
+ /* @__PURE__ */ jsx("span", { className: context.value ? "text-slate-900" : "text-slate-500", children: children || placeholder }),
77
+ /* @__PURE__ */ jsx(ChevronDown, { className: "w-4 h-4 ml-2 opacity-50" })
78
+ ]
79
+ }
80
+ );
81
+ };
82
+ var SelectContent = ({ children }) => {
83
+ const context = useContext(SelectContext);
84
+ useEffect(() => {
85
+ if (context && context.isOpen) {
86
+ context.optionsRef.current = [];
87
+ }
88
+ }, [context?.isOpen]);
89
+ if (!context || !context.isOpen) return null;
90
+ return /* @__PURE__ */ jsx(
91
+ "div",
92
+ {
93
+ className: "absolute z-10 w-full mt-1 bg-white border border-slate-200 rounded-md shadow-lg max-h-60 overflow-auto focus:outline-none",
94
+ role: "listbox",
95
+ children
96
+ }
97
+ );
98
+ };
99
+ var SelectItem = ({ value, children }) => {
100
+ const context = useContext(SelectContext);
101
+ if (!context) throw new Error("SelectItem must be used within Select");
102
+ const isSelected = context.value === value;
103
+ const index = context.optionsRef.current.length;
104
+ return /* @__PURE__ */ jsxs(
105
+ "div",
106
+ {
107
+ ref: (el) => {
108
+ context.optionsRef.current[index] = el;
109
+ },
110
+ role: "option",
111
+ "aria-selected": isSelected,
112
+ onClick: () => {
113
+ context.onChange(value);
114
+ context.setIsOpen(false);
115
+ },
116
+ className: `flex items-center justify-between px-4 py-2 text-sm cursor-pointer ${context.highlightedIndex === index ? "bg-slate-100" : ""} ${isSelected ? "bg-slate-50 text-slate-900 font-medium" : "text-slate-700"}`,
117
+ children: [
118
+ children,
119
+ isSelected && /* @__PURE__ */ jsx(Check, { className: "w-4 h-4 text-slate-900" })
120
+ ]
121
+ }
122
+ );
123
+ };
124
+
125
+ export {
126
+ Select,
127
+ SelectTrigger,
128
+ SelectContent,
129
+ SelectItem
130
+ };
@@ -0,0 +1,14 @@
1
+ // src/Heading/Heading.tsx
2
+ import React from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var Heading = React.forwardRef(
5
+ ({ level, children, className, ...props }, ref) => {
6
+ const Tag = `h${level}`;
7
+ return /* @__PURE__ */ jsx(Tag, { ref, className, ...props, children });
8
+ }
9
+ );
10
+ Heading.displayName = "Heading";
11
+
12
+ export {
13
+ Heading
14
+ };