@cdx-ui/components 0.0.1-beta.159 → 0.0.1-beta.160

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 (105) hide show
  1. package/coverage.svg +1 -1
  2. package/lib/commonjs/components/Menu/index.js +499 -0
  3. package/lib/commonjs/components/Menu/index.js.map +1 -0
  4. package/lib/commonjs/components/Menu/styles.js +100 -0
  5. package/lib/commonjs/components/Menu/styles.js.map +1 -0
  6. package/lib/commonjs/components/Select/index.js +57 -22
  7. package/lib/commonjs/components/Select/index.js.map +1 -1
  8. package/lib/commonjs/components/Select/styles.js +1 -40
  9. package/lib/commonjs/components/Select/styles.js.map +1 -1
  10. package/lib/commonjs/components/Slider/getSliderPopupPosition.js +16 -6
  11. package/lib/commonjs/components/Slider/getSliderPopupPosition.js.map +1 -1
  12. package/lib/commonjs/components/Slider/index.js +17 -5
  13. package/lib/commonjs/components/Slider/index.js.map +1 -1
  14. package/lib/commonjs/components/Toast/index.js +1 -1
  15. package/lib/commonjs/components/Toast/index.js.map +1 -1
  16. package/lib/commonjs/components/index.js +12 -0
  17. package/lib/commonjs/components/index.js.map +1 -1
  18. package/lib/commonjs/components/picker/surfaces.js +16 -9
  19. package/lib/commonjs/components/picker/surfaces.js.map +1 -1
  20. package/lib/commonjs/figma/Menu.Item.figma.js +52 -0
  21. package/lib/commonjs/figma/Menu.Item.figma.js.map +1 -0
  22. package/lib/commonjs/figma/Menu.figma.js +29 -0
  23. package/lib/commonjs/figma/Menu.figma.js.map +1 -0
  24. package/lib/commonjs/providers/forge-design/provider.js +25 -11
  25. package/lib/commonjs/providers/forge-design/provider.js.map +1 -1
  26. package/lib/commonjs/providers/toast/insets-container.js +4 -1
  27. package/lib/commonjs/providers/toast/insets-container.js.map +1 -1
  28. package/lib/commonjs/providers/toast/provider.js +126 -45
  29. package/lib/commonjs/providers/toast/provider.js.map +1 -1
  30. package/lib/commonjs/providers/toast/toast-item-renderer.js +2 -1
  31. package/lib/commonjs/providers/toast/toast-item-renderer.js.map +1 -1
  32. package/lib/module/components/Menu/index.js +495 -0
  33. package/lib/module/components/Menu/index.js.map +1 -0
  34. package/lib/module/components/Menu/styles.js +96 -0
  35. package/lib/module/components/Menu/styles.js.map +1 -0
  36. package/lib/module/components/Select/index.js +55 -20
  37. package/lib/module/components/Select/index.js.map +1 -1
  38. package/lib/module/components/Select/styles.js +0 -39
  39. package/lib/module/components/Select/styles.js.map +1 -1
  40. package/lib/module/components/Slider/getSliderPopupPosition.js +16 -6
  41. package/lib/module/components/Slider/getSliderPopupPosition.js.map +1 -1
  42. package/lib/module/components/Slider/index.js +20 -8
  43. package/lib/module/components/Slider/index.js.map +1 -1
  44. package/lib/module/components/Toast/index.js +1 -1
  45. package/lib/module/components/Toast/index.js.map +1 -1
  46. package/lib/module/components/index.js +1 -0
  47. package/lib/module/components/index.js.map +1 -1
  48. package/lib/module/components/picker/surfaces.js +18 -11
  49. package/lib/module/components/picker/surfaces.js.map +1 -1
  50. package/lib/module/figma/Menu.Item.figma.js +46 -0
  51. package/lib/module/figma/Menu.Item.figma.js.map +1 -0
  52. package/lib/module/figma/Menu.figma.js +23 -0
  53. package/lib/module/figma/Menu.figma.js.map +1 -0
  54. package/lib/module/providers/forge-design/provider.js +26 -12
  55. package/lib/module/providers/forge-design/provider.js.map +1 -1
  56. package/lib/module/providers/toast/insets-container.js +5 -2
  57. package/lib/module/providers/toast/insets-container.js.map +1 -1
  58. package/lib/module/providers/toast/provider.js +126 -45
  59. package/lib/module/providers/toast/provider.js.map +1 -1
  60. package/lib/module/providers/toast/toast-item-renderer.js +2 -1
  61. package/lib/module/providers/toast/toast-item-renderer.js.map +1 -1
  62. package/lib/typescript/components/Menu/index.d.ts +119 -0
  63. package/lib/typescript/components/Menu/index.d.ts.map +1 -0
  64. package/lib/typescript/components/Menu/styles.d.ts +24 -0
  65. package/lib/typescript/components/Menu/styles.d.ts.map +1 -0
  66. package/lib/typescript/components/Select/index.d.ts.map +1 -1
  67. package/lib/typescript/components/Select/styles.d.ts +0 -7
  68. package/lib/typescript/components/Select/styles.d.ts.map +1 -1
  69. package/lib/typescript/components/Slider/getSliderPopupPosition.d.ts +1 -1
  70. package/lib/typescript/components/Slider/getSliderPopupPosition.d.ts.map +1 -1
  71. package/lib/typescript/components/Slider/index.d.ts.map +1 -1
  72. package/lib/typescript/components/Toast/types.d.ts +2 -2
  73. package/lib/typescript/components/Toast/types.d.ts.map +1 -1
  74. package/lib/typescript/components/index.d.ts +1 -0
  75. package/lib/typescript/components/index.d.ts.map +1 -1
  76. package/lib/typescript/components/picker/surfaces.d.ts.map +1 -1
  77. package/lib/typescript/figma/Menu.Item.figma.d.ts +8 -0
  78. package/lib/typescript/figma/Menu.Item.figma.d.ts.map +1 -0
  79. package/lib/typescript/figma/Menu.figma.d.ts +8 -0
  80. package/lib/typescript/figma/Menu.figma.d.ts.map +1 -0
  81. package/lib/typescript/providers/forge-design/provider.d.ts.map +1 -1
  82. package/lib/typescript/providers/toast/insets-container.d.ts.map +1 -1
  83. package/lib/typescript/providers/toast/lifecycle.context.d.ts +2 -2
  84. package/lib/typescript/providers/toast/lifecycle.context.d.ts.map +1 -1
  85. package/lib/typescript/providers/toast/provider.d.ts.map +1 -1
  86. package/lib/typescript/providers/toast/toast-item-renderer.d.ts.map +1 -1
  87. package/lib/typescript/providers/toast/types.d.ts +22 -8
  88. package/lib/typescript/providers/toast/types.d.ts.map +1 -1
  89. package/package.json +5 -5
  90. package/src/components/Menu/index.tsx +654 -0
  91. package/src/components/Menu/styles.ts +153 -0
  92. package/src/components/Select/index.tsx +58 -23
  93. package/src/components/Select/styles.ts +0 -59
  94. package/src/components/Slider/getSliderPopupPosition.ts +19 -7
  95. package/src/components/Slider/index.tsx +25 -9
  96. package/src/components/Toast/index.tsx +1 -1
  97. package/src/components/Toast/types.ts +2 -2
  98. package/src/components/index.ts +1 -0
  99. package/src/components/picker/surfaces.tsx +13 -9
  100. package/src/providers/forge-design/provider.tsx +35 -16
  101. package/src/providers/toast/insets-container.tsx +3 -2
  102. package/src/providers/toast/lifecycle.context.tsx +2 -2
  103. package/src/providers/toast/provider.tsx +150 -51
  104. package/src/providers/toast/toast-item-renderer.tsx +5 -1
  105. package/src/providers/toast/types.ts +22 -8
