reshaped 3.7.4 → 3.8.0-canary.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.
Files changed (110) hide show
  1. package/CHANGELOG-extra.md +3 -0
  2. package/CHANGELOG.md +68 -55
  3. package/LICENSE.md +1 -1
  4. package/README.md +24 -0
  5. package/dist/bundle.css +1 -1
  6. package/dist/bundle.js +11 -11
  7. package/dist/cjs/themes/index.d.ts +1 -0
  8. package/dist/cjs/themes/index.js +3 -1
  9. package/dist/components/Accordion/Accordion.types.d.ts +13 -2
  10. package/dist/components/ActionBar/ActionBar.types.d.ts +10 -0
  11. package/dist/components/Actionable/Actionable.types.d.ts +14 -0
  12. package/dist/components/Alert/Alert.types.d.ts +11 -0
  13. package/dist/components/Autocomplete/Autocomplete.types.d.ts +7 -0
  14. package/dist/components/Avatar/Avatar.types.d.ts +18 -0
  15. package/dist/components/Avatar/tests/Avatar.stories.js +1 -1
  16. package/dist/components/Badge/Badge.types.d.ts +25 -1
  17. package/dist/components/Badge/tests/Badge.test.stories.js +3 -1
  18. package/dist/components/Breadcrumbs/Breadcrumbs.types.d.ts +11 -0
  19. package/dist/components/Button/Button.types.d.ts +20 -0
  20. package/dist/components/Calendar/Calendar.types.d.ts +31 -0
  21. package/dist/components/Calendar/tests/Calendar.stories.js +1 -1
  22. package/dist/components/Card/Card.types.d.ts +12 -0
  23. package/dist/components/Carousel/Carousel.types.d.ts +13 -0
  24. package/dist/components/Checkbox/Checkbox.types.d.ts +19 -0
  25. package/dist/components/Checkbox/tests/Checkbox.stories.js +1 -1
  26. package/dist/components/CheckboxGroup/CheckboxGroup.types.d.ts +10 -0
  27. package/dist/components/Container/Container.types.d.ts +11 -1
  28. package/dist/components/Dismissible/Dismissible.types.d.ts +10 -0
  29. package/dist/components/Divider/Divider.types.d.ts +6 -0
  30. package/dist/components/DropdownMenu/DropdownMenu.types.d.ts +4 -1
  31. package/dist/components/FileUpload/FileUpload.types.d.ts +10 -0
  32. package/dist/components/Flyout/Flyout.types.d.ts +41 -0
  33. package/dist/components/Flyout/FlyoutContent.js +1 -1
  34. package/dist/components/Flyout/FlyoutControlled.js +2 -1
  35. package/dist/components/Flyout/tests/Flyout.stories.d.ts +4 -0
  36. package/dist/components/Flyout/tests/Flyout.stories.js +59 -1
  37. package/dist/components/Flyout/useFlyout.d.ts +1 -0
  38. package/dist/components/Flyout/useFlyout.js +3 -1
  39. package/dist/components/Flyout/utilities/calculatePosition.d.ts +1 -1
  40. package/dist/components/Flyout/utilities/calculatePosition.js +52 -28
  41. package/dist/components/Flyout/utilities/flyout.js +22 -18
  42. package/dist/components/Flyout/utilities/helpers.d.ts +7 -0
  43. package/dist/components/Flyout/utilities/helpers.js +14 -0
  44. package/dist/components/Flyout/utilities/isFullyVisible.d.ts +5 -1
  45. package/dist/components/Flyout/utilities/isFullyVisible.js +1 -1
  46. package/dist/components/FormControl/FormControl.types.d.ts +9 -0
  47. package/dist/components/Grid/Grid.js +3 -1
  48. package/dist/components/Grid/Grid.module.css +1 -1
  49. package/dist/components/Grid/Grid.types.d.ts +31 -0
  50. package/dist/components/Grid/tests/Grid.stories.d.ts +3 -0
  51. package/dist/components/Grid/tests/Grid.stories.js +39 -1
  52. package/dist/components/Hidden/Hidden.types.d.ts +4 -0
  53. package/dist/components/HiddenVisually/HiddenVisually.types.d.ts +1 -0
  54. package/dist/components/Hotkey/Hotkey.types.d.ts +4 -0
  55. package/dist/components/Icon/Icon.types.d.ts +6 -0
  56. package/dist/components/Image/Image.types.d.ts +15 -0
  57. package/dist/components/Link/Link.types.d.ts +7 -0
  58. package/dist/components/Loader/Loader.types.d.ts +5 -0
  59. package/dist/components/MenuItem/MenuItem.types.d.ts +13 -1
  60. package/dist/components/Modal/Modal.types.d.ts +19 -0
  61. package/dist/components/NumberField/NumberField.types.d.ts +10 -0
  62. package/dist/components/Overlay/Overlay.types.d.ts +13 -0
  63. package/dist/components/Pagination/Pagination.types.d.ts +11 -29
  64. package/dist/components/PinField/PinField.module.css +1 -1
  65. package/dist/components/PinField/PinField.types.d.ts +13 -0
  66. package/dist/components/PinField/tests/PinField.stories.d.ts +24 -3
  67. package/dist/components/PinField/tests/PinField.stories.js +194 -47
  68. package/dist/components/Popover/Popover.types.d.ts +4 -1
  69. package/dist/components/Progress/Progress.types.d.ts +9 -0
  70. package/dist/components/ProgressIndicator/ProgressIndicator.js +13 -84
  71. package/dist/components/ProgressIndicator/ProgressIndicator.module.css +1 -1
  72. package/dist/components/ProgressIndicator/ProgressIndicator.types.d.ts +8 -0
  73. package/dist/components/ProgressIndicator/tests/ProgressIndicator.stories.js +8 -1
  74. package/dist/components/Radio/Radio.types.d.ts +12 -0
  75. package/dist/components/RadioGroup/RadioGroup.types.d.ts +14 -0
  76. package/dist/components/Reshaped/Reshaped.css +1 -1
  77. package/dist/components/Reshaped/Reshaped.types.d.ts +12 -0
  78. package/dist/components/Resizable/Resizable.types.d.ts +7 -0
  79. package/dist/components/Scrim/Scrim.types.d.ts +6 -1
  80. package/dist/components/ScrollArea/ScrollArea.types.d.ts +7 -0
  81. package/dist/components/Select/Select.types.d.ts +27 -0
  82. package/dist/components/Skeleton/Skeleton.types.d.ts +2 -0
  83. package/dist/components/Slider/Slider.types.d.ts +56 -0
  84. package/dist/components/Stepper/Stepper.types.d.ts +13 -0
  85. package/dist/components/Switch/Switch.types.d.ts +16 -0
  86. package/dist/components/Table/Table.types.d.ts +29 -0
  87. package/dist/components/Tabs/Tabs.module.css +1 -1
  88. package/dist/components/Tabs/Tabs.types.d.ts +24 -0
  89. package/dist/components/Tabs/TabsItem.js +2 -2
  90. package/dist/components/Tabs/tests/Tabs.stories.js +6 -6
  91. package/dist/components/Text/Text.types.d.ts +12 -0
  92. package/dist/components/TextArea/TextArea.types.d.ts +17 -0
  93. package/dist/components/TextField/TextField.types.d.ts +29 -0
  94. package/dist/components/Theme/Theme.js +4 -1
  95. package/dist/components/Theme/Theme.types.d.ts +5 -0
  96. package/dist/components/Timeline/Timeline.types.d.ts +7 -0
  97. package/dist/components/Toast/Toast.types.d.ts +18 -0
  98. package/dist/components/ToggleButton/ToggleButton.types.d.ts +9 -0
  99. package/dist/components/ToggleButtonGroup/ToggleButtonGroup.types.d.ts +8 -0
  100. package/dist/components/Tooltip/Tooltip.types.d.ts +5 -0
  101. package/dist/components/View/View.types.d.ts +56 -1
  102. package/dist/components/_private/Aligner/Aligner.types.d.ts +6 -0
  103. package/dist/themes/index.d.ts +1 -0
  104. package/dist/themes/index.js +1 -0
  105. package/package.json +47 -32
  106. package/LICENSE-SOURCE.md +0 -40
  107. package/dist/components/Grid/tests/Grid.test.stories.d.ts +0 -23
  108. package/dist/components/Grid/tests/Grid.test.stories.js +0 -42
  109. package/dist/components/PinField/tests/PinField.test.stories.d.ts +0 -29
  110. package/dist/components/PinField/tests/PinField.test.stories.js +0 -177
