@dbcdk/react-components 0.0.182 → 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 (52) 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 +7 -2
  5. package/dist/components/clear-button/ClearButton.d.ts +8 -1
  6. package/dist/components/clear-button/ClearButton.js +7 -2
  7. package/dist/components/clear-button/ClearButton.module.css +12 -0
  8. package/dist/components/forms/select/Select.cjs +2 -1
  9. package/dist/components/forms/select/Select.js +2 -1
  10. package/dist/components/json-viewer/JsonViewer.cjs +3 -2
  11. package/dist/components/json-viewer/JsonViewer.d.ts +1 -1
  12. package/dist/components/json-viewer/JsonViewer.js +3 -2
  13. package/dist/components/json-viewer/JsonViewer.module.css +7 -0
  14. package/dist/components/json-viewer/types.d.ts +6 -0
  15. package/dist/components/line-viewer/LineViewer.cjs +174 -0
  16. package/dist/components/line-viewer/LineViewer.d.ts +20 -0
  17. package/dist/components/line-viewer/LineViewer.js +168 -0
  18. package/dist/components/line-viewer/LineViewer.module.css +112 -0
  19. package/dist/components/line-viewer/tokenizeLineFormat.cjs +52 -0
  20. package/dist/components/line-viewer/tokenizeLineFormat.d.ts +14 -0
  21. package/dist/components/line-viewer/tokenizeLineFormat.js +50 -0
  22. package/dist/components/tree-view/TreeNodeRow.cjs +99 -0
  23. package/dist/components/tree-view/TreeNodeRow.d.ts +18 -0
  24. package/dist/components/tree-view/TreeNodeRow.js +93 -0
  25. package/dist/components/tree-view/TreeView.cjs +93 -0
  26. package/dist/components/tree-view/TreeView.d.ts +20 -0
  27. package/dist/components/tree-view/TreeView.js +79 -0
  28. package/dist/components/tree-view/TreeView.module.css +141 -0
  29. package/dist/components/tree-view/types.cjs +2 -0
  30. package/dist/components/tree-view/types.d.ts +44 -0
  31. package/dist/components/tree-view/types.js +1 -0
  32. package/dist/components/tree-view/useTreeExpansion.cjs +47 -0
  33. package/dist/components/tree-view/useTreeExpansion.d.ts +17 -0
  34. package/dist/components/tree-view/useTreeExpansion.js +45 -0
  35. package/dist/components/tree-view/utils.cjs +32 -0
  36. package/dist/components/tree-view/utils.d.ts +16 -0
  37. package/dist/components/tree-view/utils.js +29 -0
  38. package/dist/components/xml-viewer/XmlViewer.cjs +175 -0
  39. package/dist/components/xml-viewer/XmlViewer.d.ts +20 -0
  40. package/dist/components/xml-viewer/XmlViewer.js +169 -0
  41. package/dist/components/xml-viewer/XmlViewer.module.css +109 -0
  42. package/dist/components/xml-viewer/tokenizeXml.cjs +50 -0
  43. package/dist/components/xml-viewer/tokenizeXml.d.ts +12 -0
  44. package/dist/components/xml-viewer/tokenizeXml.js +48 -0
  45. package/dist/index.cjs +7 -0
  46. package/dist/index.css +3 -0
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.js +1 -0
  49. package/dist/utils/date/formatDate.cjs +29 -17
  50. package/dist/utils/date/formatDate.d.ts +7 -0
  51. package/dist/utils/date/formatDate.js +29 -17
  52. package/package.json +1 -1