@@ -0,0 +1,153 @@
1
+ import { Platform } from 'react-native';
2
+ import { cva, type VariantProps } from '@cdx-ui/utils';
3
+ import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/constants';
4
+
5
+ export const menuRootVariants = cva(
6
+ [
7
+ 'bg-surface-primary',
8
+ 'border border-stroke-secondary',
9
+ 'overflow-hidden',
10
+ 'rounded-xl',
11
+ 'p-1',
12
+ 'shadow-lg',
13
+ 'web:outline-none',
14
+ ],
15
+ {
16
+ variants: {
17
+ scrollable: {
18
+ true: 'max-h-60 shrink',
19
+ false: '',
20
+ },
21
+ },
22
+ defaultVariants: {
23
+ scrollable: false,
24
+ },
25
+ },
26
+ );
27
+
28
+ export const menuItemVariants = cva(
29
+ [
30
+ 'flex-row items-center gap-2',
31
+ 'min-h-9',
32
+ 'rounded-lg',
33
+ TRANSITION_COLORS,
34
+ 'data-[disabled=true]:opacity-50 data-[disabled=true]:pointer-events-none',
35
+ DISABLED_CURSOR,
36
+ 'data-[state=checked]:bg-surface-action-tint data-[state=checked]:font-medium',
37
+ Platform.select({
38
+ default: 'data-[active=true]:bg-surface-action-tint-active',
39
+ web: 'data-[hover=true]:bg-surface-action-tint-hover data-[active=true]:data-[hover=true]:bg-surface-action-tint-active data-[highlighted=true]:bg-surface-action-tint-hover',
40
+ }),
41
+ ],
42
+ {
43
+ variants: {
44
+ color: {
45
+ neutral: 'text-content-primary',
46
+ danger:
47
+ 'text-content-danger data-[hover=true]:bg-surface-danger-tint-hover data-[highlighted=true]:bg-surface-danger-tint-hover',
48
+ },
49
+ inset: {
50
+ true: '',
51
+ false: '',
52
+ },
53
+ size: {
54
+ default: 'px-3 py-2',
55
+ small: 'px-2.5 py-1.5',
56
+ },
57
+ },
58
+ compoundVariants: [
59
+ {
60
+ inset: true,
61
+ size: 'default',
62
+ class: 'ps-8 pe-3 py-2',
63
+ },
64
+ {
65
+ inset: true,
66
+ size: 'small',
67
+ class: 'ps-8 pe-2.5 py-1.5',
68
+ },
69
+ {
70
+ inset: false,
71
+ size: 'default',
72
+ class: 'px-3 py-2',
73
+ },
74
+ {
75
+ inset: false,
76
+ size: 'small',
77
+ class: 'px-2.5 py-1.5',
78
+ },
79
+ ],
80
+ defaultVariants: {
81
+ color: 'neutral',
82
+ inset: false,
83
+ size: 'default',
84
+ },
85
+ },
86
+ );
87
+
88
+ export const menuItemLabelVariants = cva(
89
+ [
90
+ 'text-start',
91
+ Platform.select({
92
+ default: 'shrink-0',
93
+ web: 'min-w-0 flex-1',
94
+ }),
95
+ ],
96
+ {
97
+ variants: {
98
+ size: {
99
+ default: 'text-[0.875rem]',
100
+ small: 'text-[0.75rem]',
101
+ },
102
+ color: {
103
+ neutral: 'text-content-primary',
104
+ danger: 'text-content-danger',
105
+ },
106
+ },
107
+ defaultVariants: {
108
+ size: 'default',
109
+ color: 'neutral',
110
+ },
111
+ },
112
+ );
113
+
114
+ export const menuItemIconVariants = cva(['shrink-0', 'size-5'], {
115
+ variants: {
116
+ color: {
117
+ neutral: 'text-content-secondary',
118
+ danger: 'text-content-danger',
119
+ },
120
+ },
121
+ defaultVariants: {
122
+ color: 'neutral',
123
+ },
124
+ });
125
+
126
+ export const menuItemShortcutVariants = cva([
127
+ 'shrink-0 text-content-tertiary',
128
+ 'text-[0.75rem]',
129
+ 'ms-auto',
130
+ ]);
131
+
132
+ export const menuItemIndicatorVariants = cva([
133
+ 'shrink-0 data-[icon=inline-end]:ms-auto',
134
+ 'text-content-action',
135
+ 'size-5',
136
+ ]);
137
+
138
+ export const menuSectionLabelVariants = cva([
139
+ 'px-3 py-1.5',
140
+ 'text-[0.75rem] font-medium',
141
+ 'text-content-tertiary',
142
+ ]);
143
+
144
+ export const menuSeparatorVariants = cva([
145
+ // Bleed past menu `p-1` inset so the line spans the full panel width (Figma: px-[-4px]).
146
+ // Omit `w-full`: percentage width ignores the negative margins and leaves a right-side gap.
147
+ 'self-stretch py-1 -mx-1',
148
+ ]);
149
+
150
+ export const menuSeparatorLineVariants = cva(['h-px w-full bg-stroke-secondary']);
151
+
152
+ export type MenuRootVariantProps = VariantProps<typeof menuRootVariants>;
153
+ export type MenuItemVariantProps = VariantProps<typeof menuItemVariants>;
@@ -1,4 +1,4 @@
1
- import { forwardRef, useMemo, type ReactNode } from 'react';
1
+ import { cloneElement, forwardRef, isValidElement, useMemo, type ReactNode } from 'react';
2
2
  import { Text, type TextProps, View, type ViewProps } from 'react-native';