@@ -1,20 +1,30 @@
1
1
  import type React from "react";
2
2
  import type * as G from "../../types/global";
3
3
  type WithClose = {
4
+ /** Hide the close button */
4
5
  hideCloseButton: true;
6
+ /** aria-label attribute for the close button */
5
7
  closeAriaLabel?: string;
6
8
  };
7
9
  type WithoutClose = {
10
+ /** Show the close button */
8
11
  hideCloseButton?: false;
12
+ /** aria-label attribute for the close button */
9
13
  closeAriaLabel: string;
10
14
  };
11
15
  export type CloseProps = WithClose | WithoutClose;
12
16
  export type Props = CloseProps & {
17
+ /** Component render variant */
13
18
  variant?: "media";
19
+ /** Close button alignment */
14
20
  align?: "top" | "center";
21
+ /** Node for inserting children */
15
22
  children?: React.ReactNode;
23
+ /** Callback when the component is dismissed */
16
24
  onClose?: () => void;
25
+ /** Additional classname for the root element */
17
26
  className?: G.ClassName;
27
+ /** Additional attributes for the root element */
18
28
  attributes?: G.Attributes<"div">;
19
29
  };
20
30
  export {};
@@ -1,10 +1,16 @@
1
1
  import type React from "react";
2
2
  import type * as G from "../../types/global";
3
3
  export type Props = {
4
+ /** Change component to take no space, useful for using it as a border in components like Tabs */
4
5
  blank?: boolean;
6
+ /** Change component to render vertically */
5
7
  vertical?: G.Responsive<boolean>;
8
+ /** Position for rendering children */
6
9
  contentPosition?: "start" | "center" | "end";
10
+ /** Node for inserting text labels or custom components as a part of divider */
7
11
  children?: React.ReactNode;
12
+ /** Additional classname for the root element */
8
13
  className?: G.ClassName;
14
+ /** Additional attributes for the root element */
9
15
  attributes?: G.Attributes<"hr">;
10
16
  };
@@ -3,15 +3,18 @@ import type { PopoverProps, PopoverInstance } from "../Popover";
3
3
  import type { MenuItemProps } from "../MenuItem";
