asma-ui-core 3.25.0 → 3.26.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 (31) hide show
  1. package/dist/asma-ui-core.css +1 -1
  2. package/dist/components/custom/module/header-layout/DynamicToolbar.js +245 -0
  3. package/dist/components/custom/module/header-layout/DynamicToolbarLayoutContext.js +13 -0
  4. package/dist/components/custom/module/header-layout/ToolbarActionGroup.js +82 -0
  5. package/dist/components/custom/module/header-layout/ToolbarMeasurement.js +56 -0
  6. package/dist/components/custom/module/header-layout/ToolbarRows.js +131 -0
  7. package/dist/components/custom/module/header-layout/estimateToolbarLayout.js +189 -0
  8. package/dist/components/custom/module/header-layout/planToolbarActions.js +130 -0
  9. package/dist/components/custom/module/header-layout/useTranslations.js +23 -0
  10. package/dist/components/custom/widget/widget-header/StyledWidgetHeader.js +15 -12
  11. package/dist/components/utils/filter-menu/StyledFilterButton.js +22 -18
  12. package/dist/components/utils/filter-menu/StyledFilterMenu.js +20 -19
  13. package/dist/datetime/components/time-picker/TimePickerInput.js +18 -18
  14. package/dist/hooks/useElementWidthPx.js +26 -0
  15. package/dist/hooks/useWidthRegistry.js +37 -0
  16. package/dist/index.js +184 -172
  17. package/dist/src/components/custom/module/header-layout/DynamicToolbar.d.ts +25 -0
  18. package/dist/src/components/custom/module/header-layout/DynamicToolbarLayoutContext.d.ts +7 -0
  19. package/dist/src/components/custom/module/header-layout/ToolbarActionGroup.d.ts +19 -0
  20. package/dist/src/components/custom/module/header-layout/ToolbarMeasurement.d.ts +27 -0
  21. package/dist/src/components/custom/module/header-layout/ToolbarRows.d.ts +65 -0
  22. package/dist/src/components/custom/module/header-layout/estimateToolbarLayout.d.ts +95 -0
  23. package/dist/src/components/custom/module/header-layout/index.d.ts +3 -0
  24. package/dist/src/components/custom/module/header-layout/planToolbarActions.d.ts +72 -0
  25. package/dist/src/components/custom/module/header-layout/useTranslations.d.ts +17 -0
  26. package/dist/src/components/utils/filter-menu/StyledFilterButton.d.ts +1 -0
  27. package/dist/src/components/utils/filter-menu/StyledFilterMenu.d.ts +1 -0
  28. package/dist/src/hooks/useElementWidthPx.d.ts +10 -0
  29. package/dist/src/hooks/useWidthRegistry.d.ts +21 -0
  30. package/dist/src/index.d.ts +1 -0
  31. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ import { CSSProperties, ReactElement } from 'react';