@@ -0,0 +1,169 @@
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 { tokenizeXml } from './tokenizeXml';
9
+ import styles from './XmlViewer.module.css';
10
+ import { Button } from '../button/Button';
11
+
12
+ const TOKEN_CLASS = {
13
+ tagName: styles.tagName,
14
+ attrName: styles.attrName,
15
+ attrValue: styles.attrValue,
16
+ comment: styles.comment,
17
+ cdata: styles.cdata,
18
+ declaration: styles.declaration,
19
+ doctype: styles.doctype,
20
+ punctuation: styles.punctuation,
21
+ text: void 0
22
+ };
23
+ function XmlViewer({
24
+ xml,
25
+ size = "md",
26
+ appearance = "dark",
27
+ storageKey,
28
+ borderless = false
29
+ }) {
30
+ const tokens = useMemo(() => tokenizeXml(xml), [xml]);
31
+ const codeRef = useRef(null);
32
+ const { value: currentAppearance, setValue: setCurrentAppearance } = usePersistentState({
33
+ initialValue: appearance,
34
+ storageKey: storageKey ? `${storageKey}:appearance` : void 0,
35
+ validate: (stored) => stored === "dark" || stored === "light"
36
+ });
37
+ const { value: viewMode, setValue: setViewMode } = usePersistentState({
38
+ initialValue: "highlighted",
39
+ storageKey: storageKey ? `${storageKey}:viewMode` : void 0,
40
+ validate: (stored) => stored === "highlighted" || stored === "raw"
41
+ });
42
+ const { value: wrap, setValue: setWrap } = usePersistentState({
43
+ initialValue: true,
44
+ storageKey: storageKey ? `${storageKey}:wrap` : void 0,
45
+ validate: (stored) => typeof stored === "boolean"
46
+ });
47
+ const handleCopy = () => {
48
+ var _a;
49
+ (_a = navigator.clipboard) == null ? void 0 : _a.writeText(xml).catch(() => {
50
+ });
51
+ };
52
+ const handleDoubleClick = () => {
53
+ const el = codeRef.current;
54
+ const selection = typeof window !== "undefined" ? window.getSelection() : null;
55
+ if (!el || !selection) return;
56
+ const range = document.createRange();
57
+ range.selectNodeContents(el);
58
+ selection.removeAllRanges();
59
+ selection.addRange(range);
60
+ };
61
+ return /* @__PURE__ */ jsxs(
62
+ "section",
63
+ {
64
+ className: [styles.viewer, borderless ? styles.borderless : ""].filter(Boolean).join(" "),
65
+ "data-appearance": currentAppearance,
66
+ children: [
67
+ /* @__PURE__ */ jsx(
68
+ "div",
69
+ {
70
+ className: styles.toolbar,
71
+ "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
72
+ children: /* @__PURE__ */ jsx(
73
+ Popover,
74
+ {
75
+ matchTriggerWidth: false,
76
+ minWidth: "200px",
77
+ trigger: (toggle, _icon, isOpen) => /* @__PURE__ */ jsx(
78
+ Button,
79
+ {
80
+ variant: "inline",
81
+ "aria-label": "Flere handlinger",
82
+ "aria-haspopup": "menu",
83
+ "aria-expanded": isOpen ? true : false,
84
+ onClick: toggle,
85
+ children: /* @__PURE__ */ jsx(MoreVertical, { "aria-hidden": "true" })
86
+ }
87
+ ),
88
+ children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
89
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
90
+ "button",
91
+ {
92
+ type: "button",
93
+ onClick: () => {
94
+ setWrap((value) => !value);
95
+ close();
96
+ },
97
+ children: [
98
+ /* @__PURE__ */ jsx(TextWrap, { "aria-hidden": "true" }),
99
+ /* @__PURE__ */ jsx("span", { children: wrap ? "Ombryd ikke tekst" : "Ombryd tekst" })
100
+ ]
101
+ }
102
+ ) }),
103
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
104
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
105
+ "button",
106
+ {
107
+ type: "button",
108
+ onClick: () => {
109
+ setViewMode((mode) => mode === "raw" ? "highlighted" : "raw");
110
+ close();
111
+ },
112
+ children: [
113
+ viewMode === "raw" ? /* @__PURE__ */ jsx(ListTree, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Code2, { "aria-hidden": "true" }),
114
+ /* @__PURE__ */ jsx("span", { children: viewMode === "raw" ? "Vis med farver" : "Vis raw" })
115
+ ]
116
+ }
117
+ ) }),
118
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ onClick: () => {
123
+ setCurrentAppearance((value) => value === "dark" ? "light" : "dark");
124
+ close();
125
+ },
126
+ children: [
127
+ currentAppearance === "dark" ? /* @__PURE__ */ jsx(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Moon, { "aria-hidden": "true" }),
128
+ /* @__PURE__ */ jsx("span", { children: currentAppearance === "dark" ? "Lys visning" : "M\xF8rk visning" })
129
+ ]
130
+ }
131
+ ) }),
132
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
133
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
134
+ "button",
135
+ {
136
+ type: "button",
137
+ onClick: () => {
138
+ handleCopy();
139
+ close();
140
+ },
141
+ children: [
142
+ /* @__PURE__ */ jsx(ClipboardCopy, { "aria-hidden": "true" }),
143
+ /* @__PURE__ */ jsx("span", { children: "Kopi\xE9r XML" })
144
+ ]
145
+ }
146
+ ) })
147
+ ] })
148
+ }
149
+ )
150
+ }
151
+ ),
152
+ /* @__PURE__ */ jsx(
153
+ "pre",
154
+ {
155
+ className: [styles.codePane, styles[size], wrap ? styles.wrap : styles.noWrap].join(" "),
156
+ tabIndex: 0,
157
+ onDoubleClick: handleDoubleClick,
158
+ children: /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? xml : tokens.map((token, index) => {
159
+ const tokenClassName = TOKEN_CLASS[token.type];
160
+ return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
161
+ }) })
162
+ }
163
+ )
164
+ ]
165
+ }
166
+ );
167
+ }
168
+
169
+ export { XmlViewer };
@@ -0,0 +1,109 @@
1
+ .viewer {
2
+ --xml-pane-bg: var(--color-bg-inverse);
3
+ --xml-pane-border: color-mix(in oklab, var(--color-fg-inverse) 12%, transparent);
4
+ --xml-pane-text: var(--color-fg-inverse);
5
+
6
+ --xml-tag: color-mix(in oklab, var(--color-fg-inverse) 66%, var(--color-syntax-accent) 34%);
7
+ --xml-attr-name: color-mix(in oklab, var(--color-fg-inverse) 50%, var(--color-syntax-accent) 50%);
8
+ --xml-attr-value: color-mix(in oklab, var(--color-fg-inverse) 20%, var(--color-syntax-string) 80%);
9
+ --xml-comment: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
10
+ --xml-punctuation: color-mix(in oklab, var(--color-fg-inverse) 60%, transparent);
11
+
12
+ position: relative;
13
+ background: var(--xml-pane-bg);
14
+ border: var(--border-width-thin) solid var(--xml-pane-border);
15
+ border-radius: var(--border-radius-default);
16
+ box-shadow: var(--shadow-md);
17
+ font-family: var(--font-family-mono);
18
+ }
19
+
20
+ .viewer[data-appearance='light'] {
21
+ --xml-pane-bg: var(--color-bg-surface);
22
+ --xml-pane-border: var(--color-border-subtle);
23
+ --xml-pane-text: var(--color-fg-default);
24
+
25
+ --xml-tag: var(--color-link);
26
+ --xml-attr-name: color-mix(in oklab, var(--color-fg-default) 55%, var(--color-link) 45%);
27
+ --xml-attr-value: var(--color-status-success-fg);
28
+ --xml-comment: var(--color-fg-muted);
29
+ --xml-punctuation: var(--color-fg-subtle);
30
+
31
+ box-shadow: var(--shadow-xs);
32
+ }
33
+
34
+ .viewer.borderless {
35
+ border: none;
36
+ border-radius: 0;
37
+ box-shadow: none;
38
+ }
39
+
40
+ .toolbar {
41
+ position: absolute;
42
+ top: var(--spacing-xs);
43
+ right: var(--spacing-xs);
44
+ z-index: 1;
45
+ }
46
+
47
+ .codePane {
48
+ margin: 0;
49
+ padding: var(--spacing-sm);
50
+ overflow: auto;
51
+ color: var(--xml-pane-text);
52
+ font-family: inherit;
53
+ line-height: 1.35;
54
+ }
55
+
56
+ .wrap {
57
+ white-space: pre-wrap;
58
+ overflow-wrap: break-word;
59
+ }
60
+
61
+ .noWrap {
62
+ white-space: pre;
63
+ }
64
+
65
+ .xs,
66
+ .sm {
67
+ font-size: var(--font-size-xs);
68
+ }
69
+
70
+ .md {
71
+ font-size: var(--font-size-sm);
72
+ }
73
+
74
+ .lg {
75
+ font-size: var(--font-size-base);
76
+ }
77
+
78
+ .tagName {
79
+ color: var(--xml-tag);
80
+ font-weight: var(--font-weight-medium);
81
+ }
82
+
83
+ .attrName {
84
+ color: var(--xml-attr-name);
85
+ }
86
+
87
+ .attrValue {
88
+ color: var(--xml-attr-value);
89
+ }
90
+
91
+ .comment {
92
+ color: var(--xml-comment);
93
+ font-style: italic;
94
+ }
95
+
96
+ .cdata {
97
+ color: var(--xml-attr-value);
98
+ opacity: 0.85;
99
+ }
100
+
101
+ .declaration,
102
+ .doctype {
103
+ color: var(--xml-comment);
104
+ font-style: italic;
105
+ }
106
+
107
+ .punctuation {
108
+ color: var(--xml-punctuation);
109
+ }
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ const CHUNK_PATTERN = /(<!--[\s\S]*?-->)|(<!\[CDATA\[[\s\S]*?\]\]>)|(<!DOCTYPE[\s\S]*?>)|(<\?[\s\S]*?\?>)|(<\/?[a-zA-Z_:][\s\S]*?>)|([^<]+)/g;
4
+ const ATTR_PATTERN = /([^\s=/>]+)(\s*=\s*)("[^"]*"|'[^']*')/g;
5
+ function tokenizeTag(raw) {
6
+ var _a;
7
+ const isClosing = raw.startsWith("</");
8
+ const isSelfClosing = raw.endsWith("/>");
9
+ const openLength = isClosing ? 2 : 1;
10
+ const closeLength = isSelfClosing ? 2 : 1;
11
+ const inner = raw.slice(openLength, raw.length - closeLength);
12
+ const tokens = [{ type: "punctuation", value: raw.slice(0, openLength) }];
13
+ const nameMatch = /^[^\s/>]+/.exec(inner);
14
+ const name = (_a = nameMatch == null ? void 0 : nameMatch[0]) != null ? _a : "";
15
+ if (name) tokens.push({ type: "tagName", value: name });
16
+ let lastIndex = name.length;
17
+ ATTR_PATTERN.lastIndex = lastIndex;
18
+ let match;
19
+ while (match = ATTR_PATTERN.exec(inner)) {
20
+ if (match.index > lastIndex) {
21
+ tokens.push({ type: "punctuation", value: inner.slice(lastIndex, match.index) });
22
+ }
23
+ tokens.push({ type: "attrName", value: match[1] });
24
+ tokens.push({ type: "punctuation", value: match[2] });
25
+ tokens.push({ type: "attrValue", value: match[3] });
26
+ lastIndex = match.index + match[0].length;
27
+ }
28
+ if (lastIndex < inner.length) {
29
+ tokens.push({ type: "punctuation", value: inner.slice(lastIndex) });
30
+ }
31
+ tokens.push({ type: "punctuation", value: raw.slice(raw.length - closeLength) });
32
+ return tokens;
33
+ }
34
+ function tokenizeXml(source) {
35
+ const tokens = [];
36
+ CHUNK_PATTERN.lastIndex = 0;
37
+ let match;
38
+ while (match = CHUNK_PATTERN.exec(source)) {
39
+ const [, comment, cdata, doctype, declaration, tag, text] = match;
40
+ if (comment) tokens.push({ type: "comment", value: comment });
41
+ else if (cdata) tokens.push({ type: "cdata", value: cdata });
42
+ else if (doctype) tokens.push({ type: "doctype", value: doctype });
43
+ else if (declaration) tokens.push({ type: "declaration", value: declaration });
44
+ else if (tag) tokens.push(...tokenizeTag(tag));
45
+ else if (text) tokens.push({ type: "text", value: text });
46
+ }
47
+ return tokens;
48
+ }
49
+
50
+ exports.tokenizeXml = tokenizeXml;
@@ -0,0 +1,12 @@
1
+ export type XmlTokenType = 'tagName' | 'attrName' | 'attrValue' | 'text' | 'comment' | 'cdata' | 'declaration' | 'doctype' | 'punctuation';
2
+ export interface XmlToken {
3
+ type: XmlTokenType;
4
+ value: string;
5
+ }
6
+ /**
7
+ * Tokenizes an XML string for syntax highlighting without parsing it into a
8
+ * DOM. Preserves the source exactly - concatenating every token's `value`
9
+ * reproduces the input - so malformed or partial XML still highlights
10
+ * whatever it recognizes instead of failing outright.
11
+ */
12
+ export declare function tokenizeXml(source: string): XmlToken[];
@@ -0,0 +1,48 @@
1
+ const CHUNK_PATTERN = /(<!--[\s\S]*?-->)|(<!\[CDATA\[[\s\S]*?\]\]>)|(<!DOCTYPE[\s\S]*?>)|(<\?[\s\S]*?\?>)|(<\/?[a-zA-Z_:][\s\S]*?>)|([^<]+)/g;
2
+ const ATTR_PATTERN = /([^\s=/>]+)(\s*=\s*)("[^"]*"|'[^']*')/g;
3
+ function tokenizeTag(raw) {
4
+ var _a;
5
+ const isClosing = raw.startsWith("</");
6
+ const isSelfClosing = raw.endsWith("/>");
7
+ const openLength = isClosing ? 2 : 1;
8
+ const closeLength = isSelfClosing ? 2 : 1;
9
+ const inner = raw.slice(openLength, raw.length - closeLength);
10
+ const tokens = [{ type: "punctuation", value: raw.slice(0, openLength) }];
11
+ const nameMatch = /^[^\s/>]+/.exec(inner);
12
+ const name = (_a = nameMatch == null ? void 0 : nameMatch[0]) != null ? _a : "";
13
+ if (name) tokens.push({ type: "tagName", value: name });
14
+ let lastIndex = name.length;
15
+ ATTR_PATTERN.lastIndex = lastIndex;
16
+ let match;
17
+ while (match = ATTR_PATTERN.exec(inner)) {
18
+ if (match.index > lastIndex) {
19
+ tokens.push({ type: "punctuation", value: inner.slice(lastIndex, match.index) });
20
+ }
21
+ tokens.push({ type: "attrName", value: match[1] });
22
+ tokens.push({ type: "punctuation", value: match[2] });
23
+ tokens.push({ type: "attrValue", value: match[3] });
24
+ lastIndex = match.index + match[0].length;
25
+ }
26
+ if (lastIndex < inner.length) {
27
+ tokens.push({ type: "punctuation", value: inner.slice(lastIndex) });
28
+ }
29
+ tokens.push({ type: "punctuation", value: raw.slice(raw.length - closeLength) });
30
+ return tokens;
31
+ }
32
+ function tokenizeXml(source) {
33
+ const tokens = [];
34
+ CHUNK_PATTERN.lastIndex = 0;
35
+ let match;
36
+ while (match = CHUNK_PATTERN.exec(source)) {
37
+ const [, comment, cdata, doctype, declaration, tag, text] = match;
38
+ if (comment) tokens.push({ type: "comment", value: comment });
39
+ else if (cdata) tokens.push({ type: "cdata", value: cdata });
40
+ else if (doctype) tokens.push({ type: "doctype", value: doctype });
41
+ else if (declaration) tokens.push({ type: "declaration", value: declaration });
42
+ else if (tag) tokens.push(...tokenizeTag(tag));
43
+ else if (text) tokens.push({ type: "text", value: text });
44
+ }
45
+ return tokens;
46
+ }
47
+
48
+ export { tokenizeXml };
package/dist/index.cjs CHANGED
@@ -43,6 +43,7 @@ var MediaCard = require('./components/media-card/MediaCard');
43
43
  var HighlightedText = require('./components/highlighted-text/HighlightedText');