4
4
  import type { FlyoutContentProps } from "../Flyout";
5
5
  export type Instance = PopoverInstance;
6
- export type Props = Pick<PopoverProps, "children" | "position" | "forcePosition" | "fallbackPositions" | "triggerType" | "contentGap" | "contentShift" | "onOpen" | "onClose" | "active" | "defaultActive" | "width" | "disableHideAnimation" | "disableCloseOnOutsideClick" | "instanceRef" | "containerRef" | "originCoordinates"> & {
6
+ export type Props = Pick<PopoverProps, "children" | "position" | "forcePosition" | "fallbackPositions" | "fallbackAdjustLayout" | "triggerType" | "contentGap" | "contentShift" | "onOpen" | "onClose" | "active" | "defaultActive" | "width" | "disableHideAnimation" | "disableCloseOnOutsideClick" | "instanceRef" | "containerRef" | "originCoordinates"> & {
7
+ /** Change component trap focus keyboard behavior and shortcuts */
7
8
  trapFocusMode?: Extract<PopoverProps["trapFocusMode"], "action-menu" | "selection-menu"> | false;
8
9
  };
9
10
  export type ContentProps = Pick<FlyoutContentProps, "attributes" | "children" | "className">;
10
11
  export type ItemProps = Omit<MenuItemProps, "roundedCorners">;
11
12
  export type SectionProps = {
13
+ /** Node for inserting children */
12
14
  children: React.ReactNode;
13
15
  };
14
16
  export type SubMenuProps = {
17
+ /** Node for inserting children */
15
18
  children: React.ReactNode;
16
19
  };
17
20
  export type SubTriggerProps = Omit<MenuItemProps, "endSlot" | "roundedCorners">;
@@ -2,18 +2,28 @@ import type React from "react";
2
2
  import type { ViewProps } from "../View";
3
3
  import type * as G from "../../types/global";
4
4
  export type Props = {
5
+ /** Name of the input element */
5
6
  name: string;
7
+ /** Node for inserting children, can be a render function that receives component state */
6
8
  children?: React.ReactNode | ((props: {
7
9
  highlighted?: boolean;
8
10
  }) => React.ReactNode);
11
+ /** Callback when the component value is changed */
9
12
  onChange?: G.ChangeHandler<File[], React.DragEvent<HTMLDivElement> | React.ChangeEvent<HTMLInputElement>>;
13
+ /** Component height, literal css value or unit token multiplier */
10
14
  height?: ViewProps["height"];
15
+ /** Component variant, headless variant is useful for rendering custom triggers like a Button */
11
16
  variant?: "outline" | "headless";
17
+ /** Change component to render inline making it more compact */
12
18
  inline?: boolean;
19
+ /** Additional classname for the root element */
13
20
  className?: G.ClassName;
21
+ /** Additional attributes for the root element */
14
22
  attributes?: G.Attributes<"div">;
23
+ /** Additional attributes for the input element */
15
24
  inputAttributes?: G.Attributes<"input">;
16
25
  };
17
26
  export type TriggerProps = {
27
+ /** Node for inserting children */
18
28
  children: React.ReactNode;
19
29
  };
@@ -21,6 +21,7 @@ export type Options = {
21
21
  container?: HTMLElement | null;
22
22
  rtl: boolean;
23
23
  fallbackPositions?: Position[];
24
+ fallbackAdjustLayout?: boolean;
24
25
  lastUsedPosition: Position;
25
26
  onPositionChoose: (position: Position) => void;
26
27
  contentGap?: number;
@@ -50,16 +51,23 @@ export type UseFlyoutData = Pick<State, "styles" | "position" | "status"> & {
50
51
  * Component
51
52
  */
52
53
  export type Instance = {
54
+ /** Open the flyout content */
53
55
  open: () => void;
56
+ /** Close the flyout content */
54
57
  close: () => void;
58
+ /** Sync the flyout content position with the trigger element position */
55
59
  updatePosition: () => void;
56
60
  } | null;
57
61
  type WithUncontrolled = {
62
+ /** Control the content visibility, enables controlled mode */
58
63
  active?: never;
64
+ /** Control the content default visibility, enables uncontrolled mode */
59
65
  defaultActive?: boolean;
60
66
  };
61
67
  type WithControlled = {
68
+ /** Control the content visibility, enables controlled mode */
62
69
  active: boolean;
70
+ /** Control the content default visibility, enables uncontrolled mode */
63
71
  defaultActive?: never;
64
72
  };
65
73
  export type TriggerAttributes = {
@@ -78,34 +86,63 @@ export type TriggerAttributes = {
78
86
  "aria-controls"?: string;
79
87
  };
80
88
  type BaseProps = {
89
+ /** Unique id for the flyout content and trigger */
81
90
  id?: string;
91
+ /** Event used for displaying the content */
82
92
  triggerType?: "hover" | "click" | "focus";
93
+ /** Removes the content display delay if another flyout is already active */
83
94
  groupTimeouts?: boolean;
95
+ /** Content position relative to the trigger element */
84
96
  position?: Position;
85
97
  /**
86
98
  * @deprecated Use fallbackPosition={false} instead, will be removed in v4
87
99
  */
88
100
  forcePosition?: boolean;
101
+ /** Fallback positions for the content when it doesn't fit into the viewport or container */
89
102
  fallbackPositions?: Position[] | false;
103
+ /** Adjust the content size and shift its position to fit into the container when none of the fallback positions work */
104
+ fallbackAdjustLayout?: boolean;
105
+ /** Change component trap focus keyboard behavior and shortcuts */
90
106
  trapFocusMode?: TrapMode | false;
107
+ /** Disable the flyout content interactivity */
91
108
  disabled?: boolean;
109
+ /** Disable the flyout content hide animation */
92
110
  disableHideAnimation?: boolean;
111
+ /** Ignore the content hover events and hide it if the triggerType is hover */
93
112
  disableContentHover?: boolean;
113
+ /** Disable the flyout content close on outside click */
94
114
  disableCloseOnOutsideClick?: boolean;
115
+ /** Automatically focus the first focusable element in the content, when false the content container will be focused instead
116
+ * @default true
117
+ */
95
118
  autoFocus?: boolean;
119
+ /** Origin coordinates for the content when there is no trigger element */
96
120
  originCoordinates?: G.Coordinates;
121
+ /** Node for inserting children */
97
122
  children?: React.ReactNode;
123
+ /** Callback when the content is opened */
98
124
  onOpen?: () => void;
125
+ /** Callback when the content is closed */
99
126
  onClose?: (args: {
100
127
  reason?: CloseReason;
101
128
  }) => void;
129
+ /** Content width, literal css value or unit token multiplier */
102
130
  width?: Width;
131
+ /** Gap between the content and the trigger element */
103
132
  contentGap?: number;
133
+ /** Shift the content on the secondary axis, relative to its original position */
104
134
  contentShift?: number;
135
+ /** Additional classname for the content element */
105
136
  contentClassName?: string;
137
+ /** Additional attributes for the content element */
106
138
  contentAttributes?: G.Attributes<"div">;
139
+ /** Ref accessor for the flyout methods */
107
140
  instanceRef?: React.Ref<Instance>;
141
+ /** Container to render the content in using a portal, position is calculated based on the container bounds
142
+ * @default document.body
143
+ */
108
144
  containerRef?: React.RefObject<HTMLElement | null>;
145
+ /** Element to focus when the content is opened */
109
146
  initialFocusRef?: React.RefObject<HTMLElement | null>;
110
147
  };
111
148
  export type DefaultProps = Required<{
@@ -116,11 +153,15 @@ export type UncontrolledProps = BaseProps & WithUncontrolled;
116
153
  export type ControlledProps = BaseProps & WithControlled;
117
154
  export type Props = ControlledProps | UncontrolledProps;
118
155
  export type TriggerProps = {
156
+ /** Node for inserting children, provides attributes for the trigger element */
119
157
  children: (attributes: TriggerAttributes) => React.ReactNode;
120
158
  };
121
159
  export type ContentProps = {
160
+ /** Node for inserting children */
122
161
  children?: React.ReactNode;
162
+ /** Additional classname for the content element */
123
163
  className?: G.ClassName;
164
+ /** Additional attributes for the content element */
124
165
  attributes?: G.Attributes<"div">;
125
166
  };
126
167
  export type ContextProps = {
@@ -94,7 +94,7 @@ const FlyoutContent = (props) => {
94
94
  const content = (_jsx(ContentProvider, { value: { elRef: flyoutElRef }, children: _jsx("div", { className: rootClassNames, style: {
95
95
  ...styles,
96
96
  "--rs-flyout-gap": contentGap,
97
- }, ref: flyoutElRef, onTransitionEnd: handleTransitionEnd, onMouseEnter: triggerType === "hover" ? handleMouseEnter : undefined, onMouseLeave: triggerType === "hover" ? handleMouseLeave : undefined, onMouseDown: handleContentMouseDown, onTouchStart: handleContentMouseDown, onMouseUp: handleContentMouseUp, onTouchEnd: handleContentMouseUp, children: _jsx("div", { role: role, ...attributes, id: id, tabIndex: !autoFocus ? -1 : undefined, "aria-modal": role === "dialog" ? true : undefined, style: contentAttributes?.style, className: innerClassNames, children: children }) }) }));
97
+ }, ref: flyoutElRef, onTransitionEnd: handleTransitionEnd, onMouseEnter: triggerType === "hover" ? handleMouseEnter : undefined, onMouseLeave: triggerType === "hover" ? handleMouseLeave : undefined, onMouseDown: handleContentMouseDown, onTouchStart: handleContentMouseDown, onMouseUp: handleContentMouseUp, onTouchEnd: handleContentMouseUp, children: _jsx("div", { role: role, ...attributes, id: id, tabIndex: !autoFocus ? -1 : undefined, "aria-modal": role === "dialog" ? true : undefined, style: { ...attributes?.style, ...contentAttributes?.style }, className: innerClassNames, children: children }) }) }));
98
98
  return _jsx(Portal, { targetRef: containerRef, children: content });
99
99
  };
