@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
package/dist/index.mjs ADDED
@@ -0,0 +1,308 @@
1
+ import {
2
+ NavigationMenu
3
+ } from "./chunk-GK4BYT56.mjs";
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectItem,
8
+ SelectTrigger
9
+ } from "./chunk-V2JYAFB7.mjs";
10
+ import {
11
+ SkipLink
12
+ } from "./chunk-MKKQLWGK.mjs";
13
+ import {
14
+ Switch
15
+ } from "./chunk-5RKBS475.mjs";
16
+ import {
17
+ ToastProvider,
18
+ useToast
19
+ } from "./chunk-2MJRKHPL.mjs";
20
+ import {
21
+ Tooltip,
22
+ TooltipContent,
23
+ TooltipProvider,
24
+ TooltipTrigger
25
+ } from "./chunk-C5M6C7KT.mjs";
26
+ import {
27
+ Button
28
+ } from "./chunk-NDYRGXQ6.mjs";
29
+ import {
30
+ Checkbox
31
+ } from "./chunk-NOE5QKC2.mjs";
32
+ import {
33
+ Heading
34
+ } from "./chunk-W4ZHBRFT.mjs";
35
+ import {
36
+ FormField
37
+ } from "./chunk-PLT5CAFO.mjs";
38
+ import {
39
+ Modal
40
+ } from "./chunk-HALLFO25.mjs";
41
+ import {
42
+ Dialog
43
+ } from "./chunk-LZ42XDDI.mjs";
44
+ import {
45
+ RadioGroup
46
+ } from "./chunk-YMSNGQN6.mjs";
47
+
48
+ // src/Breadcrumbs/Breadcrumbs.tsx
49
+ import React, { Children, isValidElement, cloneElement } from "react";
50
+ import { jsx, jsxs } from "react/jsx-runtime";
51
+ var BreadcrumbItem = ({ href, isCurrent, children, className, ...props }) => {
52
+ if (isCurrent) {
53
+ return /* @__PURE__ */ jsx("li", { className: `flex items-center text-slate-900 font-semibold ${className || ""}`, "aria-current": "page", ...props, children });
54
+ }
55
+ return /* @__PURE__ */ jsx("li", { className: `flex items-center text-slate-500 hover:text-slate-700 transition-colors ${className || ""}`, ...props, children: href ? /* @__PURE__ */ jsx("a", { href, className: "hover:underline focus:outline-none focus:ring-2 focus:ring-primary-500 rounded-sm", children }) : children });
56
+ };
57
+ var Breadcrumbs = ({ separator, children, className, ...props }) => {
58
+ const separatorIcon = separator || /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "text-slate-400 mx-2", children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
59
+ const items = Children.toArray(children).filter(isValidElement);
60
+ return /* @__PURE__ */ jsx("nav", { "aria-label": "Breadcrumb", className, ...props, children: /* @__PURE__ */ jsx("ol", { className: "flex items-center flex-wrap", children: items.map((child, index) => {
61
+ const isLast = index === items.length - 1;
62
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
63
+ cloneElement(child, {
64
+ isCurrent: isLast || child.props.isCurrent
65
+ }),
66
+ !isLast && /* @__PURE__ */ jsx("li", { "aria-hidden": "true", className: "flex items-center select-none", children: separatorIcon })
67
+ ] }, index);
68
+ }) }) });
69
+ };
70
+
71
+ // src/Accordion/Accordion.tsx
72
+ import React2, { createContext, useContext, useState } from "react";
73
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
74
+ var ChevronIcon = ({ className }) => /* @__PURE__ */ jsx2(
75
+ "svg",
76
+ {
77
+ xmlns: "http://www.w3.org/2000/svg",
78
+ width: "24",
79
+ height: "24",
80
+ viewBox: "0 0 24 24",
81
+ fill: "none",
82
+ stroke: "currentColor",
83
+ strokeWidth: "2",
84
+ strokeLinecap: "round",
85
+ strokeLinejoin: "round",
86
+ className,
87
+ children: /* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" })
88
+ }
89
+ );
90
+ var AccordionContext = createContext(void 0);
91
+ var Accordion = ({ type = "single", defaultValue, children, className }) => {
92
+ const [openItems, setOpenItems] = useState(() => {
93
+ if (Array.isArray(defaultValue)) return defaultValue;
94
+ if (defaultValue) return [defaultValue];
95
+ return [];
96
+ });
97
+ const toggleItem = (value) => {
98
+ setOpenItems((prev) => {
99
+ if (type === "single") {
100
+ return prev.includes(value) ? [] : [value];
101
+ }
102
+ return prev.includes(value) ? prev.filter((item) => item !== value) : [...prev, value];
103
+ });
104
+ };
105
+ return /* @__PURE__ */ jsx2(AccordionContext.Provider, { value: { openItems, toggleItem }, children: /* @__PURE__ */ jsx2("div", { className: `space-y-1 ${className || ""}`, children }) });
106
+ };
107
+ var AccordionItem = ({ value, children, className }) => {
108
+ const context = useContext(AccordionContext);
109
+ if (!context) throw new Error("AccordionItem must be used within an Accordion");
110
+ const isOpen = context.openItems.includes(value);
111
+ return /* @__PURE__ */ jsx2("div", { className: `border border-slate-200 rounded-lg overflow-hidden ${className || ""}`, children: React2.Children.map(children, (child) => {
112
+ if (React2.isValidElement(child)) {
113
+ return React2.cloneElement(child, {
114
+ ...child.props,
115
+ value,
116
+ // Pass value down to Trigger and Content
117
+ isOpen
118
+ });
119
+ }
120
+ return child;
121
+ }) });
122
+ };
123
+ var AccordionTrigger = ({ children, className, value, isOpen, ...props }) => {
124
+ const context = useContext(AccordionContext);
125
+ return /* @__PURE__ */ jsxs2(
126
+ "button",
127
+ {
128
+ type: "button",
129
+ className: `w-full flex items-center justify-between px-4 py-3 text-left font-medium text-slate-900 bg-white hover:bg-slate-50 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-inset ${className || ""}`,
130
+ onClick: () => value && context?.toggleItem(value),
131
+ "aria-expanded": isOpen,
132
+ "aria-controls": `accordion-content-${value}`,
133
+ id: `accordion-trigger-${value}`,
134
+ ...props,
135
+ children: [
136
+ children,
137
+ /* @__PURE__ */ jsx2(ChevronIcon, { className: `h-4 w-4 text-slate-500 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}` })
138
+ ]
139
+ }
140
+ );
141
+ };
142
+ var AccordionContent = ({ children, className, value, isOpen }) => {
143
+ return /* @__PURE__ */ jsx2(
144
+ "div",
145
+ {
146
+ id: `accordion-content-${value}`,
147
+ role: "region",
148
+ "aria-labelledby": `accordion-trigger-${value}`,
149
+ hidden: !isOpen,
150
+ className: `px-4 py-3 bg-white text-slate-600 border-t border-slate-100 text-sm leading-relaxed ${!isOpen ? "hidden" : ""} ${className || ""}`,
151
+ children
152
+ }
153
+ );
154
+ };
155
+
156
+ // src/Tabs/Tabs.tsx
157
+ import { createContext as createContext2, useContext as useContext2, useState as useState2, useRef } from "react";
158
+ import { jsx as jsx3 } from "react/jsx-runtime";
159
+ var TabsContext = createContext2(void 0);
160
+ var Tabs = ({
161
+ defaultValue,
162
+ value,
163
+ onValueChange,
164
+ orientation = "horizontal",
165
+ activationMode = "automatic",
166
+ children,
167
+ className
168
+ }) => {
169
+ const [baseId] = useState2(() => Math.random().toString(36).substr(2, 9));
170
+ const [internalValue, setInternalValue] = useState2(defaultValue || "");
171
+ const isControlled = value !== void 0;
172
+ const activeTab = isControlled ? value : internalValue;
173
+ const setActiveTab = (newValue) => {
174
+ if (!isControlled) {
175
+ setInternalValue(newValue);
176
+ }
177
+ onValueChange?.(newValue);
178
+ };
179
+ return /* @__PURE__ */ jsx3(TabsContext.Provider, { value: { activeTab, setActiveTab, orientation, activationMode, baseId }, children: /* @__PURE__ */ jsx3("div", { className: `flex ${orientation === "vertical" ? "flex-col md:flex-row gap-4" : "flex-col"} ${className || ""}`, children }) });
180
+ };
181
+ var TabsList = ({ children, className, ariaLabel }) => {
182
+ const context = useContext2(TabsContext);
183
+ if (!context) throw new Error("TabsList must be used within Tabs");
184
+ const listRef = useRef(null);
185
+ const handleKeyDown = (e) => {
186
+ const list = listRef.current;
187
+ if (!list) return;
188
+ const tabs = Array.from(list.querySelectorAll('[role="tab"]:not([disabled])'));
189
+ const index = tabs.indexOf(document.activeElement);
190
+ if (index === -1) return;
191
+ let nextIndex = index;
192
+ const lastIndex = tabs.length - 1;
193
+ switch (e.key) {
194
+ case "ArrowLeft":
195
+ case "ArrowUp":
196
+ nextIndex = index === 0 ? lastIndex : index - 1;
197
+ break;
198
+ case "ArrowRight":
199
+ case "ArrowDown":
200
+ nextIndex = index === lastIndex ? 0 : index + 1;
201
+ break;
202
+ case "Home":
203
+ nextIndex = 0;
204
+ break;
205
+ case "End":
206
+ nextIndex = lastIndex;
207
+ break;
208
+ default:
209
+ return;
210
+ }
211
+ e.preventDefault();
212
+ const nextTab = tabs[nextIndex];
213
+ nextTab.focus();
214
+ if (context.activationMode === "automatic") {
215
+ nextTab.click();
216
+ }
217
+ };
218
+ return /* @__PURE__ */ jsx3(
219
+ "div",
220
+ {
221
+ ref: listRef,
222
+ role: "tablist",
223
+ "aria-orientation": context.orientation,
224
+ "aria-label": ariaLabel,
225
+ className: `flex ${context.orientation === "vertical" ? "flex-col border-r border-slate-200" : "border-b border-slate-200"} ${className || ""}`,
226
+ onKeyDown: handleKeyDown,
227
+ tabIndex: -1,
228
+ children
229
+ }
230
+ );
231
+ };
232
+ var TabTrigger = ({ value, children, className, ...props }) => {
233
+ const context = useContext2(TabsContext);
234
+ if (!context) throw new Error("TabTrigger must be used within Tabs");
235
+ const isActive = context.activeTab === value;
236
+ const triggerId = `tab-${context.baseId}-${value}`;
237
+ const contentId = `content-${context.baseId}-${value}`;
238
+ return /* @__PURE__ */ jsx3(
239
+ "button",
240
+ {
241
+ id: triggerId,
242
+ role: "tab",
243
+ "aria-selected": isActive,
244
+ "aria-controls": contentId,
245
+ tabIndex: isActive ? 0 : -1,
246
+ onClick: () => context.setActiveTab(value),
247
+ className: `
248
+ px-4 py-2 text-sm font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500
249
+ ${context.orientation === "horizontal" ? "border-b-2" : "border-r-2 text-left"}
250
+ ${isActive ? `border-primary-500 text-primary-600 bg-primary-50/50` : `border-transparent text-slate-500 hover:text-slate-700 hover:bg-slate-50`}
251
+ ${className || ""}
252
+ `,
253
+ ...props,
254
+ children
255
+ }
256
+ );
257
+ };
258
+ var TabsContent = ({ value, children, className }) => {
259
+ const context = useContext2(TabsContext);
260
+ if (!context) throw new Error("TabsContent must be used within Tabs");
261
+ const isActive = context.activeTab === value;
262
+ const triggerId = `tab-${context.baseId}-${value}`;
263
+ const contentId = `content-${context.baseId}-${value}`;
264
+ if (!isActive) return null;
265
+ return /* @__PURE__ */ jsx3(
266
+ "div",
267
+ {
268
+ id: contentId,
269
+ role: "tabpanel",
270
+ "aria-labelledby": triggerId,
271
+ tabIndex: 0,
272
+ className: `py-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 rounded-md ${className || ""}`,
273
+ children
274
+ }
275
+ );
276
+ };
277
+ export {
278
+ Accordion,
279
+ AccordionContent,
280
+ AccordionItem,
281
+ AccordionTrigger,
282
+ BreadcrumbItem,
283
+ Breadcrumbs,
284
+ Button,
285
+ Checkbox,
286
+ Dialog,
287
+ FormField,
288
+ Heading,
289
+ Modal,
290
+ NavigationMenu,
291
+ RadioGroup,
292
+ Select,
293
+ SelectContent,
294
+ SelectItem,
295
+ SelectTrigger,
296
+ SkipLink,
297
+ Switch,
298
+ TabTrigger,
299
+ Tabs,
300
+ TabsContent,
301
+ TabsList,
302
+ ToastProvider,
303
+ Tooltip,
304
+ TooltipContent,
305
+ TooltipProvider,
306
+ TooltipTrigger,
307
+ useToast
308
+ };
package/package.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "name": "@holmdigital/components",
3
+ "author": "Holm Digital AB",
4
+ "version": "1.1.0",
5
+ "private": false,
6
+ "publishConfig": {
7
+ "access": "public",
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "description": "Prescriptive, accessible React components for regulatory compliance",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/holmdigital/a11y-hd.git",
14
+ "directory": "packages/components"
15
+ },
16
+ "main": "./dist/index.js",
17
+ "module": "./dist/index.mjs",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js",
23
+ "types": "./dist/index.d.ts"
24
+ },
25
+ "./Button": {
26
+ "import": "./dist/Button/Button.mjs",
27
+ "types": "./dist/Button/Button.d.ts"
28
+ },
29
+ "./FormField": {
30
+ "import": "./dist/FormField/FormField.mjs",
31
+ "types": "./dist/FormField/FormField.d.ts"
32
+ },
33
+ "./Dialog": {
34
+ "import": "./dist/Dialog/Dialog.mjs",
35
+ "types": "./dist/Dialog/Dialog.d.ts"
36
+ },
37
+ "./Modal": {
38
+ "import": "./dist/Modal/Modal.mjs",
39
+ "types": "./dist/Modal/Modal.d.ts"
40
+ },
41
+ "./SkipLink": {
42
+ "import": "./dist/SkipLink/SkipLink.mjs",
43
+ "types": "./dist/SkipLink/SkipLink.d.ts"
44
+ },
45
+ "./NavigationMenu": {
46
+ "import": "./dist/NavigationMenu/NavigationMenu.mjs",
47
+ "types": "./dist/NavigationMenu/NavigationMenu.d.ts"
48
+ },
49
+ "./Checkbox": {
50
+ "import": "./dist/Checkbox/Checkbox.mjs",
51
+ "types": "./dist/Checkbox/Checkbox.d.ts"
52
+ },
53
+ "./RadioGroup": {
54
+ "import": "./dist/RadioGroup/RadioGroup.mjs",
55
+ "types": "./dist/RadioGroup/RadioGroup.d.ts"
56
+ },
57
+ "./Select": {
58
+ "import": "./dist/Select/Select.mjs",
59
+ "types": "./dist/Select/Select.d.ts"
60
+ },
61
+ "./Switch": {
62
+ "import": "./dist/Switch/Switch.mjs",
63
+ "types": "./dist/Switch/Switch.d.ts"
64
+ },
65
+ "./Toast": {
66
+ "import": "./dist/Toast/Toast.mjs",
67
+ "types": "./dist/Toast/Toast.d.ts"
68
+ },
69
+ "./Tooltip": {
70
+ "import": "./dist/Tooltip/Tooltip.mjs",
71
+ "types": "./dist/Tooltip/Tooltip.d.ts"
72
+ },
73
+ "./Heading": {
74
+ "import": "./dist/Heading/Heading.mjs",
75
+ "types": "./dist/Heading/Heading.d.ts"
76
+ }
77
+ },
78
+ "files": [
79
+ "dist",
80
+ "README.md"
81
+ ],
82
+ "scripts": {
83
+ "build": "tsup src/index.ts src/Button/Button.tsx src/FormField/FormField.tsx src/Dialog/Dialog.tsx src/Modal/Modal.tsx src/SkipLink/SkipLink.tsx src/NavigationMenu/NavigationMenu.tsx src/Checkbox/Checkbox.tsx src/RadioGroup/RadioGroup.tsx src/Select/Select.tsx src/Switch/Switch.tsx src/Toast/Toast.tsx src/Tooltip/Tooltip.tsx src/Heading/Heading.tsx --format cjs,esm --dts --clean --external react",
84
+ "dev": "tsup src/index.ts src/Button/Button.tsx src/FormField/FormField.tsx src/Dialog/Dialog.tsx src/Modal/Modal.tsx src/SkipLink/SkipLink.tsx src/NavigationMenu/NavigationMenu.tsx src/Checkbox/Checkbox.tsx src/RadioGroup/RadioGroup.tsx src/Select/Select.tsx src/Switch/Switch.tsx src/Toast/Toast.tsx src/Tooltip/Tooltip.tsx src/Heading/Heading.tsx --format cjs,esm --dts --watch --external react",
85
+ "lint": "eslint src --ext .ts,.tsx",
86
+ "storybook": "storybook dev -p 6006",
87
+ "build-storybook": "storybook build"
88
+ },
89
+ "keywords": [
90
+ "react",
91
+ "components",
92
+ "accessibility",
93
+ "a11y",
94
+ "wcag",
95
+ "design-system"
96
+ ],
97
+ "peerDependencies": {
98
+ "react": ">=18.0.0",
99
+ "react-dom": ">=18.0.0"
100
+ },
101
+ "devDependencies": {
102
+ "@types/react": "^18.2.0",
103
+ "@types/react-dom": "^18.2.0",
104
+ "react": "^18.2.0",
105
+ "react-dom": "^18.2.0",
106
+ "storybook": "^7.6.0",
107
+ "tsup": "^8.3.5",
108
+ "typescript": "^5.7.2"
109
+ },
110
+ "dependencies": {
111
+ "lucide-react": "^0.556.0"
112
+ }
113
+ }