@hex-core/components 1.7.0 → 1.8.1

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 (98) hide show
  1. package/dist/_tsup-dts-rollup.d.ts +1566 -5
  2. package/dist/arc.d.ts +4 -0
  3. package/dist/arc.js +147 -0
  4. package/dist/arc.js.map +1 -0
  5. package/dist/audio-player.d.ts +2 -0
  6. package/dist/audio-player.js +119 -0
  7. package/dist/audio-player.js.map +1 -0
  8. package/dist/audio-waveform.d.ts +2 -0
  9. package/dist/audio-waveform.js +72 -0
  10. package/dist/audio-waveform.js.map +1 -0
  11. package/dist/canvas.d.ts +2 -0
  12. package/dist/canvas.js +73 -0
  13. package/dist/canvas.js.map +1 -0
  14. package/dist/chord.d.ts +4 -0
  15. package/dist/chord.js +230 -0
  16. package/dist/chord.js.map +1 -0
  17. package/dist/cloze.d.ts +3 -0
  18. package/dist/cloze.js +98 -0
  19. package/dist/cloze.js.map +1 -0
  20. package/dist/color-picker.js.map +1 -1
  21. package/dist/compare-table.d.ts +4 -0
  22. package/dist/compare-table.js +109 -0
  23. package/dist/compare-table.js.map +1 -0
  24. package/dist/data-table.js.map +1 -1
  25. package/dist/deck.d.ts +3 -0
  26. package/dist/deck.js +231 -0
  27. package/dist/deck.js.map +1 -0
  28. package/dist/dendrogram.d.ts +3 -0
  29. package/dist/dendrogram.js +162 -0
  30. package/dist/dendrogram.js.map +1 -0
  31. package/dist/diagram.d.ts +2 -0
  32. package/dist/diagram.js +70 -0
  33. package/dist/diagram.js.map +1 -0
  34. package/dist/flashcard.d.ts +2 -0
  35. package/dist/flashcard.js +107 -0
  36. package/dist/flashcard.js.map +1 -0
  37. package/dist/flowchart.d.ts +4 -0
  38. package/dist/flowchart.js +275 -0
  39. package/dist/flowchart.js.map +1 -0
  40. package/dist/funnel.d.ts +3 -0
  41. package/dist/funnel.js +157 -0
  42. package/dist/funnel.js.map +1 -0
  43. package/dist/gantt.d.ts +3 -0
  44. package/dist/gantt.js +279 -0
  45. package/dist/gantt.js.map +1 -0
  46. package/dist/image-occlusion.d.ts +3 -0
  47. package/dist/image-occlusion.js +106 -0
  48. package/dist/image-occlusion.js.map +1 -0
  49. package/dist/index.d.ts +84 -0
  50. package/dist/index.js +3946 -2
  51. package/dist/index.js.map +1 -1
  52. package/dist/matrix.d.ts +3 -0
  53. package/dist/matrix.js +155 -0
  54. package/dist/matrix.js.map +1 -0
  55. package/dist/mind-map.d.ts +3 -0
  56. package/dist/mind-map.js +167 -0
  57. package/dist/mind-map.js.map +1 -0
  58. package/dist/org-chart.d.ts +3 -0
  59. package/dist/org-chart.js +215 -0
  60. package/dist/org-chart.js.map +1 -0
  61. package/dist/pyramid.d.ts +3 -0
  62. package/dist/pyramid.js +150 -0
  63. package/dist/pyramid.js.map +1 -0
  64. package/dist/quiz.d.ts +3 -0
  65. package/dist/quiz.js +128 -0
  66. package/dist/quiz.js.map +1 -0
  67. package/dist/sankey.d.ts +4 -0
  68. package/dist/sankey.js +190 -0
  69. package/dist/sankey.js.map +1 -0
  70. package/dist/schemas.d.ts +23 -0
  71. package/dist/schemas.js +2210 -3
  72. package/dist/schemas.js.map +1 -1
  73. package/dist/sequence.d.ts +4 -0
  74. package/dist/sequence.js +229 -0
  75. package/dist/sequence.js.map +1 -0
  76. package/dist/sonner.js.map +1 -1
  77. package/dist/spaced-repetition.d.ts +3 -0
  78. package/dist/spaced-repetition.js +73 -0
  79. package/dist/spaced-repetition.js.map +1 -0
  80. package/dist/sunburst.d.ts +3 -0
  81. package/dist/sunburst.js +205 -0
  82. package/dist/sunburst.js.map +1 -0
  83. package/dist/terminal.d.ts +2 -0
  84. package/dist/terminal.js +153 -0
  85. package/dist/terminal.js.map +1 -0
  86. package/dist/textarea.js.map +1 -1
  87. package/dist/time-axis.d.ts +3 -0
  88. package/dist/time-axis.js +233 -0
  89. package/dist/time-axis.js.map +1 -0
  90. package/dist/tool-call.js +6 -1
  91. package/dist/tool-call.js.map +1 -1
  92. package/dist/tree-map.d.ts +3 -0
  93. package/dist/tree-map.js +171 -0
  94. package/dist/tree-map.js.map +1 -0
  95. package/dist/venn.d.ts +3 -0
  96. package/dist/venn.js +196 -0
  97. package/dist/venn.js.map +1 -0
  98. package/package.json +49 -5