100
100
  FlyoutContent.displayName = "Flyout.Content";
@@ -15,7 +15,7 @@ import cooldown from "./utilities/cooldown.js";
15
15
  import { Provider, useFlyoutTriggerContext, useFlyoutContext, useFlyoutContentContext, } from "./Flyout.context.js";
16
16
  import useHandlerRef from "../../hooks/useHandlerRef.js";
17
17
  const FlyoutControlled = (props) => {
18
- const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, trapFocusMode = "dialog", width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, autoFocus = true, originCoordinates, contentGap = 2, contentShift, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, initialFocusRef, } = props;
18
+ const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, fallbackAdjustLayout, trapFocusMode = "dialog", width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, autoFocus = true, originCoordinates, contentGap = 2, contentShift, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, initialFocusRef, } = props;
19
19
  const fallbackPositions = props.fallbackPositions === false || forcePosition ? [] : props.fallbackPositions;
20
20
  const onOpenRef = useHandlerRef(onOpen);
21
21
  const onCloseRef = useHandlerRef(onClose);
@@ -62,6 +62,7 @@ const FlyoutControlled = (props) => {
62
62
  defaultActive: resolvedActive,
63
63
  container: containerRef?.current,
64
64
  fallbackPositions,
65
+ fallbackAdjustLayout,
65
66
  contentGap,
66
67
  contentShift,
67
68
  });
