@olenbetong/appframe-ds 1.3.7 → 1.4.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @olenbetong/appframe-ds
2
2
 
3
+ ## 1.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8ec7b62: One open and close motion for centred dialogs: Designsystemet's `Dialog` (the filter builder and every other centred one) and anything with the new `ob-dialog-motion` class now fade and rise in over 180 ms and play the same motion in reverse over 140 ms when closed, using transitions with `@starting-style` and `allow-discrete` instead of Designsystemet's open-only keyframes. Reduced motion keeps the fade and drops the movement. The filter builder now closes on a backdrop click, and the page content slides back when a docked `AppMenuDrawer` closes instead of jumping.
8
+
9
+ ## 1.3.8
10
+
11
+ ### Patch Changes
12
+
13
+ - 2c36a7c: The `Page.Block`, `Container` and `PageContainer` widths are resized for the 2026 site's 14px root font size (87.5%), and stay in rem so they follow the browser's font size: `text` 41rem, `md` 55rem, `lg` 73rem, `xl` 110rem - 574, 770, 1022 and 1540px, the MUI-era widths the apps were laid out for. At 16px-based rem they had come out an eighth narrower, and `xl` (80rem) at 1120px against MUI's 1536px. `Page.Block width="2xl"` (90rem) would now be narrower than `xl`, so it is deprecated and renders the same as `xl`.
14
+ - 2d14c97: `AfLookup`, `AfLookupEdit` and the lookup grid follow dark mode: the dialog, list, borders and the hover and keyboard-active rows use Designsystemet colour tokens instead of hard-coded light colours, so item text is no longer white on white. On desktop the rows get 4px of vertical padding, so the items no longer sit flush against each other.
15
+
3
16
  ## 1.3.7
4
17
 
5
18
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-ds",
3
- "version": "1.3.7",
3
+ "version": "1.4.0",
4
4
  "description": "Components that bind Designsystemet components to Appframe data objects",
5
5
  "type": "module",
6
6
  "types": "./es/index.d.ts",
@@ -63,9 +63,9 @@
63
63
  "@atlaskit/pragmatic-drag-and-drop": "4.0.0",
64
64
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "3.0.0",
65
65
  "@base-ui/react": "1.8.0",
66
- "@olenbetong/appframe-core": "2.15.1",
67
- "@olenbetong/appframe-data": "1.8.2",
68
- "@olenbetong/appframe-react": "2.2.7",
66
+ "@olenbetong/appframe-core": "^2.15.2",
67
+ "@olenbetong/appframe-data": "^1.8.2",
68
+ "@olenbetong/appframe-react": "^2.2.7",
69
69
  "clsx": "^2.1.1",
70
70
  "dockview-react": "8.3.1",
71
71
  "localforage": "^1.10.0",
@@ -28,7 +28,8 @@
28
28
  padding: 1rem;
29
29
  overflow: hidden;
30
30
 
31
- background-color: whitesmoke;
31
+ background-color: var(--ds-color-neutral-background-tinted);
32
+ color: var(--ds-color-neutral-text-default);
32
33
 
33
34
  transition:
34
35
  display 0.2s allow-discrete,
@@ -15,13 +15,12 @@
15
15
  .ObCombobox-listWrap {
16
16
  overflow: hidden;
17
17
  min-height: 0;
18
- border: 1px solid rgb(0 0 0 / 0.12);
18
+ border: 1px solid var(--ds-color-neutral-border-subtle);
19
19
  border-radius: 0.25rem;
20
- background-color: white;
20
+ background-color: var(--ds-color-neutral-background-default);
21
21
  }
22
22
 
23
23
  .ObCombobox-list {
24
- border: 1px solid rgb(0 0 0 / 0.06);
25
24
  height: 100%;
26
25
  overflow-y: hidden;
27
26
  overscroll-behavior: contain;
@@ -31,23 +30,18 @@
31
30
  }
32
31
 
33
32
  .ObCombobox-input {
34
- background-color: white;
33
+ background-color: var(--ds-color-neutral-background-default);
35
34
  }
36
35
 
