@dbcdk/react-components 0.0.196 → 0.0.198

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 (61) hide show
  1. package/dist/components/json-viewer/JsonViewer.module.css +52 -0
  2. package/dist/components/json-viewer/JsonViewerShell.cjs +147 -143
  3. package/dist/components/json-viewer/JsonViewerShell.js +149 -145
  4. package/dist/components/line-viewer/LineViewer.cjs +15 -10
  5. package/dist/components/line-viewer/LineViewer.d.ts +1 -1
  6. package/dist/components/line-viewer/LineViewer.js +15 -10
  7. package/dist/components/line-viewer/LineViewer.module.css +58 -3
  8. package/dist/components/page-layout/PageLayout.module.css +1 -1
  9. package/dist/components/scroll-fade/ScrollFade.cjs +42 -0
  10. package/dist/components/scroll-fade/ScrollFade.d.ts +18 -0
  11. package/dist/components/scroll-fade/ScrollFade.js +36 -0
  12. package/dist/components/scroll-fade/ScrollFade.module.css +71 -0
  13. package/dist/components/search-box/SearchBox.cjs +39 -35
  14. package/dist/components/search-box/SearchBox.d.ts +2 -0
  15. package/dist/components/search-box/SearchBox.js +39 -35
  16. package/dist/components/table/Table.cjs +52 -23
  17. package/dist/components/table/Table.d.ts +1 -1
  18. package/dist/components/table/Table.js +53 -24
  19. package/dist/components/table/Table.module.css +4 -0
  20. package/dist/components/table/Table.types.d.ts +27 -0
  21. package/dist/components/table/TanstackTable.cjs +64 -64
  22. package/dist/components/table/TanstackTable.js +67 -67
  23. package/dist/components/table/columnLayout.utils.cjs +146 -0
  24. package/dist/components/table/columnLayout.utils.d.ts +44 -0
  25. package/dist/components/table/columnLayout.utils.js +141 -0
  26. package/dist/components/table/components/TableHeader.cjs +3 -1
  27. package/dist/components/table/components/TableHeader.d.ts +2 -1
  28. package/dist/components/table/components/TableHeader.js +3 -1
  29. package/dist/components/table/components/TableHeaderCell.cjs +29 -19
  30. package/dist/components/table/components/TableHeaderCell.d.ts +2 -1
  31. package/dist/components/table/components/TableHeaderCell.js +29 -19
  32. package/dist/components/table/hooks/useElementWidth.cjs +40 -0
  33. package/dist/components/table/hooks/useElementWidth.d.ts +7 -0
  34. package/dist/components/table/hooks/useElementWidth.js +38 -0
  35. package/dist/components/table/table.utils.cjs +24 -2
  36. package/dist/components/table/table.utils.d.ts +40 -2
  37. package/dist/components/table/table.utils.js +20 -2
  38. package/dist/components/table/tanstackTable.utils.cjs +33 -97
  39. package/dist/components/table/tanstackTable.utils.d.ts +24 -8
  40. package/dist/components/table/tanstackTable.utils.js +34 -99
  41. package/dist/components/xml-viewer/XmlViewer.cjs +15 -10
  42. package/dist/components/xml-viewer/XmlViewer.d.ts +1 -1
  43. package/dist/components/xml-viewer/XmlViewer.js +15 -10
  44. package/dist/components/xml-viewer/XmlViewer.module.css +58 -3
  45. package/dist/hooks/useColorScheme.cjs +15 -15
  46. package/dist/hooks/useColorScheme.d.ts +1 -0
  47. package/dist/hooks/useColorScheme.js +15 -15
  48. package/dist/hooks/useScrollOverflow.cjs +71 -0
  49. package/dist/hooks/useScrollOverflow.d.ts +23 -0
  50. package/dist/hooks/useScrollOverflow.js +69 -0
  51. package/dist/hooks/useTableSettings.cjs +2 -1
  52. package/dist/hooks/useTableSettings.d.ts +1 -0
  53. package/dist/hooks/useTableSettings.js +2 -1
  54. package/dist/hooks/useTheme.cjs +15 -15
  55. package/dist/hooks/useTheme.d.ts +3 -1
  56. package/dist/hooks/useTheme.js +15 -15
  57. package/dist/index.css +1 -0
  58. package/dist/utils/refs.utils.cjs +13 -0
  59. package/dist/utils/refs.utils.d.ts +7 -0
  60. package/dist/utils/refs.utils.js +11 -0
  61. package/package.json +1 -1
