asma-ui-core 3.89.0 → 3.90.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.
Files changed (27) hide show
  1. package/Readme.md +56 -10
  2. package/dist/asma-ui-core.css +1 -1
  3. package/dist/components/utils/bottom-sheet/StyledBottomSheet.js +137 -0
  4. package/dist/components/utils/bottom-sheet/StyledBottomSheet.module.js +4 -0
  5. package/dist/components/utils/bottom-sheet/formatResultsLabel.js +4 -0
  6. package/dist/components/utils/bottom-sheet/useDragToDismiss.hook.js +40 -0
  7. package/dist/components/utils/popover/PopoverSheet.js +165 -0
  8. package/dist/hooks/useBodyScrollLock.hook.js +13 -0
  9. package/dist/hooks/useDebouncedValue.hook.js +15 -0
  10. package/dist/hooks/useMediaQuery.hook.js +10 -9
  11. package/dist/index.js +184 -180
  12. package/dist/src/components/utils/bottom-sheet/StyledBottomSheet.d.ts +59 -0
  13. package/dist/src/components/utils/bottom-sheet/formatResultsLabel.d.ts +9 -0
  14. package/dist/src/components/utils/bottom-sheet/formatResultsLabel.test.d.ts +1 -0
  15. package/dist/src/components/utils/bottom-sheet/index.d.ts +2 -0
  16. package/dist/src/components/utils/bottom-sheet/story/BottomSheetCases.d.ts +1 -0
  17. package/dist/src/components/utils/bottom-sheet/story/BottomSheetDemo.d.ts +22 -0
  18. package/dist/src/components/utils/bottom-sheet/useDragToDismiss.hook.d.ts +22 -0
  19. package/dist/src/components/utils/popover/PopoverAnatomy.d.ts +1 -1
  20. package/dist/src/components/utils/popover/{StyledPopoverV2.d.ts → PopoverSheet.d.ts} +12 -2
  21. package/dist/src/components/utils/popover/index.d.ts +1 -1
  22. package/dist/src/hooks/useBodyScrollLock.hook.d.ts +7 -0
  23. package/dist/src/hooks/useDebouncedValue.hook.d.ts +2 -0
  24. package/dist/src/hooks/useMediaQuery.hook.d.ts +2 -0
  25. package/dist/src/index.d.ts +1 -0
  26. package/package.json +1 -1
  27. package/dist/components/utils/popover/StyledPopoverV2.js +0 -140
@@ -0,0 +1,22 @@
1
+ import { PointerEvent, RefObject } from 'react';
2
+ interface DragHandleProps {
3
+ onPointerDown: (event: PointerEvent<HTMLElement>) => void;
4
+ onPointerMove: (event: PointerEvent<HTMLElement>) => void;
5
+ onPointerUp: (event: PointerEvent<HTMLElement>) => void;
6
+ onPointerCancel: (event: PointerEvent<HTMLElement>) => void;
7
+ }
8
+ interface DragToDismiss {
9
+ /** Spread on the drag zone — the 48px handle row. */
10
+ dragHandleProps: DragHandleProps;
11
+ /** How far the sheet is currently dragged down, in px. `0` when at rest. */
12
+ dragOffset: number;
13
+ }
14
+ /**
15
+ * Drag the sheet down past 30% of its height to dismiss it; release earlier and it snaps back.
16
+ *
17
+ * Deliberately never the only exit (WCAG 2.5.7): the close button, the scrim and Escape all do the
18
+ * same without a dragging movement. A press that starts on a button in the drag zone is left alone,
19
+ * so the close control keeps working as a plain click.
20
+ */
21
+ export declare const useDragToDismiss: (sheetRef: RefObject<HTMLElement | null>, onDismiss: () => void) => DragToDismiss;
22
+ export {};
@@ -27,7 +27,7 @@ export interface PopoverAnatomyProps {
27
27
  }
28
28
  /**
29
29
  * Everything inside the popover surface: the content row (title + body + close column) and the two
30
- * optional footer rows. Split out of `StyledPopoverV2` so the Gallery story can render every
30
+ * optional footer rows. Split out of `PopoverSheet` so the Gallery story can render every
31
31
  * Title × Reset filter × Actions combination side by side — a real popover is portalled, anchored
32
32
  * and mutually exclusive, so the variants can never be seen together through the live component.
33
33
  *
@@ -20,7 +20,7 @@ export interface PopoverContentApi {
20
20
  /** A slot that may need to close the surface — e.g. "View results (3)" or an Actions button. */
21
21
  export type PopoverSlot = ReactNode | ((api: PopoverContentApi) => ReactNode);
22
22
  /** @figmaNode wXrXt5uKNNzV2DnQCgyYZH#44531-233781 (Design-System · "_Popover") */
