@wallarm-org/design-system 1.0.0 → 1.1.0-rc-feature-AS-1060.1

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.
@@ -22,6 +22,7 @@ const ChipsWithGaps = ({ chips, hideLeadingGap, hideTrailingGap, onChipClick, on
22
22
  children: /*#__PURE__*/ jsx(FilterInputChip, {
23
23
  chipId: chip.id,
24
24
  attribute: chip.attribute ?? '',
25
+ attributeDescription: chip.attributeDescription,
25
26
  operator: chip.operator,
26
27
  value: chip.value,
27
28
  error: chip.error,
@@ -8,6 +8,9 @@ export interface FilterInputChipProps extends Omit<HTMLAttributes<HTMLDivElement
8
8
  ref?: Ref<HTMLDivElement>;
9
9
  chipId?: string;
10
10
  attribute: string;
11
+ /** Description shown in a dark tooltip on hover of the attribute segment. When
12
+ * absent, no tooltip renders. The operator/value segments are unaffected (AS-1060). */
13
+ attributeDescription?: string;
11
14
  operator?: string;
12
15
  value?: string;
13
16
  error?: ChipErrorSegment;
@@ -1,6 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useRef } from "react";
3
3
  import { cn } from "../../../../utils/cn.js";
4
+ import { Tooltip, TooltipContent, TooltipTrigger } from "../../../Tooltip/index.js";
4
5
  import { ChipSearchInput } from "./ChipSearchInput.js";
5
6
  import { chipVariants, emptyValueHitTarget } from "./classes.js";
6
7
  import { useEditingContext } from "./context/EditingContext.js";
@@ -8,7 +9,7 @@ import { FilterInputRemoveButton } from "./FilterInputRemoveButton.js";
8
9
  import { PairSeparator } from "./PairSeparator.js";
9
10
  import { Segment } from "./Segment.js";
10
11
  import { SEGMENT_VARIANT } from "./segmentVariant.js";
11
- const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = false, valueParts, valueSeparator, errorValueIndices, building = false, disabled = false, pair, onRemove, onSegmentClick, onPairSegmentClick, className, ...props })=>{
12
+ const FilterInputChip = ({ ref, chipId, attribute, attributeDescription, operator, value, error = false, valueParts, valueSeparator, errorValueIndices, building = false, disabled = false, pair, onRemove, onSegmentClick, onPairSegmentClick, className, ...props })=>{
12
13
  const interactive = !disabled;
13
14
  const internalRef = useRef(null);
14
15
  const editing = useEditingContext();
@@ -50,6 +51,15 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
50
51
  } : {};
51
52
  const baseActiveSegment = 0 === editingSide ? activeSegment : null;
52
53
  const pairActiveSegment = 1 === editingSide ? activeSegment : null;
54
+ const attributeSegment = /*#__PURE__*/ jsx(Segment, {
55
+ variant: SEGMENT_VARIANT.attribute,
56
+ className: "shrink-0",
57
+ error: true === effectiveError || effectiveError === SEGMENT_VARIANT.attribute,
58
+ onClick: interactive ? (e)=>handleSegmentClick(SEGMENT_VARIANT.attribute, e) : void 0,
59
+ onMouseDown: interactive && building ? handleSegmentMouseDown : void 0,
60
+ ...segmentEditProps(SEGMENT_VARIANT.attribute),
61
+ children: attribute
62
+ });
53
63
  const setRefs = useCallback((node)=>{
54
64
  internalRef.current = node;
55
65
  if ('function' == typeof ref) ref(node);
@@ -71,15 +81,18 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
71
81
  },
72
82
  ...props,
73
83
  children: [
74
- /*#__PURE__*/ jsx(Segment, {
75
- variant: SEGMENT_VARIANT.attribute,
76
- className: "shrink-0",
77
- error: true === effectiveError || effectiveError === SEGMENT_VARIANT.attribute,
78
- onClick: interactive ? (e)=>handleSegmentClick(SEGMENT_VARIANT.attribute, e) : void 0,
79
- onMouseDown: interactive && building ? handleSegmentMouseDown : void 0,
80
- ...segmentEditProps(SEGMENT_VARIANT.attribute),
81
- children: attribute
82
- }),
84
+ attributeDescription ? /*#__PURE__*/ jsxs(Tooltip, {
85
+ disabled: baseActiveSegment === SEGMENT_VARIANT.attribute,
86
+ children: [
87
+ /*#__PURE__*/ jsx(TooltipTrigger, {
88
+ asChild: true,
89
+ children: attributeSegment
90
+ }),
91
+ /*#__PURE__*/ jsx(TooltipContent, {
92
+ children: attributeDescription
93
+ })
94
+ ]
95
+ }) : attributeSegment,
83
96
  (operator || baseActiveSegment === SEGMENT_VARIANT.operator) && /*#__PURE__*/ jsx(Segment, {
84
97
  variant: SEGMENT_VARIANT.operator,
85
98
  className: "shrink-0",
@@ -1,6 +1,9 @@
1
- import type { FC, FocusEvent, HTMLAttributes, KeyboardEvent } from 'react';
1
+ import type { FC, FocusEvent, HTMLAttributes, KeyboardEvent, Ref } from 'react';
2
2
  import { type SegmentVariant } from './segmentVariant';
3
3
  export type SegmentProps = HTMLAttributes<HTMLDivElement> & {
4
+ /** Forwarded to the segment container — lets a Tooltip/Popover trigger anchor
5
+ * to it via `asChild` (attribute-segment chip tooltip, AS-1060). */
6
+ ref?: Ref<HTMLDivElement>;
4
7
  variant: SegmentVariant;
5
8
  children: string;
6
9
  error?: boolean;
@@ -7,7 +7,7 @@ import { CHAR_WIDTH_PX } from "./constants.js";
7
7
  import { MultiValueSegment } from "./MultiValueSegment.js";
8
8
  import { useSizerWidth } from "./model/useSizerWidth.js";
9
9
  import { SEGMENT_VARIANT } from "./segmentVariant.js";
10
- const Segment = ({ variant, children, className, error, editing, editText, onEditChange, onEditKeyDown, onEditBlur, valueParts, valueSeparator = ', ', errorValueIndices, ...props })=>{
10
+ const Segment = ({ ref, variant, children, className, error, editing, editText, onEditChange, onEditKeyDown, onEditBlur, valueParts, valueSeparator = ', ', errorValueIndices, ...props })=>{
11
11
  const textRef = useRef(null);
12
12
  const inputRef = useRef(null);
13
13
  const sizerRef = useRef(null);
@@ -58,6 +58,7 @@ const Segment = ({ variant, children, className, error, editing, editText, onEdi
58
58
  ...props
59
59
  });
60
60
  return /*#__PURE__*/ jsx("div", {
61
+ ref: ref,
61
62
  className: cn(segmentContainer, className),
62
63
  "data-slot": `segment-${variant}`,
63
64
  ...isInteractive && {
@@ -0,0 +1,21 @@
1
+ import { type FC } from 'react';
2
+ export interface FieldMenuPopoverProps {
3
+ /** Menu open AND a described field is highlighted. */
4
+ open: boolean;
5
+ /** Filter name, shown monospace, matching the menu label's casing. */
6
+ title: string;
7
+ /** Short "what it filters by" description. */
8
+ description: string;
9
+ /** Optional monospace example block (wildcards, path patterns, ranges, IDs). */
10
+ example?: string;
11
+ /** Live rect of the highlighted row the popover aligns to. */
12
+ getAnchorRect: () => DOMRect | null;
13
+ /** Highlighted id; a change pokes a reposition as the anchor swaps between rows. */
14
+ repositionKey: string;
15
+ }
16
+ /**
17
+ * Discovery popover for the field-selection menu: on hover or keyboard focus of a
18
+ * filter attribute, surfaces its title, description, and optional example (AS-1060).
19
+ * Fully controlled by `open` + `getAnchorRect`; no trigger, never takes focus.
20
+ */
21
+ export declare const FieldMenuPopover: FC<FieldMenuPopoverProps>;
@@ -0,0 +1,75 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ import { Popover } from "@ark-ui/react";
4
+ import { PopoverContent } from "../../../Popover/index.js";
5
+ import { Separator } from "../../../Separator/index.js";
6
+ import { useFloatingRecomputeOn } from "../../hooks/useFloatingRecomputeOn.js";
7
+ const POPOVER_POSITIONING = {
8
+ placement: 'right-start',
9
+ gutter: 4,
10
+ flip: true,
11
+ overflowPadding: 8
12
+ };
13
+ const FieldMenuPopover = ({ open, title, description, example, getAnchorRect, repositionKey })=>{
14
+ useFloatingRecomputeOn(repositionKey, open);
15
+ useEffect(()=>{
16
+ if (!open) return;
17
+ let raf = 0;
18
+ const onScroll = ()=>{
19
+ if (raf) return;
20
+ raf = requestAnimationFrame(()=>{
21
+ raf = 0;
22
+ window.dispatchEvent(new Event('resize'));
23
+ });
24
+ };
25
+ window.addEventListener('scroll', onScroll, true);
26
+ return ()=>{
27
+ window.removeEventListener('scroll', onScroll, true);
28
+ if (raf) cancelAnimationFrame(raf);
29
+ };
30
+ }, [
31
+ open
32
+ ]);
33
+ return /*#__PURE__*/ jsx(Popover.Root, {
34
+ open: open,
35
+ positioning: {
36
+ ...POPOVER_POSITIONING,
37
+ getAnchorRect
38
+ },
39
+ autoFocus: false,
40
+ closeOnInteractOutside: false,
41
+ closeOnEscape: false,
42
+ modal: false,
43
+ lazyMount: true,
44
+ unmountOnExit: true,
45
+ children: /*#__PURE__*/ jsxs(PopoverContent, {
46
+ minWidth: "240px",
47
+ maxWidth: "340px",
48
+ className: "gap-6 pointer-events-none",
49
+ "data-testid": "field-menu-popover",
50
+ children: [
51
+ /*#__PURE__*/ jsx("p", {
52
+ className: "font-mono text-sm leading-sm text-text-primary",
53
+ children: title
54
+ }),
55
+ /*#__PURE__*/ jsx("p", {
56
+ className: "text-xs leading-xs text-text-secondary",
57
+ children: description
58
+ }),
59
+ example && /*#__PURE__*/ jsxs(Fragment, {
60
+ children: [
61
+ /*#__PURE__*/ jsx(Separator, {
62
+ spacing: 8
63
+ }),
64
+ /*#__PURE__*/ jsx("pre", {
65
+ className: "m-0 font-mono text-xs leading-xs text-text-secondary whitespace-pre-wrap",
66
+ children: example
67
+ })
68
+ ]
69
+ })
70
+ ]
71
+ })
72
+ });
73
+ };
74
+ FieldMenuPopover.displayName = 'FieldMenuPopover';
75
+ export { FieldMenuPopover };
@@ -1,5 +1,5 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useMemo } from "react";
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useMemo, useRef } from "react";
3
3
  import { cn } from "../../../../utils/cn.js";
4
4
  import { DropdownMenu, DropdownMenuContent, DropdownMenuFooter } from "../../../DropdownMenu/index.js";
5
5
  import { Kbd } from "../../../Kbd/Kbd.js";
@@ -7,7 +7,9 @@ import { KbdGroup } from "../../../Kbd/KbdGroup.js";
7
7
  import { buildFieldMenuSections } from "../../lib/index.js";
8
8
  import { useFieldMenuNavItems } from "../hooks/useFieldMenuNavItems.js";
9
9
  import { useKeyboardNav } from "../hooks/useKeyboardNav.js";
10
+ import { useMenuScrollHighlightSync } from "../hooks/useMenuScrollHighlightSync.js";
10
11
  import { MenuEmptyState } from "../MenuEmptyState.js";
12
+ import { FieldMenuPopover } from "./FieldMenuPopover.js";
11
13
  import { FieldSections, OperatorsSection, RecentSection, SuggestionsSection } from "./FieldMenuSections.js";
12
14
  const FilterInputFieldMenu = ({ fields, filterText = '', onSelect, open = false, onOpenChange, recentConditions = [], suggestedFields = [], fieldGroups, onSelectAnd, onSelectOr, onEscape, positioning, inputRef, menuRef, className })=>{
13
15
  const limitedRecentConditions = useMemo(()=>recentConditions.slice(0, 3), [
@@ -38,7 +40,7 @@ const FilterInputFieldMenu = ({ fields, filterText = '', onSelect, open = false,
38
40
  } else if ('and' === data.type) onSelectAnd?.();
39
41
  else if ('or' === data.type) onSelectOr?.();
40
42
  };
41
- const { highlightedValue, onHighlightChange, registerItem } = useKeyboardNav({
43
+ const { highlightedValue, onHighlightChange, registerItem, getItemElement } = useKeyboardNav({
42
44
  items: flatItems,
43
45
  open,
44
46
  onSelect: handleItemSelect,
@@ -49,74 +51,108 @@ const FilterInputFieldMenu = ({ fields, filterText = '', onSelect, open = false,
49
51
  menuRef
50
52
  });
51
53
  const hasResults = sections.length > 0 || !filterText;
52
- return /*#__PURE__*/ jsx(DropdownMenu, {
53
- open: open && hasResults,
54
- onOpenChange: onOpenChange,
55
- closeOnSelect: false,
56
- positioning: positioning,
57
- highlightedValue: highlightedValue,
58
- onHighlightChange: onHighlightChange,
59
- children: /*#__PURE__*/ jsxs(DropdownMenuContent, {
60
- ref: menuRef,
61
- className: cn('w-[300px] max-h-[430px]', className),
62
- "data-slot": "filter-input-field-menu",
63
- "data-filter-input-menu": "true",
64
- children: [
65
- !filterText && showRecent && /*#__PURE__*/ jsx(RecentSection, {
66
- conditions: limitedRecentConditions,
67
- fields: fields,
68
- onSelect: onSelect,
69
- registerItem: registerItem
70
- }),
71
- !filterText && showSuggestions && !showRecent && /*#__PURE__*/ jsx(SuggestionsSection, {
72
- fields: suggestedFields,
73
- onSelect: onSelect,
74
- registerItem: registerItem
75
- }),
76
- sections.length > 0 ? /*#__PURE__*/ jsx(FieldSections, {
77
- sections: sections,
78
- onSelect: onSelect,
79
- registerItem: registerItem
80
- }) : /*#__PURE__*/ jsx(MenuEmptyState, {}),
81
- !filterText && (onSelectAnd || onSelectOr) && /*#__PURE__*/ jsx(OperatorsSection, {
82
- onSelectAnd: onSelectAnd,
83
- onSelectOr: onSelectOr,
84
- registerItem: registerItem
85
- }),
86
- /*#__PURE__*/ jsxs(DropdownMenuFooter, {
87
- className: "justify-start",
54
+ const highlightedField = useMemo(()=>{
55
+ if (!highlightedValue) return;
56
+ const data = flatItems.find((i)=>i.id === highlightedValue)?.value;
57
+ return data?.type === 'field' ? data.field : void 0;
58
+ }, [
59
+ flatItems,
60
+ highlightedValue
61
+ ]);
62
+ const highlightedValueRef = useRef(highlightedValue);
63
+ highlightedValueRef.current = highlightedValue;
64
+ const getPopoverAnchorRect = useCallback(()=>{
65
+ const el = getItemElement(highlightedValueRef.current);
66
+ if (!el) return null;
67
+ const item = el.getBoundingClientRect();
68
+ const menu = el.closest('[data-filter-input-menu="true"]')?.getBoundingClientRect();
69
+ return menu ? new DOMRect(menu.x, item.y, menu.width, item.height) : item;
70
+ }, [
71
+ getItemElement
72
+ ]);
73
+ const popoverOpen = open && hasResults && !!highlightedField?.description;
74
+ const menuVisible = open && hasResults;
75
+ useMenuScrollHighlightSync(menuVisible, onHighlightChange);
76
+ return /*#__PURE__*/ jsxs(Fragment, {
77
+ children: [
78
+ /*#__PURE__*/ jsx(DropdownMenu, {
79
+ open: open && hasResults,
80
+ onOpenChange: onOpenChange,
81
+ closeOnSelect: false,
82
+ positioning: positioning,
83
+ highlightedValue: highlightedValue,
84
+ onHighlightChange: onHighlightChange,
85
+ children: /*#__PURE__*/ jsxs(DropdownMenuContent, {
86
+ ref: menuRef,
87
+ className: cn('w-[300px] max-h-[430px]', className),
88
+ "data-slot": "filter-input-field-menu",
89
+ "data-filter-input-menu": "true",
88
90
  children: [
89
- /*#__PURE__*/ jsxs("span", {
90
- className: "flex items-center gap-4",
91
+ !filterText && showRecent && /*#__PURE__*/ jsx(RecentSection, {
92
+ conditions: limitedRecentConditions,
93
+ fields: fields,
94
+ onSelect: onSelect,
95
+ registerItem: registerItem
96
+ }),
97
+ !filterText && showSuggestions && !showRecent && /*#__PURE__*/ jsx(SuggestionsSection, {
98
+ fields: suggestedFields,
99
+ onSelect: onSelect,
100
+ registerItem: registerItem
101
+ }),
102
+ sections.length > 0 ? /*#__PURE__*/ jsx(FieldSections, {
103
+ sections: sections,
104
+ onSelect: onSelect,
105
+ registerItem: registerItem
106
+ }) : /*#__PURE__*/ jsx(MenuEmptyState, {}),
107
+ !filterText && (onSelectAnd || onSelectOr) && /*#__PURE__*/ jsx(OperatorsSection, {
108
+ onSelectAnd: onSelectAnd,
109
+ onSelectOr: onSelectOr,
110
+ registerItem: registerItem
111
+ }),
112
+ /*#__PURE__*/ jsxs(DropdownMenuFooter, {
113
+ className: "justify-start",
91
114
  children: [
92
- /*#__PURE__*/ jsxs(KbdGroup, {
115
+ /*#__PURE__*/ jsxs("span", {
116
+ className: "flex items-center gap-4",
93
117
  children: [
94
- /*#__PURE__*/ jsx(Kbd, {
95
- children: "↑"
118
+ /*#__PURE__*/ jsxs(KbdGroup, {
119
+ children: [
120
+ /*#__PURE__*/ jsx(Kbd, {
121
+ children: "↑"
122
+ }),
123
+ /*#__PURE__*/ jsx(Kbd, {
124
+ children: "↓"
125
+ })
126
+ ]
96
127
  }),
97
- /*#__PURE__*/ jsx(Kbd, {
98
- children: "↓"
99
- })
128
+ "to navigate"
100
129
  ]
101
130
  }),
102
- "to navigate"
103
- ]
104
- }),
105
- /*#__PURE__*/ jsxs("span", {
106
- className: "flex items-center gap-4",
107
- children: [
108
- /*#__PURE__*/ jsx(KbdGroup, {
109
- children: /*#__PURE__*/ jsx(Kbd, {
110
- children: "↵"
111
- })
112
- }),
113
- "to select"
131
+ /*#__PURE__*/ jsxs("span", {
132
+ className: "flex items-center gap-4",
133
+ children: [
134
+ /*#__PURE__*/ jsx(KbdGroup, {
135
+ children: /*#__PURE__*/ jsx(Kbd, {
136
+ children: "↵"
137
+ })
138
+ }),
139
+ "to select"
140
+ ]
141
+ })
114
142
  ]
115
143
  })
116
144
  ]
117
145
  })
118
- ]
119
- })
146
+ }),
147
+ /*#__PURE__*/ jsx(FieldMenuPopover, {
148
+ open: popoverOpen,
149
+ title: highlightedField?.label ?? '',
150
+ description: highlightedField?.description ?? '',
151
+ example: highlightedField?.example,
152
+ getAnchorRect: getPopoverAnchorRect,
153
+ repositionKey: highlightedValue
154
+ })
155
+ ]
120
156
  });
121
157
  };
122
158
  FilterInputFieldMenu.displayName = 'FilterInputFieldMenu';
@@ -35,5 +35,6 @@ export declare const useKeyboardNav: ({ items, open, onSelect, onClose, onArrowR
35
35
  }) => void;
36
36
  pendingIds: Set<string>;
37
37
  registerItem: (id: string) => (el: HTMLElement | null) => void;
38
+ getItemElement: (id: string) => HTMLElement | null;
38
39
  };
39
40
  export {};
@@ -7,6 +7,7 @@ const useKeyboardNav = ({ items, open, onSelect, onClose, onArrowRight, onArrowL
7
7
  if (el) itemRegistryRef.current.set(id, el);
8
8
  else itemRegistryRef.current.delete(id);
9
9
  }, []);
10
+ const getItemElement = useCallback((id)=>itemRegistryRef.current.get(id) ?? null, []);
10
11
  const activeIndexRef = useRef(-1);
11
12
  const pendingIdsRef = useRef(pendingIds);
12
13
  pendingIdsRef.current = pendingIds;
@@ -253,7 +254,8 @@ const useKeyboardNav = ({ items, open, onSelect, onClose, onArrowRight, onArrowL
253
254
  highlightedValue,
254
255
  onHighlightChange,
255
256
  pendingIds,
256
- registerItem
257
+ registerItem,
258
+ getItemElement
257
259
  };
258
260
  };
259
261
  export { useKeyboardNav };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Keeps the field-menu highlight — and the description popover that follows it —
3
+ * on the row under the cursor when the list scrolls beneath a stationary pointer
4
+ * (wheel/trackpad). Ark moves the highlight only on `pointermove`, so a scroll
5
+ * would otherwise strand it. On scroll we hit-test the last pointer and drive the
6
+ * highlight via `onHighlightChange` (a synthetic `pointermove` is ignored by zag
7
+ * when the position is unchanged). Guarded off during arrow-key nav so a resting
8
+ * pointer can't hijack the keyboard. Pass `enabled` for the whole time the menu
9
+ * is visible so the pointer is tracked before the first scroll (AS-1060).
10
+ */
11
+ export declare const useMenuScrollHighlightSync: (enabled: boolean, onHighlightChange: (details: {
12
+ highlightedValue: string | null;
13
+ }) => void) => void;
@@ -0,0 +1,46 @@
1
+ import { useEffect, useRef } from "react";
2
+ const useMenuScrollHighlightSync = (enabled, onHighlightChange)=>{
3
+ const lastPointerRef = useRef(null);
4
+ const keyboardNavRef = useRef(false);
5
+ useEffect(()=>{
6
+ if (!enabled) return;
7
+ const trackPointer = (e)=>{
8
+ keyboardNavRef.current = false;
9
+ lastPointerRef.current = {
10
+ x: e.clientX,
11
+ y: e.clientY
12
+ };
13
+ };
14
+ const trackKeyboard = (e)=>{
15
+ if ('ArrowDown' === e.key || 'ArrowUp' === e.key) keyboardNavRef.current = true;
16
+ };
17
+ let raf = 0;
18
+ const onScroll = ()=>{
19
+ if (raf || keyboardNavRef.current) return;
20
+ raf = requestAnimationFrame(()=>{
21
+ raf = 0;
22
+ const p = lastPointerRef.current;
23
+ if (!p) return;
24
+ const item = document.elementFromPoint(p.x, p.y)?.closest('[role="menuitem"]');
25
+ if (!item?.closest('[data-filter-input-menu="true"]')) return;
26
+ const value = item.getAttribute('data-value');
27
+ if (value) onHighlightChange({
28
+ highlightedValue: value
29
+ });
30
+ });
31
+ };
32
+ window.addEventListener('pointermove', trackPointer, true);
33
+ window.addEventListener('keydown', trackKeyboard, true);
34
+ window.addEventListener('scroll', onScroll, true);
35
+ return ()=>{
36
+ window.removeEventListener('pointermove', trackPointer, true);
37
+ window.removeEventListener('keydown', trackKeyboard, true);
38
+ window.removeEventListener('scroll', onScroll, true);
39
+ if (raf) cancelAnimationFrame(raf);
40
+ };
41
+ }, [
42
+ enabled,
43
+ onHighlightChange
44
+ ]);
45
+ };
46
+ export { useMenuScrollHighlightSync };
@@ -36,6 +36,9 @@ const buildBaseChip = (i, condition, field)=>({
36
36
  id: chipId(i),
37
37
  variant: 'chip',
38
38
  attribute: field?.label || condition.field,
39
+ ...field?.description && {
40
+ attributeDescription: field.description
41
+ },
39
42
  operator: condition.operator ? getOperatorLabel(condition.operator, field?.type || DEFAULT_FIELD_TYPE) : void 0,
40
43
  ...condition.disabled && {
41
44
  disabled: true
@@ -22,6 +22,11 @@ export interface FilterInputChipData {
22
22
  id: string;
23
23
  variant: FilterInputChipVariant;
24
24
  attribute?: string;
25
+ /**
26
+ * Description shown as a dark tooltip on the chip's attribute segment; absent →
27
+ * no tooltip. Recall counterpart of the field-menu popover (AS-1060).
28
+ */
29
+ attributeDescription?: string;
25
30
  operator?: string;
26
31
  value?: string;
27
32
  error?: ChipErrorSegment;
@@ -106,6 +111,11 @@ export interface FieldMetadata {
106
111
  label: string;
107
112
  type: FieldType;
108
113
  description?: string;
114
+ /**
115
+ * Monospace example block shown under the description in the field-menu popover,
116
+ * for non-obvious value formats (wildcards, ranges, IDs). Multi-line via `\n` (AS-1060).
117
+ */
118
+ example?: string;
109
119
  operators?: FilterOperator[];
110
120
  default?: string | number | boolean;
111
121
  values?: FieldValueOption[];
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "0.89.0",
3
- "generatedAt": "2026-08-14T11:47:35.825Z",
2
+ "version": "1.0.0",
3
+ "generatedAt": "2026-08-20T14:13:55.149Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
@@ -30188,6 +30188,12 @@
30188
30188
  "type": "string",
30189
30189
  "required": true
30190
30190
  },
30191
+ {
30192
+ "name": "attributeDescription",
30193
+ "type": "string | undefined",
30194
+ "required": false,
30195
+ "description": "Description shown in a dark tooltip on hover of the attribute segment. When\n absent, no tooltip renders. The operator/value segments are unaffected (AS-1060)."
30196
+ },
30191
30197
  {
30192
30198
  "name": "operator",
30193
30199
  "type": "string | undefined",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-rc-feature-AS-1060.1",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",