@@ -1,8 +1,9 @@
1
1
  'use client';
2
- import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { Search, X, ArrowDown, ArrowUp, ListTree, Braces, Sun, Moon, ClipboardCopy, Settings2 } from 'lucide-react';
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { Search, X, Settings2, ArrowDown, ArrowUp, ListTree, Braces, Sun, Moon, ClipboardCopy } from 'lucide-react';
4
4
  import { Menu } from '../../components/menu/Menu';
5
5
  import { Popover } from '../../components/popover/Popover';
6
+ import { ScrollFade } from '../../components/scroll-fade/ScrollFade';
6
7
  import styles from './JsonViewer.module.css';
7
8
  import { Button } from '../button/Button';
8
9
  import { Icon } from '../icon/Icon';
@@ -36,164 +37,167 @@ function JsonViewerShell({
36
37
  handleExpandAll,
37
38
  handleCollapseAll
38
39
  } = state;
39
- return /* @__PURE__ */ jsxs(
40
- "section",
40
+ const surface = currentAppearance === "dark" ? "inverse" : void 0;
41
+ const showMetaRow = Boolean(helperText) || viewMode === "tree" && Boolean(normalizedQuery);
42
+ const settingsMenu = /* @__PURE__ */ jsx(
43
+ Popover,
41
44
  {
42
- className: [styles.viewer, borderless ? styles.borderless : "", className].filter(Boolean).join(" "),
43
- "data-appearance": currentAppearance,
44
- children: [
45
- /* @__PURE__ */ jsxs(
46
- "div",
45
+ matchTriggerWidth: false,
46
+ minWidth: "200px",
47
+ trigger: (toggle, _icon, isOpen) => /* @__PURE__ */ jsx(
48
+ Button,
49
+ {
50
+ variant: "inline",
51
+ "aria-label": "Flere handlinger",
52
+ "aria-haspopup": "menu",
53
+ "aria-expanded": isOpen ? true : false,
54
+ onClick: toggle,
55
+ children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true" }) })
56
+ }
57
+ ),
58
+ children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
59
+ /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
60
+ "button",
47
61
  {
48
- className: styles.toolbar,
49
- "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
62
+ type: "button",
63
+ onClick: () => {
64
+ handleExpandAll();
65
+ close();
66
+ },
50
67
  children: [
51
- /* @__PURE__ */ jsxs("div", { className: styles.toolbarTop, children: [
52
- viewMode === "tree" ? /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: styles.searchField, children: [
53
- /* @__PURE__ */ jsx(Search, { className: styles.searchIcon, "aria-hidden": "true" }),
54
- /* @__PURE__ */ jsx(
55
- "input",
56
- {
57
- id: inputId,
58
- type: "search",
59
- value: queryInput,
60
- onChange: (event) => setQueryInput(event.target.value),
61
- placeholder: searchPlaceholder,
62
- className: styles.searchInput
63
- }
64
- ),
65
- queryInput ? /* @__PURE__ */ jsx(
66
- "button",
67
- {
68
- type: "button",
69
- className: styles.clearButton,
70
- "aria-label": "Ryd s\xF8gning",
71
- onClick: () => {
72
- setQueryInput("");
73
- setQuery("");
74
- },
75
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
76
- }
77
- ) : null
78
- ] }) : /* @__PURE__ */ jsx("span", {}),
79
- /* @__PURE__ */ jsx("div", { className: styles.actions, children: /* @__PURE__ */ jsx(
80
- Popover,
81
- {
82
- matchTriggerWidth: false,
83
- minWidth: "200px",
84
- trigger: (toggle, _icon, isOpen) => /* @__PURE__ */ jsx(
85
- Button,
86
- {
87
- variant: "inline",
88
- "aria-label": "Flere handlinger",
89
- "aria-haspopup": "menu",
90
- "aria-expanded": isOpen ? true : false,
91
- onClick: toggle,
92
- children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, { "aria-hidden": "true" }) })
93
- }
94
- ),
95
- children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
96
- /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
97
- "button",
98
- {
99
- type: "button",
100
- onClick: () => {
101
- handleExpandAll();
102
- close();
103
- },
104
- children: [
105
- /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": "true" }),
106
- /* @__PURE__ */ jsx("span", { children: "Fold alle ud" })
107
- ]
108
- }
109
- ) }),
110
- /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
111
- "button",
112
- {
113
- type: "button",
114
- onClick: () => {
115
- handleCollapseAll();
116
- close();
117
- },
118
- children: [
119
- /* @__PURE__ */ jsx(ArrowUp, { "aria-hidden": "true" }),
120
- /* @__PURE__ */ jsx("span", { children: "Fold alle sammen" })
121
- ]
122
- }
123
- ) }),
124
- /* @__PURE__ */ jsx(Menu.Separator, {}),
125
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
126
- "button",
127
- {
128
- type: "button",
129
- onClick: () => {
130
- setViewMode((mode) => mode === "raw" ? "tree" : "raw");
131
- close();
132
- },
133
- children: [
134
- viewMode === "raw" ? /* @__PURE__ */ jsx(ListTree, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Braces, { "aria-hidden": "true" }),
135
- /* @__PURE__ */ jsx("span", { children: viewMode === "raw" ? "Vis tr\xE6" : "Vis raw" })
136
- ]
137
- }
138
- ) }),
139
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
140
- "button",
141
- {
142
- type: "button",
143
- onClick: () => {
144
- handleToggleAppearance();
145
- close();
146
- },
147
- children: [
148
- currentAppearance === "dark" ? /* @__PURE__ */ jsx(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Moon, { "aria-hidden": "true" }),
149
- /* @__PURE__ */ jsx("span", { children: currentAppearance === "dark" ? "Lys visning" : "M\xF8rk visning" })
150
- ]
151
- }
152
- ) }),
153
- /* @__PURE__ */ jsx(Menu.Separator, {}),
154
- /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
155
- "button",
156
- {
157
- type: "button",
158
- onClick: () => {
159
- handleCopyAll();
160
- close();
161
- },
162
- children: [
163
- /* @__PURE__ */ jsx(ClipboardCopy, { "aria-hidden": "true" }),
164
- /* @__PURE__ */ jsx("span", { children: "Kopi\xE9r JSON" })
165
- ]
166
- }
167
- ) })
168
- ] })
169
- }
170
- ) })
171
- ] }),
172
- helperText || viewMode === "tree" && normalizedQuery ? /* @__PURE__ */ jsxs("div", { className: styles.metaRow, children: [
173
- helperText ? /* @__PURE__ */ jsx("span", { className: styles.helperText, children: helperText }) : /* @__PURE__ */ jsx("span", {}),
174
- /* @__PURE__ */ jsx("span", { className: styles.statusText, children: viewMode === "tree" && normalizedQuery ? `${matches.size} matchende node${matches.size === 1 ? "" : "r"}` : "" })
175
- ] }) : null
68
+ /* @__PURE__ */ jsx(ArrowDown, { "aria-hidden": "true" }),
69
+ /* @__PURE__ */ jsx("span", { children: "Fold alle ud" })
176
70
  ]