3
3
  import { KeyboardArrowDown, KeyboardArrowUp } from '@cdx-ui/icons';
4
4
  import {
@@ -20,13 +20,12 @@ import {
20
20
  } from '@cdx-ui/primitives';
21
21
  import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
22
22
  import { Icon, IconProps } from '../Icon';
23
+ import { Menu } from '../Menu';
24
+ import { menuItemLabelVariants, menuItemVariants } from '../Menu/styles';
23
25
  import { positionToDataIcon, type IconSlotPosition } from '../../utils/positionToDataIcon';
24
26
  import {
25
27
  type SelectVariantProps,
26
- selectContentVariants,
27
28
  selectIconVariants,
28
- selectItemLabelVariants,
29
- selectItemVariants,
30
29
  selectTriggerVariants,
31
30
  selectValueVariants,
32
31
  } from './styles';
@@ -35,6 +34,9 @@ const SCOPE = 'SELECT';
35
34
 
36
35
  const useSelectStyleContext = () => useStyleContext(SCOPE) as SelectVariantProps;
37
36
 
37
+ const toMenuItemSize = (size: SelectVariantProps['size'] | undefined) =>
38
+ size === 'small' ? 'small' : 'default';
39
+
38
40
  // =============================================================================
39
41
  // STYLED ROOT
40
42
  // =============================================================================
@@ -213,7 +215,7 @@ const SelectIcon = ({ asChild, className, style, as, position, ...props }: Selec
213
215
  SelectIcon.displayName = 'Select.Icon';
214
216
 
215
217
  // =============================================================================
216
- // STYLED CONTENT
218
+ // STYLED CONTENT — composes Menu surface (listbox semantics stay on primitive)
217
219
  // =============================================================================
218
220
 
219
221
  export interface SelectContentProps extends ViewProps, ISelectContentProps {
@@ -222,18 +224,33 @@ export interface SelectContentProps extends ViewProps, ISelectContentProps {
222
224
  }
223
225
 
224
226
  const SelectContent = forwardRef<View, SelectContentProps>(
225
- ({ asChild, className, style, ...props }, ref) => {
226
- const computedClassName = cn(selectContentVariants(), className);
227
+ ({ asChild, className, style, children, ...props }, ref) => {
228
+ if (asChild) {
229
+ return (
230
+ <SelectContentPrimitive
231
+ ref={ref}
232
+ asChild
233
+ className={className}
234
+ style={style}
235
+ {...props}
236
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'select-content' }))}
237
+ >
238
+ {children}
239
+ </SelectContentPrimitive>
240
+ );
241
+ }
227
242
 
228
243
  return (
229
244
  <SelectContentPrimitive
230
245
  ref={ref}
231
- asChild={asChild}
232
- className={computedClassName}
233
246
  style={style}
234
247
  {...props}
235
248
  {...mergeDataAttributes(props, dataAttributes({ slot: 'select-content' }))}
236
- />
249
+ >
250
+ <Menu.Content asSurface scrollable className={className}>
251
+ {children}
252
+ </Menu.Content>
253
+ </SelectContentPrimitive>
237
254
  );
238
255
  },
239
256
  );