37
36
  .ObCombobox-listItem {
38
- --active-color: oklch(from var(--brand-secondary, #999) l c h / 50%);
39
37
  cursor: pointer;
40
38
 
41
39
  &.active {
42
- background-color: var(--active-color);
40
+ background-color: var(--ds-color-neutral-surface-active);
43
41
  }
44
42
 
45
43
  &:hover {
46
- background-color: rgb(0 0 0 / 0.06);
47
- }
48
-
49
- &.active:hover {
50
- background-color: oklch(from var(--active-color) calc(l + 0.1) calc(c - 0.04) h);
44
+ background-color: var(--ds-color-neutral-surface-hover);
51
45
  }
52
46
  }
53
47
 
@@ -55,7 +49,7 @@
55
49
  position: relative;
56
50
  height: 4px;
57
51
  overflow: hidden;
58
- background-color: rgb(0 0 0 / 0.08);
52
+ background-color: var(--ds-color-neutral-surface-tinted);
59
53
  }
60
54
 
61
55
  .ObCombobox-progress-bar {
@@ -76,7 +76,7 @@ function LookupComboboxRow<T extends Record<string, unknown>>({
76
76
  item={item}
77
77
  style={{
78
78
  ...style,
79
- padding: isDesktop ? "0 8px" : "8px",
79
+ padding: isDesktop ? "4px 8px" : "8px",
80
80
  }}
81
81
  id={itemId}
82
82
  />
@@ -2,15 +2,15 @@
2
2
  height: 100%;
3
3
  overflow: hidden;
4
4
  overscroll-behavior: contain;
5
- border: 1px solid rgb(0 0 0 / 0.06);
5
+ border: 1px solid var(--ds-color-neutral-border-subtle);
6
6
  }
7
7
 
8
8
  .ObLookupGrid-header {
9
9
  display: grid;
10
10
  height: 40px;
11
11
  align-items: center;
12
- border-bottom: 1px solid rgb(0 0 0 / 0.12);
13
- background-color: rgb(0 0 0 / 0.03);
12
+ border-bottom: 1px solid var(--ds-color-neutral-border-default);
13
+ background-color: var(--ds-color-neutral-background-tinted);
14
14
  font-weight: 600;
15
15
  }
16
16
 
@@ -22,22 +22,17 @@
22
22
  }
23
23
 
24
24
  .ObLookupGrid-row {
25
- --active-color: oklch(from var(--brand-secondary, #999) l c h / 50%);
26
25
  display: grid;
27
26
  align-items: center;
28
27
  cursor: pointer;
29
- border-bottom: 1px solid rgb(0 0 0 / 0.06);
28
+ border-bottom: 1px solid var(--ds-color-neutral-border-subtle);
30
29
 
31
30
  &.active {
32
- background-color: var(--active-color);
31
+ background-color: var(--ds-color-neutral-surface-active);
33
32
  }
34
33
 
35
34
  &:hover {
36
- background-color: rgb(0 0 0 / 0.06);
37
- }
38
-
39
- &.active:hover {
40
- background-color: oklch(from var(--active-color) calc(l + 0.1) calc(c - 0.04) h);
35
+ background-color: var(--ds-color-neutral-surface-hover);
41
36
  }
42
37
  }
43
38
 
@@ -51,7 +46,7 @@
51
46
  white-space: nowrap;
52
47
 
53
48
  &.active {
54
- outline: 2px solid oklch(from var(--brand-secondary, #666) calc(l - 0.2) c h);
49
+ outline: 2px solid var(--ds-color-neutral-border-strong);
55
50
  outline-offset: -2px;
56
51
  }
57
52
  }
@@ -14,13 +14,14 @@ const maxWidthToBlockWidth: Record<ContainerMaxWidth, PageBlockWidth> = {
14
14
 
15
15
  export type ContainerProps = Omit<PageBlockProps, "width"> & {
16
16
  /**
17
- * Predefined max-width, using the same scale as MUI `Container`. Content is
18
- * horizontally centered.
17
+ * Predefined max-width, with the MUI `Container` names on the `Page.Block` scale. Content is
18
+ * horizontally centered. Pixel widths are at the 2026 site's 14px root font size; the
19
+ * rem values follow the browser's font size setting.
19
20
  *
20
- * - `xs`/`sm`: 36rem (576px) — recommended line-length for running text
21
- * - `md`: 48rem (768px)
22
- * - `lg`: 64rem (1024px)
23
- * - `xl`: 80rem (1280px)
21
+ * - `xs`/`sm`: 41rem (574px) — recommended line-length for running text
22
+ * - `md`: 55rem (770px)
23
+ * - `lg`: 73rem (1022px)
24
+ * - `xl`: 110rem (1540px)
24
25
  * - `false`: no max-width
25
26
  *
26
27
  * @default "lg"
@@ -402,7 +402,7 @@ export function FilterBuilder({
402
402
  return (
403
403
  <Dialog
404
404
  open={open}
405
- closedby="closerequest"
405
+ closedby="any"
406
406
  closeButton={false}
407
407
  data-size={size}
408
408
  className="ObFilterBuilder-dialog"
@@ -18,7 +18,8 @@
18
18
  background-color: var(--ds-color-neutral-background-default, #fff);
19
19
  color: var(--ds-color-neutral-text-default, #1e2b3c);
20
20
  transform: translateX(-100%);
21
- transition: transform 225ms cubic-bezier(0, 0, 0.2, 1);
21
+ /* Same timing as the content offset in Page.css, so the two edges move together. */
22
+ transition: transform 225ms cubic-bezier(0.4, 0, 0.2, 1);
22
23
  overflow: hidden;
23
24
  display: flex;
24
25
  flex-direction: column;
@@ -0,0 +1,77 @@
1
+ /*
2
+ * One open and close motion for every centred modal: Designsystemet's Dialog (the filter builder,
3
+ * the site search, the report launcher and any app's own dialogs) and anything else that opts in
4
+ * with the `ob-dialog-motion` class (the chrome's app menu, which is a plain <dialog>).
5
+ *
6
+ * Designsystemet animates the opening with keyframes only, 300 ms, and nothing on close. This
7
+ * replaces that with transitions, so the close plays the same motion in reverse:
8
+ * - @starting-style gives the first frame after showModal() something to transition from;
9
+ * - `display` and `overlay` transition with `allow-discrete`, which keeps the dialog in the top
10
+ * layer until the fade out has finished after close().
11
+ * Browsers without `overlay` (Firefox, Safari at the time of writing) still animate the opening
12
+ * and close without the fade; nothing breaks.
13
+ *
14
+ * A dialog only fades out when it is closed with close() and stays mounted. One that React
15
+ * removes on close disappears at once.
16
+ *
17
+ * Reduced motion keeps a short fade and drops the movement.
18
+ */
19
+
20
+ :root {
21
+ --ob-dialog-enter-duration: 180ms;
22
+ --ob-dialog-exit-duration: 140ms;
23
+ --ob-dialog-offset: 1rem;
24
+ }
25
+
26
+ @media (prefers-reduced-motion: reduce) {
27
+ :root {
28
+ --ob-dialog-offset: 0;
29
+ }
30
+ }
31
+
32
+ .ds-dialog:is(:not([data-placement]), [data-placement="center"]),
33
+ .ob-dialog-motion {
34
+ opacity: 0;
35
+ translate: 0 var(--ob-dialog-offset);
36
+ transition-property: opacity, translate, overlay, display;
37
+ transition-duration: var(--ob-dialog-exit-duration);
38
+ transition-timing-function: ease-in;
39
+ transition-behavior: allow-discrete;
40
+
41
+ &[open] {
42
+ opacity: 1;
43
+ translate: 0 0;
44
+ /* Designsystemet's keyframes would play on top of the transition. */
45
+ animation: none;
46
+ transition-duration: var(--ob-dialog-enter-duration);
47
+ transition-timing-function: ease-out;
48
+ }
49
+
50
+ &::backdrop {
51
+ opacity: 0;
52
+ animation: none;
53
+ transition-property: opacity, overlay, display;
54
+ transition-duration: var(--ob-dialog-exit-duration);
55
+ transition-timing-function: ease-in;
56
+ transition-behavior: allow-discrete;
57
+ }
58
+
59
+ &[open]::backdrop {
60
+ opacity: 1;
61
+ transition-duration: var(--ob-dialog-enter-duration);
62
+ transition-timing-function: ease-out;
63
+ }
64
+ }
65
+
66
+ @starting-style {
67
+ .ds-dialog:is(:not([data-placement]), [data-placement="center"])[open],
68
+ .ob-dialog-motion[open] {
69
+ opacity: 0;
70
+ translate: 0 var(--ob-dialog-offset);
71
+ }
72
+
73
+ .ds-dialog:is(:not([data-placement]), [data-placement="center"])[open]::backdrop,
74
+ .ob-dialog-motion[open]::backdrop {
75
+ opacity: 0;
76
+ }
77
+ }
@@ -23,21 +23,21 @@
23
23
  height: 100%;
24
24
  }
25
25
 
26
- /* The content width on the MUI Container scale; the gutters come on top. */
26
+ /* The content width on the Page.Block scale (rem for a 14px root); the gutters come on top. */
27
27
  .ObPageContainer-inner.ObPageContainer-text {
28
- max-width: calc(36rem + 2 * var(--ob-page-container-gutter));
28
+ max-width: calc(41rem + 2 * var(--ob-page-container-gutter));
29
29
  }
30
30
 
31
31
  .ObPageContainer-inner.ObPageContainer-md {
32
- max-width: calc(48rem + 2 * var(--ob-page-container-gutter));
32
+ max-width: calc(55rem + 2 * var(--ob-page-container-gutter));
33
33
  }
34
34
 
35
35
  .ObPageContainer-inner.ObPageContainer-lg {
36
- max-width: calc(64rem + 2 * var(--ob-page-container-gutter));
36
+ max-width: calc(73rem + 2 * var(--ob-page-container-gutter));
37
37
  }
38
38
 
39
39
  .ObPageContainer-inner.ObPageContainer-xl {
40
- max-width: calc(80rem + 2 * var(--ob-page-container-gutter));
40
+ max-width: calc(110rem + 2 * var(--ob-page-container-gutter));
41
41
  }
42
42
  }
43
43
 
@@ -8,8 +8,8 @@ export type PageContainerMaxWidth = "xs" | "sm" | "md" | "lg" | "xl" | false;
8
8
  export type PageContainerProps = React.HTMLAttributes<HTMLDivElement> & {
9
9
  fullHeight?: boolean;
10
10
  /**
11
- * Predefined max-width of the content, on the MUI `Container` scale (`xs`/`sm` 36rem,
12
- * `md` 48rem, `lg` 64rem, `xl` 80rem, plus the gutters). The content stays against the
11
+ * Predefined max-width of the content, on the `Page.Block` scale (`xs`/`sm` 41rem,
12
+ * `md` 55rem, `lg` 73rem, `xl` 110rem, plus the gutters). The content stays against the
13
13
  * leading edge, as the MUI-era `PageContainer` did; `Page.Block`/`Container` centre it.
14
14
  * @default max-width: 100%
15
15
  */
@@ -1,4 +1,5 @@
1
1
  import "./index.css";
2
+ import "./DialogMotion.css";
2
3
 
3
4
  import { useEffect, useState } from "react";
4
5
  import type React from "react";
package/src/page/Page.css CHANGED
@@ -16,6 +16,11 @@
16
16
  rather than the viewport. That is what keeps it clear of a site toolbar rendered
17
17
  above the app, without needing to know the toolbar's height. */
18
18
  position: relative;
19
+ /* On the base rule, not the drawer-open one below: the content has to slide back when the
20
+ drawer closes too, and a transition declared only where the padding is set stops
21
+ applying the moment that rule stops matching. Same timing as the drawer itself
22
+ (AppMenuDrawer.css), so the content edge follows the drawer edge. */
23
+ transition: padding-left 225ms cubic-bezier(0.4, 0, 0.2, 1);
19
24
  }
20
25
 
21
26
  .ObPage-document {
@@ -35,7 +40,6 @@
35
40
  /* Offset content when a docked Page.Header drawer is open (desktop) */
36
41
  .ObPage-root:has(.ObPageHeader-drawer.ObPageHeader-drawerDocked.ObPageHeader-drawerOpen) {
37
42
  padding-left: var(--ob-page-drawer-width, 20rem);
38
- transition: padding-left 225ms cubic-bezier(0.4, 0, 0.2, 1);
39
43
  }
40
44
  }
41
45
 
@@ -4,24 +4,24 @@
4
4
  margin-inline: auto;
5
5
  }
6
6
 
7
+ /* The widths are in rem for a 14px root (the 2026 site's 87.5%), so they follow the browser's
8
+ font size: text 574px, md 770px, lg 1022px, xl 1540px at the default setting. */
7
9
  .ObPageBlock-text {
8
- max-width: calc(36rem + 2 * var(--ob-page-gutter, 0rem));
10
+ max-width: calc(41rem + 2 * var(--ob-page-gutter, 0rem));
9
11
  }
10
12
 
11
13
  .ObPageBlock-md {
12
- max-width: 48rem;
14
+ max-width: 55rem;
13
15
  }
14
16
 
15
17
  .ObPageBlock-lg {
16
- max-width: 64rem;
17
- }
18
-
19
- .ObPageBlock-xl {
20
- max-width: 80rem;
18
+ max-width: 73rem;
21
19
  }
22
20
 
21
+ /* 2xl is a deprecated alias: at 90rem it had become narrower than xl. */
22
+ .ObPageBlock-xl,
23
23
  .ObPageBlock-2xl {
24
- max-width: 90rem;
24
+ max-width: 110rem;
25
25
  }
26
26
 
27
27
  /* Against the leading edge instead of centred in the space left over. */
@@ -44,13 +44,14 @@ export type PageBlockProps = React.HTMLAttributes<HTMLElement> & {
44
44
  */
45
45
  overflow?: "auto" | "hidden";
46
46
  /**
47
- * Predefined max-width. Content is horizontally centered.
47
+ * Predefined max-width. Content is horizontally centered. Pixel widths are at the 2026
48
+ * site's 14px root font size; the rem values follow the browser's font size setting.
48
49
  *
49
- * - `text`: 36rem (576px) — recommended line-length for running text
50
- * - `md`: 48rem (768px)
51
- * - `lg`: 64rem (1024px)
52
- * - `xl`: 80rem (1280px)
53
- * - `2xl`: 90rem (1440px)
50
+ * - `text`: 41rem (574px) — recommended line-length for running text
51
+ * - `md`: 55rem (770px)
52
+ * - `lg`: 73rem (1022px)
53
+ * - `xl`: 110rem (1540px)
54
+ * - `2xl`: deprecated, the same as `xl`
54
55
  *
55
56
  * @default max-width: 100%
56
57
  */