@dbcdk/react-components 0.0.190 → 0.0.191

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 (92) hide show
  1. package/dist/client.cjs +7 -0
  2. package/dist/client.d.ts +1 -0
  3. package/dist/client.js +1 -0
  4. package/dist/components/button-select/ButtonSelect.cjs +2 -2
  5. package/dist/components/button-select/ButtonSelect.d.ts +1 -0
  6. package/dist/components/button-select/ButtonSelect.js +2 -2
  7. package/dist/components/button-select/ButtonSelect.module.css +14 -0
  8. package/dist/components/chip/Chip.module.css +25 -2
  9. package/dist/components/forms/checkbox/Checkbox.cjs +6 -1
  10. package/dist/components/forms/checkbox/Checkbox.js +6 -1
  11. package/dist/components/forms/checkbox/Checkbox.module.css +26 -0
  12. package/dist/components/forms/input-container/InputContainer.module.css +0 -7
  13. package/dist/components/grid/Grid.cjs +4 -2
  14. package/dist/components/grid/Grid.d.ts +2 -2
  15. package/dist/components/grid/Grid.js +4 -2
  16. package/dist/components/overlay/side-panel/SidePanel.module.css +3 -1
  17. package/dist/components/page-layout/PageLayout.module.css +12 -3
  18. package/dist/hooks/useStickyLayout.cjs +99 -0
  19. package/dist/hooks/useStickyLayout.d.ts +22 -0
  20. package/dist/hooks/useStickyLayout.js +97 -0
  21. package/dist/index.cjs +7 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/styles/css-helper-classes/scrollbar.css +10 -0
  25. package/dist/styles/styles.css +1 -21
  26. package/dist/styles/themes/dbc/base.css +2 -2
  27. package/dist/styles.css +1 -21
  28. package/package.json +1 -1
  29. package/dist/styles/fonts/Roboto/OFL.txt +0 -93
  30. package/dist/styles/fonts/Roboto/README.txt +0 -118
  31. package/dist/styles/fonts/Roboto/Roboto-Black.ttf +0 -0
  32. package/dist/styles/fonts/Roboto/Roboto-BlackItalic.ttf +0 -0
  33. package/dist/styles/fonts/Roboto/Roboto-Bold.ttf +0 -0
  34. package/dist/styles/fonts/Roboto/Roboto-BoldItalic.ttf +0 -0
  35. package/dist/styles/fonts/Roboto/Roboto-ExtraBold.ttf +0 -0
  36. package/dist/styles/fonts/Roboto/Roboto-ExtraBoldItalic.ttf +0 -0
  37. package/dist/styles/fonts/Roboto/Roboto-ExtraLight.ttf +0 -0
  38. package/dist/styles/fonts/Roboto/Roboto-ExtraLightItalic.ttf +0 -0
  39. package/dist/styles/fonts/Roboto/Roboto-Italic.ttf +0 -0
  40. package/dist/styles/fonts/Roboto/Roboto-Light.ttf +0 -0
  41. package/dist/styles/fonts/Roboto/Roboto-LightItalic.ttf +0 -0
  42. package/dist/styles/fonts/Roboto/Roboto-Medium.ttf +0 -0
  43. package/dist/styles/fonts/Roboto/Roboto-MediumItalic.ttf +0 -0
  44. package/dist/styles/fonts/Roboto/Roboto-Regular.ttf +0 -0
  45. package/dist/styles/fonts/Roboto/Roboto-SemiBold.ttf +0 -0
  46. package/dist/styles/fonts/Roboto/Roboto-SemiBoldItalic.ttf +0 -0
  47. package/dist/styles/fonts/Roboto/Roboto-Thin.ttf +0 -0
  48. package/dist/styles/fonts/Roboto/Roboto-ThinItalic.ttf +0 -0
  49. package/dist/styles/fonts/Roboto/Roboto_Condensed-Black.ttf +0 -0
  50. package/dist/styles/fonts/Roboto/Roboto_Condensed-BlackItalic.ttf +0 -0
  51. package/dist/styles/fonts/Roboto/Roboto_Condensed-Bold.ttf +0 -0
  52. package/dist/styles/fonts/Roboto/Roboto_Condensed-BoldItalic.ttf +0 -0
  53. package/dist/styles/fonts/Roboto/Roboto_Condensed-ExtraBold.ttf +0 -0
  54. package/dist/styles/fonts/Roboto/Roboto_Condensed-ExtraBoldItalic.ttf +0 -0
  55. package/dist/styles/fonts/Roboto/Roboto_Condensed-ExtraLight.ttf +0 -0
  56. package/dist/styles/fonts/Roboto/Roboto_Condensed-ExtraLightItalic.ttf +0 -0
  57. package/dist/styles/fonts/Roboto/Roboto_Condensed-Italic.ttf +0 -0
  58. package/dist/styles/fonts/Roboto/Roboto_Condensed-Light.ttf +0 -0
  59. package/dist/styles/fonts/Roboto/Roboto_Condensed-LightItalic.ttf +0 -0
  60. package/dist/styles/fonts/Roboto/Roboto_Condensed-Medium.ttf +0 -0
  61. package/dist/styles/fonts/Roboto/Roboto_Condensed-MediumItalic.ttf +0 -0
  62. package/dist/styles/fonts/Roboto/Roboto_Condensed-Regular.ttf +0 -0
  63. package/dist/styles/fonts/Roboto/Roboto_Condensed-SemiBold.ttf +0 -0
  64. package/dist/styles/fonts/Roboto/Roboto_Condensed-SemiBoldItalic.ttf +0 -0
  65. package/dist/styles/fonts/Roboto/Roboto_Condensed-Thin.ttf +0 -0
  66. package/dist/styles/fonts/Roboto/Roboto_Condensed-ThinItalic.ttf +0 -0
  67. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-Black.ttf +0 -0
  68. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-BlackItalic.ttf +0 -0
  69. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-Bold.ttf +0 -0
  70. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-BoldItalic.ttf +0 -0
  71. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-ExtraBold.ttf +0 -0
  72. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-ExtraBoldItalic.ttf +0 -0
  73. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-ExtraLight.ttf +0 -0
  74. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-ExtraLightItalic.ttf +0 -0
  75. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-Italic.ttf +0 -0
  76. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-Light.ttf +0 -0
  77. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-LightItalic.ttf +0 -0
  78. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-Medium.ttf +0 -0
  79. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-MediumItalic.ttf +0 -0
  80. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-Regular.ttf +0 -0
  81. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-SemiBold.ttf +0 -0
  82. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-SemiBoldItalic.ttf +0 -0
  83. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-Thin.ttf +0 -0
  84. package/dist/styles/fonts/Roboto/Roboto_SemiCondensed-ThinItalic.ttf +0 -0
  85. package/dist/styles/fonts/Tajawal/OFL.txt +0 -93
  86. package/dist/styles/fonts/Tajawal/Tajawal-Black.ttf +0 -0
  87. package/dist/styles/fonts/Tajawal/Tajawal-Bold.ttf +0 -0
  88. package/dist/styles/fonts/Tajawal/Tajawal-ExtraBold.ttf +0 -0
  89. package/dist/styles/fonts/Tajawal/Tajawal-ExtraLight.ttf +0 -0
  90. package/dist/styles/fonts/Tajawal/Tajawal-Light.ttf +0 -0
  91. package/dist/styles/fonts/Tajawal/Tajawal-Medium.ttf +0 -0
  92. package/dist/styles/fonts/Tajawal/Tajawal-Regular.ttf +0 -0