44
44
  var ButtonSelect = require('./components/button-select/ButtonSelect');
45
45
  var Sidebar = require('./components/sidebar/Sidebar');
46
+ var TreeView = require('./components/tree-view/TreeView');
46
47
  var SplitButton = require('./components/split-button/SplitButton');
47
48
  var ChipMultiToggle = require('./components/filtering/chip-multi-toggle/ChipMultiToggle');
48
49
  var CheckboxGroup = require('./components/forms/checkbox-group/CheckboxGroup');
@@ -307,6 +308,12 @@ Object.keys(Sidebar).forEach(function (k) {
307
308
  get: function () { return Sidebar[k]; }
308
309
  });
309
310
  });
311
+ Object.keys(TreeView).forEach(function (k) {
312
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
313
+ enumerable: true,
314
+ get: function () { return TreeView[k]; }
315
+ });
316
+ });
310
317
  Object.keys(SplitButton).forEach(function (k) {
311
318
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
312
319
  enumerable: true,
package/dist/index.css CHANGED
@@ -39,6 +39,7 @@
39
39
  @import './components/inline-status/InlineStatus.module.css';
40
40
  @import './components/json-viewer/JsonViewer.module.css';
41
41
  @import './components/lightbox/Lightbox.module.css';
42
+ @import './components/line-viewer/LineViewer.module.css';
42
43
  @import './components/media-card/MediaCard.module.css';
43
44
  @import './components/menu/Menu.module.css';
44
45
  @import './components/meta-bar/MetaBar.module.css';
@@ -72,4 +73,6 @@
72
73
  @import './components/table/components/empty-state/EmptyState.module.css';
73
74
  @import './components/tabs/Tabs.module.css';
74
75
  @import './components/toast/Toast.module.css';
76
+ @import './components/tree-view/TreeView.module.css';
75
77
  @import './components/user-display/UserDisplay.module.css';
78
+ @import './components/xml-viewer/XmlViewer.module.css';
package/dist/index.d.ts CHANGED
@@ -42,6 +42,7 @@ export * from './components/media-card/MediaCard';
42
42
  export * from './components/highlighted-text/HighlightedText';
43
43
  export * from './components/button-select/ButtonSelect';
44
44
  export * from './components/sidebar/Sidebar';
45
+ export * from './components/tree-view/TreeView';
45
46
  export * from './components/split-button/SplitButton';
46
47
  export * from './components/filtering/chip-multi-toggle/ChipMultiToggle';
47
48
  export * from './components/forms/checkbox-group/CheckboxGroup';
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ export * from './components/media-card/MediaCard';
41
41
  export * from './components/highlighted-text/HighlightedText';
42
42
  export * from './components/button-select/ButtonSelect';
43
43
  export * from './components/sidebar/Sidebar';
44
+ export * from './components/tree-view/TreeView';
44
45
  export * from './components/split-button/SplitButton';
45
46
  export * from './components/filtering/chip-multi-toggle/ChipMultiToggle';
46
47
  export * from './components/forms/checkbox-group/CheckboxGroup';
@@ -9,41 +9,53 @@ function formatDate(date, options = {}) {
9
9
  userFriendlyLabel = false,
10
10
  timeOnly = false,
11
11
  ignoreDate = false,
12
- ignoreDateIfSame = false
12
+ ignoreDateIfSame = false,
13
+ ignoreTimezone = false
13
14
  } = options;
14
15
  const useTimeOnly = timeOnly || ignoreDate;
15
16
  const pad = (n) => n.toString().padStart(2, "0");
16
- const formatTime = () => {
17
- const hours = pad(d.getHours());
18
- const minutes = pad(d.getMinutes());
17
+ const parts = (dt, useUTC) => ({
18
+ date: useUTC ? dt.getUTCDate() : dt.getDate(),
19
+ month: useUTC ? dt.getUTCMonth() : dt.getMonth(),
20
+ year: useUTC ? dt.getUTCFullYear() : dt.getFullYear(),
21
+ hours: useUTC ? dt.getUTCHours() : dt.getHours(),
22
+ minutes: useUTC ? dt.getUTCMinutes() : dt.getMinutes(),
23
+ seconds: useUTC ? dt.getUTCSeconds() : dt.getSeconds()
24
+ });
25
+ const dParts = parts(d, ignoreTimezone);
26
+ const formatTime = (p) => {
27
+ const hours = pad(p.hours);
28
+ const minutes = pad(p.minutes);
19
29
  if (!showSeconds) return `${hours}:${minutes}`;
20
- const seconds = pad(d.getSeconds());
30
+ const seconds = pad(p.seconds);
21
31
  return `${hours}:${minutes}:${seconds}`;
22
32
  };
23
- const isSameCalendarDay = (a, b) => a.getDate() === b.getDate() && a.getMonth() === b.getMonth() && a.getFullYear() === b.getFullYear();
33
+ const isSameCalendarDay = (a, b) => a.date === b.date && a.month === b.month && a.year === b.year;
24
34
  if (useTimeOnly) {
25
- return formatTime();
35
+ return formatTime(dParts);
26
36
  }
27
- if (ignoreDateIfSame && isSameCalendarDay(d, /* @__PURE__ */ new Date())) {
28
- return formatTime();
37
+ const nowParts = parts(/* @__PURE__ */ new Date(), false);
38
+ if (ignoreDateIfSame && isSameCalendarDay(dParts, nowParts)) {
39
+ return formatTime(dParts);
29
40
  }
30
41
  if (userFriendlyLabel) {
31
42
  const now = /* @__PURE__ */ new Date();
32
43
  const yesterday = new Date(now);
33
44
  yesterday.setDate(now.getDate() - 1);
34
- if (isSameCalendarDay(d, now)) {
35
- return showTime ? `I dag, kl. ${formatTime()}` : "I dag";
45
+ const yesterdayParts = parts(yesterday, false);
46
+ if (isSameCalendarDay(dParts, nowParts)) {
47
+ return showTime ? `I dag, kl. ${formatTime(dParts)}` : "I dag";
36
48
  }
37
- if (isSameCalendarDay(d, yesterday)) {
38
- return showTime ? `I g\xE5r, kl. ${formatTime()}` : "I g\xE5r";
49
+ if (isSameCalendarDay(dParts, yesterdayParts)) {
50
+ return showTime ? `I g\xE5r, kl. ${formatTime(dParts)}` : "I g\xE5r";
39
51
  }
40
52
  }
41
- const day = pad(d.getDate());
42
- const month = pad(d.getMonth() + 1);
43
- const year = d.getFullYear();
53
+ const day = pad(dParts.date);
54
+ const month = pad(dParts.month + 1);
55
+ const year = dParts.year;
44
56
  const base = `${day}.${month}.${year}`;
45
57
  if (!showTime) return base;
46
- return `${base} ${formatTime()}`;
58
+ return `${base} ${formatTime(dParts)}`;
47
59
  }
48
60
 
49
61
  exports.formatDate = formatDate;
@@ -8,6 +8,7 @@
8
8
  * - formatDate(date, { showTime: true }) => "10.03.2026 14:30"
9
9
  * - formatDate(date, { timeOnly: true }) => "14:30"
10
10
  * - formatDate(date, { showTime: true, ignoreDateIfSame: true }) => "14:30" for today, otherwise "10.03.2026 14:30"
11
+ * - formatDate('2026-03-10T14:30:00.000Z', { showTime: true, ignoreTimezone: true }) => "10.03.2026 14:30" regardless of the viewer's timezone
11
12
  *
12
13
  * @param date - The Date or date-parsable value to format
13
14
  * @param options.showTime - If true, append time "hh:mm" or "hh:mm:ss"
@@ -16,6 +17,11 @@
16
17
  * @param options.timeOnly - If true, return only the time portion
17
18
  * @param options.ignoreDate - Alias for timeOnly
18
19
  * @param options.ignoreDateIfSame - If true, return only the time when the date is today
20
+ * @param options.ignoreTimezone - If true, read the date's UTC field values verbatim instead of converting
21
+ * to the viewer's local timezone. Use this when `date` already carries the intended wall-clock values in
22
+ * its UTC fields (e.g. a "timestamp without time zone" DB column serialized with a trailing "Z") and any
23
+ * further timezone conversion would be wrong. "I dag"/"I går" comparisons still use the viewer's real
24
+ * local "today", since that part is genuinely about the viewer's current day.
19
25
  * @returns A formatted string
20
26
  */
21
27
  export declare function formatDate(date: Date | string | number, options?: {
@@ -25,4 +31,5 @@ export declare function formatDate(date: Date | string | number, options?: {
25
31
  timeOnly?: boolean;
26
32
  ignoreDate?: boolean;
27
33
  ignoreDateIfSame?: boolean;
34
+ ignoreTimezone?: boolean;
28
35
  }): string;
@@ -7,41 +7,53 @@ function formatDate(date, options = {}) {
7
7
  userFriendlyLabel = false,
8
8
  timeOnly = false,
9
9
  ignoreDate = false,
10
- ignoreDateIfSame = false
10
+ ignoreDateIfSame = false,
11
+ ignoreTimezone = false
11
12
  } = options;
12
13
  const useTimeOnly = timeOnly || ignoreDate;
13
14
  const pad = (n) => n.toString().padStart(2, "0");
14
- const formatTime = () => {
15
- const hours = pad(d.getHours());
16
- const minutes = pad(d.getMinutes());
15
+ const parts = (dt, useUTC) => ({
16
+ date: useUTC ? dt.getUTCDate() : dt.getDate(),
17
+ month: useUTC ? dt.getUTCMonth() : dt.getMonth(),
18
+ year: useUTC ? dt.getUTCFullYear() : dt.getFullYear(),
19
+ hours: useUTC ? dt.getUTCHours() : dt.getHours(),
20
+ minutes: useUTC ? dt.getUTCMinutes() : dt.getMinutes(),
21
+ seconds: useUTC ? dt.getUTCSeconds() : dt.getSeconds()
22
+ });
23
+ const dParts = parts(d, ignoreTimezone);
24
+ const formatTime = (p) => {
25
+ const hours = pad(p.hours);
26
+ const minutes = pad(p.minutes);
17
27
  if (!showSeconds) return `${hours}:${minutes}`;
18
- const seconds = pad(d.getSeconds());
28
+ const seconds = pad(p.seconds);
19
29
  return `${hours}:${minutes}:${seconds}`;
20
30
  };
21
- const isSameCalendarDay = (a, b) => a.getDate() === b.getDate() && a.getMonth() === b.getMonth() && a.getFullYear() === b.getFullYear();
31
+ const isSameCalendarDay = (a, b) => a.date === b.date && a.month === b.month && a.year === b.year;
22
32
  if (useTimeOnly) {
23
- return formatTime();
33
+ return formatTime(dParts);
24
34
  }
25
- if (ignoreDateIfSame && isSameCalendarDay(d, /* @__PURE__ */ new Date())) {
26
- return formatTime();
35
+ const nowParts = parts(/* @__PURE__ */ new Date(), false);
36
+ if (ignoreDateIfSame && isSameCalendarDay(dParts, nowParts)) {
37
+ return formatTime(dParts);
27
38
  }
28
39
  if (userFriendlyLabel) {
29
40
  const now = /* @__PURE__ */ new Date();
30
41
  const yesterday = new Date(now);
31
42
  yesterday.setDate(now.getDate() - 1);
32
- if (isSameCalendarDay(d, now)) {
33
- return showTime ? `I dag, kl. ${formatTime()}` : "I dag";
43
+ const yesterdayParts = parts(yesterday, false);
44
+ if (isSameCalendarDay(dParts, nowParts)) {
45
+ return showTime ? `I dag, kl. ${formatTime(dParts)}` : "I dag";
34
46
  }
35
- if (isSameCalendarDay(d, yesterday)) {
36
- return showTime ? `I g\xE5r, kl. ${formatTime()}` : "I g\xE5r";
47
+ if (isSameCalendarDay(dParts, yesterdayParts)) {
48
+ return showTime ? `I g\xE5r, kl. ${formatTime(dParts)}` : "I g\xE5r";
37
49
  }
38
50
  }
39
- const day = pad(d.getDate());
40
- const month = pad(d.getMonth() + 1);
41
- const year = d.getFullYear();
51
+ const day = pad(dParts.date);
52
+ const month = pad(dParts.month + 1);
53
+ const year = dParts.year;
42
54
  const base = `${day}.${month}.${year}`;
43
55
  if (!showTime) return base;
44
- return `${base} ${formatTime()}`;
56
+ return `${base} ${formatTime(dParts)}`;
45
57
  }
46
58
 
47
59
  export { formatDate };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.182",
3
+ "version": "0.0.183",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",