177
71
  }
178
- ),
179
- viewMode === "raw" ? /* @__PURE__ */ jsx(
180
- "pre",
72
+ ) }),
73
+ /* @__PURE__ */ jsx(Menu.Item, { disabled: viewMode === "raw" || Boolean(normalizedQuery), children: /* @__PURE__ */ jsxs(
74
+ "button",
181
75
  {
182
- className: [styles.rawPane, deleted ? styles.deleted : ""].filter(Boolean).join(" "),
183
- style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
184
- children: rawJson
76
+ type: "button",
77
+ onClick: () => {
78
+ handleCollapseAll();
79
+ close();
80
+ },
81
+ children: [
82
+ /* @__PURE__ */ jsx(ArrowUp, { "aria-hidden": "true" }),
83
+ /* @__PURE__ */ jsx("span", { children: "Fold alle sammen" })
84
+ ]
185
85
  }
186
- ) : /* @__PURE__ */ jsx(
86
+ ) }),
87
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
88
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ onClick: () => {
93
+ setViewMode((mode) => mode === "raw" ? "tree" : "raw");
94
+ close();
95
+ },
96
+ children: [
97
+ viewMode === "raw" ? /* @__PURE__ */ jsx(ListTree, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Braces, { "aria-hidden": "true" }),
98
+ /* @__PURE__ */ jsx("span", { children: viewMode === "raw" ? "Vis tr\xE6" : "Vis raw" })
99
+ ]
100
+ }
101
+ ) }),
102
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ onClick: () => {
107
+ handleToggleAppearance();
108
+ close();
109
+ },
110
+ children: [
111
+ currentAppearance === "dark" ? /* @__PURE__ */ jsx(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Moon, { "aria-hidden": "true" }),
112
+ /* @__PURE__ */ jsx("span", { children: currentAppearance === "dark" ? "Lys visning" : "M\xF8rk visning" })
113
+ ]
114
+ }
115
+ ) }),
116
+ /* @__PURE__ */ jsx(Menu.Separator, {}),
117
+ /* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
118
+ "button",
119
+ {
120
+ type: "button",
121
+ onClick: () => {
122
+ handleCopyAll();
123
+ close();
124
+ },
125
+ children: [
126
+ /* @__PURE__ */ jsx(ClipboardCopy, { "aria-hidden": "true" }),
127
+ /* @__PURE__ */ jsx("span", { children: "Kopi\xE9r JSON" })
128
+ ]
129
+ }
130
+ ) })
131
+ ] })
132
+ }
133
+ );
134
+ return /* @__PURE__ */ jsxs(
135
+ "section",
136
+ {
137
+ className: [styles.viewer, borderless ? styles.borderless : "", className].filter(Boolean).join(" "),
138
+ "data-appearance": currentAppearance,
139
+ children: [
140
+ viewMode === "tree" || showMetaRow ? /* @__PURE__ */ jsxs("div", { className: styles.toolbar, "data-surface": surface, children: [
141
+ viewMode === "tree" ? /* @__PURE__ */ jsxs("div", { className: styles.toolbarTop, children: [
142
+ /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: styles.searchField, children: [
143
+ /* @__PURE__ */ jsx(Search, { className: styles.searchIcon, "aria-hidden": "true" }),
144
+ /* @__PURE__ */ jsx(
145
+ "input",
146
+ {
147
+ id: inputId,
148
+ type: "search",
149
+ value: queryInput,
150
+ onChange: (event) => setQueryInput(event.target.value),
151
+ placeholder: searchPlaceholder,
152
+ className: styles.searchInput
153
+ }
154
+ ),
155
+ queryInput ? /* @__PURE__ */ jsx(
156
+ "button",
157
+ {
158
+ type: "button",
159
+ className: styles.clearButton,
160
+ "aria-label": "Ryd s\xF8gning",
161
+ onClick: () => {
162
+ setQueryInput("");
163
+ setQuery("");
164
+ },
165
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
166
+ }
167
+ ) : null
168
+ ] }),
169
+ /* @__PURE__ */ jsx("div", { className: styles.actions, children: settingsMenu })
170
+ ] }) : null,
171
+ showMetaRow ? /* @__PURE__ */ jsxs("div", { className: styles.metaRow, children: [
172
+ helperText ? /* @__PURE__ */ jsx("span", { className: styles.helperText, children: helperText }) : /* @__PURE__ */ jsx("span", {}),
173
+ /* @__PURE__ */ jsx("span", { className: styles.statusText, children: viewMode === "tree" && normalizedQuery ? `${matches.size} matchende node${matches.size === 1 ? "" : "r"}` : "" })
174
+ ] }) : null
175
+ ] }) : null,
176
+ viewMode === "raw" ? /* @__PURE__ */ jsxs(Fragment, { children: [
177
+ /* @__PURE__ */ jsx("div", { className: styles.floatingToolbar, children: /* @__PURE__ */ jsx("span", { className: styles.floatingButton, "data-surface": surface, children: settingsMenu }) }),
178
+ /* @__PURE__ */ jsx(ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxs(
179
+ "pre",
180
+ {
181
+ ref: scrollRef,
182
+ className: [styles.rawPane, deleted ? styles.deleted : ""].filter(Boolean).join(" "),
183
+ style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
184
+ children: [
185
+ /* @__PURE__ */ jsx("span", { className: styles.floatingSpacer, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Button, { variant: "inline", tabIndex: -1, children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, {}) }) }) }),
186
+ rawJson
187
+ ]
188
+ }
189
+ ) })
190
+ ] }) : /* @__PURE__ */ jsx(ScrollFade, { scrollRef: treePaneRef, children: (scrollRef) => /* @__PURE__ */ jsx(
187
191
  "div",
188
192
  {
189
193
  className: [styles.treePane, deleted ? styles.deleted : ""].filter(Boolean).join(" "),
190
194
  role: "tree",
191
195
  "aria-label": "JSON-visning",
192
- ref: treePaneRef,
196
+ ref: scrollRef,
193
197
  style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
194
198
  children: hasResults ? children : /* @__PURE__ */ jsx("div", { className: styles.emptyState, children: emptySearchText })
195
199
  }
196
- )
200
+ ) })
197
201
  ]