@@ -241,7 +258,7 @@ const SelectContent = forwardRef<View, SelectContentProps>(
241
258
  SelectContent.displayName = 'Select.Content';
242
259
 
243
260
  // =============================================================================
244
- // STYLED ITEM
261
+ // STYLED ITEM — composes Menu item visuals (option semantics stay on primitive)
245
262
  // =============================================================================
246
263
 
247
264
  export interface SelectItemProps extends ISelectItemProps {
@@ -251,21 +268,38 @@ export interface SelectItemProps extends ISelectItemProps {
251
268
  }
252
269
 
253
270
  const SelectItem = forwardRef<View, SelectItemProps>(
254
- ({ asChild, className, children, style, ...props }, ref) => {
271
+ ({ asChild, className, children, style, value: itemValue, ...props }, ref) => {
255
272
  const { size } = useSelectStyleContext();
256
-
257
- const computedClassName = cn(selectItemVariants({ size }), className);
273
+ const { value: selectedValue } = useSelectContext();
274
+ const menuSize = toMenuItemSize(size);
275
+ const checked = selectedValue === itemValue;
276
+
277
+ const computedClassName = cn(menuItemVariants({ size: menuSize }), className);
278
+ const selectedIcon = <Menu.ItemIndicator isChecked={checked} />;
279
+ const itemChildren = asChild ? (
280
+ selectedIcon && isValidElement<{ children?: ReactNode }>(children) ? (
281
+ cloneElement(children, undefined, children.props.children, selectedIcon)
282
+ ) : (
283
+ children
284
+ )
285
+ ) : (
286
+ <>
287
+ {children}
288
+ {selectedIcon}
289
+ </>
290
+ );
258
291
 
259
292
  return (
260
293
  <SelectItemPrimitive
261
294
  ref={ref}
262
295
  asChild={asChild}
296
+ value={itemValue}
263
297
  className={computedClassName}
264
298
  style={style}
265
299
  {...props}
266
300
  {...mergeDataAttributes(props, dataAttributes({ slot: 'select-item' }))}
267
301
  >
268
- {children}
302
+ {itemChildren}
269
303
  </SelectItemPrimitive>
270
304
  );
271
305
  },
@@ -274,7 +308,7 @@ const SelectItem = forwardRef<View, SelectItemProps>(
274
308
  SelectItem.displayName = 'Select.Item';
275
309
 
276
310
  // =============================================================================
277
- // STYLED ITEM LABEL (styled-only, reads item context for checked state)
311
+ // STYLED ITEM LABEL — composes Menu.ItemLabel
278
312
  // =============================================================================
279
313
 
280
314
  export interface SelectItemLabelProps extends TextProps, ISelectItemLabelProps {
@@ -286,26 +320,27 @@ const SelectItemLabel = forwardRef<Text, SelectItemLabelProps>(
286
320
  ({ asChild, className, style, children, ...props }, ref) => {
287
321
  const { size } = useSelectStyleContext();
288
322
  const { checked } = useSelectItemContext();
289
-
290
- const computedClassName = cn(selectItemLabelVariants({ size }), className);
291
- const Comp = asChild ? Slot : Text;
323
+ const menuSize = toMenuItemSize(size);
292
324
 
293
325
  return (
294
- <Comp
326
+ <Menu.ItemLabel
295
327
  ref={ref}
296
- className={computedClassName}
328
+ asChild={asChild}
329
+ isChecked={checked}
330
+ size={menuSize}
331
+ className={cn(menuItemLabelVariants({ size: menuSize }), className)}
297
332
  style={style}
298
333
  {...props}
299
334
  {...mergeDataAttributes(
300
- props,
301
335
  dataAttributes({
302
336
  slot: 'select-item-label',
303
337
  state: checked ? 'checked' : 'unchecked',
304
338
  }),
339
+ props,
305
340
  )}
306
341
  >
307
342
  {children}
308
- </Comp>
343
+ </Menu.ItemLabel>
309
344
  );
310
345
  },
311
346
  );
@@ -1,4 +1,3 @@
1
- import { Platform } from 'react-native';
2
1
  import { cva, type VariantProps } from '@cdx-ui/utils';
3
2
  import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/constants';
4
3
 
@@ -112,62 +111,4 @@ export const selectIconVariants = cva(
112
111
  },
113
112
  );
114
113
 
115
- // ── Content ─────────────────────────────────────────────────
116
-
117
- export const selectContentVariants = cva([
118
- 'bg-surface-primary',
119
- 'border border-stroke-secondary',
120
- 'overflow-hidden',
121
- 'rounded-xl',
122
- Platform.select({
123
- web: 'shadow-sm',
124
- default: '',
125
- }),
126
- 'max-h-60',
127
- 'py-1',
128
- ]);
129
-
130
- // ── Item ────────────────────────────────────────────────────
131
-
132
- export const selectItemVariants = cva(
133
- [
134
- 'flex-row items-center',
135
- 'transition-colors duration-100',
136
- 'data-[disabled=true]:opacity-50 data-[disabled=true]:pointer-events-none',
137
- 'data-[state=checked]:bg-surface-action-tint data-[state=checked]:font-medium',
138
- Platform.select({
139
- default: 'data-[active=true]:bg-surface-action-tint-active',
140
- web: 'data-[hover=true]:bg-surface-action-tint-hover data-[active=true]:data-[hover=true]:bg-surface-action-tint-active data-[highlighted=true]:bg-surface-action-tint-hover',
141
- }),
142
- ],
143
- {
144
- variants: {
145
- size: {
146
- default: 'px-3 py-2',
147
- small: 'px-2.5 py-1.5',
148
- },
149
- },
150
- defaultVariants: {
151
- size: 'default',
152
- },
153
- },
154
- );
155
-
156
- // ── ItemLabel ───────────────────────────────────────────────
157
-
158
- export const selectItemLabelVariants = cva(
159
- ['text-content-primary', 'data-[state=checked]:font-medium'],
160
- {
161
- variants: {
162
- size: {
163
- default: 'text-[0.875rem]',
164
- small: 'text-[0.75rem]',
165
- },
166
- },
167
- defaultVariants: {
168
- size: 'default',
169
- },
170
- },
171
- );
172
-
173
114
  export type SelectVariantProps = VariantProps<typeof selectTriggerVariants>;
@@ -1,5 +1,9 @@
1
1
  import { Platform, type ViewStyle } from 'react-native';
2
- import type { AnchorLayout, SliderOrientation } from '@cdx-ui/primitives';
2
+ import {
3
+ getAnchoredOverlayPosition,
4
+ type AnchorLayout,
5
+ type SliderOrientation,
6
+ } from '@cdx-ui/primitives';
3
7
 
4
8
  export const SLIDER_POPUP_OFFSET = 8;
5
9
  export const SLIDER_POPUP_Z_INDEX = 9999;
@@ -13,24 +17,32 @@ export function getSliderPopupAnchorStyle(
13
17
  orientation: SliderOrientation,
14
18
  offset = SLIDER_POPUP_OFFSET,
15
19
  ): ViewStyle {
20
+ const result = getAnchoredOverlayPosition(
21
+ anchor,
22
+ { width: 0, height: 0 },
23
+ { width: Number.MAX_SAFE_INTEGER, height: Number.MAX_SAFE_INTEGER },
24
+ {
25
+ placement: orientation === 'vertical' ? 'right' : 'top',
26
+ offset,
27
+ collisionBehavior: 'none',
28
+ },
29
+ );
16
30
  const position = (Platform.OS === 'web' ? 'fixed' : 'absolute') as ViewStyle['position'];
17
31
 
18
32
  if (orientation === 'vertical') {
19
- const centerY = anchor.y + anchor.height / 2;
20
33
  return {
21
34
  position,
22
- left: anchor.x + anchor.width + offset,
23
- top: centerY,
35
+ left: result.style.left,
36
+ top: result.style.top,
24
37
  zIndex: SLIDER_POPUP_Z_INDEX,
25
38
  transform: [{ translateY: '-50%' }] as ViewStyle['transform'],
26
39
  };
27
40
  }
28
41
 
29
- const centerX = anchor.x + anchor.width / 2;
30
42
  return {
31
43
  position,
32
- left: centerX,
33
- top: anchor.y - offset,
44
+ left: result.style.left,
45
+ top: result.style.top,
34
46
  zIndex: SLIDER_POPUP_Z_INDEX,
35
47
  transform: [{ translateX: '-50%' }, { translateY: '-100%' }] as ViewStyle['transform'],
36
48
  };
@@ -16,7 +16,7 @@ import {
16
16
  Slot,
17
17
  Portal,
18
18
  createPortalFn,
19
- useAnchorPosition,
19
+ useAnchoredOverlay,
20
20
  dataAttributes,
21
21
  getMarkerPositionStyle,
22
22
  getMarkerStepValues,
@@ -29,7 +29,14 @@ import {
29
29
  type ISliderThumbProps,
30
30
  type SliderOrientation,
31
31
  } from '@cdx-ui/primitives';
32
- import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
32
+ import {
33
+ cn,
34
+ composeEventHandlers,
35
+ mergeRefs,
36
+ ParentContext,
37
+ useParentContext,
38
+ useStyleContext,
39
+ } from '@cdx-ui/utils';
33
40
  import {
34
41
  sliderMarkerVariants,
35
42
  sliderPopupBaseVariants,
@@ -42,7 +49,7 @@ import {
42
49
  type SliderColor,
43
50
  type SliderVariantProps,
44
51
  } from './styles';
45
- import { getSliderPopupAnchorStyle, SLIDER_POPUP_Z_INDEX } from './getSliderPopupPosition';
52
+ import { SLIDER_POPUP_OFFSET, SLIDER_POPUP_Z_INDEX } from './getSliderPopupPosition';
46
53
 
47
54
  const SCOPE = 'SLIDER';
48
55
 
@@ -256,33 +263,42 @@ export interface SliderPopupProps extends ViewProps {
256
263
  }
257
264
 
258
265
  const SliderPopup = forwardRef<View, SliderPopupProps>(
259
- ({ asChild, className, style, children, ...props }, ref) => {
266
+ ({ asChild, className, style, children, onLayout, ...props }, ref) => {
260
267
  const { orientation } = useSliderStyleContext();
261
268
  const { isDragging, isHovered, isFocused, getValueLabel, thumbRef, percent } =
262
269
  useSliderContext();
263
270
  const portalName = useId();
264
271
 
265
272
  const visible = isDragging || isHovered || isFocused;
266
- const anchorLayout = useAnchorPosition(thumbRef, visible, {
273
+ const {
274
+ floatingRef,
275
+ onFloatingLayout,
276
+ style: popupPositionStyle,
277
+ } = useAnchoredOverlay(thumbRef, visible, {
278
+ placement: orientation === 'vertical' ? 'right' : 'top',
279
+ offset: SLIDER_POPUP_OFFSET,
280
+ collisionBehavior: 'none',
281
+ width: 'content',
267
282
  remeasureKey: percent,
268
283
  trackWhileOpen: true,
269
284
  });
270
285
 
271
- if (!visible || anchorLayout == null) {
286
+ if (!visible) {
272
287
  return null;
273
288
  }
274
289
 
275
290
  const label = renderPopupLabel(children, getValueLabel);
276
291
  const computedClassName = cn(sliderPopupBaseVariants(), className);
277
292
  const Comp = asChild ? Slot : View;
278
- const anchorStyle = getSliderPopupAnchorStyle(anchorLayout, orientation);
279
293
 
280
294
  const popup = (
281
- <View pointerEvents="none" style={anchorStyle}>
295
+ <View pointerEvents="none" style={[popupPositionStyle, { zIndex: SLIDER_POPUP_Z_INDEX }]}>
282
296
  <Comp
283
- ref={ref}
297
+ ref={mergeRefs(ref, floatingRef)}
284
298
  className={computedClassName}
285
299
  style={style}
300
+ onLayout={composeEventHandlers(onLayout, onFloatingLayout)}
301
+ {...props}
286
302
  accessibilityElementsHidden
287
303
  importantForAccessibility="no-hide-descendants"
288
304
  {...mergeDataAttributes(
@@ -155,7 +155,7 @@ const ToastRoot = forwardRef<View, ToastProps>((props, ref) => {
155
155
 
156
156
  const lifecycle = useToastLifecycle();
157
157
  const isPendingRemoval = lifecycle?.pendingRemovalIds.has(id) ?? false;
158
- const isFrontmost = lifecycle?.frontmostId === id;
158
+ const isFrontmost = lifecycle?.frontmostIds.has(id) ?? false;
159
159
 
160
160
  const [localState, setLocalState] = useState<Exclude<ToastDataState, 'closed'>>('entering');
161
161
 
@@ -123,7 +123,7 @@ export type ToastRootAnimation = AnimationRoot<{
123
123
  /**
124
124
  * Props for the Toast.Root component
125
125
  */
126
- export interface ToastProps extends ToastRootProps, Omit<ToastComponentProps, 'id'> {
126
+ export interface ToastProps extends ToastRootProps, Omit<ToastComponentProps, 'id' | 'placement'> {
127
127
  /**
128
128
  * Color of the toast
129
129
  * @default 'neutral'
@@ -291,7 +291,7 @@ export type UseToastRootAnimationOptions = Pick<
291
291
  * Props for the DefaultToast component
292
292
  * Used internally when showing toasts with string or config object (without component)
293
293
  */
294
- export interface DefaultToastProps extends ToastComponentProps {
294
+ export interface DefaultToastProps extends Omit<ToastComponentProps, 'placement'> {
295
295
  /**
296
296
  * Color of the toast
297
297
  * @default 'neutral'
@@ -31,6 +31,7 @@ export * from './Input';
31
31
  export * from './OtpInput';
32
32
  export * from './Link';
33
33
  export * from './ListItem';
34
+ export * from './Menu';
34
35
  export * from './ProgressBar';
35
36
  export * from './ProgressCircle';
36
37
  export * from './Skeleton';
@@ -3,12 +3,11 @@ import { ScrollView, View } from 'react-native';
3
3
  import { FadeIn, FadeOut } from 'react-native-reanimated';
4
4
  import {
5
5
  OverlayContainer,
6
- useAnchorPosition,
6
+ useAnchoredOverlay,
7
7
  useDismissOverlay,
8
8
  useEscapeKey,
9
- useOverlayPosition,
10
9
  } from '@cdx-ui/primitives';
11
- import { cn } from '@cdx-ui/utils';
10
+ import { cn, mergeRefs } from '@cdx-ui/utils';
12
11
  import { Dialog } from '../Dialog';
13
12
  import { BottomSheet, useBottomSheet, type BottomSheetModalProps } from '../BottomSheet';
14
13
  import { PICKER_POPOVER_Z_INDEX, pickerPopoverContentVariants } from './styles';
@@ -141,14 +140,18 @@ export function PickerPopoverSurface({
141
140
 
142
141
  const dismiss = useCallback(() => setOpen(false), [setOpen]);
143
142
 
144
- const anchorLayout = useAnchorPosition(anchorRef, open);
145
- const position = useOverlayPosition(anchorLayout);
143
+ const { floatingRef, onFloatingLayout, style } = useAnchoredOverlay(anchorRef, open, {
144
+ placement: 'bottom-start',
145
+ offset: 4,
146
+ collisionBehavior: 'flip-shift',
147
+ width: 'anchor',
148
+ });
146
149
 
147
150
  const excludeRefs = triggerRef ? [anchorRef, triggerRef] : [anchorRef];
148
151
  useDismissOverlay(dismissEnabled, contentRef, excludeRefs, dismiss);
149
152
  useEscapeKey(open, dismiss);
150
153
 
151
- if (!open || !position) {
154
+ if (!open) {
152
155
  return null;
153
156
  }
154
157
 
@@ -157,12 +160,13 @@ export function PickerPopoverSurface({
157
160
  onDismiss={dismiss}
158
161
  entering={FadeIn.duration(150)}
159
162
  exiting={FadeOut.duration(100)}
160
- style={[position.style, { zIndex: PICKER_POPOVER_Z_INDEX }]}
163
+ style={{ zIndex: PICKER_POPOVER_Z_INDEX }}
161
164
  >
162
165
  <View
163
- ref={contentRef}
166
+ ref={mergeRefs(contentRef, floatingRef)}
167
+ onLayout={onFloatingLayout}
164
168
  className={cn(pickerPopoverContentVariants(), className)}
165
- style={{ maxHeight: position.maxHeight }}
169
+ style={style}
166
170
  accessibilityViewIsModal
167
171
  >
168
172
  <ScrollView scrollEventThrottle={16} showsVerticalScrollIndicator={false}>