@@ -29,6 +29,10 @@ export declare const positionFallbacks: {
29
29
  name: string;
30
30
  render: () => React.JSX.Element;
31
31
  };
32
+ export declare const fallbackAdjustLayout: {
33
+ name: string;
34
+ render: () => React.JSX.Element;
35
+ };
32
36
  export declare const originCoordinates: {
33
37
  name: string;
34
38
  render: () => React.JSX.Element;
@@ -40,7 +40,7 @@ const Demo = (props) => {
40
40
  export const position = {
41
41
  name: "position",
42
42
  render: () => {
43
- return (<View gap={4} padding={50} align="center" justify="center" height="120vh" width="120%">
43
+ return (<View gap={4} padding={50} align="center" justify="center" height="100vh" width="120%">
44
44
  <View gap={4} direction="row">
45
45
  <Demo position="top-start" defaultActive/>
46
46
  <Demo position="top"/>
@@ -222,6 +222,64 @@ export const positionFallbacks = {
222
222
  </Example>);
223
223
  },
224
224
  };
225
+ const FallbackAdjustLayoutControls = ({ containerRef, }) => (<>
226
+ {/* Left side */}
227
+ <View position="absolute" insetStart={4} insetTop={10} gap={2}>
228
+ <Demo contentHeight="200px" position="end" fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef}/>
229
+ <Demo contentHeight="200px" position="end-bottom" fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef}/>
230
+ </View>
231
+
232
+ <View position="absolute" insetStart={4} insetTop={80} gap={2}>
233
+ <Demo position="bottom-end" fallbackPositions={false} fallbackAdjustLayout contentWidth={300} containerRef={containerRef}/>
234
+ <Demo position="bottom" fallbackPositions={false} fallbackAdjustLayout contentWidth={300} containerRef={containerRef}/>
235
+ </View>
236
+
237
+ <View position="absolute" insetBottom={4} insetStart={4} gap={2}>
238
+ <Demo contentHeight="200px" position="end-top" fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef}/>
239
+ <Demo contentHeight="200px" position="end" fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef}/>
240
+ </View>
241
+
242
+ {/* Right side */}
243
+
244
+ <View position="absolute" insetTop={10} insetEnd={4} gap={2}>
245
+ <Demo contentHeight="200px" position="start" fallbackPositions={false} fallbackAdjustLayout/>
246
+ <Demo contentHeight="200px" position="start-bottom" fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef}/>
247
+ </View>
248
+
249
+ <View position="absolute" insetEnd={4} insetTop={80} gap={2}>
250
+ <Demo position="top-start" fallbackPositions={false} fallbackAdjustLayout contentWidth={300} containerRef={containerRef}/>
251
+ <Demo position="top" fallbackPositions={false} fallbackAdjustLayout contentWidth={300} containerRef={containerRef}/>
252
+ </View>
253
+
254
+ <View position="absolute" insetBottom={4} insetEnd={4} gap={2}>
255
+ <Demo contentHeight="200px" position="start-top" fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef}/>
256
+ <Demo contentHeight="200px" position="start" fallbackPositions={false} fallbackAdjustLayout containerRef={containerRef}/>
257
+ </View>
258
+ </>);
259
+ export const fallbackAdjustLayout = {
260
+ name: "fallbackAdjustLayout",
261
+ render: () => {
262
+ const containerRef = React.useRef(null);
263
+ return (<View gap={10}>
264
+ <View height="95vh" width="100%" align="center" justify="center">
265
+ <View backgroundColor="neutral-faded" borderRadius="medium" height="1000px" width="600px" padding={4} paddingBlock={15} overflow="auto">
266
+ <FallbackAdjustLayoutControls />
267
+ <View height="150%" width="150%" attributes={{ style: { pointerEvents: "none" } }}/>
268
+ </View>
269
+ </View>
270
+
271
+ <View height="95vh" width="100%" align="center" justify="center">
272
+ <View backgroundColor="neutral-faded" borderRadius="medium" height="1000px" width="600px" attributes={{ ref: containerRef }} padding={4} paddingBlock={15} overflow="auto">
273
+ <FallbackAdjustLayoutControls containerRef={containerRef}/>
274
+ <View height="150%" width="150%" attributes={{ style: { pointerEvents: "none" } }}/>
275
+ </View>
276
+ </View>
277
+
278
+ <FallbackAdjustLayoutControls />
279
+ <div style={{ height: "100vh", width: "250%" }}/>
280
+ </View>);
281
+ },
282
+ };
225
283
  export const originCoordinates = {
226
284
  name: "originCoordinates",
227
285
  render: () => {
@@ -6,6 +6,7 @@ type UseFlyout = (args: {
6
6
  position?: T.Position;
7
7
  defaultActive?: boolean;
8
8
  fallbackPositions?: T.Position[];
9
+ fallbackAdjustLayout?: boolean;
9
10
  contentGap?: number;
10
11
  contentShift?: number;
11
12
  container?: HTMLElement | null;
@@ -38,7 +38,7 @@ const flyoutReducer = (state, action) => {
38
38
  };
39
39
  const useFlyout = (args) => {
40
40
  const { triggerElRef, flyoutElRef, triggerBounds, contentGap, contentShift, ...options } = args;
41
- const { position: defaultPosition = "bottom", fallbackPositions, width, container } = options;
41
+ const { position: defaultPosition = "bottom", fallbackPositions, width, container, fallbackAdjustLayout, } = options;
42
42
  const lastUsedPositionRef = React.useRef(defaultPosition);
43
43
  // Memo the array internally to avoid new arrays triggering useCallback
44
44
  const cachedFallbackPositions = React.useMemo(() => fallbackPositions,
@@ -76,6 +76,7 @@ const useFlyout = (args) => {
76
76
  width,
77
77
  position: changePositon ? defaultPosition : lastUsedPositionRef.current,
78
78
  fallbackPositions: changePositon ? cachedFallbackPositions : [],
79
+ fallbackAdjustLayout,
79
80
  lastUsedPosition: lastUsedPositionRef.current,
80
81
  onPositionChoose: handlePosition,
81
82
  rtl: isRTL,
@@ -93,6 +94,7 @@ const useFlyout = (args) => {
93
94
  container,
94
95
  defaultPosition,
95
96
  cachedFallbackPositions,
97
+ fallbackAdjustLayout,
96
98
  isRTL,
97
99
  flyoutElRef,
98
100
  triggerElRef,
@@ -10,7 +10,7 @@ declare const calculatePosition: (args: {
10
10
  };
11
11
  passedContainer?: HTMLElement | null;
12
12
  containerBounds: DOMRect;
13
- } & Pick<T.Options, "position" | "rtl" | "width" | "contentGap" | "contentShift">) => {
13
+ } & Pick<T.Options, "position" | "rtl" | "width" | "contentGap" | "contentShift" | "fallbackAdjustLayout">) => {
14
14
  position: T.Position;
15
15
  styles: {
16
16
  width: string | number | undefined;
@@ -1,22 +1,10 @@
1
- const SCREEN_OFFSET = 16;
2
- const getRTLPosition = (position) => {
3
- if (position.includes("start"))
4
- return position.replace("start", "end");
5
- if (position.includes("end"))
6
- return position.replace("end", "start");
7
- return position;
8
- };
9
- /**
10
- * Get a position value which centers 2 elements vertically or horizontally
11
- */
12
- const centerBySize = (originSize, targetSize) => {
13
- return Math.floor(originSize / 2 - targetSize / 2);
14
- };
1
+ import { getRTLPosition, centerBySize } from "./helpers.js";
2
+ const SCREEN_OFFSET = 8;
15
3
  /**
16
4
  * Calculate styles for the current position
17
5
  */
18
6
  const calculatePosition = (args) => {
19
- const { triggerBounds, flyoutBounds, containerBounds, position: passedPosition, rtl, width, contentGap = 0, contentShift = 0, passedContainer, } = args;
7
+ const { triggerBounds, flyoutBounds, containerBounds, position: passedPosition, rtl, width, contentGap = 0, contentShift = 0, passedContainer, fallbackAdjustLayout, } = args;
20
8
  const isFullWidth = width === "full" || width === "100%";
21
9
  let left = 0;
22
10
  let top = 0;
@@ -29,25 +17,34 @@ const calculatePosition = (args) => {
29
17
  position = position.includes("top") ? "top" : "bottom";
30
18
  }
31
19
  const isHorizontalPosition = !!position.match(/^(start|end)/);
32
- const isVerticalPosition = !!position.match(/^(top|bottom)/);
33
20
  // contentGap adds padding to the flyout to make sure it doesn't disapper while moving the mouse to the content
34
21
  // So its width/height is bigger than the visible part of the content
35
22
  const flyoutWidth = flyoutBounds.width + (isHorizontalPosition ? contentGap : 0);
36
- const flyoutHeight = flyoutBounds.height + (isVerticalPosition ? contentGap : 0);
37
- const triggerHeight = triggerBounds.height;
23
+ const flyoutHeight = flyoutBounds.height + (!isHorizontalPosition ? contentGap : 0);
38
24
  const triggerWidth = triggerBounds.width;
39
- const containerY = passedContainer?.scrollTop || 0;
40
- const containerX = passedContainer?.scrollLeft || 0;
41
- const relativeLeft = triggerBounds.left - containerBounds.left + containerX;
42
- const relativeTop = triggerBounds.top - containerBounds.top + containerY;
43
- const relativeRight = containerBounds.right - triggerBounds.right - containerX;
44
- const relativeBottom = containerBounds.bottom - triggerBounds.bottom - containerY;
25
+ const triggerHeight = triggerBounds.height;
26
+ // Detect passed container scroll to sync the flyout position with it
27
+ const containerX = passedContainer?.scrollLeft;
28
+ const containerY = passedContainer?.scrollTop;
29
+ const scrollX = containerX ?? window.scrollX;
30
+ const scrollY = containerY ?? window.scrollY;
31
+ const renderContainerHeight = passedContainer?.clientHeight ?? window.innerHeight;
32
+ const renderContainerWidth = passedContainer?.clientWidth ?? window.innerWidth;
33
+ // When rendering in the body, bottom bounds will be larrger than the viewport so we calculate it manually
34
+ const containerBoundsBottom = passedContainer
35
+ ? containerBounds.bottom
36
+ : window.innerHeight - scrollY;
37
+ // When inside a container, adjut position based on the container scroll since flyout is rendered outside the scroll area
38
+ const relativeLeft = triggerBounds.left - containerBounds.left + (containerX || 0);
39
+ const relativeRight = containerBounds.right - triggerBounds.right - (containerX || 0);
40
+ const relativeTop = triggerBounds.top - containerBounds.top + (containerY || 0);
41
+ const relativeBottom = containerBoundsBottom - triggerBounds.bottom - (containerY || 0);
45
42
  switch (position) {
46
43
  case "start":
47
44
  case "start-top":
48
45
  case "start-bottom":
49
- right = relativeRight + triggerWidth;
50
46
  left = relativeLeft - flyoutWidth;
47
+ right = relativeRight + triggerWidth;
51
48
  break;
52
49
  case "end":
53
50
  case "end-top":
@@ -64,8 +61,8 @@ const calculatePosition = (args) => {
64
61
  break;
65
62
  case "top-end":
66
63
  case "bottom-end":
67
- right = relativeRight - contentShift;
68
64
  left = relativeLeft + triggerWidth - flyoutWidth + contentShift;
65
+ right = relativeRight - contentShift;
69
66
  break;
70
67
  default:
71
68
  break;
@@ -74,8 +71,8 @@ const calculatePosition = (args) => {
74
71
  case "top":
75
72
  case "top-start":
76
73
  case "top-end":
77
- bottom = relativeBottom + triggerHeight;
78
74
  top = relativeTop - flyoutHeight;
75
+ bottom = relativeBottom + triggerHeight;
79
76
  break;
80
77
  case "bottom":
81
78
  case "bottom-start":
@@ -92,12 +89,39 @@ const calculatePosition = (args) => {
92
89
  break;
93
90
  case "start-bottom":
94
91
  case "end-bottom":
95
- bottom = relativeBottom - contentShift;
96
92
  top = relativeTop + triggerHeight - flyoutHeight + contentShift;
93
+ bottom = relativeBottom - contentShift;
97
94
  break;
98
95
  default:
99
96
  break;
100
97
  }
98
+ if (fallbackAdjustLayout) {
99
+ const topOverflowSize = -top + scrollY + SCREEN_OFFSET;
100
+ const bottomOverflowSize = top + flyoutHeight + SCREEN_OFFSET - scrollY - renderContainerHeight;
101
+ const leftOverflowSize = -left + scrollX + SCREEN_OFFSET;
102
+ const rightOverflowSize = left + flyoutWidth + SCREEN_OFFSET - scrollX - renderContainerWidth;
103
+ if (isHorizontalPosition) {
104
+ if (topOverflowSize > 0) {
105
+ top = SCREEN_OFFSET + scrollY;
106
+ if (bottom !== null)
107
+ bottom = bottom - topOverflowSize;
108
+ }
109
+ else if (bottomOverflowSize > 0) {
110
+ console.log({ bottomOverflowSize, renderContainerHeight });
111
+ top = top - bottomOverflowSize;
112
+ }
113
+ }
114
+ else {
115
+ if (leftOverflowSize > 0) {
116
+ left = SCREEN_OFFSET + scrollX;
117
+ if (right !== null)
118
+ right = right - leftOverflowSize;
119
+ }
120
+ else if (rightOverflowSize > 0) {
121
+ left = left - rightOverflowSize;
122
+ }
123
+ }
124
+ }
101
125
  let widthStyle;
102
126
  if (isFullWidth) {
103
127
  left = SCREEN_OFFSET;
@@ -7,7 +7,7 @@ import { resetStyles } from "../Flyout.constants.js";
7
7
  * Set position of the target element to fit on the screen
8
8
  */
9
9
  const flyout = (args) => {
10
- const { triggerEl, flyoutEl, triggerBounds: passedTriggerBounds, contentShift = 0, contentGap = 0, position, fallbackPositions, width, container: passedContainer, lastUsedPosition, onPositionChoose, rtl, } = args;
10
+ const { triggerEl, flyoutEl, triggerBounds: passedTriggerBounds, contentShift = 0, contentGap = 0, position, fallbackPositions, fallbackAdjustLayout, width, container: passedContainer, lastUsedPosition, onPositionChoose, rtl, } = args;
11
11
  const targetClone = flyoutEl.cloneNode(true);
12
12
  const baseUnit = getComputedStyle(flyoutEl).getPropertyValue("--rs-unit-x1");
13
13
  const unitModifier = baseUnit ? parseInt(baseUnit) : 4;
@@ -42,37 +42,41 @@ const flyout = (args) => {
42
42
  document.body;
43
43
  const renderContainerBounds = container.getBoundingClientRect();
44
44
  const visualContainerBounds = (passedContainer || document.body).getBoundingClientRect();
45
- let calculated = null;
46
- const testOrder = getPositionFallbacks(position, fallbackPositions);
47
- testOrder.some((currentPosition) => {
48
- const tested = calculatePosition({
45
+ const applyPosition = (position) => {
46
+ return calculatePosition({
49
47
  triggerBounds: resolvedTriggerBounds,
50
48
  flyoutBounds,
51
49
  containerBounds: renderContainerBounds,
52
- position: currentPosition,
50
+ position,
53
51
  contentGap: contentGap * unitModifier,
54
52
  contentShift: contentShift * unitModifier,
55
53
  rtl,
56
54
  width,
57
- passedContainer,
55
+ passedContainer: passedContainer ||
56
+ (closestFixedContainer !== document.body ? closestFixedContainer : undefined),
57
+ fallbackAdjustLayout,
58
58
  });
59
- const visible = isFullyVisible({
60
- flyoutBounds: tested.boundaries,
59
+ };
60
+ const testVisibility = (calculated) => {
61
+ return isFullyVisible({
62
+ flyoutBounds: calculated.boundaries,
61
63
  visualContainerBounds,
62
64
  renderContainerBounds,
63
65
  container,
64
66
  });
65
- const validPosition = visible || fallbackPositions?.length === 0;
66
- // Saving first try in case none of the options work
67
- if (validPosition || lastUsedPosition === currentPosition) {
67
+ };
68
+ let calculated = null;
69
+ const testOrder = getPositionFallbacks(position, fallbackPositions);
70
+ testOrder.some((currentPosition) => {
71
+ const tested = applyPosition(currentPosition);
72
+ const visible = testVisibility(tested);
73
+ if (visible)
68
74
  calculated = tested;
69
- onPositionChoose(currentPosition);
70
- }
71
- return validPosition;
75
+ return visible;
72
76
  });
73
- if (!calculated) {
74
- throw new Error(`[Reshaped] Can't calculate styles for the ${position} position`);
75
- }
77
+ if (!calculated)
78
+ calculated = applyPosition(lastUsedPosition);
79
+ onPositionChoose(calculated.position);
76
80
  targetClone.parentNode?.removeChild(targetClone);
77
81
  return calculated;
78
82
  };
@@ -0,0 +1,7 @@
1
+ import type * as T from "../Flyout.types";
2
+ /** Mirror the position for RTL */
3
+ export declare const getRTLPosition: (position: T.Position) => T.Position;
4
+ /**
5
+ * Get a position value which centers 2 elements vertically or horizontally
6
+ */
7
+ export declare const centerBySize: (originSize: number, targetSize: number) => number;
@@ -0,0 +1,14 @@
1
+ /** Mirror the position for RTL */
2
+ export const getRTLPosition = (position) => {
3
+ if (position.includes("start"))
4
+ return position.replace("start", "end");
5
+ if (position.includes("end"))
6
+ return position.replace("end", "start");
7
+ return position;
8
+ };
9
+ /**
10
+ * Get a position value which centers 2 elements vertically or horizontally
11
+ */
12
+ export const centerBySize = (originSize, targetSize) => {
13
+ return Math.floor(originSize / 2 - targetSize / 2);
14
+ };
@@ -1,10 +1,14 @@
1
1
  /**
2
- * Check if element visually fits on the screen
2
+ * Check if element visually fits within its render container
3
3
  */
4
4
  declare const isFullyVisible: (args: {
5
+ /** Bounds of the flyout content */
5
6
  flyoutBounds: Pick<DOMRect, "left" | "top" | "width" | "height">;
7
+ /** Bounds of the container where the flyout content should fit */
6
8
  visualContainerBounds: DOMRect;
9
+ /** Bounds of the container where flyout content is rendered */
7
10
  renderContainerBounds: DOMRect;
11
+ /** Container where the flyout content is rendered */
8
12
  container: HTMLElement;
9
13
  }) => boolean;
10
14
  export default isFullyVisible;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Check if element visually fits on the screen
2
+ * Check if element visually fits within its render container
3
3
  */
4
4
  const isFullyVisible = (args) => {
5
5
  const { flyoutBounds, visualContainerBounds, renderContainerBounds, container } = args;
@@ -1,17 +1,26 @@
1
1
  import type React from "react";
2
2
  export type Props = {
3
+ /** Node for inserting children */
3
4
  children: React.ReactNode;
5
+ /** Component size, to be used together with the other form component sizes */
4
6
  size?: "medium" | "large";
7
+ /** Change component to show an error state and display FormControl.Error */
5
8
  hasError?: boolean;
9
+ /** Change component to show a required indicator */
6
10
  required?: boolean;
11
+ /** Apply disabled styles */
7
12
  disabled?: boolean;
13
+ /** Apply semantic html markup when used for displaying multiple form fields together with a single label */
8
14
  group?: boolean;
15
+ /** Custom id for the form control */
9
16
  id?: string;
10
17
  };
11
18
  export type LabelProps = {
19
+ /** Node for inserting the label text */
12
20
  children: React.ReactNode;
13
21
  };
14
22
  export type CaptionProps = {
23
+ /** Node for inserting the caption text */
15
24
  children: React.ReactNode;
16
25
  };
17
26
  export type PrivateCaptionProps = CaptionProps & {