@udixio/ui-react 2.9.13 → 2.9.15

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 (100) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/index.cjs +3 -3
  3. package/dist/index.js +4348 -3781
  4. package/dist/lib/components/AnchorPositioner.d.ts +11 -0
  5. package/dist/lib/components/AnchorPositioner.d.ts.map +1 -0
  6. package/dist/lib/components/Button.d.ts.map +1 -1
  7. package/dist/lib/components/Card.d.ts +2 -2
  8. package/dist/lib/components/Card.d.ts.map +1 -1
  9. package/dist/lib/components/Checkbox.d.ts +15 -0
  10. package/dist/lib/components/Checkbox.d.ts.map +1 -0
  11. package/dist/lib/components/DatePicker.d.ts +9 -0
  12. package/dist/lib/components/DatePicker.d.ts.map +1 -0
  13. package/dist/lib/components/FabMenu.d.ts.map +1 -1
  14. package/dist/lib/components/IconButton.d.ts.map +1 -1
  15. package/dist/lib/components/TabGroup.d.ts +1 -0
  16. package/dist/lib/components/TabGroup.d.ts.map +1 -1
  17. package/dist/lib/components/TabGroupContext.d.ts +1 -0
  18. package/dist/lib/components/TabGroupContext.d.ts.map +1 -1
  19. package/dist/lib/components/TabPanel.d.ts +1 -0
  20. package/dist/lib/components/TabPanel.d.ts.map +1 -1
  21. package/dist/lib/components/TabPanels.d.ts +1 -0
  22. package/dist/lib/components/TabPanels.d.ts.map +1 -1
  23. package/dist/lib/components/TextField.d.ts +4 -5
  24. package/dist/lib/components/TextField.d.ts.map +1 -1
  25. package/dist/lib/components/Tooltip.d.ts +1 -1
  26. package/dist/lib/components/Tooltip.d.ts.map +1 -1
  27. package/dist/lib/components/index.d.ts +3 -0
  28. package/dist/lib/components/index.d.ts.map +1 -1
  29. package/dist/lib/effects/State.d.ts +3 -1
  30. package/dist/lib/effects/State.d.ts.map +1 -1
  31. package/dist/lib/effects/smooth-scroll.effect.d.ts +14 -0
  32. package/dist/lib/effects/smooth-scroll.effect.d.ts.map +1 -1
  33. package/dist/lib/hooks/index.d.ts +0 -1
  34. package/dist/lib/hooks/index.d.ts.map +1 -1
  35. package/dist/lib/interfaces/card.interface.d.ts +1 -1
  36. package/dist/lib/interfaces/card.interface.d.ts.map +1 -1
  37. package/dist/lib/interfaces/checkbox.interface.d.ts +38 -0
  38. package/dist/lib/interfaces/checkbox.interface.d.ts.map +1 -0
  39. package/dist/lib/interfaces/date-picker.interface.d.ts +67 -0
  40. package/dist/lib/interfaces/date-picker.interface.d.ts.map +1 -0
  41. package/dist/lib/interfaces/icon-button.interface.d.ts +2 -1
  42. package/dist/lib/interfaces/icon-button.interface.d.ts.map +1 -1
  43. package/dist/lib/interfaces/index.d.ts +1 -0
  44. package/dist/lib/interfaces/index.d.ts.map +1 -1
  45. package/dist/lib/interfaces/text-field.interface.d.ts +8 -5
  46. package/dist/lib/interfaces/text-field.interface.d.ts.map +1 -1
  47. package/dist/lib/interfaces/tooltip.interface.d.ts +2 -0
  48. package/dist/lib/interfaces/tooltip.interface.d.ts.map +1 -1
  49. package/dist/lib/styles/card.style.d.ts +5 -5
  50. package/dist/lib/styles/checkbox.style.d.ts +45 -0
  51. package/dist/lib/styles/checkbox.style.d.ts.map +1 -0
  52. package/dist/lib/styles/date-picker.style.d.ts +45 -0
  53. package/dist/lib/styles/date-picker.style.d.ts.map +1 -0
  54. package/dist/lib/styles/fab.style.d.ts +2 -2
  55. package/dist/lib/styles/icon-button.style.d.ts +10 -4
  56. package/dist/lib/styles/icon-button.style.d.ts.map +1 -1
  57. package/dist/lib/styles/index.d.ts +1 -0
  58. package/dist/lib/styles/index.d.ts.map +1 -1
  59. package/dist/lib/styles/navigation-rail-item.style.d.ts +2 -2
  60. package/dist/lib/styles/side-sheet.style.d.ts +2 -2
  61. package/dist/lib/styles/slider.style.d.ts +2 -2
  62. package/dist/lib/styles/tab.style.d.ts +2 -2
  63. package/dist/lib/styles/text-field.style.d.ts +22 -13
  64. package/dist/lib/styles/text-field.style.d.ts.map +1 -1
  65. package/dist/lib/styles/tooltip.style.d.ts +8 -4
  66. package/dist/lib/styles/tooltip.style.d.ts.map +1 -1
  67. package/package.json +3 -3
  68. package/src/lib/components/AnchorPositioner.tsx +132 -0
  69. package/src/lib/components/Button.tsx +1 -0
  70. package/src/lib/components/Card.tsx +9 -4
  71. package/src/lib/components/Checkbox.tsx +120 -0
  72. package/src/lib/components/DatePicker.tsx +432 -0
  73. package/src/lib/components/FabMenu.tsx +4 -5
  74. package/src/lib/components/IconButton.tsx +9 -7
  75. package/src/lib/components/TabGroup.tsx +8 -6
  76. package/src/lib/components/TabGroupContext.tsx +1 -1
  77. package/src/lib/components/TabPanel.tsx +1 -0
  78. package/src/lib/components/TabPanels.tsx +1 -0
  79. package/src/lib/components/TextField.tsx +222 -123
  80. package/src/lib/components/Tooltip.tsx +13 -13
  81. package/src/lib/components/index.ts +3 -0
  82. package/src/lib/effects/State.tsx +4 -1
  83. package/src/lib/effects/smooth-scroll.effect.tsx +15 -1
  84. package/src/lib/hooks/index.ts +0 -1
  85. package/src/lib/interfaces/card.interface.ts +1 -1
  86. package/src/lib/interfaces/checkbox.interface.ts +39 -0
  87. package/src/lib/interfaces/date-picker.interface.ts +79 -0
  88. package/src/lib/interfaces/icon-button.interface.ts +2 -1
  89. package/src/lib/interfaces/index.ts +1 -0
  90. package/src/lib/interfaces/text-field.interface.ts +8 -5
  91. package/src/lib/interfaces/tooltip.interface.ts +2 -0
  92. package/src/lib/styles/checkbox.style.ts +64 -0
  93. package/src/lib/styles/date-picker.style.ts +43 -0
  94. package/src/lib/styles/index.ts +1 -0
  95. package/src/lib/styles/side-sheet.style.ts +2 -2
  96. package/src/lib/styles/text-field.style.ts +2 -2
  97. package/src/stories/containment/card.stories.tsx +1 -1
  98. package/dist/lib/hooks/useTooltipPosition.d.ts +0 -22
  99. package/dist/lib/hooks/useTooltipPosition.d.ts.map +0 -1
  100. package/src/lib/hooks/useTooltipPosition.ts +0 -95
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+
3
+ type Props = {
4
+ checked?: boolean;
5
+ defaultChecked?: boolean;
6
+ indeterminate?: boolean;
7
+ disabled?: boolean;
8
+ error?: boolean;
9
+ onChange?: React.ChangeEventHandler<HTMLInputElement>;
10
+ name?: string;
11
+ id?: string;
12
+ value?: string;
13
+ style?: React.CSSProperties;
14
+ className?: string;
15
+ };
16
+
17
+ export type CheckboxStates = {
18
+ isChecked: boolean;
19
+ isIndeterminate: boolean;
20
+ isDisabled: boolean;
21
+ isError: boolean;
22
+ isFocused: boolean;
23
+ isHovered: boolean;
24
+ };
25
+
26
+ export interface CheckboxInterface {
27
+ type: 'div';
28
+ props: Props;
29
+ states: CheckboxStates;
30
+ elements: [
31
+ 'checkbox',
32
+ 'input',
33
+ 'container',
34
+ 'box',
35
+ 'icon',
36
+ 'stateLayer',
37
+ 'ripple',
38
+ ];
39
+ }
@@ -0,0 +1,79 @@
1
+ import React from 'react';
2
+
3
+ export type DateRange = [Date | null, Date | null];
4
+
5
+ type Props = {
6
+ /**
7
+ * Selection mode: 'single' for one date, 'range' for start/end period.
8
+ * @default 'single'
9
+ */
10
+ mode?: 'single' | 'range';
11
+
12
+ /**
13
+ * The currently selected date(s).
14
+ * Date for single mode, [start, end] tuple for range mode.
15
+ */
16
+ value?: Date | DateRange | null;
17
+
18
+ /**
19
+ * Default selected date(s) for uncontrolled usage.
20
+ */
21
+ defaultValue?: Date | DateRange | null;
22
+
23
+ /**
24
+ * Callback fired when selection changes.
25
+ * Returns Date in single mode, DateRange in range mode.
26
+ */
27
+ onChange?: (value: any) => void;
28
+
29
+ /**
30
+ * Minimum selectable date.
31
+ */
32
+ minDate?: Date;
33
+
34
+ /**
35
+ * Maximum selectable date.
36
+ */
37
+ maxDate?: Date;
38
+
39
+ /**
40
+ * Disables specific dates.
41
+ */
42
+ shouldDisableDate?: (date: Date) => boolean;
43
+
44
+ /**
45
+ * Locale for formatting dates.
46
+ */
47
+ locale?: string;
48
+
49
+ /**
50
+ * First day of the week (0=Sunday, 1=Monday).
51
+ * @default 0
52
+ */
53
+ weekStartDay?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
54
+
55
+ className?: string;
56
+ style?: React.CSSProperties;
57
+ };
58
+
59
+ export type DatePickerStates = {
60
+ // Can be expanded if we need specific state-driven styles exposed to the config
61
+ hasSelected: boolean;
62
+ };
63
+
64
+ export interface DatePickerInterface {
65
+ type: 'div';
66
+ props: Props;
67
+ states: DatePickerStates;
68
+ elements: [
69
+ 'datePicker',
70
+ 'header',
71
+ 'monthNav',
72
+ 'monthLabel',
73
+ 'weekDays',
74
+ 'weekDay',
75
+ 'daysGrid',
76
+ 'dayCell',
77
+ 'dayButton', // The interactive part of the day
78
+ ];
79
+ }
@@ -7,7 +7,7 @@ import { Icon } from '../icon';
7
7
  type Props = {
8
8
  label?: string;
9
9
  children?: string;
10
- icon: Icon;
10
+ icon?: Icon;
11
11
  size?: 'xSmall' | 'small' | 'medium' | 'large' | 'xLarge';
12
12
  width?: 'default' | 'narrow' | 'wide';
13
13
  iconSelected?: IconDefinition;
@@ -15,6 +15,7 @@ type Props = {
15
15
  variant?: IconButtonVariant;
16
16
  disabled?: boolean;
17
17
  activated?: boolean;
18
+ title?: string | null;
18
19
 
19
20
  /**
20
21
  * The shape of the button defines whether it is squared or rounded.
@@ -2,6 +2,7 @@ export * from './button.interface';
2
2
  export * from './card.interface';
3
3
  export * from './carousel-item.interface';
4
4
  export * from './carousel.interface';
5
+ export * from './checkbox.interface';
5
6
  export * from './chip.interface';
6
7
  export * from './chips.interface';
7
8
  export * from './divider.interface';
@@ -6,22 +6,25 @@ export type TextFieldVariant = 'filled' | 'outlined';
6
6
 
7
7
  type Props = {
8
8
  placeholder?: string;
9
- name: string;
9
+ name?: string;
10
10
  label: string;
11
11
  disabled?: boolean;
12
12
  errorText?: string | null;
13
13
  supportingText?: string;
14
14
  trailingIcon?: React.ReactElement<typeof IconButton> | Icon;
15
15
  leadingIcon?: React.ReactElement<typeof IconButton> | Icon;
16
- onChange?: (value: string) => void;
16
+ onChange?: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
17
17
  showSupportingText?: boolean;
18
18
  suffix?: string;
19
19
 
20
- value: string;
20
+ value?: string;
21
+ defaultValue?: string;
22
+ id?: string;
23
+ style?: React.CSSProperties;
21
24
  variant?: TextFieldVariant;
22
- type?: 'text' | 'password' | 'number';
25
+ type?: 'text' | 'password' | 'number' | 'date';
23
26
  autoComplete?: 'on' | 'off' | string;
24
- textLine?: 'singleLine' | 'multiLine' | 'textAreas';
27
+ multiline?: boolean;
25
28
  };
26
29
  export type TextFieldStates = {
27
30
  isFocused: boolean;
@@ -38,6 +38,8 @@ export type ToolTipInterface<T extends HTMLElement = any> = {
38
38
  onOpenChange?: (open: boolean) => void;
39
39
  /** Custom ID for accessibility linking. Auto-generated if not provided. */
40
40
  id?: string;
41
+ /** Custom anchor for positioning. Defaults to the trigger element. */
42
+ anchorRef?: RefObject<HTMLElement>;
41
43
  } & (
42
44
  | {
43
45
  children?: never;
@@ -0,0 +1,64 @@
1
+ import {
2
+ type ClassNameComponent,
3
+ classNames,
4
+ createUseClassNames,
5
+ defaultClassNames,
6
+ } from '../utils';
7
+ import { CheckboxInterface } from '../interfaces/checkbox.interface';
8
+
9
+ const checkboxConfig: ClassNameComponent<CheckboxInterface> = ({
10
+ isChecked,
11
+ isIndeterminate,
12
+ isDisabled,
13
+ isError,
14
+ isFocused,
15
+ isHovered,
16
+ }) => ({
17
+ checkbox: classNames(
18
+ 'inline-flex items-center justify-center relative size-4.5 ',
19
+ {
20
+ 'pointer-events-none opacity-[0.38]': isDisabled,
21
+ },
22
+ ),
23
+ input: classNames(
24
+ 'absolute inset-0 w-full h-full opacity-0 z-10 cursor-pointer',
25
+ ),
26
+ container: classNames(
27
+ 'relative flex items-center justify-center w-[18px] h-[18px] ',
28
+ ),
29
+ box: classNames(
30
+ 'absolute left-1/2 top-1/2 -translate-1/2 to rounded-[2px] size-4 border-2 transition-colors duration-200',
31
+ // Unchecked state (Border only)
32
+ !isChecked &&
33
+ !isIndeterminate && {
34
+ 'border-on-surface-variant': !isError && !isDisabled,
35
+ 'border-error': isError && !isDisabled,
36
+ 'border-on-surface': isDisabled,
37
+ },
38
+ // Checked or Indeterminate state (Filled)
39
+ (isChecked || isIndeterminate) && {
40
+ 'bg-primary border-primary': !isError && !isDisabled,
41
+ 'bg-error border-error': isError && !isDisabled,
42
+ 'bg-on-surface border-on-surface': isDisabled,
43
+ },
44
+ ),
45
+ icon: classNames(
46
+ 'z-10 relative text-on-primary w-full h-full flex items-center justify-center pointer-events-none',
47
+ {
48
+ 'text-on-error': isError && !isDisabled,
49
+ 'text-surface': isDisabled, // Usually on-surface with opacity against on-surface bg? No, checked disabled is on-surface bg with surface icon usually.
50
+ },
51
+ ),
52
+ stateLayer:
53
+ 'size-10 state-ripple-group-[checkbox] rounded-full cursor-pointer pointer-events-auto absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
54
+ });
55
+
56
+ export const checkboxStyle = defaultClassNames<CheckboxInterface>(
57
+ 'checkbox',
58
+ checkboxConfig,
59
+ );
60
+
61
+ export const useCheckboxStyle = createUseClassNames<CheckboxInterface>(
62
+ 'checkbox',
63
+ checkboxConfig,
64
+ );
@@ -0,0 +1,43 @@
1
+ import {
2
+ type ClassNameComponent,
3
+ classNames,
4
+ createUseClassNames,
5
+ defaultClassNames,
6
+ } from '../utils';
7
+ import { DatePickerInterface } from '../interfaces/date-picker.interface';
8
+
9
+ const datePickerConfig: ClassNameComponent<DatePickerInterface> = ({
10
+ hasSelected,
11
+ }) => ({
12
+ datePicker: classNames(
13
+ 'inline-flex flex-col bg-surface-container-high rounded-[28px] p-3 select-none', // Using shadow-sm as placeholder for elevation
14
+ 'min-w-[320px]',
15
+ ),
16
+ header: classNames('flex items-center justify-between h-12 mb-2 px-2'),
17
+ monthNav: classNames(
18
+ 'flex items-center justify-center w-10 h-10 rounded-full text-on-surface-variant hover:bg-on-surface-variant/8 transition-colors cursor-pointer',
19
+ ),
20
+ monthLabel: classNames(
21
+ 'text-label-large text-on-surface font-bold capitalize',
22
+ ),
23
+ weekDays: classNames('grid grid-cols-7 mb-2'),
24
+ weekDay: classNames(
25
+ 'h-10 flex items-center justify-center text-body-small text-on-surface-variant',
26
+ ),
27
+ daysGrid: classNames('grid grid-cols-7 row-auto gap-y-2'),
28
+ dayCell: classNames('flex items-center justify-center h-10 p-0 relative'),
29
+ dayButton: classNames(
30
+ 'w-10 h-10 rounded-full flex items-center justify-center text-body-large transition-all duration-200 relative overflow-hidden z-10 outline-none',
31
+ // Base style is implicit text-on-surface
32
+ ),
33
+ });
34
+
35
+ export const datePickerStyle = defaultClassNames<DatePickerInterface>(
36
+ 'datePicker',
37
+ datePickerConfig,
38
+ );
39
+
40
+ export const useDatePickerStyle = createUseClassNames<DatePickerInterface>(
41
+ 'datePicker',
42
+ datePickerConfig,
43
+ );
@@ -2,6 +2,7 @@ export * from './button.style';
2
2
  export * from './card.style';
3
3
  export * from './carousel-item.style';
4
4
  export * from './carousel.style';
5
+ export * from './checkbox.style';
5
6
  export * from './chip.style';
6
7
  export * from './chips.style';
7
8
  export * from './divider.style';
@@ -25,9 +25,9 @@ export const sideSheetConfig: ClassNameComponent<SideSheetInterface> = ({
25
25
  },
26
26
  ],
27
27
  ),
28
- container: classNames('w-full overflow-hidden', {}),
29
- content: classNames('w-fit '),
28
+ container: classNames('w-full overflow-hidden flex flex-col', {}),
30
29
  header: classNames('p-4 flex items-center gap-2'),
30
+ content: classNames('flex-1 overflow-y-auto'),
31
31
  title: classNames('text-on-surface-variant text-title-large'),
32
32
  closeButton: classNames('ml-auto'),
33
33
  divider: classNames({ hidden: variant == 'modal' }),
@@ -16,14 +16,14 @@ const textFieldConfig: ClassNameComponent<TextFieldInterface> = ({
16
16
  isFocused,
17
17
  value,
18
18
  suffix,
19
- textLine,
19
+ multiline,
20
20
  }) => ({
21
21
  textField: classNames({
22
22
  'opacity-[.38]': disabled,
23
23
  }),
24
24
  content: classNames(
25
25
  'group/text-field transition-border duration-200 relative flex items-center ',
26
- { 'h-14': textLine == 'singleLine' },
26
+ { 'h-14': !multiline },
27
27
  {
28
28
  'border-on-surface-variant':
29
29
  !errorText?.length && !isFocused && variant == 'filled',
@@ -25,7 +25,7 @@ const createCardStory = (variant: ReactProps<CardInterface>['variant']) => {
25
25
  <Card className={'w-[360px] h-[360px]'} {...args}></Card>
26
26
  <a className={'group'} href={'https://example.com/'} target={'_blank'}>
27
27
  <Card
28
- isInteractive={true}
28
+ interactive={true}
29
29
  className={'w-[360px] h-[360px]'}
30
30
  {...args}
31
31
  ></Card>
@@ -1,22 +0,0 @@
1
- import { RefObject } from 'react';
2
- type Position = 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
3
- type Variant = 'plain' | 'rich';
4
- export interface UseTooltipPositionOptions {
5
- targetRef: RefObject<HTMLElement | null>;
6
- position?: Position;
7
- variant?: Variant;
8
- isOpen: boolean;
9
- }
10
- export interface UseTooltipPositionReturn {
11
- resolvedPosition: Position;
12
- }
13
- /**
14
- * Hook to calculate tooltip position using useLayoutEffect.
15
- * Auto-flips position if not enough viewport space.
16
- *
17
- * For plain variant: prefers left/right, falls back to top/bottom
18
- * For rich variant: uses corner positions (top-left, top-right, bottom-left, bottom-right)
19
- */
20
- export declare function useTooltipPosition({ targetRef, position: positionProp, variant, isOpen, }: UseTooltipPositionOptions): UseTooltipPositionReturn;
21
- export {};
22
- //# sourceMappingURL=useTooltipPosition.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"useTooltipPosition.d.ts","sourceRoot":"","sources":["../../../src/lib/hooks/useTooltipPosition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA6B,MAAM,OAAO,CAAC;AAE7D,KAAK,QAAQ,GACT,KAAK,GACL,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,GACV,WAAW,GACX,aAAa,GACb,cAAc,CAAC;AAEnB,KAAK,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhC,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,gBAAgB,EAAE,QAAQ,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,QAAQ,EAAE,YAAY,EACtB,OAAiB,EACjB,MAAM,GACP,EAAE,yBAAyB,GAAG,wBAAwB,CAyDtD"}
@@ -1,95 +0,0 @@
1
- import { RefObject, useLayoutEffect, useState } from 'react';
2
-
3
- type Position =
4
- | 'top'
5
- | 'bottom'
6
- | 'left'
7
- | 'right'
8
- | 'top-left'
9
- | 'top-right'
10
- | 'bottom-left'
11
- | 'bottom-right';
12
-
13
- type Variant = 'plain' | 'rich';
14
-
15
- export interface UseTooltipPositionOptions {
16
- targetRef: RefObject<HTMLElement | null>;
17
- position?: Position;
18
- variant?: Variant;
19
- isOpen: boolean;
20
- }
21
-
22
- export interface UseTooltipPositionReturn {
23
- resolvedPosition: Position;
24
- }
25
-
26
- /**
27
- * Hook to calculate tooltip position using useLayoutEffect.
28
- * Auto-flips position if not enough viewport space.
29
- *
30
- * For plain variant: prefers left/right, falls back to top/bottom
31
- * For rich variant: uses corner positions (top-left, top-right, bottom-left, bottom-right)
32
- */
33
- export function useTooltipPosition({
34
- targetRef,
35
- position: positionProp,
36
- variant = 'plain',
37
- isOpen,
38
- }: UseTooltipPositionOptions): UseTooltipPositionReturn {
39
- const [resolvedPosition, setResolvedPosition] = useState<Position>(
40
- positionProp ?? 'bottom',
41
- );
42
-
43
- useLayoutEffect(() => {
44
- // If position is explicitly set, use it
45
- if (positionProp) {
46
- setResolvedPosition(positionProp);
47
- return;
48
- }
49
-
50
- // Only calculate if open and we have a target
51
- if (!isOpen || !targetRef.current || typeof window === 'undefined') {
52
- return;
53
- }
54
-
55
- const targetElement = targetRef.current;
56
- const rect = targetElement.getBoundingClientRect();
57
-
58
- const viewportWidth = window.innerWidth;
59
- const viewportHeight = window.innerHeight;
60
-
61
- // Normalized position (0-1 range)
62
- const x = rect.left / viewportWidth;
63
- const y = rect.top / viewportHeight;
64
-
65
- let newPosition: Position;
66
-
67
- if (variant === 'plain') {
68
- // Plain variant: prefer horizontal positioning, fall back to vertical
69
- if (x < 1 / 3) {
70
- newPosition = 'right';
71
- } else if (x > 2 / 3) {
72
- newPosition = 'left';
73
- } else {
74
- newPosition = y > 0.5 ? 'top' : 'bottom';
75
- }
76
- } else {
77
- // Rich variant: use corner positions
78
- if (x < 0.5 && y < 0.5) {
79
- newPosition = 'bottom-right';
80
- } else if (x >= 0.5 && y < 0.5) {
81
- newPosition = 'bottom-left';
82
- } else if (x >= 0.5 && y >= 0.5) {
83
- newPosition = 'top-left';
84
- } else {
85
- newPosition = 'top-right';
86
- }
87
- }
88
-
89
- setResolvedPosition(newPosition);
90
- }, [isOpen, targetRef, positionProp, variant]);
91
-
92
- return {
93
- resolvedPosition,
94
- };
95
- }