@dbcdk/react-components 0.0.181 → 0.0.183

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 (56) hide show
  1. package/dist/client.cjs +21 -0
  2. package/dist/client.d.ts +3 -0
  3. package/dist/client.js +3 -0
  4. package/dist/components/clear-button/ClearButton.cjs +35 -12
  5. package/dist/components/clear-button/ClearButton.d.ts +17 -1
  6. package/dist/components/clear-button/ClearButton.js +35 -12
  7. package/dist/components/clear-button/ClearButton.module.css +12 -0
  8. package/dist/components/forms/multi-select/MultiSelect.cjs +2 -1
  9. package/dist/components/forms/multi-select/MultiSelect.js +2 -1
  10. package/dist/components/forms/select/Select.cjs +3 -1
  11. package/dist/components/forms/select/Select.js +3 -1
  12. package/dist/components/interval-select/IntervalSelect.cjs +2 -1
  13. package/dist/components/interval-select/IntervalSelect.js +2 -1
  14. package/dist/components/json-viewer/JsonViewer.cjs +3 -2
  15. package/dist/components/json-viewer/JsonViewer.d.ts +1 -1
  16. package/dist/components/json-viewer/JsonViewer.js +3 -2
  17. package/dist/components/json-viewer/JsonViewer.module.css +7 -0
  18. package/dist/components/json-viewer/types.d.ts +6 -0
  19. package/dist/components/line-viewer/LineViewer.cjs +174 -0
  20. package/dist/components/line-viewer/LineViewer.d.ts +20 -0
  21. package/dist/components/line-viewer/LineViewer.js +168 -0
  22. package/dist/components/line-viewer/LineViewer.module.css +112 -0
  23. package/dist/components/line-viewer/tokenizeLineFormat.cjs +52 -0
  24. package/dist/components/line-viewer/tokenizeLineFormat.d.ts +14 -0
  25. package/dist/components/line-viewer/tokenizeLineFormat.js +50 -0
  26. package/dist/components/tree-view/TreeNodeRow.cjs +99 -0
  27. package/dist/components/tree-view/TreeNodeRow.d.ts +18 -0
  28. package/dist/components/tree-view/TreeNodeRow.js +93 -0
  29. package/dist/components/tree-view/TreeView.cjs +93 -0
  30. package/dist/components/tree-view/TreeView.d.ts +20 -0
  31. package/dist/components/tree-view/TreeView.js +79 -0
  32. package/dist/components/tree-view/TreeView.module.css +141 -0
  33. package/dist/components/tree-view/types.cjs +2 -0
  34. package/dist/components/tree-view/types.d.ts +44 -0
  35. package/dist/components/tree-view/types.js +1 -0
  36. package/dist/components/tree-view/useTreeExpansion.cjs +47 -0
  37. package/dist/components/tree-view/useTreeExpansion.d.ts +17 -0
  38. package/dist/components/tree-view/useTreeExpansion.js +45 -0
  39. package/dist/components/tree-view/utils.cjs +32 -0
  40. package/dist/components/tree-view/utils.d.ts +16 -0
  41. package/dist/components/tree-view/utils.js +29 -0
  42. package/dist/components/xml-viewer/XmlViewer.cjs +175 -0
  43. package/dist/components/xml-viewer/XmlViewer.d.ts +20 -0
  44. package/dist/components/xml-viewer/XmlViewer.js +169 -0
  45. package/dist/components/xml-viewer/XmlViewer.module.css +109 -0
  46. package/dist/components/xml-viewer/tokenizeXml.cjs +50 -0
  47. package/dist/components/xml-viewer/tokenizeXml.d.ts +12 -0
  48. package/dist/components/xml-viewer/tokenizeXml.js +48 -0
  49. package/dist/index.cjs +7 -0
  50. package/dist/index.css +3 -0
  51. package/dist/index.d.ts +1 -0
  52. package/dist/index.js +1 -0
  53. package/dist/utils/date/formatDate.cjs +29 -17
  54. package/dist/utils/date/formatDate.d.ts +7 -0
  55. package/dist/utils/date/formatDate.js +29 -17
  56. package/package.json +1 -1