198
202
  }
199
203
  );
@@ -6,6 +6,7 @@ var lucideReact = require('lucide-react');
6
6
  var react = require('react');
7
7
  var Menu = require('../../components/menu/Menu');
8
8
  var Popover = require('../../components/popover/Popover');
9
+ var ScrollFade = require('../../components/scroll-fade/ScrollFade');
9
10
  var usePersistentState = require('../../hooks/usePersistentState');
10
11
  var styles = require('./LineViewer.module.css');
11
12
  var tokenizeLineFormat = require('./tokenizeLineFormat');
@@ -71,10 +72,10 @@ function LineViewer({
71
72
  className: [styles__default.default.viewer, borderless ? styles__default.default.borderless : ""].filter(Boolean).join(" "),
72
73
  "data-appearance": currentAppearance,
73
74
  children: [
74
- /* @__PURE__ */ jsxRuntime.jsx(
75
- "div",
75
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.toolbar, children: /* @__PURE__ */ jsxRuntime.jsx(
76
+ "span",
76
77
  {
77
- className: styles__default.default.toolbar,
78
+ className: styles__default.default.toolbarButton,
78
79
  "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
79
80
  children: /* @__PURE__ */ jsxRuntime.jsx(
80
81
  Popover.Popover,
@@ -155,10 +156,11 @@ function LineViewer({
155
156
  }
156
157
  )
157
158
  }
158
- ),
159
- /* @__PURE__ */ jsxRuntime.jsx(
159
+ ) }),
160
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollFade.ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxRuntime.jsxs(
160
161
  "pre",
161
162
  {
163
+ ref: scrollRef,
162
164
  className: [
163
165
  styles__default.default.codePane,
164
166
  styles__default.default[size],
@@ -167,12 +169,15 @@ function LineViewer({
167
169
  ].filter(Boolean).join(" "),
168
170
  tabIndex: 0,
169
171
  onDoubleClick: handleDoubleClick,
170
- children: /* @__PURE__ */ jsxRuntime.jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
171
- const tokenClassName = TOKEN_CLASS[token.type];
172
- return tokenClassName ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
173
- }) })
172
+ children: [
173
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.toolbarSpacer, "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(Button.Button, { variant: "inline", tabIndex: -1, children: /* @__PURE__ */ jsxRuntime.jsx(Icon.Icon, { customIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings2, {}) }) }) }),
174
+ /* @__PURE__ */ jsxRuntime.jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
175
+ const tokenClassName = TOKEN_CLASS[token.type];
176
+ return tokenClassName ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
177
+ }) })
178
+ ]
174
179
  }