2
+ import { DynamicToolbarAction } from './planToolbarActions';
3
+ import { ToolbarSlot } from './ToolbarRows';
4
+ import { ToolbarLocale } from './useTranslations';
5
+ export type { DynamicToolbarAction } from './planToolbarActions';
6
+ export type { ToolbarSlot } from './ToolbarRows';
7
+ export type { ToolbarLocale } from './useTranslations';
8
+ export { useDynamicToolbarLayout } from './DynamicToolbarLayoutContext';
9
+ export interface DynamicToolbarProps {
10
+ title?: ToolbarSlot;
11
+ helperText?: ToolbarSlot;
12
+ beforeFilterActions?: DynamicToolbarAction[];
13
+ filter?: ToolbarSlot;
14
+ search?: ToolbarSlot;
15
+ afterSearchActions?: DynamicToolbarAction[];
16
+ selectedCount?: number;
17
+ onClearSelection?: () => void;
18
+ bulkActions?: DynamicToolbarAction[];
19
+ maxVisibleBulkActions?: number;
20
+ locale?: ToolbarLocale;
21
+ className?: string;
22
+ style?: CSSProperties;
23
+ dataTest?: string;
24
+ }
25
+ export declare function DynamicToolbar(props: DynamicToolbarProps): ReactElement;
@@ -0,0 +1,7 @@
1
+ export interface DynamicToolbarLayoutContextValue {
2
+ containerWidth: number;
3
+ filterIconOnly: boolean;
4
+ trailingIconOnly: boolean;
5
+ }
6
+ export declare const DynamicToolbarLayoutContext: import('node_modules/@types/react').Context<DynamicToolbarLayoutContextValue>;
7
+ export declare function useDynamicToolbarLayout(): DynamicToolbarLayoutContextValue;
@@ -0,0 +1,19 @@
1
+ import { DynamicToolbarAction, PlannedToolbarActions } from './planToolbarActions';
2
+ /** True when the plan renders anything (inline buttons or the More menu). */
3
+ export declare function hasPlannedActions(plan?: PlannedToolbarActions): boolean;
4
+ export declare function ToolbarActionButton({ action, showLabel, selectionTone, }: {
5
+ action: DynamicToolbarAction;
6
+ showLabel: boolean;
7
+ selectionTone?: boolean;
8
+ }): JSX.Element;
9
+ export declare function MoreTriggerButton({ overflowMenuLabel, onOpen, }: {
10
+ overflowMenuLabel: string;
11
+ onOpen?: (anchor: HTMLElement) => void;
12
+ }): JSX.Element;
13
+ /** Inline action buttons plus the "More" overflow menu for a planned strip. */
14
+ export declare function ToolbarActionGroup({ plan, overflowMenuLabel, className, selectionTone, }: {
15
+ plan: PlannedToolbarActions;
16
+ overflowMenuLabel: string;
17
+ className?: string;
18
+ selectionTone?: boolean;
19
+ }): JSX.Element | null;
@@ -0,0 +1,27 @@
1
+ import { WidthRegistry } from '../../../../hooks/useWidthRegistry';
2
+ import { DynamicToolbarAction } from './planToolbarActions';
3
+ import { ToolbarTranslations } from './useTranslations';
4
+ export declare const KEY_TITLE = "title";
5
+ export declare const KEY_LEADING = "slot:leading";
6
+ export declare const KEY_SEARCH = "slot:search";
7
+ export declare const KEY_SELECTION_INDICATOR = "selection-indicator";
8
+ export declare const KEY_MORE_BUTTON = "more-button";
9
+ export declare const filterKey: (iconOnly: boolean) => string;
10
+ export declare const trailingKey: (iconOnly: boolean) => string;
11
+ export declare const actionKey: (id: string, showLabel: boolean) => string;
12
+ export interface SelectionIndicatorMeasurement {
13
+ selectedCount: number;
14
+ hasClear: boolean;
15
+ translations: ToolbarTranslations;
16
+ }
17
+ /**
18
+ * Invisible, inert strip rendering every action in both variants (label and
19
+ * icon-only) plus the More trigger and selection indicator, so the planners
20
+ * work with real rendered widths instead of font-dependent guesses.
21
+ */
22
+ export declare function ToolbarMeasurementStrip({ actions, register, overflowMenuLabel, selectionIndicator, }: {
23
+ actions: DynamicToolbarAction[];
24
+ register: WidthRegistry['register'];
25
+ overflowMenuLabel: string;
26
+ selectionIndicator?: SelectionIndicatorMeasurement;
27
+ }): JSX.Element;
@@ -0,0 +1,65 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ import { PlannedToolbarActions } from './planToolbarActions';
3
+ import { ToolbarTranslations } from './useTranslations';
4
+ /**
5
+ * Slot for toolbar content: JSX widgets or plain text titles.
6
+ *
7
+ * Slot components that support collapsing their label to icon-only should read
8
+ * `useDynamicToolbarLayout()` (`filterIconOnly` / `trailingIconOnly`) — the
9
+ * toolbar never mutates slot elements.
10
+ */
11
+ export type ToolbarSlot = ReactElement | string;
12
+ /** Callback ref used to track a slot's rendered width. */
13
+ export type MeasureRef = (element: HTMLElement | null) => void;
14
+ export type UtilityRowVariant = 'inline-normal' | 'inline-selection' | 'utility-row';
15
+ export declare function SelectionIndicator({ selectedCount, onClearSelection, translations, }: {
16
+ selectedCount: number;
17
+ onClearSelection?: () => void;
18
+ translations: ToolbarTranslations;
19
+ }): JSX.Element;
20
+ export declare function SelectionRow({ selectedCount, onClearSelection, translations, bulkActionsPlan, }: {
21
+ selectedCount: number;
22
+ onClearSelection?: () => void;
23
+ translations: ToolbarTranslations;
24
+ bulkActionsPlan: PlannedToolbarActions;
25
+ }): JSX.Element;
26
+ export declare function SearchSlot({ search, expanded, measureRef, }: {
27
+ search: ToolbarSlot;
28
+ expanded?: boolean;
29
+ measureRef?: MeasureRef;
30
+ }): JSX.Element;
31
+ /** Keeps children mounted while hidden so slots fade instead of popping. */
32
+ export declare function FadeSlot({ visible, children }: {
33
+ visible: boolean;
34
+ children: ReactNode;
35
+ }): JSX.Element;
36
+ export declare function TitleText({ title, measureRef }: {
37
+ title: ToolbarSlot;
38
+ measureRef?: MeasureRef;
39
+ }): JSX.Element;
40
+ export declare function TitleBlock({ title, helperText, titleMeasureRef, }: {
41
+ title?: ToolbarSlot;
42
+ helperText?: ToolbarSlot;
43
+ titleMeasureRef?: MeasureRef;
44
+ }): JSX.Element | null;
45
+ /**
46
+ * Utility cluster — fixed internal order per variant (flex-nowrap).
47
+ * - inline-normal: before-filter actions → filter → search → after-search actions
48
+ * - inline-selection: bulk → filter → search
49
+ * - utility-row (§4/§7): before-filter actions → search (flex) → filter → after-search actions
50
+ *
51
+ * Bulk actions render only in the `inline-selection` variant; the other
52
+ * variants pair with a separate {@link SelectionRow} when selection is active.
53
+ */
54
+ export declare function UtilityCluster({ variant, beforeFilterActionsPlan, filter, search, afterSearchActionsPlan, bulkActionsPlan, showPageActions, overflowMenuLabel, searchMeasureRef, align, }: {
55
+ variant: UtilityRowVariant;
56
+ beforeFilterActionsPlan: PlannedToolbarActions;
57
+ filter?: ReactNode;
58
+ search?: ToolbarSlot;
59
+ afterSearchActionsPlan: PlannedToolbarActions;
60
+ bulkActionsPlan?: PlannedToolbarActions;
61
+ showPageActions: boolean;
62
+ overflowMenuLabel: string;
63
+ searchMeasureRef?: MeasureRef;
64
+ align?: 'start' | 'end';
65
+ }): JSX.Element | null;
@@ -0,0 +1,95 @@
1
+ import { ToolbarSlot } from './ToolbarRows';
2
+ import { DynamicToolbarAction, ResolveToolbarActionWidth } from './planToolbarActions';
3
+ export type ToolbarLayoutMode = 'compact' | 'stacked' | 'selection-split' | 'inline';
4
+ export interface ToolbarLayoutResolution {
5
+ mode: ToolbarLayoutMode;
6
+ filterIconOnly: boolean;
7
+ trailingIconOnly: boolean;
8
+ }
9
+ /**
10
+ * Real, rendered widths measured by DynamicToolbar. Every field is optional:
11
+ * a missing value falls back to the heuristic estimate, so layout planning
12
+ * works on first render and degrades gracefully.
13
+ */
14
+ export interface MeasuredToolbarWidths {
15
+ titleWidthPx?: number;
16
+ leadingUtilityWidthPx?: number;
17
+ searchWidthPx?: number;
18
+ filterLabelWidthPx?: number;
19
+ filterIconWidthPx?: number;
20
+ trailingLabelWidthPx?: number;
21
+ trailingIconWidthPx?: number;
22
+ selectionIndicatorWidthPx?: number;
23
+ moreButtonWidthPx?: number;
24
+ actionWidthPx?: ResolveToolbarActionWidth;
25
+ }
26
+ export declare function estimateTitleAreaWidth(title?: ToolbarSlot): number;
27
+ /**
28
+ * Width reserved for the title area. Prefers the measured natural width
29
+ * (capped — the title is allowed to truncate rather than dominate layout),
30
+ * falling back to the character-count estimate before measurement.
31
+ */
32
+ export declare function resolveTitleAreaWidth(title: ToolbarSlot | undefined, measuredTitleWidthPx?: number): number;
33
+ export declare function estimateActionsStripWidth(actions: DynamicToolbarAction[], showLabel: boolean, resolveActionWidth?: ResolveToolbarActionWidth): number;
34
+ /**
35
+ * Bulk zone width as it actually renders: the top-priority actions inline
36
+ * (labels always shown) up to `maxVisibleBulkActions`, plus the More trigger
37
+ * when the remaining actions overflow into the menu.
38
+ */
39
+ export declare function estimateBulkStripWidth(bulkActions: DynamicToolbarAction[], maxVisibleBulkActions: number, resolveActionWidth?: ResolveToolbarActionWidth): number;
40
+ export declare function resolveReserveBulkActionsWidthPx(reserveBulkActionsWidthPx: number, bulkActions: DynamicToolbarAction[], resolveActionWidth?: ResolveToolbarActionWidth, maxVisibleBulkActions?: number): number;
41
+ export interface EstimateUtilitiesWidthOptions {
42
+ hasLeadingUtility: boolean;
43
+ hasFilter: boolean;
44
+ hasSearch: boolean;
45
+ hasTrailingUtility: boolean;
46
+ showPageActions: boolean;
47
+ showBulkActions: boolean;
48
+ filterIconOnly: boolean;
49
+ trailingIconOnly: boolean;
50
+ pageActionsWidthPx: number;
51
+ bulkActionsWidthPx: number;
52
+ /** §4/§7: search before filter on utility row */
53
+ searchBeforeFilter?: boolean;
54
+ measured?: MeasuredToolbarWidths;
55
+ }
56
+ export declare function estimateUtilitiesClusterWidth(options: EstimateUtilitiesWidthOptions): number;
57
+ export declare function resolveToolbarLayout(options: {
58
+ containerWidth: number;
59
+ isMobile: boolean;
60
+ isSelectionMode: boolean;
61
+ compactBreakpointPx: number;
62
+ reserveBulkActionsWidthPx: number;
63
+ title?: ToolbarSlot;
64
+ hasLeadingUtility: boolean;
65
+ hasFilter: boolean;
66
+ hasSearch: boolean;
67
+ hasTrailingUtility: boolean;
68
+ pageActions: DynamicToolbarAction[];
69
+ bulkActions: DynamicToolbarAction[];
70
+ maxVisibleBulkActions?: number;
71
+ measured?: MeasuredToolbarWidths;
72
+ }): ToolbarLayoutResolution;
73
+ /** Width budget for `planToolbarActions` based on resolved layout. */
74
+ export declare function resolvePageActionsAvailableWidth(options: {
75
+ layoutMode: ToolbarLayoutMode;
76
+ containerWidth: number;
77
+ titleWidth: number;
78
+ filterIconOnly: boolean;
79
+ trailingIconOnly: boolean;
80
+ hasFilter: boolean;
81
+ hasSearch: boolean;
82
+ hasLeadingUtility: boolean;
83
+ hasTrailingUtility: boolean;
84
+ reserveBulkActionsWidthPx: number;
85
+ measured?: MeasuredToolbarWidths;
86
+ }): number;
87
+ export declare function resolveBulkActionsAvailableWidth(options: {
88
+ layoutMode: ToolbarLayoutMode;
89
+ containerWidth: number;
90
+ titleWidth: number;
91
+ filterIconOnly: boolean;
92
+ hasFilter: boolean;
93
+ hasSearch: boolean;
94
+ measured?: MeasuredToolbarWidths;
95
+ }): number;
@@ -0,0 +1,3 @@
1
+ export * from './DynamicToolbar';
2
+ export * from './DynamicToolbarLayoutContext';
3
+ export * from './planToolbarActions';
@@ -0,0 +1,72 @@
1
+ import { ReactElement } from 'react';
2
+ export interface DynamicToolbarActionRenderOptions {
3
+ showLabel: boolean;
4
+ }
5
+ export interface DynamicToolbarAction {
6
+ id: string;
7
+ label: string;
8
+ onClick: () => void;
9
+ icon?: ReactElement;
10
+ disabled?: boolean;
11
+ hidden?: boolean;
12
+ variant?: 'contained' | 'outlined' | 'text' | 'textGray' | 'textWhite';
13
+ /** Visual tone. `danger` renders the action in the destructive/red palette. */
14
+ tone?: 'default' | 'danger';
15
+ size?: 'large' | 'small' | 'medium';
16
+ /**
17
+ * Higher priority resists overflow longer — lower-priority actions move
18
+ * into the More menu first. Label collapse is positional (right to left).
19
+ */
20
+ priority?: number;
21
+ /** Never move into the More overflow menu. */
22
+ keepVisible?: boolean;
23
+ /** Allow label + icon to collapse to icon-only. */
24
+ canHideLabel?: boolean;
25
+ /** Allow moving into the More overflow menu. */
26
+ canOverflow?: boolean;
27
+ dataTest?: string;
28
+ estimatedWidthPx?: number;
29
+ ariaLabel?: string;
30
+ /** Custom inline renderer for richer actions such as popover triggers. */
31
+ render?: (options: DynamicToolbarActionRenderOptions) => ReactElement;
32
+ }
33
+ export interface PlannedToolbarAction {
34
+ action: DynamicToolbarAction;
35
+ showLabel: boolean;
36
+ width: number;
37
+ }
38
+ export interface PlannedToolbarActions {
39
+ inlineActions: PlannedToolbarAction[];
40
+ overflowActions: DynamicToolbarAction[];
41
+ showMoreMenu: boolean;
42
+ }
43
+ export declare const ACTION_GAP_PX = 8;
44
+ /** Fallback width of the icon-only "More" overflow trigger button. */
45
+ export declare const MORE_BUTTON_PX = 40;
46
+ /**
47
+ * Supplies the real (measured) width of an action. Returning `undefined`
48
+ * falls back to `estimateToolbarActionWidth`.
49
+ */
50
+ export type ResolveToolbarActionWidth = (action: DynamicToolbarAction, showLabel: boolean) => number | undefined;
51
+ export declare function estimateToolbarActionWidth(action: DynamicToolbarAction, showLabel: boolean): number;
52
+ export declare function resolveToolbarActionWidth(action: DynamicToolbarAction, showLabel: boolean, resolveActionWidth?: ResolveToolbarActionWidth): number;
53
+ export declare function planToolbarActions(options: {
54
+ actions: DynamicToolbarAction[];
55
+ availableWidth: number;
56
+ maxInlineActions?: number;
57
+ collapseLabels?: boolean;
58
+ resolveActionWidth?: ResolveToolbarActionWidth;
59
+ moreButtonWidthPx?: number;
60
+ }): PlannedToolbarActions;
61
+ /**
62
+ * Bulk actions overflow progressively: every action keeps its full label,
63
+ * and the lowest-priority actions drop into the "More" menu until the
64
+ * remaining inline strip fits the available width.
65
+ */
66
+ export declare function planBulkToolbarActions(options: {
67
+ actions: DynamicToolbarAction[];
68
+ availableWidth: number;
69
+ maxVisibleBulkActions?: number;
70
+ resolveActionWidth?: ResolveToolbarActionWidth;
71
+ moreButtonWidthPx?: number;
72
+ }): PlannedToolbarActions;
@@ -0,0 +1,17 @@
1
+ export type ToolbarLocale = 'en' | 'no';
2
+ declare const translations: {
3
+ en: {
4
+ more: string;
5
+ clearSelection: string;
6
+ selected: string;
7
+ };
8
+ no: {
9
+ more: string;
10
+ clearSelection: string;
11
+ selected: string;
12
+ };
13
+ };
14
+ export type ToolbarTranslations = (typeof translations)['en'];
15
+ export declare function useToolbarTranslations(locale?: ToolbarLocale): ToolbarTranslations;
16
+ export declare function formatSelectionLabel(count: number, t: ToolbarTranslations): string;
17
+ export {};
@@ -2,6 +2,7 @@ import { StyledButtonProps } from '../../inputs/button';
2
2
  /**
3
3
  * Custom props:
4
4
  * @param filterIsActive - needed to determine whether or not to show the dot in the top right corner indicating some changes were made
5
+ * @param hideLabel - icon-only mode. When omitted, follows the surrounding DynamicToolbar layout (filterIconOnly).
5
6
  */