@@ -0,0 +1,168 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { TextWrap, ListTree, Code2, Sun, Moon, ClipboardCopy, MoreVertical } from 'lucide-react';
4
+ import { useMemo, useRef } from 'react';
5
+ import { Menu } from '../../components/menu/Menu';
6
+ import { Popover } from '../../components/popover/Popover';
7
+ import { usePersistentState } from '../../hooks/usePersistentState';
8
+ import styles from './LineViewer.module.css';
9
+ import { tokenizeLineFormat } from './tokenizeLineFormat';
10
+ import { Button } from '../button/Button';
11
+
12
+ const TOKEN_CLASS = {
13
+ tag: styles.tag,
14
+ indicator: styles.indicator,
15
+ subfieldMarker: styles.subfieldMarker,
16
+ subfieldCode: styles.subfieldCode,
17
+ value: styles.value,
18
+ terminator: styles.terminator,
19
+ text: styles.text,
20
+ whitespace: void 0
21
+ };
22
+ function LineViewer({
23
+ lines,
24
+ size = "md",
25
+ appearance = "dark",
26
+ storageKey,
27
+ borderless = false
28
+ }) {
29
+ const tokens = useMemo(() => tokenizeLineFormat(lines), [lines]);
30
+ const codeRef = useRef(null);
31
+ const { value: currentAppearance, setValue: setCurrentAppearance } = usePersistentState({
32
+ initialValue: appearance,
33
+ storageKey: storageKey ? `${storageKey}:appearance` : void 0,
34
+ validate: (stored) => stored === "dark" || stored === "light"
35
+ });
36
+ const { value: viewMode, setValue: setViewMode } = usePersistentState({
37
+ initialValue: "highlighted",
38
+ storageKey: storageKey ? `${storageKey}:viewMode` : void 0,
39
+ validate: (stored) => stored === "highlighted" || stored === "raw"
40
+ });
41
+ const { value: wrap, setValue: setWrap } = usePersistentState({
42
+ initialValue: true,
43
+ storageKey: storageKey ? `${storageKey}:wrap` : void 0,
44
+ validate: (stored) => typeof stored === "boolean"
45
+ });
46
+ const handleCopy = () => {
47
+ var _a;
48
+ (_a = navigator.clipboard) == null ? void 0 : _a.writeText(lines).catch(() => {
49
+ });
50
+ };
51
+ const handleDoubleClick = () => {
52
+ const el = codeRef.current;
53
+ const selection = typeof window !== "undefined" ? window.getSelection() : null;
54
+ if (!el || !selection) return;
55
+ const range = document.createRange();
56
+ range.selectNodeContents(el);
57
+ selection.removeAllRanges();
58
+ selection.addRange(range);
59
+ };
60
+ return /* @__PURE__ */ jsxs(
61
+ "section",
62
+ {
63
+ className: [styles.viewer, borderless ? styles.borderless : ""].filter(Boolean).join(" "),
64
+ "data-appearance": currentAppearance,
65
+ children: [
66
+ /* @__PURE__ */ jsx(
67
+ "div",
68
+ {
69
+ className: styles.toolbar,
70
+ "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
71
+ children: /* @__PURE__ */ jsx(
72
+ Popover,
73
+ {
74
+ matchTriggerWidth: false,
75
+ minWidth: "200px",
76
+ trigger: (toggle, _icon, isOpen) => /* @__PURE__ */ jsx(
77
+ Button,
78
+ {
79
+ variant: "inline",
80
+ "aria-label": "Flere handlinger",
81
+ "aria-haspopup": "menu",
82
+ "aria-expanded": isOpen ? true : false,
83
+ onClick: toggle,
84
+ children: /* @__PURE__ */ jsx(MoreVertical, { "aria-hidden": "true" })
85
+ }
86
+ ),
87
+ children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
88
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ onClick: () => {
93
+ setWrap((value) => !value);
94
+ close();
95
+ },
96
+ children: [
97
+ /* @__PURE__ */ jsx(TextWrap, { "aria-hidden": "true" }),
98
+ /* @__PURE__ */ jsx("span", { children: wrap ? "Ombryd ikke tekst" : "Ombryd tekst" })
99
+ ]
100
+ }
101
+ ) }),
102
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
103
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ onClick: () => {
108
+ setViewMode((mode) => mode === "raw" ? "highlighted" : "raw");
109
+ close();
110
+ },
111
+ children: [
112
+ viewMode === "raw" ? /* @__PURE__ */ jsx(ListTree, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Code2, { "aria-hidden": "true" }),
113
+ /* @__PURE__ */ jsx("span", { children: viewMode === "raw" ? "Vis med farver" : "Vis raw" })
114
+ ]
115
+ }
116
+ ) }),
117
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
118
+ "button",
119
+ {
120
+ type: "button",
121
+ onClick: () => {
122
+ setCurrentAppearance((value) => value === "dark" ? "light" : "dark");
123
+ close();
124
+ },
125
+ children: [
126
+ currentAppearance === "dark" ? /* @__PURE__ */ jsx(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Moon, { "aria-hidden": "true" }),
127
+ /* @__PURE__ */ jsx("span", { children: currentAppearance === "dark" ? "Lys visning" : "M\xF8rk visning" })
128
+ ]
129
+ }
130
+ ) }),
131
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
132
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ onClick: () => {
137
+ handleCopy();
138
+ close();
139
+ },
140
+ children: [
141
+ /* @__PURE__ */ jsx(ClipboardCopy, { "aria-hidden": "true" }),
142
+ /* @__PURE__ */ jsx("span", { children: "Kopi\xE9r" })
143
+ ]
144
+ }
145
+ ) })
146
+ ] })
147
+ }
148
+ )
149
+ }
150
+ ),
151
+ /* @__PURE__ */ jsx(
152
+ "pre",
153
+ {
154
+ className: [styles.codePane, styles[size], wrap ? styles.wrap : styles.noWrap].join(" "),
155
+ tabIndex: 0,
156
+ onDoubleClick: handleDoubleClick,
157
+ children: /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
158
+ const tokenClassName = TOKEN_CLASS[token.type];
159
+ return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
160
+ }) })
161
+ }
162
+ )
163
+ ]
164
+ }
165
+ );
166
+ }
167
+
168
+ export { LineViewer };
@@ -0,0 +1,112 @@
1
+ .viewer {
2
+ --line-pane-bg: var(--color-bg-inverse);
3
+ --line-pane-border: color-mix(in oklab, var(--color-fg-inverse) 12%, transparent);
4
+ --line-pane-text: var(--color-fg-inverse);
5
+
6
+ --line-tag: color-mix(in oklab, var(--color-fg-inverse) 66%, var(--color-syntax-accent) 34%);
7
+ --line-indicator: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
8
+ --line-subfield-code: color-mix(in oklab, var(--color-fg-inverse) 30%, var(--color-syntax-number) 70%);
9
+ --line-value: color-mix(in oklab, var(--color-fg-inverse) 20%, var(--color-syntax-string) 80%);
10
+ --line-terminator: color-mix(in oklab, var(--color-fg-inverse) 30%, var(--color-syntax-boolean) 70%);
11
+ --line-punctuation: color-mix(in oklab, var(--color-fg-inverse) 60%, transparent);
12
+ --line-continuation: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
13
+
14
+ position: relative;
15
+ background: var(--line-pane-bg);
16
+ border: var(--border-width-thin) solid var(--line-pane-border);
17
+ border-radius: var(--border-radius-default);
18
+ box-shadow: var(--shadow-md);
19
+ font-family: var(--font-family-mono);
20
+ }
21
+
22
+ .viewer[data-appearance='light'] {
23
+ --line-pane-bg: var(--color-bg-surface);
24
+ --line-pane-border: var(--color-border-subtle);
25
+ --line-pane-text: var(--color-fg-default);
26
+
27
+ --line-tag: var(--color-link);
28
+ --line-indicator: var(--color-fg-subtle);
29
+ --line-subfield-code: var(--color-status-warning-fg);
30
+ --line-value: var(--color-status-success-fg);
31
+ --line-terminator: var(--color-accent-fg);
32
+ --line-punctuation: var(--color-fg-subtle);
33
+ --line-continuation: var(--color-fg-muted);
34
+
35
+ box-shadow: var(--shadow-xs);
36
+ }
37
+
38
+ .viewer.borderless {
39
+ border: none;
40
+ border-radius: 0;
41
+ box-shadow: none;
42
+ }
43
+
44
+ .toolbar {
45
+ position: absolute;
46
+ top: var(--spacing-xs);
47
+ right: var(--spacing-xs);
48
+ z-index: 1;
49
+ }
50
+
51
+ .codePane {
52
+ margin: 0;
53
+ padding: var(--spacing-sm);
54
+ overflow: auto;
55
+ color: var(--line-pane-text);
56
+ font-family: inherit;
57
+ line-height: 1.35;
58
+ }
59
+
60
+ .wrap {
61
+ white-space: pre-wrap;
62
+ overflow-wrap: break-word;
63
+ }
64
+
65
+ .noWrap {
66
+ white-space: pre;
67
+ }
68
+
69
+ .xs,
70
+ .sm {
71
+ font-size: var(--font-size-xs);
72
+ }
73
+
74
+ .md {
75
+ font-size: var(--font-size-sm);
76
+ }
77
+
78
+ .lg {
79
+ font-size: var(--font-size-base);
80
+ }
81
+
82
+ .tag {
83
+ color: var(--line-tag);
84
+ font-weight: var(--font-weight-medium);
85
+ }
86
+
87
+ .indicator {
88
+ color: var(--line-indicator);
89
+ }
90
+
91
+ .subfieldMarker {
92
+ color: var(--line-punctuation);
93
+ }
94
+
95
+ .subfieldCode {
96
+ color: var(--line-subfield-code);
97
+ font-weight: var(--font-weight-medium);
98
+ }
99
+
100
+ .value {
101
+ color: var(--line-value);
102
+ }
103
+
104
+ .terminator {
105
+ color: var(--line-terminator);
106
+ font-weight: var(--font-weight-medium);
107
+ }
108
+
109
+ .text {
110
+ color: var(--line-continuation);
111
+ font-style: italic;
112
+ }
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ const FIELD_LINE_PATTERN = /^(\S+)(\s+)(\S+)(\s+)(\*[\s\S]*)$/;
4
+ const SUBFIELD_PATTERN = /\*(\S)([^*]*)/g;
5
+ function tokenizeFieldLine(line) {
6
+ const match = FIELD_LINE_PATTERN.exec(line);
7
+ if (!match) return [{ type: "text", value: line }];
8
+ const [, tag, gap1, indicator, gap2, rest] = match;
9
+ const tokens = [
10
+ { type: "tag", value: tag },
11
+ { type: "whitespace", value: gap1 },
12
+ { type: "indicator", value: indicator },
13
+ { type: "whitespace", value: gap2 }
14
+ ];
15
+ SUBFIELD_PATTERN.lastIndex = 0;
16
+ let match2;
17
+ let lastIndex = 0;
18
+ while (match2 = SUBFIELD_PATTERN.exec(rest)) {
19
+ tokens.push({ type: "subfieldMarker", value: "*" });
20
+ tokens.push({ type: "subfieldCode", value: match2[1] });
21
+ tokens.push({ type: "value", value: match2[2] });
22
+ lastIndex = SUBFIELD_PATTERN.lastIndex;
23
+ }
24
+ if (lastIndex < rest.length) {
25
+ tokens.push({ type: "text", value: rest.slice(lastIndex) });
26
+ }
27
+ return tokens;
28
+ }
29
+ function tokenizeLineFormat(source) {
30
+ const tokens = [];
31
+ const chunks = source.split(/(\n)/);
32
+ for (const chunk of chunks) {
33
+ if (chunk === "") continue;
34
+ if (chunk === "\n") {
35
+ tokens.push({ type: "whitespace", value: "\n" });
36
+ continue;
37
+ }
38
+ if (chunk.trim() === "$") {
39
+ const dollarIndex = chunk.indexOf("$");
40
+ const leading = chunk.slice(0, dollarIndex);
41
+ const trailing = chunk.slice(dollarIndex + 1);
42
+ if (leading) tokens.push({ type: "text", value: leading });
43
+ tokens.push({ type: "terminator", value: "$" });
44
+ if (trailing) tokens.push({ type: "text", value: trailing });
45
+ continue;
46
+ }
47
+ tokens.push(...tokenizeFieldLine(chunk));
48
+ }
49
+ return tokens;
50
+ }
51
+
52
+ exports.tokenizeLineFormat = tokenizeLineFormat;
@@ -0,0 +1,14 @@
1
+ export type LineTokenType = 'tag' | 'indicator' | 'subfieldMarker' | 'subfieldCode' | 'value' | 'terminator' | 'text' | 'whitespace';
2
+ export interface LineToken {
3
+ type: LineTokenType;
4
+ value: string;
5
+ }
6
+ /**
7
+ * Tokenizes a MARC-style "line format" record (field tag, indicators,
8
+ * `*code value` subfields, terminated by a lone `$`) for syntax
9
+ * highlighting without imposing a strict grammar. Preserves the source
10
+ * exactly - concatenating every token's `value` reproduces the input - so a
11
+ * wrapped continuation line or malformed field still displays as plain
12
+ * text instead of breaking the tokenizer.
13
+ */
14
+ export declare function tokenizeLineFormat(source: string): LineToken[];
@@ -0,0 +1,50 @@
1
+ const FIELD_LINE_PATTERN = /^(\S+)(\s+)(\S+)(\s+)(\*[\s\S]*)$/;
2
+ const SUBFIELD_PATTERN = /\*(\S)([^*]*)/g;
3
+ function tokenizeFieldLine(line) {
4
+ const match = FIELD_LINE_PATTERN.exec(line);
5
+ if (!match) return [{ type: "text", value: line }];
6
+ const [, tag, gap1, indicator, gap2, rest] = match;
7
+ const tokens = [
8
+ { type: "tag", value: tag },
9
+ { type: "whitespace", value: gap1 },
10
+ { type: "indicator", value: indicator },
11
+ { type: "whitespace", value: gap2 }
12
+ ];
13
+ SUBFIELD_PATTERN.lastIndex = 0;
14
+ let match2;
15
+ let lastIndex = 0;
16
+ while (match2 = SUBFIELD_PATTERN.exec(rest)) {
17
+ tokens.push({ type: "subfieldMarker", value: "*" });
18
+ tokens.push({ type: "subfieldCode", value: match2[1] });
19
+ tokens.push({ type: "value", value: match2[2] });
20
+ lastIndex = SUBFIELD_PATTERN.lastIndex;
21
+ }
22
+ if (lastIndex < rest.length) {
23
+ tokens.push({ type: "text", value: rest.slice(lastIndex) });
24
+ }
25
+ return tokens;
26
+ }
27
+ function tokenizeLineFormat(source) {
28
+ const tokens = [];
29
+ const chunks = source.split(/(\n)/);
30
+ for (const chunk of chunks) {
31
+ if (chunk === "") continue;
32
+ if (chunk === "\n") {
33
+ tokens.push({ type: "whitespace", value: "\n" });
34
+ continue;
35
+ }
36
+ if (chunk.trim() === "$") {
37
+ const dollarIndex = chunk.indexOf("$");
38
+ const leading = chunk.slice(0, dollarIndex);
39
+ const trailing = chunk.slice(dollarIndex + 1);
40
+ if (leading) tokens.push({ type: "text", value: leading });
41
+ tokens.push({ type: "terminator", value: "$" });
42
+ if (trailing) tokens.push({ type: "text", value: trailing });
43
+ continue;
44
+ }
45
+ tokens.push(...tokenizeFieldLine(chunk));
46
+ }
47
+ return tokens;
48
+ }
49
+
50
+ export { tokenizeLineFormat };
@@ -0,0 +1,99 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var lucideReact = require('lucide-react');
6
+ var react = require('react');
7
+ var styles = require('./TreeView.module.css');
8
+
9
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
+
11
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
12
+
13
+ function TreeNodeRow({
14
+ node,
15
+ depth,
16
+ isExpanded,
17
+ toggle,
18
+ resolvedChildren,
19
+ loadingIds,
20
+ errorIds,
21
+ requestLoad,
22
+ loadChildren,
23
+ selectedId,
24
+ onSelectNode,
25
+ renderLabel
26
+ }) {
27
+ var _a;
28
+ const children = (_a = node.children) != null ? _a : resolvedChildren[node.id];
29
+ const isResolved = children !== void 0;
30
+ const expandable = isResolved ? children.length > 0 : Boolean(loadChildren) && node.hasChildren !== false;
31
+ const expanded = isExpanded(node.id);
32
+ const loading = loadingIds.has(node.id);
33
+ const error = errorIds[node.id];
34
+ const selected = selectedId !== void 0 && selectedId === node.id;
35
+ react.useEffect(() => {
36
+ if (expanded && children === void 0 && !loading && !error && loadChildren) {
37
+ requestLoad(node);
38
+ }
39
+ }, [expanded, children, loading, error]);
40
+ return /* @__PURE__ */ jsxRuntime.jsxs(
41
+ "div",
42
+ {
43
+ role: "treeitem",
44
+ "aria-expanded": expandable ? expanded : void 0,
45
+ "aria-selected": selected,
46
+ style: { ["--tree-depth"]: depth },
47
+ children: [
48
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.row, "data-selected": selected || void 0, children: [
49
+ /* @__PURE__ */ jsxRuntime.jsx(
50
+ "button",
51
+ {
52
+ type: "button",
53
+ className: styles__default.default.toggle,
54
+ onClick: () => expandable && toggle(node.id),
55
+ disabled: !expandable,
56
+ "aria-hidden": !expandable,
57
+ tabIndex: expandable ? 0 : -1,
58
+ children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: `spin ${styles__default.default.spinner}`, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderCircle, { "aria-hidden": "true" }) }) : expandable ? (
59
+ // A resolved node's chevron means "this has children"; an
60
+ // unresolved lazy node's dimmer chevron means "might have
61
+ // children, click to check" - we genuinely don't know yet,
62
+ // and shouldn't visually assert otherwise.
63
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: isResolved ? void 0 : styles__default.default.toggleUnresolved, children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { "aria-hidden": "true" }) })
64
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.leafSpacer, "aria-hidden": "true" })
65
+ }
66
+ ),
67
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: styles__default.default.labelButton, onClick: () => onSelectNode == null ? void 0 : onSelectNode(node), children: [
68
+ node.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.icon, "aria-hidden": "true", children: node.icon }) : null,
69
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.label, children: renderLabel ? renderLabel(node) : node.label })
70
+ ] })
71
+ ] }),
72
+ error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.errorRow, children: [
73
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: error }),
74
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: styles__default.default.retryButton, onClick: () => requestLoad(node), children: "Pr\xF8v igen" })
75
+ ] }) : null,
76
+ expanded && children && children.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { role: "group", className: styles__default.default.children, children: children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
77
+ TreeNodeRow,
78
+ {
79
+ node: child,
80
+ depth: depth + 1,
81
+ isExpanded,
82
+ toggle,
83
+ resolvedChildren,
84
+ loadingIds,
85
+ errorIds,
86
+ requestLoad,
87
+ loadChildren,
88
+ selectedId,
89
+ onSelectNode,
90
+ renderLabel
91
+ },
92
+ child.id
93
+ )) }) : null
94
+ ]
95
+ }
96
+ );
97
+ }
98
+
99
+ exports.TreeNodeRow = TreeNodeRow;
@@ -0,0 +1,18 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { LoadTreeChildren, TreeNode } from './types';
3
+ type TreeNodeRowProps<T> = {
4
+ node: TreeNode<T>;
5
+ depth: number;
6
+ isExpanded: (id: string) => boolean;
7
+ toggle: (id: string) => void;
8
+ resolvedChildren: Record<string, TreeNode<T>[]>;
9
+ loadingIds: Set<string>;
10
+ errorIds: Record<string, ReactNode>;
11
+ requestLoad: (node: TreeNode<T>) => void;
12
+ loadChildren?: LoadTreeChildren<T>;
13
+ selectedId?: string;
14
+ onSelectNode?: (node: TreeNode<T>) => void;
15
+ renderLabel?: (node: TreeNode<T>) => ReactNode;
16
+ };
17
+ export declare function TreeNodeRow<T>({ node, depth, isExpanded, toggle, resolvedChildren, loadingIds, errorIds, requestLoad, loadChildren, selectedId, onSelectNode, renderLabel, }: TreeNodeRowProps<T>): JSX.Element;
18
+ export {};
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { LoaderCircle, ChevronDown, ChevronRight } from 'lucide-react';
4
+ import { useEffect } from 'react';
5
+ import styles from './TreeView.module.css';
6
+
7
+ function TreeNodeRow({
8
+ node,
9
+ depth,
10
+ isExpanded,
11
+ toggle,
12
+ resolvedChildren,
13
+ loadingIds,
14
+ errorIds,
15
+ requestLoad,
16
+ loadChildren,
17
+ selectedId,
18
+ onSelectNode,
19
+ renderLabel
20
+ }) {
21
+ var _a;
22
+ const children = (_a = node.children) != null ? _a : resolvedChildren[node.id];
23
+ const isResolved = children !== void 0;
24
+ const expandable = isResolved ? children.length > 0 : Boolean(loadChildren) && node.hasChildren !== false;
25
+ const expanded = isExpanded(node.id);
26
+ const loading = loadingIds.has(node.id);
27
+ const error = errorIds[node.id];
28
+ const selected = selectedId !== void 0 && selectedId === node.id;
29
+ useEffect(() => {
30
+ if (expanded && children === void 0 && !loading && !error && loadChildren) {
31
+ requestLoad(node);
32
+ }
33
+ }, [expanded, children, loading, error]);
34
+ return /* @__PURE__ */ jsxs(
35
+ "div",
36
+ {
37
+ role: "treeitem",
38
+ "aria-expanded": expandable ? expanded : void 0,
39
+ "aria-selected": selected,
40
+ style: { ["--tree-depth"]: depth },
41
+ children: [
42
+ /* @__PURE__ */ jsxs("div", { className: styles.row, "data-selected": selected || void 0, children: [
43
+ /* @__PURE__ */ jsx(
44
+ "button",
45
+ {
46
+ type: "button",
47
+ className: styles.toggle,
48
+ onClick: () => expandable && toggle(node.id),
49
+ disabled: !expandable,
50
+ "aria-hidden": !expandable,
51
+ tabIndex: expandable ? 0 : -1,
52
+ children: loading ? /* @__PURE__ */ jsx("span", { className: `spin ${styles.spinner}`, children: /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": "true" }) }) : expandable ? (
53
+ // A resolved node's chevron means "this has children"; an
54
+ // unresolved lazy node's dimmer chevron means "might have
55
+ // children, click to check" - we genuinely don't know yet,
56
+ // and shouldn't visually assert otherwise.
57
+ /* @__PURE__ */ jsx("span", { className: isResolved ? void 0 : styles.toggleUnresolved, children: expanded ? /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" }) })
58
+ ) : /* @__PURE__ */ jsx("span", { className: styles.leafSpacer, "aria-hidden": "true" })
59
+ }
60
+ ),
61
+ /* @__PURE__ */ jsxs("button", { type: "button", className: styles.labelButton, onClick: () => onSelectNode == null ? void 0 : onSelectNode(node), children: [
62
+ node.icon ? /* @__PURE__ */ jsx("span", { className: styles.icon, "aria-hidden": "true", children: node.icon }) : null,
63
+ /* @__PURE__ */ jsx("span", { className: styles.label, children: renderLabel ? renderLabel(node) : node.label })
64
+ ] })
65
+ ] }),
66
+ error ? /* @__PURE__ */ jsxs("div", { className: styles.errorRow, children: [
67
+ /* @__PURE__ */ jsx("span", { children: error }),
68
+ /* @__PURE__ */ jsx("button", { type: "button", className: styles.retryButton, onClick: () => requestLoad(node), children: "Pr\xF8v igen" })
69
+ ] }) : null,
70
+ expanded && children && children.length > 0 ? /* @__PURE__ */ jsx("div", { role: "group", className: styles.children, children: children.map((child) => /* @__PURE__ */ jsx(
71
+ TreeNodeRow,
72
+ {
73
+ node: child,
74
+ depth: depth + 1,
75
+ isExpanded,
76
+ toggle,
77
+ resolvedChildren,
78
+ loadingIds,
79
+ errorIds,
80
+ requestLoad,
81
+ loadChildren,
82
+ selectedId,
83
+ onSelectNode,
84
+ renderLabel
85
+ },
86
+ child.id
87
+ )) }) : null
88
+ ]
89
+ }
90
+ );
91
+ }
92
+
93
+ export { TreeNodeRow };