175
- )
180
+ ) })
176
181
  ]
177
182
  }
178
183
  );
@@ -16,7 +16,7 @@ export interface LineViewerProps {
16
16
  */
17
17
  storageKey?: string;
18
18
  /**
19
- * Removes the outer border, shadow and corner radius, so the viewer sits
19
+ * Removes the outer border, shadow, corner radius and padding, so the viewer sits
20
20
  * flush in a container that already provides its own framing (e.g. a
21
21
  * modal).
22
22
  */
@@ -4,6 +4,7 @@ import { TextWrap, ListTree, Code2, Sun, Moon, ClipboardCopy, Settings2 } from '
4
4
  import { useMemo, useRef } from 'react';
5
5
  import { Menu } from '../../components/menu/Menu';
6
6
  import { Popover } from '../../components/popover/Popover';
7
+ import { ScrollFade } from '../../components/scroll-fade/ScrollFade';
7
8
  import { usePersistentState } from '../../hooks/usePersistentState';
8
9
  import styles from './LineViewer.module.css';
9
10
  import { tokenizeLineFormat } from './tokenizeLineFormat';
@@ -65,10 +66,10 @@ function LineViewer({
65
66
  className: [styles.viewer, borderless ? styles.borderless : ""].filter(Boolean).join(" "),
66
67
  "data-appearance": currentAppearance,
67
68
  children: [
68
- /* @__PURE__ */ jsx(
69
- "div",
69
+ /* @__PURE__ */ jsx("div", { className: styles.toolbar, children: /* @__PURE__ */ jsx(
70
+ "span",
70
71
  {
71
- className: styles.toolbar,
72
+ className: styles.toolbarButton,
72
73
  "data-surface": currentAppearance === "dark" ? "inverse" : void 0,
73
74
  children: /* @__PURE__ */ jsx(
74
75
  Popover,
@@ -149,10 +150,11 @@ function LineViewer({
149
150
  }
150
151
  )
151
152
  }
152
- ),
153
- /* @__PURE__ */ jsx(
153
+ ) }),
154
+ /* @__PURE__ */ jsx(ScrollFade, { children: (scrollRef) => /* @__PURE__ */ jsxs(
154
155
  "pre",
155
156
  {
157
+ ref: scrollRef,
156
158
  className: [
157
159
  styles.codePane,
158
160
  styles[size],
@@ -161,12 +163,15 @@ function LineViewer({
161
163
  ].filter(Boolean).join(" "),
162
164
  tabIndex: 0,
163
165
  onDoubleClick: handleDoubleClick,
164
- children: /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
165
- const tokenClassName = TOKEN_CLASS[token.type];
166
- return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
167
- }) })
166
+ children: [
167
+ /* @__PURE__ */ jsx("span", { className: styles.toolbarSpacer, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Button, { variant: "inline", tabIndex: -1, children: /* @__PURE__ */ jsx(Icon, { customIcon: /* @__PURE__ */ jsx(Settings2, {}) }) }) }),
168
+ /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
169
+ const tokenClassName = TOKEN_CLASS[token.type];
170
+ return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
171
+ }) })
172
+ ]
168
173
  }
169
- )
174
+ ) })
170
175
  ]