@@ -0,0 +1,109 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ var EMPTY_PLACEHOLDER = "\u2014";
11
+ function CompareTable({
12
+ subjects,
13
+ attributes,
14
+ highlightDifferences = false,
15
+ onCellClick,
16
+ className,
17
+ ...rest
18
+ }) {
19
+ const warnedRef = React.useRef(false);
20
+ const nodeEnv = globalThis.process?.env?.NODE_ENV;
21
+ if (nodeEnv !== "production" && !warnedRef.current) {
22
+ const subjectIds = new Set(subjects.map((s) => s.id));
23
+ for (const attr of attributes) {
24
+ for (const valueId of Object.keys(attr.values)) {
25
+ if (!subjectIds.has(valueId)) {
26
+ warnedRef.current = true;
27
+ console.warn(
28
+ `[hex-core/CompareTable] Attribute "${attr.id}" has a value for subjectId "${valueId}" that isn't in the subjects array. The cell will not render.`
29
+ );
30
+ break;
31
+ }
32
+ }
33
+ if (warnedRef.current) break;
34
+ }
35
+ }
36
+ return /* @__PURE__ */ jsx(
37
+ "div",
38
+ {
39
+ ...rest,
40
+ "data-hex-compare-table": true,
41
+ className: cn("overflow-x-auto rounded-lg border", className),
42
+ children: /* @__PURE__ */ jsxs("table", { className: "w-full border-collapse text-sm", children: [
43
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { "data-hex-compare-table-header": true, children: [
44
+ /* @__PURE__ */ jsx(
45
+ "th",
46
+ {
47
+ scope: "col",
48
+ className: "sticky left-0 z-10 border-b bg-card px-3 py-2 text-left text-xs font-medium text-muted-foreground"
49
+ }
50
+ ),
51
+ subjects.map((subject) => /* @__PURE__ */ jsx(
52
+ "th",
53
+ {
54
+ scope: "col",
55
+ "data-hex-compare-table-subject": true,
56
+ "data-subject-id": subject.id,
57
+ className: "border-b bg-card px-3 py-2 text-left font-semibold",
58
+ children: subject.label
59
+ },
60
+ subject.id
61
+ ))
62
+ ] }) }),
63
+ /* @__PURE__ */ jsx("tbody", { children: attributes.map((attr) => {
64
+ const reference = highlightDifferences ? subjects.map((s) => attr.values[s.id]).find((v) => v != null && v !== "") : void 0;
65
+ return /* @__PURE__ */ jsxs("tr", { "data-hex-compare-table-row": true, "data-attribute-id": attr.id, children: [
66
+ /* @__PURE__ */ jsx(
67
+ "th",
68
+ {
69
+ scope: "row",
70
+ className: "sticky left-0 z-10 border-b bg-card px-3 py-2 text-left font-medium",
71
+ children: attr.label
72
+ }
73
+ ),
74
+ subjects.map((subject) => {
75
+ const raw = attr.values[subject.id];
76
+ const isEmpty = raw == null || raw === "";
77
+ const displayValue = isEmpty ? EMPTY_PLACEHOLDER : raw;
78
+ const isComparable = (typeof raw === "string" || typeof raw === "number" || typeof raw === "boolean") && (typeof reference === "string" || typeof reference === "number" || typeof reference === "boolean");
79
+ const differs = highlightDifferences && !isEmpty && reference !== void 0 && isComparable && String(raw) !== String(reference);
80
+ const interactive = Boolean(onCellClick);
81
+ return /* @__PURE__ */ jsx(
82
+ "td",
83
+ {
84
+ "data-hex-compare-table-cell": true,
85
+ "data-subject-id": subject.id,
86
+ "data-attribute-id": attr.id,
87
+ "data-differs": differs,
88
+ className: cn(
89
+ "border-b px-3 py-2 align-top",
90
+ differs && "bg-accent/30 text-accent-foreground",
91
+ isEmpty && "text-muted-foreground",
92
+ interactive && "cursor-pointer hover:bg-muted/40"
93
+ ),
94
+ onClick: interactive ? () => onCellClick?.(subject.id, attr.id) : void 0,
95
+ children: displayValue
96
+ },
97
+ subject.id
98
+ );
99
+ })
100
+ ] }, attr.id);
101
+ }) })
102
+ ] })
103
+ }
104
+ );
105
+ }
106
+
107
+ export { CompareTable };
108
+ //# sourceMappingURL=compare-table.js.map
109
+ //# sourceMappingURL=compare-table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/compare-table/compare-table.tsx"],"names":[],"mappings":"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","file":"compare-table.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Side-by-side comparison table. Subjects are columns; attributes are\n * rows; cells render the per-subject value for that attribute. With\n * `highlightDifferences`, cells whose value differs from the row's\n * first non-empty cell get a subtle accent — handy for vocab pairs\n * (term ↔ translation), feature matrices (Linux vs Mac vs Windows),\n * before/after comparisons. Pure HTML; no heavy peer.\n *\n * @example\n * <CompareTable\n * subjects={[\n * { id: \"linux\", label: \"Linux\" },\n * { id: \"mac\", label: \"Mac\" },\n * { id: \"win\", label: \"Windows\" },\n * ]}\n * attributes={[\n * { id: \"kernel\", label: \"Kernel\", values: { linux: \"Linux\", mac: \"Darwin\", win: \"NT\" } },\n * { id: \"fs\", label: \"Default FS\", values: { linux: \"ext4\", mac: \"APFS\", win: \"NTFS\" } },\n * ]}\n * />\n */\nexport type CompareSubject = {\n\tid: string;\n\tlabel: React.ReactNode;\n};\n\nexport type CompareAttribute = {\n\tid: string;\n\tlabel: React.ReactNode;\n\t/** Map of subjectId → cell content. Missing keys render as \"—\". */\n\tvalues: Record<string, React.ReactNode>;\n};\n\nexport interface CompareTableProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/** Columns. */\n\tsubjects: CompareSubject[];\n\t/** Rows. */\n\tattributes: CompareAttribute[];\n\t/** When true, cells whose value differs from the row's first non-empty cell get a subtle accent. */\n\thighlightDifferences?: boolean;\n\t/** Fired when a body cell is clicked. */\n\tonCellClick?: (subjectId: string, attributeId: string) => void;\n}\n\nconst EMPTY_PLACEHOLDER = \"—\";\n\nfunction CompareTable({\n\tsubjects,\n\tattributes,\n\thighlightDifferences = false,\n\tonCellClick,\n\tclassName,\n\t...rest\n}: CompareTableProps) {\n\t// Surface a developer-facing console.warn when an attribute references a\n\t// subjectId that isn't actually in `subjects` — easy mistake when the\n\t// consumer rebuilds rows from a different source than columns.\n\tconst warnedRef = React.useRef(false);\n\tconst nodeEnv = (globalThis as { process?: { env?: { NODE_ENV?: string } } }).process?.env?.NODE_ENV;\n\tif (nodeEnv !== \"production\" && !warnedRef.current) {\n\t\tconst subjectIds = new Set(subjects.map((s) => s.id));\n\t\tfor (const attr of attributes) {\n\t\t\tfor (const valueId of Object.keys(attr.values)) {\n\t\t\t\tif (!subjectIds.has(valueId)) {\n\t\t\t\t\twarnedRef.current = true;\n\t\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t`[hex-core/CompareTable] Attribute \"${attr.id}\" has a value for subjectId \"${valueId}\" that isn't in the subjects array. The cell will not render.`,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (warnedRef.current) break;\n\t\t}\n\t}\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-compare-table\n\t\t\tclassName={cn(\"overflow-x-auto rounded-lg border\", className)}\n\t\t>\n\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t<thead>\n\t\t\t\t\t<tr data-hex-compare-table-header>\n\t\t\t\t\t\t<th\n\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\tclassName=\"sticky left-0 z-10 border-b bg-card px-3 py-2 text-left text-xs font-medium text-muted-foreground\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{/* Empty corner — attribute labels live here per row */}\n\t\t\t\t\t\t</th>\n\t\t\t\t\t\t{subjects.map((subject) => (\n\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\tkey={subject.id}\n\t\t\t\t\t\t\t\tscope=\"col\"\n\t\t\t\t\t\t\t\tdata-hex-compare-table-subject\n\t\t\t\t\t\t\t\tdata-subject-id={subject.id}\n\t\t\t\t\t\t\t\tclassName=\"border-b bg-card px-3 py-2 text-left font-semibold\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{subject.label}\n\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t))}\n\t\t\t\t\t</tr>\n\t\t\t\t</thead>\n\t\t\t\t<tbody>\n\t\t\t\t\t{attributes.map((attr) => {\n\t\t\t\t\t\t// Find the row's reference value (first non-empty cell among the\n\t\t\t\t\t\t// subjects we're rendering, preserving display order).\n\t\t\t\t\t\tconst reference = highlightDifferences\n\t\t\t\t\t\t\t? subjects.map((s) => attr.values[s.id]).find((v) => v != null && v !== \"\")\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<tr key={attr.id} data-hex-compare-table-row data-attribute-id={attr.id}>\n\t\t\t\t\t\t\t\t<th\n\t\t\t\t\t\t\t\t\tscope=\"row\"\n\t\t\t\t\t\t\t\t\tclassName=\"sticky left-0 z-10 border-b bg-card px-3 py-2 text-left font-medium\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{attr.label}\n\t\t\t\t\t\t\t\t</th>\n\t\t\t\t\t\t\t\t{subjects.map((subject) => {\n\t\t\t\t\t\t\t\t\tconst raw = attr.values[subject.id];\n\t\t\t\t\t\t\t\t\tconst isEmpty = raw == null || raw === \"\";\n\t\t\t\t\t\t\t\t\tconst displayValue = isEmpty ? EMPTY_PLACEHOLDER : raw;\n\t\t\t\t\t\t\t\t\t// Skip diff for non-primitive values: ReactElements stringify\n\t\t\t\t\t\t\t\t\t// to \"[object Object]\" and would all flag as different. Only\n\t\t\t\t\t\t\t\t\t// run the comparison when both raw + reference are scalar.\n\t\t\t\t\t\t\t\t\tconst isComparable =\n\t\t\t\t\t\t\t\t\t\t(typeof raw === \"string\" || typeof raw === \"number\" || typeof raw === \"boolean\") &&\n\t\t\t\t\t\t\t\t\t\t(typeof reference === \"string\" ||\n\t\t\t\t\t\t\t\t\t\t\ttypeof reference === \"number\" ||\n\t\t\t\t\t\t\t\t\t\t\ttypeof reference === \"boolean\");\n\t\t\t\t\t\t\t\t\tconst differs =\n\t\t\t\t\t\t\t\t\t\thighlightDifferences &&\n\t\t\t\t\t\t\t\t\t\t!isEmpty &&\n\t\t\t\t\t\t\t\t\t\treference !== undefined &&\n\t\t\t\t\t\t\t\t\t\tisComparable &&\n\t\t\t\t\t\t\t\t\t\tString(raw) !== String(reference);\n\t\t\t\t\t\t\t\t\tconst interactive = Boolean(onCellClick);\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<td\n\t\t\t\t\t\t\t\t\t\t\tkey={subject.id}\n\t\t\t\t\t\t\t\t\t\t\tdata-hex-compare-table-cell\n\t\t\t\t\t\t\t\t\t\t\tdata-subject-id={subject.id}\n\t\t\t\t\t\t\t\t\t\t\tdata-attribute-id={attr.id}\n\t\t\t\t\t\t\t\t\t\t\tdata-differs={differs}\n\t\t\t\t\t\t\t\t\t\t\tclassName={cn(\n\t\t\t\t\t\t\t\t\t\t\t\t\"border-b px-3 py-2 align-top\",\n\t\t\t\t\t\t\t\t\t\t\t\tdiffers && \"bg-accent/30 text-accent-foreground\",\n\t\t\t\t\t\t\t\t\t\t\t\tisEmpty && \"text-muted-foreground\",\n\t\t\t\t\t\t\t\t\t\t\t\tinteractive && \"cursor-pointer hover:bg-muted/40\",\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\tonClick={interactive ? () => onCellClick?.(subject.id, attr.id) : undefined}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t{displayValue}\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t);\n\t\t\t\t\t})}\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t</div>\n\t);\n}\n\nexport { CompareTable };\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/utils.ts","../src/components/table/table.tsx","../src/components/data-table/data-table.tsx"],"names":["jsx"],"mappings":"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","file":"data-table.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/** A responsive container + styled HTML table. */\nconst Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(\n\t({ className, ...props }, ref) => (\n\t\t<div className=\"relative w-full overflow-auto\">\n\t\t\t<table\n\t\t\t\tref={ref}\n\t\t\t\tclassName={cn(\"w-full caption-bottom text-sm\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</div>\n\t),\n);\nTable.displayName = \"Table\";\n\n/** `<thead>` wrapper with bottom border. */\nconst TableHeader = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-b-foreground/[0.08]\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\n/** `<tbody>` wrapper removing bottom border on last row. */\nconst TableBody = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n));\nTableBody.displayName = \"TableBody\";\n\n/** `<tfoot>` wrapper with muted background. */\nconst TableFooter = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tfoot\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-t border-t-foreground/[0.08] bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableFooter.displayName = \"TableFooter\";\n\n/** `<tr>` with hover + selected states. */\nconst TableRow = React.forwardRef<\n\tHTMLTableRowElement,\n\tReact.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n\t<tr\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-b border-b-foreground/[0.08] transition-all duration-[var(--duration-normal,200ms)] ease-out hover:bg-muted/50 data-[state=selected]:bg-muted\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableRow.displayName = \"TableRow\";\n\n/** `<th>` with left-aligned muted text. */\nconst TableHead = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<th\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"h-[var(--control-height-md,2.5rem)] px-[var(--space-4,1rem)] text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableHead.displayName = \"TableHead\";\n\n/** `<td>` with consistent padding. */\nconst TableCell = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<td\n\t\tref={ref}\n\t\tclassName={cn(\"p-[var(--space-4,1rem)] align-middle [&:has([role=checkbox])]:pr-0\", className)}\n\t\t{...props}\n\t/>\n));\nTableCell.displayName = \"TableCell\";\n\n/**\n * Visible `<caption>` rendered below the table. The parent `<Table>` sets\n * `caption-bottom`, so the caption is announced first by screen readers when\n * entering the table, then visually placed below the rows.\n */\nconst TableCaption = React.forwardRef<\n\tHTMLTableCaptionElement,\n\tReact.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n\t<caption\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"caption-bottom mt-[var(--space-4,1rem)] text-sm text-muted-foreground\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n\tTable,\n\tTableHeader,\n\tTableBody,\n\tTableFooter,\n\tTableHead,\n\tTableRow,\n\tTableCell,\n\tTableCaption,\n};\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n\ttype ColumnDef,\n\tflexRender,\n\tgetCoreRowModel,\n\tuseReactTable,\n} from \"@tanstack/react-table\";\nimport {\n\tTable,\n\tTableBody,\n\tTableCaption,\n\tTableCell,\n\tTableHead,\n\tTableHeader,\n\tTableRow,\n} from \"../table/table.js\";\n\n/**\n * Generic DataTable wrapper that renders a TanStack Table model using Hex UI's\n * Table primitives. Pass columns + data; use TanStack hooks for sorting,\n * filtering, pagination, row-selection as needed.\n * @template TData - Row data type. Cell value types are inferred per column by TanStack.\n */\nexport interface DataTableProps<TData> {\n\tcolumns: ColumnDef<TData, unknown>[];\n\tdata: TData[];\n\t/**\n\t * Visible caption rendered below the table. Announced by screen readers\n\t * when the user enters the table. Provide either `caption` or `aria-label`.\n\t */\n\tcaption?: React.ReactNode;\n\t/**\n\t * Accessible label for the table when no visible caption is shown.\n\t * Forwarded as `aria-label` on the underlying `<table>` element. Kebab-case\n\t * to match the canonical ARIA prop convention used elsewhere in Hex UI.\n\t */\n\t\"aria-label\"?: string;\n}\n\n/**\n * Render a data-driven table from TanStack column definitions.\n * @param props - Columns, data, and optional accessible labelling (`caption` or `aria-label`)\n * @returns A styled Table rendered from the TanStack row model\n */\nexport function DataTable<TData>({\n\tcolumns,\n\tdata,\n\tcaption,\n\t\"aria-label\": ariaLabel,\n}: DataTableProps<TData>) {\n\tconst table = useReactTable({\n\t\tdata,\n\t\tcolumns,\n\t\tgetCoreRowModel: getCoreRowModel(),\n\t});\n\n\treturn (\n\t\t<div className=\"rounded-md border border-foreground/[0.08]\">\n\t\t\t<Table aria-label={ariaLabel}>\n\t\t\t\t{caption ? <TableCaption>{caption}</TableCaption> : null}\n\t\t\t\t<TableHeader>\n\t\t\t\t\t{table.getHeaderGroups().map((headerGroup) => (\n\t\t\t\t\t\t<TableRow key={headerGroup.id}>\n\t\t\t\t\t\t\t{headerGroup.headers.map((header) => (\n\t\t\t\t\t\t\t\t<TableHead key={header.id}>\n\t\t\t\t\t\t\t\t\t{header.isPlaceholder\n\t\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t\t: flexRender(header.column.columnDef.header, header.getContext())}\n\t\t\t\t\t\t\t\t</TableHead>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t))}\n\t\t\t\t</TableHeader>\n\t\t\t\t<TableBody>\n\t\t\t\t\t{table.getRowModel().rows?.length ? (\n\t\t\t\t\t\ttable.getRowModel().rows.map((row) => (\n\t\t\t\t\t\t\t<TableRow key={row.id} data-state={row.getIsSelected() && \"selected\"}>\n\t\t\t\t\t\t\t\t{row.getVisibleCells().map((cell) => (\n\t\t\t\t\t\t\t\t\t<TableCell key={cell.id}>\n\t\t\t\t\t\t\t\t\t\t{flexRender(cell.column.columnDef.cell, cell.getContext())}\n\t\t\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t\t))\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<TableRow>\n\t\t\t\t\t\t\t<TableCell colSpan={columns.length} className=\"h-24 text-center\">\n\t\t\t\t\t\t\t\tNo results.\n\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t)}\n\t\t\t\t</TableBody>\n\t\t\t</Table>\n\t\t</div>\n\t);\n}\n"]}
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/components/table/table.tsx","../src/components/data-table/data-table.tsx"],"names":["jsx"],"mappings":"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","file":"data-table.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","import * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/** A responsive container + styled HTML table. */\nconst Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(\n\t({ className, ...props }, ref) => (\n\t\t<div className=\"relative w-full overflow-auto\">\n\t\t\t<table\n\t\t\t\tref={ref}\n\t\t\t\tclassName={cn(\"w-full caption-bottom text-sm\", className)}\n\t\t\t\t{...props}\n\t\t\t/>\n\t\t</div>\n\t),\n);\nTable.displayName = \"Table\";\n\n/** `<thead>` wrapper with bottom border. */\nconst TableHeader = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<thead ref={ref} className={cn(\"[&_tr]:border-b [&_tr]:border-b-foreground/[0.08]\", className)} {...props} />\n));\nTableHeader.displayName = \"TableHeader\";\n\n/** `<tbody>` wrapper removing bottom border on last row. */\nconst TableBody = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tbody ref={ref} className={cn(\"[&_tr:last-child]:border-0\", className)} {...props} />\n));\nTableBody.displayName = \"TableBody\";\n\n/** `<tfoot>` wrapper with muted background. */\nconst TableFooter = React.forwardRef<\n\tHTMLTableSectionElement,\n\tReact.HTMLAttributes<HTMLTableSectionElement>\n>(({ className, ...props }, ref) => (\n\t<tfoot\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-t border-t-foreground/[0.08] bg-muted/50 font-medium [&>tr]:last:border-b-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableFooter.displayName = \"TableFooter\";\n\n/** `<tr>` with hover + selected states. */\nconst TableRow = React.forwardRef<\n\tHTMLTableRowElement,\n\tReact.HTMLAttributes<HTMLTableRowElement>\n>(({ className, ...props }, ref) => (\n\t<tr\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"border-b border-b-foreground/[0.08] transition-all duration-[var(--duration-normal,200ms)] ease-out hover:bg-muted/50 data-[state=selected]:bg-muted\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableRow.displayName = \"TableRow\";\n\n/** `<th>` with left-aligned muted text. */\nconst TableHead = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.ThHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<th\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"h-[var(--control-height-md,2.5rem)] px-[var(--space-4,1rem)] text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableHead.displayName = \"TableHead\";\n\n/** `<td>` with consistent padding. */\nconst TableCell = React.forwardRef<\n\tHTMLTableCellElement,\n\tReact.TdHTMLAttributes<HTMLTableCellElement>\n>(({ className, ...props }, ref) => (\n\t<td\n\t\tref={ref}\n\t\tclassName={cn(\"p-[var(--space-4,1rem)] align-middle [&:has([role=checkbox])]:pr-0\", className)}\n\t\t{...props}\n\t/>\n));\nTableCell.displayName = \"TableCell\";\n\n/**\n * Visible `<caption>` rendered below the table. The parent `<Table>` sets\n * `caption-bottom`, so the caption is announced first by screen readers when\n * entering the table, then visually placed below the rows.\n */\nconst TableCaption = React.forwardRef<\n\tHTMLTableCaptionElement,\n\tReact.HTMLAttributes<HTMLTableCaptionElement>\n>(({ className, ...props }, ref) => (\n\t<caption\n\t\tref={ref}\n\t\tclassName={cn(\n\t\t\t\"caption-bottom mt-[var(--space-4,1rem)] text-sm text-muted-foreground\",\n\t\t\tclassName,\n\t\t)}\n\t\t{...props}\n\t/>\n));\nTableCaption.displayName = \"TableCaption\";\n\nexport {\n\tTable,\n\tTableHeader,\n\tTableBody,\n\tTableFooter,\n\tTableHead,\n\tTableRow,\n\tTableCell,\n\tTableCaption,\n};\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n\ttype ColumnDef,\n\tflexRender,\n\tgetCoreRowModel,\n\tuseReactTable,\n} from \"@tanstack/react-table\";\nimport {\n\tTable,\n\tTableBody,\n\tTableCaption,\n\tTableCell,\n\tTableHead,\n\tTableHeader,\n\tTableRow,\n} from \"../table/table.js\";\n\n/**\n * Generic DataTable wrapper that renders a TanStack Table model using Hex Core's\n * Table primitives. Pass columns + data; use TanStack hooks for sorting,\n * filtering, pagination, row-selection as needed.\n * @template TData - Row data type. Cell value types are inferred per column by TanStack.\n */\nexport interface DataTableProps<TData> {\n\tcolumns: ColumnDef<TData, unknown>[];\n\tdata: TData[];\n\t/**\n\t * Visible caption rendered below the table. Announced by screen readers\n\t * when the user enters the table. Provide either `caption` or `aria-label`.\n\t */\n\tcaption?: React.ReactNode;\n\t/**\n\t * Accessible label for the table when no visible caption is shown.\n\t * Forwarded as `aria-label` on the underlying `<table>` element. Kebab-case\n\t * to match the canonical ARIA prop convention used elsewhere in Hex Core.\n\t */\n\t\"aria-label\"?: string;\n}\n\n/**\n * Render a data-driven table from TanStack column definitions.\n * @param props - Columns, data, and optional accessible labelling (`caption` or `aria-label`)\n * @returns A styled Table rendered from the TanStack row model\n */\nexport function DataTable<TData>({\n\tcolumns,\n\tdata,\n\tcaption,\n\t\"aria-label\": ariaLabel,\n}: DataTableProps<TData>) {\n\tconst table = useReactTable({\n\t\tdata,\n\t\tcolumns,\n\t\tgetCoreRowModel: getCoreRowModel(),\n\t});\n\n\treturn (\n\t\t<div className=\"rounded-md border border-foreground/[0.08]\">\n\t\t\t<Table aria-label={ariaLabel}>\n\t\t\t\t{caption ? <TableCaption>{caption}</TableCaption> : null}\n\t\t\t\t<TableHeader>\n\t\t\t\t\t{table.getHeaderGroups().map((headerGroup) => (\n\t\t\t\t\t\t<TableRow key={headerGroup.id}>\n\t\t\t\t\t\t\t{headerGroup.headers.map((header) => (\n\t\t\t\t\t\t\t\t<TableHead key={header.id}>\n\t\t\t\t\t\t\t\t\t{header.isPlaceholder\n\t\t\t\t\t\t\t\t\t\t? null\n\t\t\t\t\t\t\t\t\t\t: flexRender(header.column.columnDef.header, header.getContext())}\n\t\t\t\t\t\t\t\t</TableHead>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t))}\n\t\t\t\t</TableHeader>\n\t\t\t\t<TableBody>\n\t\t\t\t\t{table.getRowModel().rows?.length ? (\n\t\t\t\t\t\ttable.getRowModel().rows.map((row) => (\n\t\t\t\t\t\t\t<TableRow key={row.id} data-state={row.getIsSelected() && \"selected\"}>\n\t\t\t\t\t\t\t\t{row.getVisibleCells().map((cell) => (\n\t\t\t\t\t\t\t\t\t<TableCell key={cell.id}>\n\t\t\t\t\t\t\t\t\t\t{flexRender(cell.column.columnDef.cell, cell.getContext())}\n\t\t\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t\t))\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<TableRow>\n\t\t\t\t\t\t\t<TableCell colSpan={columns.length} className=\"h-24 text-center\">\n\t\t\t\t\t\t\t\tNo results.\n\t\t\t\t\t\t\t</TableCell>\n\t\t\t\t\t\t</TableRow>\n\t\t\t\t\t)}\n\t\t\t\t</TableBody>\n\t\t\t</Table>\n\t\t</div>\n\t);\n}\n"]}
package/dist/deck.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { DeckCard_alias_1 as DeckCard } from './_tsup-dts-rollup.js';
2
+ export { DeckProps_alias_1 as DeckProps } from './_tsup-dts-rollup.js';
3
+ export { Deck_alias_1 as Deck } from './_tsup-dts-rollup.js';
package/dist/deck.js ADDED
@@ -0,0 +1,231 @@
1
+ "use client";
2
+ import * as React2 from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function Flashcard({
11
+ front,
12
+ back,
13
+ defaultFlipped = false,
14
+ flipped: flippedProp,
15
+ onFlipChange,
16
+ width = 360,
17
+ height = 240,
18
+ flipDurationMs = 500,
19
+ className,
20
+ ...rest
21
+ }) {
22
+ const [internalFlipped, setInternalFlipped] = React2.useState(defaultFlipped);
23
+ const isControlled = flippedProp !== void 0;
24
+ const flipped = isControlled ? flippedProp : internalFlipped;
25
+ const toggle = React2.useCallback(() => {
26
+ const next = !flipped;
27
+ if (!isControlled) setInternalFlipped(next);
28
+ onFlipChange?.(next);
29
+ }, [flipped, isControlled, onFlipChange]);
30
+ const handleKey = React2.useCallback(
31
+ (e) => {
32
+ if (e.key === "Enter" || e.key === " ") {
33
+ e.preventDefault();
34
+ toggle();
35
+ }
36
+ },
37
+ [toggle]
38
+ );
39
+ return /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ ...rest,
43
+ "data-hex-flashcard": true,
44
+ "data-flipped": flipped,
45
+ role: "button",
46
+ tabIndex: 0,
47
+ "aria-pressed": flipped,
48
+ "aria-label": flipped ? "Flashcard, back side. Activate to flip to front." : "Flashcard, front side. Activate to reveal back.",
49
+ onClick: toggle,
50
+ onKeyDown: handleKey,
51
+ className: cn(
52
+ "relative inline-block cursor-pointer select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
53
+ className
54
+ ),
55
+ style: {
56
+ width,
57
+ height,
58
+ perspective: 1e3
59
+ },
60
+ children: /* @__PURE__ */ jsxs(
61
+ "div",
62
+ {
63
+ "data-hex-flashcard-inner": true,
64
+ className: "relative h-full w-full transition-transform",
65
+ style: {
66
+ transformStyle: "preserve-3d",
67
+ transform: flipped ? "rotateY(180deg)" : "rotateY(0deg)",
68
+ transitionDuration: `${flipDurationMs}ms`
69
+ },
70
+ children: [
71
+ /* @__PURE__ */ jsx(
72
+ "div",
73
+ {
74
+ "data-hex-flashcard-face": true,
75
+ "data-side": "front",
76
+ className: "absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm",
77
+ style: {
78
+ backfaceVisibility: "hidden",
79
+ WebkitBackfaceVisibility: "hidden"
80
+ },
81
+ children: front
82
+ }
83
+ ),
84
+ /* @__PURE__ */ jsx(
85
+ "div",
86
+ {
87
+ "data-hex-flashcard-face": true,
88
+ "data-side": "back",
89
+ className: "absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm",
90
+ style: {
91
+ backfaceVisibility: "hidden",
92
+ WebkitBackfaceVisibility: "hidden",
93
+ transform: "rotateY(180deg)"
94
+ },
95
+ children: back
96
+ }
97
+ )
98
+ ]
99
+ }
100
+ )
101
+ }
102
+ );
103
+ }
104
+ function shuffleArray(arr) {
105
+ const out = arr.slice();
106
+ for (let i = out.length - 1; i > 0; i--) {
107
+ const j = Math.floor(Math.random() * (i + 1));
108
+ [out[i], out[j]] = [out[j], out[i]];
109
+ }
110
+ return out;
111
+ }
112
+ function Deck({
113
+ cards,
114
+ shuffle = false,
115
+ ratingSlot,
116
+ onCardChange,
117
+ cardWidth = 360,
118
+ cardHeight = 240,
119
+ className,
120
+ ...rest
121
+ }) {
122
+ const order = React2.useMemo(() => shuffle ? shuffleArray(cards) : cards.slice(), [cards, shuffle]);
123
+ const [index, setIndex] = React2.useState(0);
124
+ const [flipped, setFlipped] = React2.useState(false);
125
+ const onCardChangeRef = React2.useRef(onCardChange);
126
+ onCardChangeRef.current = onCardChange;
127
+ React2.useEffect(() => {
128
+ setIndex(0);
129
+ setFlipped(false);
130
+ }, [order]);
131
+ const total = order.length;
132
+ const currentCard = order[index];
133
+ const goPrev = React2.useCallback(() => {
134
+ if (index === 0) return;
135
+ const next = index - 1;
136
+ setIndex(next);
137
+ setFlipped(false);
138
+ const card = order[next];
139
+ if (card) onCardChangeRef.current?.(next, card);
140
+ }, [index, order]);
141
+ const goNext = React2.useCallback(() => {
142
+ if (index >= total - 1) return;
143
+ const next = index + 1;
144
+ setIndex(next);
145
+ setFlipped(false);
146
+ const card = order[next];
147
+ if (card) onCardChangeRef.current?.(next, card);
148
+ }, [index, order, total]);
149
+ if (total === 0) {
150
+ return /* @__PURE__ */ jsx(
151
+ "div",
152
+ {
153
+ ...rest,
154
+ "data-hex-deck": true,
155
+ "data-empty": "true",
156
+ className: cn("rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", className),
157
+ children: "No cards in this deck."
158
+ }
159
+ );
160
+ }
161
+ const progressPercent = total > 0 ? (index + 1) / total * 100 : 0;
162
+ return /* @__PURE__ */ jsxs(
163
+ "div",
164
+ {
165
+ ...rest,
166
+ "data-hex-deck": true,
167
+ "data-index": index,
168
+ "data-total": total,
169
+ className: cn("inline-flex flex-col items-center gap-4", className),
170
+ children: [
171
+ currentCard ? /* @__PURE__ */ jsx(
172
+ Flashcard,
173
+ {
174
+ front: currentCard.front,
175
+ back: currentCard.back,
176
+ flipped,
177
+ onFlipChange: setFlipped,
178
+ width: cardWidth,
179
+ height: cardHeight
180
+ },
181
+ currentCard.id
182
+ ) : null,
183
+ /* @__PURE__ */ jsxs("div", { "data-hex-deck-controls": true, className: "flex w-full items-center gap-3", style: { width: cardWidth }, children: [
184
+ /* @__PURE__ */ jsx(
185
+ "button",
186
+ {
187
+ type: "button",
188
+ "data-hex-deck-prev": true,
189
+ disabled: index === 0,
190
+ onClick: goPrev,
191
+ "aria-label": `Previous card. Currently ${index + 1} of ${total}.`,
192
+ className: "inline-flex h-9 items-center justify-center rounded-md border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
193
+ children: "Prev"
194
+ }
195
+ ),
196
+ /* @__PURE__ */ jsxs("div", { "data-hex-deck-progress": true, className: "flex-1", "aria-hidden": "true", children: [
197
+ /* @__PURE__ */ jsx("div", { className: "h-1.5 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
198
+ "div",
199
+ {
200
+ className: "h-full bg-primary transition-all",
201
+ style: { width: `${progressPercent}%` }
202
+ }
203
+ ) }),
204
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 text-center text-xs text-muted-foreground", children: [
205
+ index + 1,
206
+ " / ",
207
+ total
208
+ ] })
209
+ ] }),
210
+ /* @__PURE__ */ jsx(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ "data-hex-deck-next": true,
215
+ disabled: index >= total - 1,
216
+ onClick: goNext,
217
+ "aria-label": `Next card. Currently ${index + 1} of ${total}.`,
218
+ className: "inline-flex h-9 items-center justify-center rounded-md border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
219
+ children: "Next"
220
+ }
221
+ )
222
+ ] }),
223
+ ratingSlot && currentCard ? /* @__PURE__ */ jsx("div", { "data-hex-deck-rating-slot": true, className: "w-full", style: { width: cardWidth }, children: ratingSlot(currentCard) }) : null
224
+ ]
225
+ }
226
+ );
227
+ }
228
+
229
+ export { Deck };
230
+ //# sourceMappingURL=deck.js.map
231
+ //# sourceMappingURL=deck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/utils.ts","../src/artifacts/flashcard/flashcard.tsx","../src/artifacts/deck/deck.tsx"],"names":["React","jsx","jsxs"],"mappings":"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","file":"deck.js","sourcesContent":["import { type ClassValue, clsx } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\n/**\n * Merge class names with Tailwind CSS conflict resolution.\n * @param inputs - Class values (strings, arrays, objects) to merge\n * @returns A single merged class string with Tailwind conflicts resolved\n */\nexport function cn(...inputs: ClassValue[]) {\n\treturn twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\n\n/**\n * Flashcard — front/back card with a 3D flip animation. Click, Enter, or\n * Space to flip. Pure CSS 3D transform, no animation peer required.\n *\n * Headless on content: pass any ReactNode for `front` and `back`. Pair\n * with Deck (artifacts/deck) for shuffle / next / prev / progress, or\n * with SpacedRepetition (artifacts/spaced-repetition) for confidence\n * rating after each reveal.\n *\n * @example\n * <Flashcard\n * front={<>What is the capital of France?</>}\n * back={<>Paris</>}\n * />\n *\n * <Flashcard\n * flipped={isFlipped}\n * onFlipChange={setFlipped}\n * front={term}\n * back={definition}\n * />\n */\nexport interface FlashcardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/** Content of the front face. */\n\tfront: React.ReactNode;\n\t/** Content of the back face. */\n\tback: React.ReactNode;\n\t/** Uncontrolled initial flipped state. Default false. */\n\tdefaultFlipped?: boolean;\n\t/** Controlled flipped state. */\n\tflipped?: boolean;\n\t/** Fired with the new flipped value when the user toggles. */\n\tonFlipChange?: (flipped: boolean) => void;\n\t/** Pixel width. Default 360. */\n\twidth?: number;\n\t/** Pixel height. Default 240. */\n\theight?: number;\n\t/** Flip animation duration in ms. Default 500. Set to 0 to disable the animation entirely. */\n\tflipDurationMs?: number;\n}\n\nfunction Flashcard({\n\tfront,\n\tback,\n\tdefaultFlipped = false,\n\tflipped: flippedProp,\n\tonFlipChange,\n\twidth = 360,\n\theight = 240,\n\tflipDurationMs = 500,\n\tclassName,\n\t...rest\n}: FlashcardProps) {\n\tconst [internalFlipped, setInternalFlipped] = React.useState(defaultFlipped);\n\tconst isControlled = flippedProp !== undefined;\n\tconst flipped = isControlled ? flippedProp : internalFlipped;\n\n\tconst toggle = React.useCallback(() => {\n\t\tconst next = !flipped;\n\t\tif (!isControlled) setInternalFlipped(next);\n\t\tonFlipChange?.(next);\n\t}, [flipped, isControlled, onFlipChange]);\n\n\tconst handleKey = React.useCallback(\n\t\t(e: React.KeyboardEvent) => {\n\t\t\tif (e.key === \"Enter\" || e.key === \" \") {\n\t\t\t\te.preventDefault();\n\t\t\t\ttoggle();\n\t\t\t}\n\t\t},\n\t\t[toggle],\n\t);\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-flashcard\n\t\t\tdata-flipped={flipped}\n\t\t\trole=\"button\"\n\t\t\ttabIndex={0}\n\t\t\taria-pressed={flipped}\n\t\t\taria-label={flipped ? \"Flashcard, back side. Activate to flip to front.\" : \"Flashcard, front side. Activate to reveal back.\"}\n\t\t\tonClick={toggle}\n\t\t\tonKeyDown={handleKey}\n\t\t\tclassName={cn(\n\t\t\t\t\"relative inline-block cursor-pointer select-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n\t\t\t\tclassName,\n\t\t\t)}\n\t\t\tstyle={{\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tperspective: 1000,\n\t\t\t}}\n\t\t>\n\t\t\t<div\n\t\t\t\tdata-hex-flashcard-inner\n\t\t\t\tclassName=\"relative h-full w-full transition-transform\"\n\t\t\t\tstyle={{\n\t\t\t\t\ttransformStyle: \"preserve-3d\",\n\t\t\t\t\ttransform: flipped ? \"rotateY(180deg)\" : \"rotateY(0deg)\",\n\t\t\t\t\ttransitionDuration: `${flipDurationMs}ms`,\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<div\n\t\t\t\t\tdata-hex-flashcard-face\n\t\t\t\t\tdata-side=\"front\"\n\t\t\t\t\tclassName=\"absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tbackfaceVisibility: \"hidden\",\n\t\t\t\t\t\tWebkitBackfaceVisibility: \"hidden\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{front}\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tdata-hex-flashcard-face\n\t\t\t\t\tdata-side=\"back\"\n\t\t\t\t\tclassName=\"absolute inset-0 flex items-center justify-center rounded-lg border bg-card p-4 text-center text-card-foreground shadow-sm\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\tbackfaceVisibility: \"hidden\",\n\t\t\t\t\t\tWebkitBackfaceVisibility: \"hidden\",\n\t\t\t\t\t\ttransform: \"rotateY(180deg)\",\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{back}\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n\nexport { Flashcard };\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/utils.js\";\nimport { Flashcard } from \"../flashcard/flashcard.js\";\n\n/**\n * Deck — a paged sequence of flashcards with optional shuffle, prev/next\n * navigation, a progress bar, and a slot for per-card SRS rating.\n * Composes [Flashcard] internally; consumers don't render Flashcard\n * themselves when they're inside a Deck.\n *\n * @example\n * <Deck\n * cards={[\n * { id: \"1\", front: \"Term 1\", back: \"Definition 1\" },\n * { id: \"2\", front: \"Term 2\", back: \"Definition 2\" },\n * ]}\n * shuffle\n * ratingSlot={(card) => (\n * <SpacedRepetition cardId={card.id} onRate={(rating) => save(rating, card.id)} />\n * )}\n * />\n */\nexport type DeckCard = {\n\tid: string;\n\tfront: React.ReactNode;\n\tback: React.ReactNode;\n};\n\nexport interface DeckProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"children\"> {\n\t/** Cards in order. */\n\tcards: DeckCard[];\n\t/** Initial shuffle. Default false (preserves order). */\n\tshuffle?: boolean;\n\t/** Optional render slot below the card; passed the current card. Useful for SpacedRepetition. */\n\tratingSlot?: (card: DeckCard) => React.ReactNode;\n\t/** Fired whenever the active card changes (after shuffle / prev / next). */\n\tonCardChange?: (index: number, card: DeckCard) => void;\n\t/** Pixel width of the inner Flashcard. Default 360. */\n\tcardWidth?: number;\n\t/** Pixel height of the inner Flashcard. Default 240. */\n\tcardHeight?: number;\n}\n\nfunction shuffleArray<T>(arr: T[]): T[] {\n\t// Fisher-Yates. Cheap and deterministic-shape (just non-deterministic\n\t// order on each call). Consumers wanting reproducibility wire their own\n\t// pre-shuffled array and pass `shuffle={false}`.\n\tconst out = arr.slice();\n\tfor (let i = out.length - 1; i > 0; i--) {\n\t\tconst j = Math.floor(Math.random() * (i + 1));\n\t\t[out[i], out[j]] = [out[j], out[i]];\n\t}\n\treturn out;\n}\n\nfunction Deck({\n\tcards,\n\tshuffle = false,\n\tratingSlot,\n\tonCardChange,\n\tcardWidth = 360,\n\tcardHeight = 240,\n\tclassName,\n\t...rest\n}: DeckProps) {\n\t// Order is recomputed only when `cards` identity OR `shuffle` flips —\n\t// not on every prev/next, so the user never gets re-shuffled mid-session.\n\tconst order = React.useMemo(() => (shuffle ? shuffleArray(cards) : cards.slice()), [cards, shuffle]);\n\tconst [index, setIndex] = React.useState(0);\n\tconst [flipped, setFlipped] = React.useState(false);\n\tconst onCardChangeRef = React.useRef(onCardChange);\n\tonCardChangeRef.current = onCardChange;\n\n\t// Reset to the first card when the order changes (cards swap, shuffle toggles).\n\tReact.useEffect(() => {\n\t\tsetIndex(0);\n\t\tsetFlipped(false);\n\t}, [order]);\n\n\tconst total = order.length;\n\tconst currentCard = order[index];\n\n\tconst goPrev = React.useCallback(() => {\n\t\tif (index === 0) return;\n\t\tconst next = index - 1;\n\t\tsetIndex(next);\n\t\tsetFlipped(false);\n\t\tconst card = order[next];\n\t\tif (card) onCardChangeRef.current?.(next, card);\n\t}, [index, order]);\n\n\tconst goNext = React.useCallback(() => {\n\t\tif (index >= total - 1) return;\n\t\tconst next = index + 1;\n\t\tsetIndex(next);\n\t\tsetFlipped(false);\n\t\tconst card = order[next];\n\t\tif (card) onCardChangeRef.current?.(next, card);\n\t}, [index, order, total]);\n\n\tif (total === 0) {\n\t\treturn (\n\t\t\t<div\n\t\t\t\t{...rest}\n\t\t\t\tdata-hex-deck\n\t\t\t\tdata-empty=\"true\"\n\t\t\t\tclassName={cn(\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\", className)}\n\t\t\t>\n\t\t\t\tNo cards in this deck.\n\t\t\t</div>\n\t\t);\n\t}\n\n\tconst progressPercent = total > 0 ? ((index + 1) / total) * 100 : 0;\n\n\treturn (\n\t\t<div\n\t\t\t{...rest}\n\t\t\tdata-hex-deck\n\t\t\tdata-index={index}\n\t\t\tdata-total={total}\n\t\t\tclassName={cn(\"inline-flex flex-col items-center gap-4\", className)}\n\t\t>\n\t\t\t{currentCard ? (\n\t\t\t\t<Flashcard\n\t\t\t\t\tkey={currentCard.id}\n\t\t\t\t\tfront={currentCard.front}\n\t\t\t\t\tback={currentCard.back}\n\t\t\t\t\tflipped={flipped}\n\t\t\t\t\tonFlipChange={setFlipped}\n\t\t\t\t\twidth={cardWidth}\n\t\t\t\t\theight={cardHeight}\n\t\t\t\t/>\n\t\t\t) : null}\n\t\t\t<div data-hex-deck-controls className=\"flex w-full items-center gap-3\" style={{ width: cardWidth }}>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-hex-deck-prev\n\t\t\t\t\tdisabled={index === 0}\n\t\t\t\t\tonClick={goPrev}\n\t\t\t\t\taria-label={`Previous card. Currently ${index + 1} of ${total}.`}\n\t\t\t\t\tclassName=\"inline-flex h-9 items-center justify-center rounded-md border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n\t\t\t\t>\n\t\t\t\t\tPrev\n\t\t\t\t</button>\n\t\t\t\t<div data-hex-deck-progress className=\"flex-1\" aria-hidden=\"true\">\n\t\t\t\t\t<div className=\"h-1.5 overflow-hidden rounded-full bg-muted\">\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\tclassName=\"h-full bg-primary transition-all\"\n\t\t\t\t\t\t\tstyle={{ width: `${progressPercent}%` }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"mt-1 text-center text-xs text-muted-foreground\">\n\t\t\t\t\t\t{index + 1} / {total}\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tdata-hex-deck-next\n\t\t\t\t\tdisabled={index >= total - 1}\n\t\t\t\t\tonClick={goNext}\n\t\t\t\t\taria-label={`Next card. Currently ${index + 1} of ${total}.`}\n\t\t\t\t\tclassName=\"inline-flex h-9 items-center justify-center rounded-md border bg-background px-3 text-sm font-medium transition-colors hover:bg-muted disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n\t\t\t\t>\n\t\t\t\t\tNext\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t\t{ratingSlot && currentCard ? (\n\t\t\t\t<div data-hex-deck-rating-slot className=\"w-full\" style={{ width: cardWidth }}>\n\t\t\t\t\t{ratingSlot(currentCard)}\n\t\t\t\t</div>\n\t\t\t) : null}\n\t\t</div>\n\t);\n}\n\nexport { Deck };\n"]}
@@ -0,0 +1,3 @@
1
+ export { DendrogramNode_alias_1 as DendrogramNode } from './_tsup-dts-rollup.js';
2
+ export { DendrogramProps_alias_1 as DendrogramProps } from './_tsup-dts-rollup.js';
3
+ export { Dendrogram_alias_1 as Dendrogram } from './_tsup-dts-rollup.js';
@@ -0,0 +1,162 @@
1
+ "use client";
2
+ import * as React from 'react';
3
+ import { clsx } from 'clsx';
4
+ import { twMerge } from 'tailwind-merge';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
10
+ function Dendrogram({
11
+ root,
12
+ orientation = "horizontal",
13
+ linkShape = "step",
14
+ width = 600,
15
+ height = 400,
16
+ onLeafClick,
17
+ className,
18
+ ...rest
19
+ }) {
20
+ const [d3h, setD3h] = React.useState(null);
21
+ React.useEffect(() => {
22
+ let cancelled = false;
23
+ void import('d3-hierarchy').then((mod) => {
24
+ if (!cancelled) setD3h(mod);
25
+ });
26
+ return () => {
27
+ cancelled = true;
28
+ };
29
+ }, []);
30
+ if (!d3h) {
31
+ return /* @__PURE__ */ jsx(
32
+ "div",
33
+ {
34
+ "data-hex-dendrogram-loading": true,
35
+ "aria-busy": "true",
36
+ className: cn("inline-block bg-muted/20", className),
37
+ style: { width, height }
38
+ }
39
+ );
40
+ }
41
+ const { nodes, links } = layout(d3h, root, orientation, width, height);
42
+ const leafCount = nodes.filter((n) => n.isLeaf).length;
43
+ const desc = `Dendrogram with ${leafCount} leaves, rooted at "${root.label}"`;
44
+ return /* @__PURE__ */ jsxs(
45
+ "svg",
46
+ {
47
+ ...rest,
48
+ "data-hex-dendrogram": true,
49
+ "data-orientation": orientation,
50
+ "data-link-shape": linkShape,
51
+ role: "img",
52
+ width,
53
+ height,
54
+ viewBox: `0 0 ${width} ${height}`,
55
+ className: cn("block", className),
56
+ children: [
57
+ /* @__PURE__ */ jsx("title", { children: "Dendrogram" }),
58
+ /* @__PURE__ */ jsx("desc", { children: desc }),
59
+ /* @__PURE__ */ jsx("g", { "data-hex-dendrogram-links": true, children: links.map((l) => /* @__PURE__ */ jsx(
60
+ "path",
61
+ {
62
+ d: linkPath(l, orientation, linkShape),
63
+ fill: "none",
64
+ stroke: "hsl(var(--muted-foreground))",
65
+ strokeOpacity: 0.8,
66
+ strokeWidth: 1
67
+ },
68
+ l.id
69
+ )) }),
70
+ /* @__PURE__ */ jsx("g", { "data-hex-dendrogram-nodes": true, children: nodes.map((n) => /* @__PURE__ */ jsxs(
71
+ "g",
72
+ {
73
+ "data-hex-dendrogram-node": true,
74
+ "data-leaf": n.isLeaf ? "true" : "false",
75
+ "data-depth": n.depth,
76
+ transform: `translate(${n.x},${n.y})`,
77
+ style: n.isLeaf && onLeafClick ? { cursor: "pointer" } : void 0,
78
+ onClick: n.isLeaf && onLeafClick ? () => onLeafClick(n.node) : void 0,
79
+ children: [
80
+ /* @__PURE__ */ jsx(
81
+ "circle",
82
+ {
83
+ r: n.isLeaf ? 3 : 2,
84
+ fill: n.isLeaf ? "hsl(var(--primary))" : "hsl(var(--muted-foreground))"
85
+ }
86
+ ),
87
+ n.isLeaf ? /* @__PURE__ */ jsx(
88
+ "text",
89
+ {
90
+ x: orientation === "horizontal" ? 6 : 0,
91
+ y: orientation === "horizontal" ? 4 : 14,
92
+ textAnchor: orientation === "horizontal" ? "start" : "middle",
93
+ fontSize: 11,
94
+ fill: "hsl(var(--foreground))",
95
+ style: { paintOrder: "stroke" },
96
+ stroke: "hsl(var(--background))",
97
+ strokeWidth: 3,
98
+ strokeLinejoin: "round",
99
+ children: n.node.label
100
+ }
101
+ ) : null
102
+ ]
103
+ },
104
+ n.node.id
105
+ )) })
106
+ ]
107
+ }
108
+ );
109
+ }
110
+ function layout(d3h, root, orientation, width, height) {
111
+ const hierarchy = d3h.hierarchy(root);
112
+ const clusterFn = d3h.cluster();
113
+ if (orientation === "horizontal") {
114
+ clusterFn.size([height - 32, width - 120]);
115
+ } else {
116
+ clusterFn.size([width - 32, height - 64]);
117
+ }
118
+ const layoutRoot = clusterFn(hierarchy);
119
+ const nodes = [];
120
+ layoutRoot.each((d) => {
121
+ const isLeaf = !d.children || d.children.length === 0;
122
+ if (orientation === "horizontal") {
123
+ nodes.push({ node: d.data, x: d.y + 16, y: d.x + 16, depth: d.depth, isLeaf });
124
+ } else {
125
+ nodes.push({ node: d.data, x: d.x + 16, y: d.y + 16, depth: d.depth, isLeaf });
126
+ }
127
+ });
128
+ const links = layoutRoot.links().map((link, i) => {
129
+ if (orientation === "horizontal") {
130
+ return {
131
+ source: { x: link.source.y + 16, y: link.source.x + 16 },
132
+ target: { x: link.target.y + 16, y: link.target.x + 16 },
133
+ id: `l-${i}`
134
+ };
135
+ }
136
+ return {
137
+ source: { x: link.source.x + 16, y: link.source.y + 16 },
138
+ target: { x: link.target.x + 16, y: link.target.y + 16 },
139
+ id: `l-${i}`
140
+ };
141
+ });
142
+ return { nodes, links };
143
+ }
144
+ function linkPath(link, orientation, linkShape) {
145
+ const { source: s, target: t } = link;
146
+ if (linkShape === "step") {
147
+ if (orientation === "horizontal") {
148
+ return `M${s.x},${s.y} H${t.x} V${t.y}`;
149
+ }
150
+ return `M${s.x},${s.y} V${t.y} H${t.x}`;
151
+ }
152
+ if (orientation === "horizontal") {
153
+ const mx = (s.x + t.x) / 2;
154
+ return `M${s.x},${s.y} C${mx},${s.y} ${mx},${t.y} ${t.x},${t.y}`;
155
+ }
156
+ const my = (s.y + t.y) / 2;
157
+ return `M${s.x},${s.y} C${s.x},${my} ${t.x},${my} ${t.x},${t.y}`;
158
+ }
159
+
160
+ export { Dendrogram };
161
+ //# sourceMappingURL=dendrogram.js.map
162
+ //# sourceMappingURL=dendrogram.js.map