6
7
  interface StyledFilterMenuProps {
7
8
  filterIsActive: boolean;
@@ -4,6 +4,7 @@ import { PopoverProps } from '@mui/material';
4
4
  * Custom props:
5
5
  * @param filterIsActive - needed to determine whether or not to show the dot in the top right corner indicating some changes were made
6
6
  * @param popoverContent
7
+ * @param hideLabel - icon-only mode. When omitted, follows the surrounding DynamicToolbar layout (filterIconOnly).
7
8
  */
8
9
  interface StyledFilterMenuProps {
9
10
  dataTest: string;
@@ -0,0 +1,10 @@
1
+ import { RefObject } from 'react';
2
+ /**
3
+ * Tracks an element's width. The first measurement happens synchronously in a
4
+ * layout effect (before paint) so width-driven layouts never flash an
5
+ * unmeasured state; subsequent resize events are rAF-throttled.
6
+ */
7
+ export declare function useElementWidthPx<T extends HTMLElement>(): {
8
+ ref: RefObject<T>;
9
+ widthPx: number;
10
+ };
@@ -0,0 +1,21 @@
1
+ export type WidthMeasureMode = 'box' | 'scroll';
2
+ export interface WidthRegistry {
3
+ /**
4
+ * Returns a stable callback ref for `key`. Attach it to the element whose
5
+ * width should be tracked. `scroll` mode measures `scrollWidth` (natural
6
+ * content width, ignoring truncation); `box` measures the border box.
7
+ */
8
+ register: (key: string, mode?: WidthMeasureMode) => (element: HTMLElement | null) => void;
9
+ /**
10
+ * Last known width per key. Keys keep their last value after the element
11
+ * unmounts, so variant widths (e.g. label vs icon-only) stay available
12
+ * once each variant has been rendered at least once.
13
+ */
14
+ widths: Readonly<Record<string, number>>;
15
+ }
16
+ /**
17
+ * Tracks real, rendered widths of a dynamic set of keyed elements with a
18
+ * single shared ResizeObserver. Used by DynamicToolbar to drive layout
19
+ * planning from measured widths instead of hard-coded estimates.
20
+ */
21
+ export declare function useWidthRegistry(): WidthRegistry;
@@ -46,5 +46,6 @@ export * from './components/custom/widget/widget-title/StyledWidgetTitle';
46
46
  export * from './components/custom/widget/widget-header/StyledWidgetHeader';
47
47
  export * from './components/custom/widget/widget/StyledWidget';
48
48
  export * from './components/custom/module/module-title/StyledModuleTitle';
49
+ export * from './components/custom/module/header-layout';
49
50
  export * from './components/feedback/minimizable-dialog';
50
51
  export { Fade, Paper, ClickAwayListener, Popper, Avatar, Skeleton, Container, Stack, FormLabel } from '@mui/material';
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "3.25.0",
6
+ "version": "3.26.0",
7
7
  "type": "module",
8
8
  "sideEffects": [
9
9
  "**/*.css",