171
176
  }
172
177
  );
@@ -2,6 +2,7 @@
2
2
  --line-pane-bg: var(--color-bg-inverse);
3
3
  --line-pane-border: color-mix(in oklab, var(--color-fg-inverse) 12%, transparent);
4
4
  --line-pane-text: var(--color-fg-inverse);
5
+ --scroll-fade-color: var(--line-pane-bg);
5
6
 
6
7
  --line-tag: color-mix(in oklab, var(--color-fg-inverse) 66%, var(--color-syntax-accent) 34%);
7
8
  --line-indicator: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
@@ -20,6 +21,10 @@
20
21
  --line-continuation: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
21
22
 
22
23
  position: relative;
24
+ display: flex;
25
+ flex-direction: column;
26
+ /* In a parent with a definite height, scroll inside instead of overflowing it. */
27
+ max-height: 100%;
23
28
  background: var(--line-pane-bg);
24
29
  border: var(--border-width-thin) solid var(--line-pane-border);
25
30
  border-radius: var(--border-radius-default);
@@ -49,11 +54,61 @@
49
54
  box-shadow: none;
50
55
  }
51
56
 
57
+ .borderless .codePane {
58
+ padding: 0;
59
+ }
60
+
61
+ /* Zero-height sticky row: sticks within the nearest scrolling ancestor, which may be outside the viewer. */
52
62
  .toolbar {
53
- position: absolute;
54
- top: var(--spacing-xs);
55
- right: var(--spacing-xs);
63
+ position: sticky;
64
+ top: 0;
56
65
  z-index: 1;
66
+ display: flex;
67
+ justify-content: flex-end;
68
+ flex: none;
69
+ height: 0;
70
+ }
71
+
72
+ .toolbar,
73
+ .toolbarSpacer {
74
+ font-size: var(--font-size-md);
75
+ line-height: normal;
76
+ white-space: normal;
77
+ }
78
+
79
+ .toolbarButton {
80
+ display: block;
81
+ margin: var(--spacing-xs) var(--spacing-xs) 0 0;
82
+ border-radius: var(--border-radius-default);
83
+ /* Opaque so scrolled text doesn't show through once it's stuck. */
84
+ background: var(--line-pane-bg);
85
+ }
86
+
87
+ .toolbarSpacer {
88
+ float: right;
89
+ visibility: hidden;
90
+ margin-block-start: calc(var(--spacing-xs) - var(--spacing-sm));
91
+ margin-inline: var(--spacing-xs) calc(var(--spacing-xs) - var(--spacing-sm));
92
+ }
93
+
94
+ .borderless .toolbarSpacer {
95
+ margin: 0;
96
+ padding: var(--spacing-xs) var(--spacing-xs) 0;
97
+ }
98
+
99
+ /* While the pane scrolls vertically, keep the button clear of its scrollbar
100
+ (overlay scrollbars take no space, so nothing else moves it). */
101
+ .viewer:has([data-scrollable-y='true']) .toolbarButton {
102
+ margin-inline-end: var(--spacing-md);
103
+ }
104
+
105
+ .viewer:has([data-scrollable-y='true']) .toolbarSpacer {
106
+ margin-inline-end: calc(var(--spacing-md) - var(--spacing-sm));
107
+ }
108
+
109
+ .viewer.borderless:has([data-scrollable-y='true']) .toolbarSpacer {
110
+ margin-inline-end: 0;
111
+ padding-inline-end: var(--spacing-md);
57
112
  }
