@seed-design/react-floating 0.0.0-alpha-20260622094810

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.
package/lib/index.cjs ADDED
@@ -0,0 +1,153 @@
1
+ var react$1 = require('@floating-ui/react');
2
+ var reactUseControllableState = require('@radix-ui/react-use-controllable-state');
3
+ var react = require('react');
4
+
5
+ const defaultPositioningOptions = {
6
+ strategy: "absolute",
7
+ placement: "bottom",
8
+ flip: true,
9
+ slide: true,
10
+ overflowPadding: 8,
11
+ arrowPadding: 4
12
+ };
13
+ function getArrowMiddleware(arrowElement, opts) {
14
+ if (!arrowElement) return;
15
+ return react$1.arrow({
16
+ element: arrowElement,
17
+ padding: opts.arrowPadding
18
+ });
19
+ }
20
+ function getOffsetMiddleware(arrowOffset, opts) {
21
+ const offsetMainAxis = (opts.gutter ?? 0) + arrowOffset;
22
+ return react$1.offset(offsetMainAxis);
23
+ }
24
+ function getFlipMiddleware(opts) {
25
+ if (!opts.flip) return;
26
+ return react$1.flip({
27
+ padding: opts.overflowPadding,
28
+ fallbackPlacements: opts.flip === true ? undefined : opts.flip
29
+ });
30
+ }
31
+ function getShiftMiddleware(opts) {
32
+ if (!opts.slide) return;
33
+ return react$1.shift({
34
+ mainAxis: opts.slide,
35
+ padding: opts.overflowPadding,
36
+ limiter: react$1.limitShift()
37
+ });
38
+ }
39
+ function getSizeMiddleware(opts) {
40
+ return react$1.size({
41
+ padding: opts.overflowPadding,
42
+ apply ({ availableWidth, elements }) {
43
+ elements.floating.style.setProperty("--seed-popover-available-width", `${Math.max(0, availableWidth)}px`);
44
+ }
45
+ });
46
+ }
47
+ const rectMiddleware = {
48
+ name: "rects",
49
+ fn ({ rects }) {
50
+ return {
51
+ data: rects
52
+ };
53
+ }
54
+ };
55
+ const ARROW_FLOATING_STYLE = {
56
+ top: "",
57
+ right: "rotate(90deg)",
58
+ bottom: "rotate(180deg)",
59
+ left: "rotate(270deg)"
60
+ };
61
+ function usePositionedFloating(props) {
62
+ const options = {
63
+ ...defaultPositioningOptions,
64
+ ...props
65
+ };
66
+ const [open, onOpenChange] = reactUseControllableState.useControllableState({
67
+ prop: props.open,
68
+ defaultProp: props.defaultOpen ?? false,
69
+ onChange: props.onOpenChange
70
+ });
71
+ const [arrowEl, setArrowEl] = react.useState(null);
72
+ const [arrowTipEl, setArrowTipEl] = react.useState(null);
73
+ const arrowTipWidth = arrowTipEl?.clientWidth ?? 0;
74
+ const arrowTipHeight = arrowTipEl?.clientHeight ?? 0;
75
+ const arrowTipOffset = arrowTipHeight;
76
+ const { refs, context, floatingStyles, middlewareData, isPositioned } = react$1.useFloating({
77
+ strategy: options.strategy,
78
+ open,
79
+ placement: options.placement,
80
+ onOpenChange: onOpenChange,
81
+ middleware: [
82
+ getOffsetMiddleware(arrowTipOffset, options),
83
+ getFlipMiddleware(options),
84
+ getShiftMiddleware(options),
85
+ getSizeMiddleware(options),
86
+ getArrowMiddleware(arrowEl, options),
87
+ rectMiddleware
88
+ ]
89
+ });
90
+ // https://floating-ui.com/docs/react#anchoring
91
+ react.useEffect(()=>{
92
+ if (!open) return;
93
+ if (!refs.reference.current || !refs.floating.current) return;
94
+ return react$1.autoUpdate(refs.reference.current, refs.floating.current, context.update);
95
+ }, [
96
+ open,
97
+ refs.reference,
98
+ refs.floating,
99
+ context
100
+ ]);
101
+ const [side, alignment] = context.placement.split("-");
102
+ const arrowStyles = react.useMemo(()=>({
103
+ position: "absolute",
104
+ left: middlewareData.arrow?.x,
105
+ top: middlewareData.arrow?.y,
106
+ [side]: "100%",
107
+ transform: ARROW_FLOATING_STYLE[side]
108
+ }), [
109
+ middlewareData.arrow,
110
+ side
111
+ ]);
112
+ return react.useMemo(()=>({
113
+ open,
114
+ onOpenChange,
115
+ refs: {
116
+ ...refs,
117
+ arrow: arrowEl,
118
+ setArrow: setArrowEl,
119
+ arrowTip: arrowTipEl,
120
+ setArrowTip: setArrowTipEl
121
+ },
122
+ rects: {
123
+ ...middlewareData["rects"],
124
+ arrowTip: {
125
+ width: arrowTipWidth,
126
+ height: arrowTipHeight
127
+ }
128
+ },
129
+ isPositioned,
130
+ side,
131
+ alignment,
132
+ context,
133
+ floatingStyles,
134
+ arrowStyles
135
+ }), [
136
+ open,
137
+ onOpenChange,
138
+ refs,
139
+ arrowEl,
140
+ arrowTipEl,
141
+ middlewareData["rects"],
142
+ context,
143
+ side,
144
+ alignment,
145
+ floatingStyles,
146
+ arrowStyles,
147
+ isPositioned,
148
+ arrowTipWidth,
149
+ arrowTipHeight
150
+ ]);
151
+ }
152
+
153
+ exports.usePositionedFloating = usePositionedFloating;
package/lib/index.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ import { Placement, ReferenceType, ExtendedRefs, Rect, Side, Alignment, FloatingContext } from '@floating-ui/react';
2
+ import { CSSProperties } from 'react';
3
+
4
+ interface PositioningOptions {
5
+ /**
6
+ * The strategy to use for positioning
7
+ * @default "absolute"
8
+ */
9
+ strategy?: "absolute" | "fixed";
10
+ /**
11
+ * The initial placement of the floating element
12
+ * @default "bottom"
13
+ */
14
+ placement?: Placement;
15
+ /**
16
+ * The gutter between the floating element and the reference element
17
+ */
18
+ gutter?: number;
19
+ /**
20
+ * Whether to flip the placement
21
+ * @default true
22
+ */
23
+ flip?: boolean | Placement[];
24
+ /**
25
+ * Whether the popover should slide when it overflows.
26
+ * @default true
27
+ */
28
+ slide?: boolean;
29
+ /**
30
+ * The virtual padding around the viewport edges to check for overflow
31
+ * @default 8
32
+ */
33
+ overflowPadding?: number;
34
+ /**
35
+ * The minimum padding between the arrow and the floating element's corner.
36
+ * @default 4
37
+ */
38
+ arrowPadding?: number;
39
+ }
40
+ interface UsePositionedFloatingProps extends PositioningOptions {
41
+ /**
42
+ * Whether the floating element is initially open
43
+ */
44
+ defaultOpen?: boolean;
45
+ /**
46
+ * Whether the floating element is open
47
+ */
48
+ open?: boolean;
49
+ /**
50
+ * Callback when the floating element is opened or closed
51
+ */
52
+ onOpenChange?: (open: boolean) => void;
53
+ }
54
+ interface UsePositionedFloatingReturn<RT extends ReferenceType = ReferenceType> {
55
+ open: boolean;
56
+ onOpenChange: ((open: boolean) => void) | undefined;
57
+ refs: ExtendedRefs<RT> & {
58
+ arrow: HTMLElement | null;
59
+ setArrow: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
60
+ arrowTip: HTMLElement | null;
61
+ setArrowTip: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
62
+ };
63
+ rects: {
64
+ reference: Rect;
65
+ floating: Rect;
66
+ arrowTip: {
67
+ width: number;
68
+ height: number;
69
+ };
70
+ };
71
+ isPositioned: boolean;
72
+ side: Side;
73
+ alignment: Alignment | undefined;
74
+ context: FloatingContext<RT>;
75
+ floatingStyles: CSSProperties;
76
+ arrowStyles: CSSProperties;
77
+ }
78
+ declare function usePositionedFloating<RT extends ReferenceType = ReferenceType>(props: UsePositionedFloatingProps): UsePositionedFloatingReturn<RT>;
79
+
80
+ export { usePositionedFloating };
81
+ export type { PositioningOptions, UsePositionedFloatingProps, UsePositionedFloatingReturn };
package/lib/index.js ADDED
@@ -0,0 +1,153 @@
1
+ import { useFloating, autoUpdate, offset, flip, shift, limitShift, size, arrow } from '@floating-ui/react';
2
+ import { useControllableState } from '@radix-ui/react-use-controllable-state';
3
+ import { useState, useEffect, useMemo } from 'react';
4
+
5
+ const defaultPositioningOptions = {
6
+ strategy: "absolute",
7
+ placement: "bottom",
8
+ flip: true,
9
+ slide: true,
10
+ overflowPadding: 8,
11
+ arrowPadding: 4
12
+ };
13
+ function getArrowMiddleware(arrowElement, opts) {
14
+ if (!arrowElement) return;
15
+ return arrow({
16
+ element: arrowElement,
17
+ padding: opts.arrowPadding
18
+ });
19
+ }
20
+ function getOffsetMiddleware(arrowOffset, opts) {
21
+ const offsetMainAxis = (opts.gutter ?? 0) + arrowOffset;
22
+ return offset(offsetMainAxis);
23
+ }
24
+ function getFlipMiddleware(opts) {
25
+ if (!opts.flip) return;
26
+ return flip({
27
+ padding: opts.overflowPadding,
28
+ fallbackPlacements: opts.flip === true ? undefined : opts.flip
29
+ });
30
+ }
31
+ function getShiftMiddleware(opts) {
32
+ if (!opts.slide) return;
33
+ return shift({
34
+ mainAxis: opts.slide,
35
+ padding: opts.overflowPadding,
36
+ limiter: limitShift()
37
+ });
38
+ }
39
+ function getSizeMiddleware(opts) {
40
+ return size({
41
+ padding: opts.overflowPadding,
42
+ apply ({ availableWidth, elements }) {
43
+ elements.floating.style.setProperty("--seed-popover-available-width", `${Math.max(0, availableWidth)}px`);
44
+ }
45
+ });
46
+ }
47
+ const rectMiddleware = {
48
+ name: "rects",
49
+ fn ({ rects }) {
50
+ return {
51
+ data: rects
52
+ };
53
+ }
54
+ };
55
+ const ARROW_FLOATING_STYLE = {
56
+ top: "",
57
+ right: "rotate(90deg)",
58
+ bottom: "rotate(180deg)",
59
+ left: "rotate(270deg)"
60
+ };
61
+ function usePositionedFloating(props) {
62
+ const options = {
63
+ ...defaultPositioningOptions,
64
+ ...props
65
+ };
66
+ const [open, onOpenChange] = useControllableState({
67
+ prop: props.open,
68
+ defaultProp: props.defaultOpen ?? false,
69
+ onChange: props.onOpenChange
70
+ });
71
+ const [arrowEl, setArrowEl] = useState(null);
72
+ const [arrowTipEl, setArrowTipEl] = useState(null);
73
+ const arrowTipWidth = arrowTipEl?.clientWidth ?? 0;
74
+ const arrowTipHeight = arrowTipEl?.clientHeight ?? 0;
75
+ const arrowTipOffset = arrowTipHeight;
76
+ const { refs, context, floatingStyles, middlewareData, isPositioned } = useFloating({
77
+ strategy: options.strategy,
78
+ open,
79
+ placement: options.placement,
80
+ onOpenChange: onOpenChange,
81
+ middleware: [
82
+ getOffsetMiddleware(arrowTipOffset, options),
83
+ getFlipMiddleware(options),
84
+ getShiftMiddleware(options),
85
+ getSizeMiddleware(options),
86
+ getArrowMiddleware(arrowEl, options),
87
+ rectMiddleware
88
+ ]
89
+ });
90
+ // https://floating-ui.com/docs/react#anchoring
91
+ useEffect(()=>{
92
+ if (!open) return;
93
+ if (!refs.reference.current || !refs.floating.current) return;
94
+ return autoUpdate(refs.reference.current, refs.floating.current, context.update);
95
+ }, [
96
+ open,
97
+ refs.reference,
98
+ refs.floating,
99
+ context
100
+ ]);
101
+ const [side, alignment] = context.placement.split("-");
102
+ const arrowStyles = useMemo(()=>({
103
+ position: "absolute",
104
+ left: middlewareData.arrow?.x,
105
+ top: middlewareData.arrow?.y,
106
+ [side]: "100%",
107
+ transform: ARROW_FLOATING_STYLE[side]
108
+ }), [
109
+ middlewareData.arrow,
110
+ side
111
+ ]);
112
+ return useMemo(()=>({
113
+ open,
114
+ onOpenChange,
115
+ refs: {
116
+ ...refs,
117
+ arrow: arrowEl,
118
+ setArrow: setArrowEl,
119
+ arrowTip: arrowTipEl,
120
+ setArrowTip: setArrowTipEl
121
+ },
122
+ rects: {
123
+ ...middlewareData["rects"],
124
+ arrowTip: {
125
+ width: arrowTipWidth,
126
+ height: arrowTipHeight
127
+ }
128
+ },
129
+ isPositioned,
130
+ side,
131
+ alignment,
132
+ context,
133
+ floatingStyles,
134
+ arrowStyles
135
+ }), [
136
+ open,
137
+ onOpenChange,
138
+ refs,
139
+ arrowEl,
140
+ arrowTipEl,
141
+ middlewareData["rects"],
142
+ context,
143
+ side,
144
+ alignment,
145
+ floatingStyles,
146
+ arrowStyles,
147
+ isPositioned,
148
+ arrowTipWidth,
149
+ arrowTipHeight
150
+ ]);
151
+ }
152
+
153
+ export { usePositionedFloating };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@seed-design/react-floating",
3
+ "version": "0.0.0-alpha-20260622094810",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/daangn/seed-design.git",
7
+ "directory": "packages/react-headless/floating"
8
+ },
9
+ "sideEffects": false,
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/index.d.ts",
14
+ "import": "./lib/index.js",
15
+ "require": "./lib/index.cjs"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "main": "./lib/index.cjs",
20
+ "files": [
21
+ "lib",
22
+ "src"
23
+ ],
24
+ "scripts": {
25
+ "clean": "rm -rf lib",
26
+ "build": "bunchee",
27
+ "lint:publish": "bun publint"
28
+ },
29
+ "dependencies": {
30
+ "@floating-ui/react": "^0.27.0",
31
+ "@radix-ui/react-use-controllable-state": "1.2.2"
32
+ },
33
+ "devDependencies": {
34
+ "@types/react": "^19.1.6",
35
+ "react": "^19.1.0",
36
+ "react-dom": "^19.1.0"
37
+ },
38
+ "peerDependencies": {
39
+ "react": ">=18.0.0",
40
+ "react-dom": ">=18.0.0"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }
package/src/index.ts ADDED
@@ -0,0 +1,258 @@
1
+ import {
2
+ arrow,
3
+ autoUpdate,
4
+ flip,
5
+ limitShift,
6
+ offset,
7
+ shift,
8
+ size,
9
+ useFloating,
10
+ type Alignment,
11
+ type ExtendedRefs,
12
+ type FloatingContext,
13
+ type Middleware,
14
+ type Placement,
15
+ type Rect,
16
+ type ReferenceType,
17
+ type Side,
18
+ } from "@floating-ui/react";
19
+ import { useControllableState } from "@radix-ui/react-use-controllable-state";
20
+ import { useEffect, useMemo, useState, type CSSProperties } from "react";
21
+
22
+ export interface PositioningOptions {
23
+ /**
24
+ * The strategy to use for positioning
25
+ * @default "absolute"
26
+ */
27
+ strategy?: "absolute" | "fixed";
28
+ /**
29
+ * The initial placement of the floating element
30
+ * @default "bottom"
31
+ */
32
+ placement?: Placement;
33
+ /**
34
+ * The gutter between the floating element and the reference element
35
+ */
36
+ gutter?: number;
37
+ /**
38
+ * Whether to flip the placement
39
+ * @default true
40
+ */
41
+ flip?: boolean | Placement[];
42
+ /**
43
+ * Whether the popover should slide when it overflows.
44
+ * @default true
45
+ */
46
+ slide?: boolean;
47
+ /**
48
+ * The virtual padding around the viewport edges to check for overflow
49
+ * @default 8
50
+ */
51
+ overflowPadding?: number;
52
+ /**
53
+ * The minimum padding between the arrow and the floating element's corner.
54
+ * @default 4
55
+ */
56
+ arrowPadding?: number;
57
+ }
58
+
59
+ const defaultPositioningOptions: PositioningOptions = {
60
+ strategy: "absolute",
61
+ placement: "bottom",
62
+ flip: true,
63
+ slide: true,
64
+ overflowPadding: 8,
65
+ arrowPadding: 4,
66
+ };
67
+
68
+ function getArrowMiddleware(arrowElement: HTMLElement | null, opts: PositioningOptions) {
69
+ if (!arrowElement) return;
70
+ return arrow({ element: arrowElement, padding: opts.arrowPadding });
71
+ }
72
+
73
+ function getOffsetMiddleware(arrowOffset: number, opts: PositioningOptions) {
74
+ const offsetMainAxis = (opts.gutter ?? 0) + arrowOffset;
75
+ return offset(offsetMainAxis);
76
+ }
77
+
78
+ function getFlipMiddleware(opts: PositioningOptions) {
79
+ if (!opts.flip) return;
80
+ return flip({
81
+ padding: opts.overflowPadding,
82
+ fallbackPlacements: opts.flip === true ? undefined : opts.flip,
83
+ });
84
+ }
85
+
86
+ function getShiftMiddleware(opts: PositioningOptions) {
87
+ if (!opts.slide) return;
88
+ return shift({
89
+ mainAxis: opts.slide,
90
+ padding: opts.overflowPadding,
91
+ limiter: limitShift(),
92
+ });
93
+ }
94
+
95
+ function getSizeMiddleware(opts: PositioningOptions) {
96
+ return size({
97
+ padding: opts.overflowPadding,
98
+ apply({ availableWidth, elements }) {
99
+ elements.floating.style.setProperty(
100
+ "--seed-popover-available-width",
101
+ `${Math.max(0, availableWidth)}px`,
102
+ );
103
+ },
104
+ });
105
+ }
106
+
107
+ const rectMiddleware: Middleware = {
108
+ name: "rects",
109
+ fn({ rects }) {
110
+ return {
111
+ data: rects,
112
+ };
113
+ },
114
+ };
115
+
116
+ export interface UsePositionedFloatingProps extends PositioningOptions {
117
+ /**
118
+ * Whether the floating element is initially open
119
+ */
120
+ defaultOpen?: boolean;
121
+ /**
122
+ * Whether the floating element is open
123
+ */
124
+ open?: boolean;
125
+ /**
126
+ * Callback when the floating element is opened or closed
127
+ */
128
+ onOpenChange?: (open: boolean) => void;
129
+ }
130
+
131
+ const ARROW_FLOATING_STYLE = {
132
+ top: "",
133
+ right: "rotate(90deg)",
134
+ bottom: "rotate(180deg)",
135
+ left: "rotate(270deg)",
136
+ } as const;
137
+
138
+ // Explicit return type interface - leveraging @floating-ui/react types
139
+ export interface UsePositionedFloatingReturn<RT extends ReferenceType = ReferenceType> {
140
+ open: boolean;
141
+ onOpenChange: ((open: boolean) => void) | undefined;
142
+ refs: ExtendedRefs<RT> & {
143
+ arrow: HTMLElement | null;
144
+ setArrow: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
145
+ arrowTip: HTMLElement | null;
146
+ setArrowTip: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
147
+ };
148
+ rects: {
149
+ reference: Rect;
150
+ floating: Rect;
151
+ arrowTip: { width: number; height: number };
152
+ };
153
+ isPositioned: boolean;
154
+ side: Side;
155
+ alignment: Alignment | undefined;
156
+ context: FloatingContext<RT>;
157
+ floatingStyles: CSSProperties;
158
+ arrowStyles: CSSProperties;
159
+ }
160
+
161
+ export function usePositionedFloating<RT extends ReferenceType = ReferenceType>(
162
+ props: UsePositionedFloatingProps,
163
+ ): UsePositionedFloatingReturn<RT> {
164
+ const options = { ...defaultPositioningOptions, ...props };
165
+
166
+ const [open, onOpenChange] = useControllableState({
167
+ prop: props.open,
168
+ defaultProp: props.defaultOpen ?? false,
169
+ onChange: props.onOpenChange,
170
+ });
171
+ const [arrowEl, setArrowEl] = useState<HTMLElement | null>(null);
172
+ const [arrowTipEl, setArrowTipEl] = useState<HTMLElement | null>(null);
173
+
174
+ const arrowTipWidth = arrowTipEl?.clientWidth ?? 0;
175
+ const arrowTipHeight = arrowTipEl?.clientHeight ?? 0;
176
+ const arrowTipOffset = arrowTipHeight;
177
+
178
+ const { refs, context, floatingStyles, middlewareData, isPositioned } = useFloating<RT>({
179
+ strategy: options.strategy,
180
+ open,
181
+ placement: options.placement,
182
+ onOpenChange: onOpenChange,
183
+ middleware: [
184
+ getOffsetMiddleware(arrowTipOffset, options),
185
+ getFlipMiddleware(options),
186
+ getShiftMiddleware(options),
187
+ getSizeMiddleware(options),
188
+ getArrowMiddleware(arrowEl, options),
189
+ rectMiddleware,
190
+ ],
191
+ // instead of defining `whileElementsMounted` here, we use an effect below
192
+ });
193
+
194
+ // https://floating-ui.com/docs/react#anchoring
195
+ useEffect(() => {
196
+ if (!open) return;
197
+ if (!refs.reference.current || !refs.floating.current) return;
198
+
199
+ return autoUpdate(refs.reference.current, refs.floating.current, context.update);
200
+ }, [open, refs.reference, refs.floating, context]);
201
+
202
+ const [side, alignment] = context.placement.split("-") as [Side, Alignment | undefined];
203
+
204
+ const arrowStyles = useMemo(
205
+ () =>
206
+ ({
207
+ position: "absolute",
208
+ left: middlewareData.arrow?.x,
209
+ top: middlewareData.arrow?.y,
210
+ [side]: "100%",
211
+ transform: ARROW_FLOATING_STYLE[side],
212
+ }) as const,
213
+ [middlewareData.arrow, side],
214
+ );
215
+
216
+ return useMemo(
217
+ () => ({
218
+ open,
219
+ onOpenChange,
220
+ refs: {
221
+ ...refs,
222
+ arrow: arrowEl,
223
+ setArrow: setArrowEl,
224
+ arrowTip: arrowTipEl,
225
+ setArrowTip: setArrowTipEl,
226
+ },
227
+ rects: {
228
+ ...middlewareData["rects"],
229
+ arrowTip: {
230
+ width: arrowTipWidth,
231
+ height: arrowTipHeight,
232
+ },
233
+ },
234
+ isPositioned,
235
+ side,
236
+ alignment,
237
+ context,
238
+ floatingStyles,
239
+ arrowStyles,
240
+ }),
241
+ [
242
+ open,
243
+ onOpenChange,
244
+ refs,
245
+ arrowEl,
246
+ arrowTipEl,
247
+ middlewareData["rects"],
248
+ context,
249
+ side,
250
+ alignment,
251
+ floatingStyles,
252
+ arrowStyles,
253
+ isPositioned,
254
+ arrowTipWidth,
255
+ arrowTipHeight,
256
+ ],
257
+ );
258
+ }