package/dist/client.cjs CHANGED
@@ -48,6 +48,7 @@ var TreeView = require('./components/tree-view/TreeView');
48
48
  var nestedFiltering = require('./utils/arrays/nested-filtering');
49
49
  var MultiSelect = require('./components/forms/multi-select/MultiSelect');
50
50
  var useViewportFill = require('./hooks/useViewportFill');
51
+ var useStickyLayout = require('./hooks/useStickyLayout');
51
52
  var formatDate = require('./utils/date/formatDate');
52
53
  var FilterField = require('./components/filter-field/FilterField');
53
54
  var Select = require('./components/forms/select/Select');
@@ -386,6 +387,12 @@ Object.keys(useViewportFill).forEach(function (k) {
386
387
  get: function () { return useViewportFill[k]; }
387
388
  });
388
389
  });
390
+ Object.keys(useStickyLayout).forEach(function (k) {
391
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
392
+ enumerable: true,
393
+ get: function () { return useStickyLayout[k]; }
394
+ });
395
+ });
389
396
  Object.keys(formatDate).forEach(function (k) {
390
397
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
391
398
  enumerable: true,
package/dist/client.d.ts CHANGED
@@ -45,6 +45,7 @@ export * from './components/tree-view/TreeView';
45
45
  export * from './utils/arrays/nested-filtering';
46
46
  export * from './components/forms/multi-select/MultiSelect';
47
47
  export * from './hooks/useViewportFill';
48
+ export * from './hooks/useStickyLayout';
48
49
  export * from './utils/date/formatDate';
49
50
  export * from './components/filter-field/FilterField';
50
51
  export * from './components/forms/select/Select';
package/dist/client.js CHANGED
@@ -46,6 +46,7 @@ export * from './components/tree-view/TreeView';
46
46
  export * from './utils/arrays/nested-filtering';
47
47
  export * from './components/forms/multi-select/MultiSelect';
48
48
  export * from './hooks/useViewportFill';
49
+ export * from './hooks/useStickyLayout';
49
50
  export * from './utils/date/formatDate';
50
51
  export * from './components/filter-field/FilterField';
51
52
  export * from './components/forms/select/Select';
@@ -10,7 +10,7 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
10
10
  var styles__default = /*#__PURE__*/_interopDefault(styles);
11
11
 
12
12
  function ButtonSelect(props) {
13
- const { options, size = "md", disabled } = props;
13
+ const { options, size = "md", disabled, modified = false } = props;
14
14
  const selectedValues = Array.isArray(props.value) ? props.value : [props.value];
15
15
  function handleSelect(optionValue) {
16
16
  if (props.multiple) {
@@ -20,7 +20,7 @@ function ButtonSelect(props) {
20
20
  }
21
21
  props.onChange(optionValue);
22
22
  }
23
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.group, role: "group", children: options.map((option) => {
23
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.group, role: "group", "data-modified": modified ? "true" : void 0, children: options.map((option) => {
24
24
  const isSelected = selectedValues.includes(option.value);
25
25
  return /* @__PURE__ */ jsxRuntime.jsx(
26
26
  Button.Button,
@@ -10,6 +10,7 @@ interface ButtonSelectBaseProps<T extends string = string> {
10
10
  options: ButtonSelectOption<T>[];
11
11
  size?: ButtonSize;
12
12
  disabled?: boolean;
13
+ modified?: boolean;
13
14
  }
14
15
  export interface ButtonSelectSingleProps<T extends string = string> extends ButtonSelectBaseProps<T> {
15
16
  value: T;
@@ -4,7 +4,7 @@ import { Button } from '../../components/button/Button';
4
4
  import styles from './ButtonSelect.module.css';
5
5
 
6
6
  function ButtonSelect(props) {
7
- const { options, size = "md", disabled } = props;
7
+ const { options, size = "md", disabled, modified = false } = props;
8
8
  const selectedValues = Array.isArray(props.value) ? props.value : [props.value];
9
9
  function handleSelect(optionValue) {
10
10
  if (props.multiple) {
@@ -14,7 +14,7 @@ function ButtonSelect(props) {
14
14
  }
15
15
  props.onChange(optionValue);
16
16
  }
17
- return /* @__PURE__ */ jsx("div", { className: styles.group, role: "group", children: options.map((option) => {
17
+ return /* @__PURE__ */ jsx("div", { className: styles.group, role: "group", "data-modified": modified ? "true" : void 0, children: options.map((option) => {
18
18
  const isSelected = selectedValues.includes(option.value);
19
19
  return /* @__PURE__ */ jsx(
20
20
  Button,
@@ -3,6 +3,20 @@
3
3
  flex-wrap: nowrap;
4
4
  }
5
5
 
6
+ .group[data-modified='true'] {
7
+ background-color: color-mix(in srgb, var(--color-status-warning-bg) 35%, transparent);
8
+ border-radius: var(--border-radius-default);
9
+ padding: 0;
10
+ }
11
+
12
+ .group[data-modified='true'] .btn {
13
+ border-color: color-mix(
14
+ in srgb,
15
+ var(--color-status-warning-border) 60%,
16
+ var(--color-border-default)
17
+ );
18
+ }
19
+
6
20
  /* Collapse the doubled border between adjacent buttons */
7
21
  .group .btn:not(:first-child) {
8
22
  margin-left: -1px;
@@ -43,14 +43,14 @@
43
43
  /* Types */
44
44
 
45
45
  .outlined {
46
- --chip-bg: var(--color-bg-surface);
46
+ --chip-bg: transparent;
47
47
  --chip-border: var(--chip-outlined-border, var(--color-border-subtle));
48
48
  --chip-fg: var(--chip-outlined-fg, var(--color-fg-muted));
49
49
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
50
50
  }
51
51
 
52
52
  .outlined:hover {
53
- background-color: var(--color-bg-surface);
53
+ background-color: transparent;
54
54
  border-color: var(--chip-outlined-border-hover, var(--color-border-default));
55
55
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
56
56
  color: var(--chip-outlined-fg-hover, var(--color-fg-default));
@@ -124,6 +124,16 @@
124
124
  box-shadow: none;
125
125
  }
126
126
 
127
+ .outlined.neutral {
128
+ --chip-bg: transparent;
129
+ --chip-border: var(--chip-outlined-border, var(--color-border-subtle));
130
+ }
131
+
132
+ .outlined.neutral:hover,
133
+ .outlined.neutral:focus-visible {
134
+ --chip-bg: transparent;
135
+ }
136
+
127
137
  .neutral .close {
128
138
  color: var(--color-fg-subtle);
129
139
  }
@@ -208,6 +218,19 @@
208
218
  box-shadow: inset 0 0 0 1px var(--color-border-selected);
209
219
  }
210
220
 
221
+ .outlined.selected,
222
+ .outlined.selected:hover,
223
+ .outlined.selected:focus-visible {
224
+ background: color-mix(in srgb, var(--color-bg-selected) 65%, var(--color-bg-surface));
225
+ border-color: var(--color-border-selected);
226
+ box-shadow: inset 0 0 0 1px var(--color-border-selected);
227
+ color: var(--color-brand);
228
+ }
229
+
230
+ .outlined.selected:hover {
231
+ background: color-mix(in srgb, var(--color-bg-selected-hover) 65%, var(--color-bg-surface));
232
+ }
233
+
211
234
  .selected:hover {
212
235
  background-color: var(--color-bg-selected);
213
236
  border-color: var(--color-border-selected);
@@ -49,7 +49,11 @@ function Checkbox({
49
49
  const content = /* @__PURE__ */ jsxRuntime.jsxs(
50
50
  "span",
51
51
  {
52
- className: [styles__default.default.container, size === "sm" ? styles__default.default.containerSm : ""].filter(Boolean).join(" "),
52
+ className: [
53
+ styles__default.default.container,
54
+ size === "sm" ? styles__default.default.containerSm : "",
55
+ modified ? styles__default.default.modified : ""
56
+ ].filter(Boolean).join(" "),
53
57
  "data-cy": dataCy,
54
58
  children: [
55
59
  /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: tooltip, placement: tooltipPlacement, children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -65,6 +69,7 @@ function Checkbox({
65
69
  onClick: toggle,
66
70
  className: [
67
71
  styles__default.default.checkbox,
72
+ disabled ? "disabled" : "",
68
73
  isChecked ? styles__default.default.checked : "",
69
74
  styles__default.default[variant],
70
75
  styles__default.default[size]
@@ -43,7 +43,11 @@ function Checkbox({
43
43
  const content = /* @__PURE__ */ jsxs(
44
44
  "span",
45
45
  {
46
- className: [styles.container, size === "sm" ? styles.containerSm : ""].filter(Boolean).join(" "),
46
+ className: [
47
+ styles.container,
48
+ size === "sm" ? styles.containerSm : "",
49
+ modified ? styles.modified : ""
50
+ ].filter(Boolean).join(" "),
47
51
  "data-cy": dataCy,
48
52
  children: [
49
53
  /* @__PURE__ */ jsx(Tooltip, { content: tooltip, placement: tooltipPlacement, children: /* @__PURE__ */ jsx(
@@ -59,6 +63,7 @@ function Checkbox({
59
63
  onClick: toggle,
60
64
  className: [
61
65
  styles.checkbox,
66
+ disabled ? "disabled" : "",
62
67
  isChecked ? styles.checked : "",
63
68
  styles[variant],
64
69
  styles[size]
@@ -7,6 +7,13 @@
7
7
  color: var(--color-fg-default);
8
8
  }
9
9
 
10
+ .container.modified {
11
+ background-color: color-mix(in srgb, var(--color-status-warning-bg) 75%, transparent);
12
+ border-radius: var(--border-radius-default);
13
+ padding: 0;
14
+ display: inline-flex;
15
+ }
16
+
10
17
  .containerSm {
11
18
  gap: var(--spacing-xs);
12
19
  }
@@ -28,6 +35,12 @@
28
35
  box-shadow var(--transition-fast) var(--ease-standard);
29
36
  }
30
37
 
38
+ .checkbox:disabled {
39
+ cursor: not-allowed;
40
+ opacity: 0.5;
41
+ filter: grayscale(0.2);
42
+ }
43
+
31
44
  .checkbox.sm {
32
45
  width: var(--component-size-xs);
33
46
  height: var(--component-size-xs);
@@ -54,11 +67,24 @@
54
67
  border-color: var(--color-fg-default);
55
68
  }
56
69
 
70
+ .checkbox:disabled:hover {
71
+ border-color: var(--color-border-default);
72
+ }
73
+
57
74
  .checked {
58
75
  background-color: var(--color-brand);
59
76
  border-color: var(--color-brand);
60
77
  }
61
78
 
79
+ .checkbox:disabled.checked {
80
+ background-color: var(--color-bg-subtle);
81
+ border-color: var(--color-border-muted);
82
+ }
83
+
84
+ .checkbox:disabled.checked .icon {
85
+ color: var(--color-fg-muted);
86
+ }
87
+
62
88
  .icon {
63
89
  width: var(--icon-size-md);
64
90
  height: var(--icon-size-md);
@@ -50,10 +50,3 @@
50
50
  .messageRow {
51
51
  min-height: 1lh;
52
52
  }
53
-
54
- /* Optional: keep this only for controls whose own label lives inside children,
55
- such as checkbox/radio wrappers. It will not affect Input's top label. */
56
- .inputContainer[data-modified] label:not(.label) {
57
- background-color: color-mix(in srgb, var(--color-status-warning-bg) 35%, transparent);
58
- border-radius: var(--border-radius-default);
59
- }
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
+ var react = require('react');
4
5
  var styles = require('./Grid.module.css');
5
6
 
6
7
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -38,11 +39,12 @@ function Grid({
38
39
  }
39
40
  );
40
41
  }
41
- function GridItem({ children, base = 12, md, lg, className, style }) {
42
+ const GridItem = react.forwardRef(function GridItem2({ children, base = 12, md, lg, className, style }, ref) {
42
43
  const toTrackSpan = (span) => span * 2;
43
44
  return /* @__PURE__ */ jsxRuntime.jsx(
44
45
  "div",
45
46
  {
47
+ ref,
46
48
  className: [styles__default.default.item, className].filter(Boolean).join(" "),
47
49
  style: {
48
50
  "--span-base": toTrackSpan(base),
@@ -53,7 +55,7 @@ function GridItem({ children, base = 12, md, lg, className, style }) {
53
55
  children
54
56
  }
55
57
  );
56
- }
58
+ });
57
59
 
58
60
  exports.Grid = Grid;
59
61
  exports.GridItem = GridItem;
@@ -1,4 +1,4 @@
1
- import type { CSSProperties, ReactNode } from 'react';
1
+ import { type CSSProperties, type ReactNode } from 'react';
2
2
  export type GridGap = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
3
3
  export interface GridMaxWidth {
4
4
  base?: string;
@@ -26,4 +26,4 @@ export interface GridItemProps {
26
26
  className?: string;
27
27
  style?: CSSProperties;
28
28
  }
29
- export declare function GridItem({ children, base, md, lg, className, style }: GridItemProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare const GridItem: import("react").ForwardRefExoticComponent<GridItemProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,4 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
+ import { forwardRef } from 'react';
2
3
  import styles from './Grid.module.css';
3
4
 
4
5
  const GAP_TOKEN = {
@@ -32,11 +33,12 @@ function Grid({
32
33
  }
33
34
  );
34
35
  }
35
- function GridItem({ children, base = 12, md, lg, className, style }) {
36
+ const GridItem = forwardRef(function GridItem2({ children, base = 12, md, lg, className, style }, ref) {
36
37
  const toTrackSpan = (span) => span * 2;
37
38
  return /* @__PURE__ */ jsx(
38
39
  "div",
39
40
  {
41
+ ref,
40
42
  className: [styles.item, className].filter(Boolean).join(" "),
41
43
  style: {
42
44
  "--span-base": toTrackSpan(base),
@@ -47,6 +49,6 @@ function GridItem({ children, base = 12, md, lg, className, style }) {
47
49
  children
48
50
  }
49
51
  );
50
- }
52
+ });
51
53
 
52
54
  export { Grid, GridItem };
@@ -96,7 +96,9 @@
96
96
 
97
97
  display: flex;
98
98
  flex-direction: column;
99
- gap: var(--spacing-md);
99
+ /* Header/content spacing is defined by their own padding, so avoid adding another
100
+ gap that creates double spacing between the panel title and the content area. */
101
+ gap: 0;
100
102
  }
101
103
 
102
104
  .header {
@@ -17,8 +17,9 @@
17
17
  var(--spacing-2xs) * 2 + var(--font-size-md) * var(--line-height-normal)
18
18
  );
19
19
 
20
- /* Structural bottom breathing room for all page content. */
21
- --page-layout-content-padding-bottom: var(--spacing-2xl);
20
+ /* Structural bottom breathing room for all page content, so it doesn't sit
21
+ flush against the viewport edge. */
22
+ --page-layout-content-padding-bottom: var(--spacing-xl);
22
23
  }
23
24
 
24
25
  /* Viewport handling:
@@ -211,7 +212,15 @@
211
212
  /* Content area (not a scroll container anymore) */
212
213
  .content {
213
214
  min-width: 0;
214
- min-height: 0;
215
+ /* Deliberately no min-height:0 here (unlike `.mainScroll` above, which does
216
+ need it): this lets `.content` size to its own natural content height —
217
+ including its own padding-block-end — instead of being flex-shrunk
218
+ shorter than that content. `.mainScroll` (overflow:auto) already has its
219
+ own min-height:0, which is what lets it stay bounded to the available
220
+ space and scroll; if `.content` could *also* shrink below its content,
221
+ that shrunk-but-visible-overflow content would spill straight past
222
+ wherever its own bottom padding was supposed to land, so it would never
223
+ actually render (this happened in practice before this comment existed). */
215
224
  flex: 1 1 auto; /* take remaining space inside mainScroll */
216
225
  display: flex; /* lets contentInner stretch */
217
226
  flex-direction: column;
@@ -0,0 +1,99 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var react = require('react');
5
+
6
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? react.useLayoutEffect : react.useEffect;
7
+ const getScrollport = (element) => {
8
+ for (let parent = element.parentElement; parent; parent = parent.parentElement) {
9
+ if (parent === document.body || parent === document.documentElement) return null;
10
+ const { overflowY } = window.getComputedStyle(parent);
11
+ if (/^(auto|scroll|hidden|overlay)$/.test(overflowY)) return parent;
12
+ }
13
+ return null;
14
+ };
15
+ function useStickyLayout(ref, {
16
+ mediaQuery,
17
+ offset = 0,
18
+ min = 0,
19
+ includeMarginTop = false,
20
+ boundaryRef,
21
+ watchRef,
22
+ bottomOffset = 0
23
+ } = {}) {
24
+ const [layout, setLayout] = react.useState(
25
+ () => ({
26
+ active: !mediaQuery,
27
+ top: Math.max(0, min, offset)
28
+ })
29
+ );
30
+ const raf = react.useRef(null);
31
+ const measure = react.useCallback(() => {
32
+ const element = ref.current;
33
+ if (typeof window === "undefined" || !element) return;
34
+ if (mediaQuery && !window.matchMedia(mediaQuery).matches) {
35
+ setLayout((prev) => prev.active ? { top: 0, active: false } : prev);
36
+ return;
37
+ }
38
+ const scrollport = getScrollport(element);
39
+ const scrollportTop = scrollport ? scrollport.getBoundingClientRect().top + scrollport.clientTop : 0;
40
+ const scrollportBottom = scrollport ? Math.min(window.innerHeight, scrollportTop + scrollport.clientHeight) : window.innerHeight;
41
+ const boundary = boundaryRef == null ? void 0 : boundaryRef.current;
42
+ const boundaryBottom = boundary && boundary !== element ? boundary.getBoundingClientRect().bottom : scrollportTop;
43
+ const marginTop = includeMarginTop ? parseFloat(window.getComputedStyle(element).marginTop) || 0 : 0;
44
+ const top = Math.max(0, min, boundaryBottom - scrollportTop + offset - marginTop);
45
+ const visibleTop = Math.max(element.getBoundingClientRect().top, scrollportTop + top);
46
+ const maxHeight = Math.max(0, Math.floor(scrollportBottom - visibleTop - bottomOffset));
47
+ setLayout(
48
+ (prev) => prev.active && prev.top === top && prev.maxHeight === maxHeight ? prev : { top, maxHeight, active: true }
49
+ );
50
+ }, [ref, boundaryRef, offset, min, includeMarginTop, bottomOffset, mediaQuery]);
51
+ const scheduleMeasure = react.useCallback(() => {
52
+ if (raf.current !== null) return;
53
+ raf.current = window.requestAnimationFrame(() => {
54
+ raf.current = null;
55
+ measure();
56
+ });
57
+ }, [measure]);
58
+ useIsomorphicLayoutEffect(() => {
59
+ measure();
60
+ }, [measure]);
61
+ react.useEffect(() => {
62
+ if (!ref.current) return;
63
+ const element = ref.current;
64
+ const media = mediaQuery ? window.matchMedia(mediaQuery) : null;
65
+ media == null ? void 0 : media.addEventListener("change", scheduleMeasure);
66
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(scheduleMeasure);
67
+ const targets = /* @__PURE__ */ new Set([element]);
68
+ for (let parent = element.parentElement; parent; parent = parent.parentElement)
69
+ targets.add(parent);
70
+ if (boundaryRef == null ? void 0 : boundaryRef.current) targets.add(boundaryRef.current);
71
+ if (watchRef == null ? void 0 : watchRef.current) targets.add(watchRef.current);
72
+ targets.forEach((target) => observer == null ? void 0 : observer.observe(target));
73
+ window.addEventListener("resize", scheduleMeasure);
74
+ document.addEventListener("scroll", scheduleMeasure, true);
75
+ document.addEventListener("transitionend", scheduleMeasure, true);
76
+ document.addEventListener("animationend", scheduleMeasure, true);
77
+ return () => {
78
+ observer == null ? void 0 : observer.disconnect();
79
+ media == null ? void 0 : media.removeEventListener("change", scheduleMeasure);
80
+ window.removeEventListener("resize", scheduleMeasure);
81
+ document.removeEventListener("scroll", scheduleMeasure, true);
82
+ document.removeEventListener("transitionend", scheduleMeasure, true);
83
+ document.removeEventListener("animationend", scheduleMeasure, true);
84
+ if (raf.current !== null) cancelAnimationFrame(raf.current);
85
+ raf.current = null;
86
+ };
87
+ }, [ref, boundaryRef, watchRef, scheduleMeasure, mediaQuery]);
88
+ const style = react.useMemo(
89
+ () => layout.active ? { position: "sticky", top: `${layout.top}px` } : {},
90
+ [layout.top, layout.active]
91
+ );
92
+ const viewportStyle = react.useMemo(
93
+ () => layout.active ? { maxHeight: layout.maxHeight, overflowY: "auto", overscrollBehavior: "contain" } : {},
94
+ [layout.maxHeight, layout.active]
95
+ );
96
+ return { top: layout.top, style, viewportStyle, recompute: measure };
97
+ }
98
+
99
+ exports.useStickyLayout = useStickyLayout;
@@ -0,0 +1,22 @@
1
+ import { type CSSProperties, type RefObject } from 'react';
2
+ type Options = {
3
+ /** Enable sticky behavior only while this media query matches. */
4
+ mediaQuery?: string;
5
+ /** Extra inset below the scrollport edge or boundary, in pixels. */
6
+ offset?: number;
7
+ min?: number;
8
+ includeMarginTop?: boolean;
9
+ /** Header whose bottom edge the sticky element must stay below. */
10
+ boundaryRef?: RefObject<HTMLElement | null>;
11
+ /** Optional additional element whose resizing affects the layout. */
12
+ watchRef?: RefObject<HTMLElement | null>;
13
+ /** Breathing room below the inner scrollable content, in pixels. */
14
+ bottomOffset?: number;
15
+ };
16
+ export declare function useStickyLayout<T extends HTMLElement>(ref: RefObject<T | null>, { mediaQuery, offset, min, includeMarginTop, boundaryRef, watchRef, bottomOffset, }?: Options): {
17
+ top: number;
18
+ style: CSSProperties;
19
+ viewportStyle: CSSProperties;
20
+ recompute: () => void;
21
+ };
22
+ export {};
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+ import { useState, useRef, useCallback, useEffect, useMemo, useLayoutEffect } from 'react';
3
+
4
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
5
+ const getScrollport = (element) => {
6
+ for (let parent = element.parentElement; parent; parent = parent.parentElement) {
7
+ if (parent === document.body || parent === document.documentElement) return null;
8
+ const { overflowY } = window.getComputedStyle(parent);
9
+ if (/^(auto|scroll|hidden|overlay)$/.test(overflowY)) return parent;
10
+ }
11
+ return null;
12
+ };
13
+ function useStickyLayout(ref, {
14
+ mediaQuery,
15
+ offset = 0,
16
+ min = 0,
17
+ includeMarginTop = false,
18
+ boundaryRef,
19
+ watchRef,
20
+ bottomOffset = 0
21
+ } = {}) {
22
+ const [layout, setLayout] = useState(
23
+ () => ({
24
+ active: !mediaQuery,
25
+ top: Math.max(0, min, offset)
26
+ })
27
+ );
28
+ const raf = useRef(null);
29
+ const measure = useCallback(() => {
30
+ const element = ref.current;
31
+ if (typeof window === "undefined" || !element) return;
32
+ if (mediaQuery && !window.matchMedia(mediaQuery).matches) {
33
+ setLayout((prev) => prev.active ? { top: 0, active: false } : prev);
34
+ return;
35
+ }
36
+ const scrollport = getScrollport(element);
37
+ const scrollportTop = scrollport ? scrollport.getBoundingClientRect().top + scrollport.clientTop : 0;
38
+ const scrollportBottom = scrollport ? Math.min(window.innerHeight, scrollportTop + scrollport.clientHeight) : window.innerHeight;
39
+ const boundary = boundaryRef == null ? void 0 : boundaryRef.current;
40
+ const boundaryBottom = boundary && boundary !== element ? boundary.getBoundingClientRect().bottom : scrollportTop;
41
+ const marginTop = includeMarginTop ? parseFloat(window.getComputedStyle(element).marginTop) || 0 : 0;
42
+ const top = Math.max(0, min, boundaryBottom - scrollportTop + offset - marginTop);
43
+ const visibleTop = Math.max(element.getBoundingClientRect().top, scrollportTop + top);
44
+ const maxHeight = Math.max(0, Math.floor(scrollportBottom - visibleTop - bottomOffset));
45
+ setLayout(
46
+ (prev) => prev.active && prev.top === top && prev.maxHeight === maxHeight ? prev : { top, maxHeight, active: true }
47
+ );
48
+ }, [ref, boundaryRef, offset, min, includeMarginTop, bottomOffset, mediaQuery]);
49
+ const scheduleMeasure = useCallback(() => {
50
+ if (raf.current !== null) return;
51
+ raf.current = window.requestAnimationFrame(() => {
52
+ raf.current = null;
53
+ measure();
54
+ });
55
+ }, [measure]);
56
+ useIsomorphicLayoutEffect(() => {
57
+ measure();
58
+ }, [measure]);
59
+ useEffect(() => {
60
+ if (!ref.current) return;
61
+ const element = ref.current;
62
+ const media = mediaQuery ? window.matchMedia(mediaQuery) : null;
63
+ media == null ? void 0 : media.addEventListener("change", scheduleMeasure);
64
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(scheduleMeasure);
65
+ const targets = /* @__PURE__ */ new Set([element]);
66
+ for (let parent = element.parentElement; parent; parent = parent.parentElement)
67
+ targets.add(parent);
68
+ if (boundaryRef == null ? void 0 : boundaryRef.current) targets.add(boundaryRef.current);
69
+ if (watchRef == null ? void 0 : watchRef.current) targets.add(watchRef.current);
70
+ targets.forEach((target) => observer == null ? void 0 : observer.observe(target));
71
+ window.addEventListener("resize", scheduleMeasure);
72
+ document.addEventListener("scroll", scheduleMeasure, true);
73
+ document.addEventListener("transitionend", scheduleMeasure, true);
74
+ document.addEventListener("animationend", scheduleMeasure, true);
75
+ return () => {
76
+ observer == null ? void 0 : observer.disconnect();
77
+ media == null ? void 0 : media.removeEventListener("change", scheduleMeasure);
78
+ window.removeEventListener("resize", scheduleMeasure);
79
+ document.removeEventListener("scroll", scheduleMeasure, true);
80
+ document.removeEventListener("transitionend", scheduleMeasure, true);
81
+ document.removeEventListener("animationend", scheduleMeasure, true);
82
+ if (raf.current !== null) cancelAnimationFrame(raf.current);
83
+ raf.current = null;
84
+ };
85
+ }, [ref, boundaryRef, watchRef, scheduleMeasure, mediaQuery]);
86
+ const style = useMemo(
87
+ () => layout.active ? { position: "sticky", top: `${layout.top}px` } : {},
88
+ [layout.top, layout.active]
89
+ );
90
+ const viewportStyle = useMemo(
91
+ () => layout.active ? { maxHeight: layout.maxHeight, overflowY: "auto", overscrollBehavior: "contain" } : {},
92
+ [layout.maxHeight, layout.active]
93
+ );
94
+ return { top: layout.top, style, viewportStyle, recompute: measure };
95
+ }
96
+
97
+ export { useStickyLayout };
package/dist/index.cjs CHANGED
@@ -8,6 +8,7 @@ var Icon = require('./components/icon/Icon');
8
8
  var UserDisplay = require('./components/user-display/UserDisplay');
9
9
  var Headline = require('./components/headline/Headline');
10
10
  var SectionLabel = require('./components/section-label/SectionLabel');
11
+ var useStickyLayout = require('./hooks/useStickyLayout');
11
12
  var PageLayout = require('./components/page-layout/PageLayout');
12
13
  var Footer = require('./components/page-layout/components/footer/Footer');
13
14
  var Card = require('./components/card/Card');
@@ -100,6 +101,12 @@ Object.keys(SectionLabel).forEach(function (k) {
100
101
  get: function () { return SectionLabel[k]; }
101
102
  });
102
103
  });
104
+ Object.keys(useStickyLayout).forEach(function (k) {
105
+ if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
106
+ enumerable: true,
107
+ get: function () { return useStickyLayout[k]; }
108
+ });
109
+ });
103
110
  Object.keys(PageLayout).forEach(function (k) {
104
111
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
105
112
  enumerable: true,
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './components/icon/Icon';
6
6
  export * from './components/user-display/UserDisplay';
7
7
  export * from './components/headline/Headline';
8
8
  export * from './components/section-label/SectionLabel';
9
+ export * from './hooks/useStickyLayout';
9
10
  export * from './components/page-layout/PageLayout';
10
11
  export * from './components/page-layout/components/footer/Footer';
11
12
  export * from './components/card/Card';
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ export * from './components/icon/Icon';
6
6
  export * from './components/user-display/UserDisplay';
7
7
  export * from './components/headline/Headline';
8
8
  export * from './components/section-label/SectionLabel';
9
+ export * from './hooks/useStickyLayout';
9
10
  export * from './components/page-layout/PageLayout';
10
11
  export * from './components/page-layout/components/footer/Footer';
11
12
  export * from './components/card/Card';
@@ -0,0 +1,10 @@
1
+ .dbc-scrollbar-hidden {
2
+ scrollbar-width: none;
3
+ -ms-overflow-style: none;
4
+ }
5
+
6
+ .dbc-scrollbar-hidden::-webkit-scrollbar {
7
+ display: none;
8
+ width: 0;
9
+ height: 0;
10
+ }