58
113
 
59
114
  .codePane {
@@ -19,7 +19,7 @@
19
19
 
20
20
  /* Structural bottom breathing room for all page content, so it doesn't sit
21
21
  flush against the viewport edge. */
22
- --page-layout-content-padding-bottom: var(--spacing-xl);
22
+ --page-layout-content-padding-bottom: var(--spacing-md);
23
23
  }
24
24
 
25
25
  /* Viewport handling:
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var react = require('react');
6
+ var useScrollOverflow = require('../../hooks/useScrollOverflow');
7
+ var refs_utils = require('../../utils/refs.utils');
8
+ var styles = require('./ScrollFade.module.css');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
13
+
14
+ function ScrollFade({
15
+ children,
16
+ scrollRef,
17
+ className
18
+ }) {
19
+ const [scrollElement, setScrollElement] = react.useState(null);
20
+ const overflow = useScrollOverflow.useScrollOverflow(scrollElement);
21
+ const ref = react.useMemo(() => refs_utils.mergeRefs(setScrollElement, scrollRef), [scrollRef]);
22
+ return /* @__PURE__ */ jsxRuntime.jsxs(
23
+ "div",
24
+ {
25
+ className: [styles__default.default.frame, className].filter(Boolean).join(" "),
26
+ "data-scrollable-y": overflow.top || overflow.bottom,
27
+ style: {
28
+ "--scroll-fade-scrollbar-width": `${overflow.scrollbarWidth}px`,
29
+ "--scroll-fade-scrollbar-height": `${overflow.scrollbarHeight}px`
30
+ },
31
+ children: [
32
+ children(ref),
33
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeTop, "data-visible": overflow.top, "aria-hidden": "true" }),
34
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeBottom, "data-visible": overflow.bottom, "aria-hidden": "true" }),
35
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeLeft, "data-visible": overflow.left, "aria-hidden": "true" }),
36
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.fadeRight, "data-visible": overflow.right, "aria-hidden": "true" })
37
+ ]
38
+ }
39
+ );
40
+ }
41
+
42
+ exports.ScrollFade = ScrollFade;
@@ -0,0 +1,18 @@
1
+ import type { JSX, ReactNode, Ref } from 'react';
2
+ export interface ScrollFadeProps<E extends HTMLElement = HTMLElement> {
3
+ /**
4
+ * Renders the scroll container. Attach the given ref to the element that
5
+ * scrolls (`overflow: auto`). It fills the frame and shrinks with it.
6
+ */
7
+ children: (scrollRef: (node: E | null) => void) => ReactNode;
8
+ /** Also receives the scroll container, e.g. for a virtualizer. */
9
+ scrollRef?: Ref<E>;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Fades the edges of a scroll container that hide more content, so overflow is
14
+ * discoverable without restyling the native scrollbars. The fades blend into
15
+ * `--scroll-fade-color` (default: the surface background), which callers set
16
+ * to the background behind the content; `--scroll-fade-size` sets their depth.
17
+ */
18
+ export declare function ScrollFade<E extends HTMLElement = HTMLElement>({ children, scrollRef, className, }: ScrollFadeProps<E>): JSX.Element;
@@ -0,0 +1,36 @@
1
+ 'use client';
2
+ import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { useState, useMemo } from 'react';
4
+ import { useScrollOverflow } from '../../hooks/useScrollOverflow';
5
+ import { mergeRefs } from '../../utils/refs.utils';
6
+ import styles from './ScrollFade.module.css';
7
+
8
+ function ScrollFade({
9
+ children,
10
+ scrollRef,
11
+ className
12
+ }) {
13
+ const [scrollElement, setScrollElement] = useState(null);
14
+ const overflow = useScrollOverflow(scrollElement);
15
+ const ref = useMemo(() => mergeRefs(setScrollElement, scrollRef), [scrollRef]);
16
+ return /* @__PURE__ */ jsxs(
17
+ "div",
18
+ {
19
+ className: [styles.frame, className].filter(Boolean).join(" "),
20
+ "data-scrollable-y": overflow.top || overflow.bottom,
21
+ style: {
22
+ "--scroll-fade-scrollbar-width": `${overflow.scrollbarWidth}px`,
23
+ "--scroll-fade-scrollbar-height": `${overflow.scrollbarHeight}px`
24
+ },
25
+ children: [
26
+ children(ref),
27
+ /* @__PURE__ */ jsx("span", { className: styles.fadeTop, "data-visible": overflow.top, "aria-hidden": "true" }),
28
+ /* @__PURE__ */ jsx("span", { className: styles.fadeBottom, "data-visible": overflow.bottom, "aria-hidden": "true" }),
29
+ /* @__PURE__ */ jsx("span", { className: styles.fadeLeft, "data-visible": overflow.left, "aria-hidden": "true" }),
30
+ /* @__PURE__ */ jsx("span", { className: styles.fadeRight, "data-visible": overflow.right, "aria-hidden": "true" })
31
+ ]
32
+ }
33
+ );
34
+ }
35
+
36
+ export { ScrollFade };