23
- export interface StyledPopoverV2Props {
23
+ export interface PopoverSheetProps {
24
24
  /** @figmaProp none — test hook */
25
25
  dataTest: string;
26
26
  /** @figmaProp none — behavioral: the consumer owns the trigger element, this wires its aria + click */
@@ -45,6 +45,13 @@ export interface StyledPopoverV2Props {
45
45
  resetAction?: PopoverSlot;
46
46
  /** @figmaProp Actions = ReactNode→true | undefined→false — the 12px footer row, right-aligned */
47
47
  footerActions?: PopoverSlot;
48
+ /**
49
+ * Result total for the standard "Vis resultater (N)" control, rendered when no `viewResultsAction`
50
+ * is given (see `formatResultsLabel`). On mobile the sheet also announces it from its own live
51
+ * region, because the list behind is inert. `null` = total unavailable.
52
+ * @figmaProp none — behavioral
53
+ */
54
+ resultCount?: number | null;
48
55
  /** @figmaProp none — a11y: accessible name of the close control */
49
56
  closeLabel?: string;
50
57
  /** @figmaProp none — a11y: required when `variant='action'` and no `title` is given, so the dialog has a name */
@@ -70,7 +77,10 @@ export interface StyledPopoverV2Props {
70
77
  * There is also **no arrow/anchor pointer**: the surface is edge-aligned at an 8px offset and
71
78
  * proximity carries the relationship.
72
79
  *
80
+ * Below 744px `action` renders as a {@link StyledBottomSheet} (ASMA-8184) — same props, same content;
81
+ * `info` stays anchored at every size, because for an explanation the link to its term is the point.
82
+ *
73
83
  * Separate from {@link StyledPopover}, which stays as the MUI-parity positioning primitive its eight
74
84
  * internal consumers still depend on; those migrate here gradually.
75
85
  */
76
- export declare const StyledPopoverV2: ({ dataTest, renderTrigger, variant, title, children, viewResultsAction, resetAction, footerActions, closeLabel, ariaLabel, onOpenChange, className, }: StyledPopoverV2Props) => JSX.Element;
86
+ export declare const PopoverSheet: ({ dataTest, renderTrigger, variant, title, children, viewResultsAction, resetAction, footerActions, resultCount, closeLabel, ariaLabel, onOpenChange, className, }: PopoverSheetProps) => JSX.Element;
@@ -1,2 +1,2 @@
1
1
  export * from './StyledPopover';
2
- export * from './StyledPopoverV2';
2
+ export * from './PopoverSheet';
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Locks page scroll while `active` and puts the page back exactly where it was afterwards.
3
+ *
4
+ * `overflow: hidden` on both roots stops wheel and touch scrolling; the saved offset is restored on
5
+ * release because some mobile engines jump to the top when overflow is toggled back.
6
+ */
7
+ export declare const useBodyScrollLock: (active: boolean) => void;
@@ -0,0 +1,2 @@
1
+ /** `value`, but only once it has stopped changing for `delayMs`. The first value is returned at once. */
2
+ export declare const useDebouncedValue: <T>(value: T, delayMs: number) => T;
@@ -1,2 +1,4 @@
1
1
  export declare function makeMediaQueryStore(mediaQuery: string): () => boolean;
2
2
  export declare const useMobileMediaQuery: () => boolean;
3
+ /** A phone in landscape: too short for a sheet capped at 90vh to leave a usable body. */
4
+ export declare const useShortViewportMediaQuery: () => boolean;
@@ -44,6 +44,7 @@ export * from './components/navigation/listbox';
44
44
  export * from './components/utils/accordion';
45
45
  export * from './table';
46
46
  export * from './components/utils/popover';
47
+ export * from './components/utils/bottom-sheet';
47
48
  export * from './components/utils/filter-menu';
48
49
  export * from './components/utils/copy-wrapper';
49
50
  export * from './components/utils/virtual-list';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.89.0",
6
+ "version": "3.90.0",
7
7
  "type": "module",
8
8
  "sideEffects": [
9
9
  "**/*.css",
@@ -1,140 +0,0 @@
1
- import { cn as K } from "../../../helpers/cn.js";
2
- import { firstTabbable as Q } from "../../../helpers/focusable.js";
3
- import { TOP_LAYER_PROPS as J, TOP_LAYER_RESET_STYLE as Z, getOpenModalDialogAncestor as ee, shouldUsePopoverTopLayer as te, useTopLayerRef as re } from "../../../hooks/useTopLayer.hook.js";
4
- import { useMobileMediaQuery as oe } from "../../../hooks/useMediaQuery.hook.js";
5
- import { useFocusTrap as ne } from "../../../hooks/useFocusTrap.hook.js";
6
- import { PopoverAnatomy as ie } from "./PopoverAnatomy.js";
7
- import { useCallback as d, useEffect as P, useId as se, useRef as b, useState as u } from "react";
8
- import { Fragment as ae, jsx as _, jsxs as ce } from "react/jsx-runtime";
9
- import { FloatingPortal as le, autoUpdate as de, flip as ue, offset as fe, shift as pe, size as ge, useDismiss as me, useFloating as ve, useInteractions as he, useMergeRefs as xe } from "@floating-ui/react";
10
- var Pe = 240, be = {
11
- info: 360,
12
- action: 400
13
- }, _e = 0.6, M = "calc(100vw - 32px)", Se = ({ dataTest: f, renderTrigger: F, variant: A = "info", title: p, children: I, viewResultsAction: O, resetAction: w, footerActions: L, closeLabel: S = "Lukk", ariaLabel: H, onOpenChange: R, className: k }) => {
14
- const s = se(), T = `${s}-title`, [r, C] = u(!1), [y, N] = u(), [D, W] = u(), g = oe(), t = A === "action", X = g && !t, a = d((e) => {
15
- C(e), R?.(e);
16
- }, [R]), { refs: m, floatingStyles: U, context: Y } = ve({
17
- open: r,
18
- onOpenChange: a,
19
- placement: "bottom-start",
20
- strategy: "fixed",
21
- whileElementsMounted: de,
22
- middleware: [
23
- fe(8),
24
- ...X ? [] : [ue({ padding: 8 })],
25
- pe({ padding: 8 }),
26
- ge({
27
- padding: 8,
28
- apply({ availableHeight: e }) {
29
- W(Math.min(e, Math.round(window.innerHeight * _e)));
30
- }
31
- })
32
- ]
33
- }), { getFloatingProps: j } = he([me(Y, {
34
- escapeKey: !0,
35
- outsidePress: (e) => {
36
- const n = c.current;
37
- return !(n && e.target instanceof Node && n.contains(e.target));
38
- }
39
- })]), v = te(y), E = b(null), [o, q] = u(null), z = d((e) => {
40
- E.current = e, q(e);
41
- }, []), B = xe([re(m.setFloating, v), z]), c = b(null), V = d((e) => {
42
- c.current = e, m.setReference(e);
43
- }, [m]), i = d(() => {
44
- a(!1);
45
- }, [a]);
46
- ne(r && t && o !== null, E), P(() => {
47
- if (!o) return;
48
- const e = requestAnimationFrame(() => {
49
- (t ? Q(o) : o)?.focus({ preventScroll: !0 });
50
- });
51
- return () => {
52
- cancelAnimationFrame(e);
53
- };
54
- }, [o, t]), P(() => {
55
- if (!o || t) return;
56
- const e = (n) => {
57
- const x = n.relatedTarget;
58
- x instanceof Node && (o.contains(x) || c.current?.contains(x)) || i();
59
- };
60
- return o.addEventListener("focusout", e), () => {
61
- o.removeEventListener("focusout", e);
62
- };
63
- }, [
64
- o,
65
- t,
66
- i
67
- ]);
68
- const h = b(!1);
69
- P(() => {
70
- if (r) {
71
- h.current = !0;
72
- return;
73
- }
74
- if (!h.current) return;
75
- h.current = !1;
76
- const e = c.current;
77
- if (!e) return;
78
- const n = requestAnimationFrame(() => {
79
- document.activeElement === document.body && e.focus();
80
- });
81
- return () => {
82
- cancelAnimationFrame(n);
83
- };
84
- }, [r]);
85
- const $ = {
86
- ref: V,
87
- isOpen: r,
88
- triggerProps: {
89
- onClick: (e) => {
90
- if (r) {
91
- i();
92
- return;
93
- }
94
- N(ee(e.currentTarget)), a(!0);
95
- },
96
- "aria-expanded": r,
97
- "aria-controls": r ? s : void 0,
98
- "aria-haspopup": t ? "dialog" : void 0,
99
- "aria-describedby": !t && r ? s : void 0
100
- }
101
- }, l = (e) => typeof e == "function" ? e({ close: i }) : e, G = g ? M : be[A];
102
- return /* @__PURE__ */ ce(ae, { children: [F($), r && /* @__PURE__ */ _(le, {
103
- root: y,
104
- children: /* @__PURE__ */ _("div", {
105
- ...j(),
106
- ref: B,
107
- id: s,
108
- "data-test": f,
109
- "data-testid": f,
110
- role: t ? "dialog" : void 0,
111
- "aria-labelledby": t && p ? T : void 0,
112
- "aria-label": t && !p ? H : void 0,
113
- tabIndex: t ? void 0 : -1,
114
- ...v ? J : {},
115
- style: {
116
- ...v ? Z : {},
117
- ...U,
118
- minWidth: Pe,
119
- width: g ? M : void 0,
120
- maxWidth: G,
121
- maxHeight: D
122
- },
123
- className: K("z-[1300]", "box-border flex flex-col overflow-hidden rounded-lg border border-solid border-delta-300 bg-white text-delta-700 outline-none shadow-[0px_4px_40px_0px_#22213366]", k),
124
- children: /* @__PURE__ */ _(ie, {
125
- dataTest: f,
126
- title: p,
127
- titleId: T,
128
- viewResultsAction: l(O),
129
- resetAction: l(w),
130
- footerActions: l(L),
131
- closeLabel: S,
132
- onClose: i,
133
- children: l(I)
134
- })
135
- })
136
- })] });
137
- };
138
- export {
139
- Se as StyledPopoverV2
140
- };