@simplybusiness/mobius 9.3.0 → 9.3.2
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/CHANGELOG.md +12 -0
- package/dist/cjs/components/AddressLookup/AddressLookup.js.map +2 -2
- package/dist/cjs/components/AddressLookup/index.js.map +2 -2
- package/dist/cjs/components/Checkbox/Checkbox.js.map +2 -2
- package/dist/cjs/components/Checkbox/CheckboxGroup.js.map +2 -2
- package/dist/cjs/components/Checkbox/index.js.map +2 -2
- package/dist/cjs/components/Combobox/Combobox.js.map +2 -2
- package/dist/cjs/components/Combobox/index.js.map +2 -2
- package/dist/cjs/components/DateField/DateField.js.map +2 -2
- package/dist/cjs/components/DateField/index.js.map +2 -2
- package/dist/cjs/components/ErrorMessage/ErrorMessage.js.map +2 -2
- package/dist/cjs/components/ErrorMessage/index.js.map +2 -2
- package/dist/cjs/components/ExpandableText/ExpandableText.js.map +2 -2
- package/dist/cjs/components/ExpandableText/index.js.map +2 -2
- package/dist/cjs/components/MaskedField/MaskedField.js.map +2 -2
- package/dist/cjs/components/MaskedField/index.js.map +2 -2
- package/dist/cjs/components/NumberField/NumberField.js.map +2 -2
- package/dist/cjs/components/NumberField/index.js.map +2 -2
- package/dist/cjs/components/PasswordField/PasswordField.js.map +2 -2
- package/dist/cjs/components/PasswordField/ShowHideButton.js.map +2 -2
- package/dist/cjs/components/PasswordField/index.js.map +2 -2
- package/dist/cjs/components/Popover/Popover.js +21 -1
- package/dist/cjs/components/Popover/Popover.js.map +2 -2
- package/dist/cjs/components/Popover/index.js +21 -1
- package/dist/cjs/components/Popover/index.js.map +2 -2
- package/dist/cjs/components/Radio/Radio.js.map +2 -2
- package/dist/cjs/components/Radio/RadioGroup.js.map +2 -2
- package/dist/cjs/components/Radio/index.js.map +2 -2
- package/dist/cjs/components/Select/Select.js.map +2 -2
- package/dist/cjs/components/Select/index.js.map +2 -2
- package/dist/cjs/components/TextArea/TextArea.js.map +2 -2
- package/dist/cjs/components/TextArea/index.js.map +2 -2
- package/dist/cjs/components/TextField/TextField.js.map +2 -2
- package/dist/cjs/components/TextField/index.js.map +2 -2
- package/dist/cjs/components/index.js +21 -1
- package/dist/cjs/components/index.js.map +2 -2
- package/dist/cjs/index.js +21 -1
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/meta.json +42 -42
- package/dist/esm/{chunk-ASLEVOVU.js → chunk-MXQ4PJF4.js} +1 -1
- package/dist/esm/{chunk-IS74FB4O.js → chunk-O5YEU5TG.js} +23 -3
- package/dist/esm/chunk-O5YEU5TG.js.map +7 -0
- package/dist/esm/components/AddressLookup/AddressLookup.js +2 -2
- package/dist/esm/components/AddressLookup/index.js +2 -2
- package/dist/esm/components/Checkbox/Checkbox.js +2 -2
- package/dist/esm/components/Checkbox/CheckboxGroup.js +2 -2
- package/dist/esm/components/Checkbox/index.js +2 -2
- package/dist/esm/components/Combobox/Combobox.js +2 -2
- package/dist/esm/components/Combobox/index.js +2 -2
- package/dist/esm/components/DateField/DateField.js +2 -2
- package/dist/esm/components/DateField/index.js +2 -2
- package/dist/esm/components/ErrorMessage/ErrorMessage.js +2 -2
- package/dist/esm/components/ErrorMessage/index.js +2 -2
- package/dist/esm/components/ExpandableText/ExpandableText.js +2 -2
- package/dist/esm/components/ExpandableText/index.js +2 -2
- package/dist/esm/components/MaskedField/MaskedField.js +2 -2
- package/dist/esm/components/MaskedField/index.js +2 -2
- package/dist/esm/components/NumberField/NumberField.js +2 -2
- package/dist/esm/components/NumberField/index.js +2 -2
- package/dist/esm/components/PasswordField/PasswordField.js +2 -2
- package/dist/esm/components/PasswordField/ShowHideButton.js +2 -2
- package/dist/esm/components/PasswordField/index.js +2 -2
- package/dist/esm/components/Popover/Popover.js +1 -1
- package/dist/esm/components/Popover/index.js +1 -1
- package/dist/esm/components/Radio/Radio.js +2 -2
- package/dist/esm/components/Radio/RadioGroup.js +2 -2
- package/dist/esm/components/Radio/index.js +2 -2
- package/dist/esm/components/Select/Select.js +2 -2
- package/dist/esm/components/Select/index.js +2 -2
- package/dist/esm/components/TextArea/TextArea.js +2 -2
- package/dist/esm/components/TextArea/index.js +2 -2
- package/dist/esm/components/TextField/TextField.js +2 -2
- package/dist/esm/components/TextField/index.js +2 -2
- package/dist/esm/components/index.js +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/meta.json +72 -72
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -1
- package/src/components/Combobox/Combobox.mdx +50 -6
- package/src/components/ErrorMessage/ErrorMessage.css +4 -4
- package/src/components/Modal/Modal.css +1 -1
- package/src/components/Popover/Popover.css +2 -2
- package/src/components/Popover/Popover.test.tsx +94 -0
- package/src/components/Popover/Popover.tsx +30 -2
- package/dist/esm/chunk-IS74FB4O.js.map +0 -7
- /package/dist/esm/{chunk-ASLEVOVU.js.map → chunk-MXQ4PJF4.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplybusiness/mobius",
|
|
3
3
|
"license": "UNLICENSED",
|
|
4
|
-
"version": "9.3.
|
|
4
|
+
"version": "9.3.2",
|
|
5
5
|
"description": "Core library of Mobius react components",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"import": "./dist/esm/index.js",
|
|
24
24
|
"default": "./dist/esm/index.js"
|
|
25
25
|
},
|
|
26
|
+
"./package.json": "./package.json",
|
|
26
27
|
"./src/*.css": "./src/*.css"
|
|
27
28
|
},
|
|
28
29
|
"publishConfig": {
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"import": "./dist/esm/index.js",
|
|
37
38
|
"default": "./dist/esm/index.js"
|
|
38
39
|
},
|
|
40
|
+
"./package.json": "./package.json",
|
|
39
41
|
"./src/*.css": "./src/*.css"
|
|
40
42
|
}
|
|
41
43
|
},
|
|
@@ -35,15 +35,58 @@ type ComboboxOptions = T[] | ComboboxOptionGroup<T>[];
|
|
|
35
35
|
|
|
36
36
|
### Dynamic / asynchronous use
|
|
37
37
|
|
|
38
|
-
`Combobox` provides a useful primitive with which to build more dynamic selector components, such as `Trade Selector` and `
|
|
38
|
+
`Combobox` provides a useful primitive with which to build more dynamic selector components, such as `Trade Selector` and `Address Lookup`.
|
|
39
39
|
|
|
40
|
-
For these kinds of component, options (list) data is not known and must be fetched, typically from an API, as the user types.
|
|
40
|
+
For these kinds of component, options (list) data is not known and must be fetched, typically from an API, as the user types. Use the `asyncOptions` prop with a function that receives the current input and returns a promise of options:
|
|
41
41
|
|
|
42
42
|
```ts
|
|
43
|
-
type
|
|
43
|
+
type AsyncOptionsFetcher = (
|
|
44
|
+
inputValue: string,
|
|
45
|
+
options?: { signal?: AbortSignal },
|
|
46
|
+
) => Promise<Options>;
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
The Combobox calls `asyncOptions` **imperatively** when the debounced input changes. Pass the optional `signal` to your fetch so in-flight requests can be cancelled when the user types again.
|
|
50
|
+
|
|
51
|
+
- Provide a `delay` value (e.g. 300ms) to debounce API requests.
|
|
52
|
+
- Use `minSearchLength` to avoid fetching until the user has typed enough characters.
|
|
53
|
+
|
|
54
|
+
**When options come from parent state** (e.g. TanStack Query's `data` from `useQuery`, or a separate fetch triggered by `onChange`), use the sync `options` prop instead of `asyncOptions`. The Combobox only re-calls `asyncOptions` when the debounced input changes, so it won't see updates when your parent state changes. Pass `options={...}` derived from your state so the options update when the fetch completes.
|
|
55
|
+
|
|
56
|
+
**When using TanStack Query** (or similar) with `asyncOptions`, avoid calling Hooks (like `useQuery`) inside `asyncOptions`, as this violates the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks). Instead, use `queryClient.fetchQuery` inside `asyncOptions`, or call `useQuery` at the component level and pass `options={data ?? []}` to the Combobox:
|
|
57
|
+
|
|
58
|
+
- ❌ **Bad**: `useQuery` inside `asyncOptions` — `useQuery` is a React Hook and must be called at the top level of a component, not from an imperative callback. While `useQuery` *can* re-run when the query key includes the input, it should not be invoked from `asyncOptions`.
|
|
59
|
+
- ✅ **Good**: `queryClient.fetchQuery` inside `asyncOptions` — imperative, query key includes input, respects AbortSignal.
|
|
60
|
+
|
|
61
|
+
Example with TanStack Query:
|
|
62
|
+
|
|
63
|
+
{/* prettier-ignore */}
|
|
64
|
+
```jsx
|
|
65
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
66
|
+
import { Combobox } from "@simplybusiness/mobius";
|
|
67
|
+
|
|
68
|
+
function FruitSelector() {
|
|
69
|
+
const queryClient = useQueryClient();
|
|
70
|
+
const fetchOptions = async (
|
|
71
|
+
inputValue: string,
|
|
72
|
+
{ signal }: { signal?: AbortSignal } = {}
|
|
73
|
+
) => {
|
|
74
|
+
return queryClient.fetchQuery({
|
|
75
|
+
queryKey: ["fruits", inputValue],
|
|
76
|
+
queryFn: () => fetchFruits(inputValue, { signal }),
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
return (
|
|
80
|
+
<Combobox
|
|
81
|
+
label="Select a fruit"
|
|
82
|
+
asyncOptions={fetchOptions}
|
|
83
|
+
delay={300}
|
|
84
|
+
minSearchLength={3}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
// fetchFruits(inputValue, { signal }) is your API function — pass signal for cancellation
|
|
89
|
+
```
|
|
47
90
|
|
|
48
91
|
### Overriding selection behaviour
|
|
49
92
|
|
|
@@ -138,6 +181,7 @@ import { search } from "@simplybusiness/icons";
|
|
|
138
181
|
You can customize how options are rendered using the `optionComponent` prop. This allows you to create custom layouts, add icons, or apply custom styling to individual options.
|
|
139
182
|
|
|
140
183
|
The `optionComponent` prop accepts a component that receives two props:
|
|
184
|
+
|
|
141
185
|
- `option`: The option data object
|
|
142
186
|
- `isHighlighted`: A boolean indicating if the option is currently highlighted
|
|
143
187
|
|
|
@@ -147,7 +191,7 @@ The `optionComponent` prop accepts a component that receives two props:
|
|
|
147
191
|
const options = [
|
|
148
192
|
{ id: "apple", label: "Apple", title: "Heading 1" },
|
|
149
193
|
{ id: "apricot", label: "Apricot", title: "Heading 2" },
|
|
150
|
-
{ id: "etc", label: "etc.", title: "Heading 3" }
|
|
194
|
+
{ id: "etc", label: "etc.", title: "Heading 3" },
|
|
151
195
|
];
|
|
152
196
|
|
|
153
197
|
function MyCustomOptionComponent({ option, isHighlighted }) {
|
|
@@ -159,7 +203,7 @@ function MyCustomOptionComponent({ option, isHighlighted }) {
|
|
|
159
203
|
);
|
|
160
204
|
}
|
|
161
205
|
|
|
162
|
-
<Combobox options={OPTIONS} optionComponent={MyCustomOptionComponent}
|
|
206
|
+
<Combobox options={OPTIONS} optionComponent={MyCustomOptionComponent} />;
|
|
163
207
|
```
|
|
164
208
|
|
|
165
209
|
<Story of={ComboboxStories.CustomOptionComponent} />
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
gap: var(--error-message-grid-gap, var(--size-xs));
|
|
5
5
|
grid-template-columns: min-content 1fr;
|
|
6
6
|
color: var(--color-error);
|
|
7
|
-
|
|
7
|
+
|
|
8
8
|
& a {
|
|
9
9
|
color: var(--color-link);
|
|
10
10
|
outline: none;
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
.mobius-error-message__icon {
|
|
28
|
-
width: var(--error-message-icon-width,
|
|
29
|
-
height: var(--error-message-icon-height,
|
|
27
|
+
.mobius-error-message__icon.mobius-icon {
|
|
28
|
+
width: var(--error-message-icon-width, 1em);
|
|
29
|
+
height: var(--error-message-icon-height, 1em);
|
|
30
30
|
overflow: hidden;
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
padding-bottom: var(--size-sm);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
.mobius-popover__close-button {
|
|
22
|
+
.mobius-popover__close-button.mobius-button {
|
|
23
23
|
padding: 0;
|
|
24
24
|
border: none;
|
|
25
25
|
margin-left: auto;
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
color: var(--color-text-popover);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
.mobius-popover__close-icon {
|
|
30
|
+
.mobius-popover__close-icon.mobius-icon {
|
|
31
31
|
height: 1.1em;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -127,6 +127,100 @@ describe("Popover", () => {
|
|
|
127
127
|
});
|
|
128
128
|
});
|
|
129
129
|
|
|
130
|
+
describe("click isolation", () => {
|
|
131
|
+
it("does not activate a parent label when clicking popover content", async () => {
|
|
132
|
+
const triggerText = "Click me";
|
|
133
|
+
const bodyText = "Popover body";
|
|
134
|
+
const onCheckboxChange = vi.fn();
|
|
135
|
+
|
|
136
|
+
render(
|
|
137
|
+
<label htmlFor="test-checkbox">
|
|
138
|
+
<input
|
|
139
|
+
id="test-checkbox"
|
|
140
|
+
type="checkbox"
|
|
141
|
+
onChange={onCheckboxChange}
|
|
142
|
+
/>
|
|
143
|
+
<Popover trigger={<button type="button">{triggerText}</button>}>
|
|
144
|
+
<span>{bodyText}</span>
|
|
145
|
+
</Popover>
|
|
146
|
+
</label>,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
fireEvent.click(screen.getByText(triggerText));
|
|
150
|
+
|
|
151
|
+
await waitFor(() => {
|
|
152
|
+
expect(screen.getByText(bodyText)).toBeInTheDocument();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
fireEvent.click(screen.getByText(bodyText));
|
|
156
|
+
|
|
157
|
+
expect(onCheckboxChange).not.toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("allows default behavior for links inside the popover", async () => {
|
|
161
|
+
const triggerText = "Click me";
|
|
162
|
+
const onCheckboxChange = vi.fn();
|
|
163
|
+
|
|
164
|
+
render(
|
|
165
|
+
<label htmlFor="test-checkbox">
|
|
166
|
+
<input
|
|
167
|
+
id="test-checkbox"
|
|
168
|
+
type="checkbox"
|
|
169
|
+
onChange={onCheckboxChange}
|
|
170
|
+
/>
|
|
171
|
+
<Popover trigger={<button type="button">{triggerText}</button>}>
|
|
172
|
+
<a href="https://example.com">Example link</a>
|
|
173
|
+
</Popover>
|
|
174
|
+
</label>,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
fireEvent.click(screen.getByText(triggerText));
|
|
178
|
+
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(screen.getByText("Example link")).toBeInTheDocument();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const link = screen.getByText("Example link");
|
|
184
|
+
|
|
185
|
+
const clickEvent = new MouseEvent("click", { bubbles: true });
|
|
186
|
+
const preventDefaultSpy = vi.spyOn(clickEvent, "preventDefault");
|
|
187
|
+
|
|
188
|
+
link.dispatchEvent(clickEvent);
|
|
189
|
+
|
|
190
|
+
expect(preventDefaultSpy).not.toHaveBeenCalled();
|
|
191
|
+
expect(onCheckboxChange).not.toHaveBeenCalled();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("does not activate a parent label when clicking the close button", async () => {
|
|
195
|
+
const triggerText = "Click me";
|
|
196
|
+
const bodyText = "Popover body";
|
|
197
|
+
const onCheckboxChange = vi.fn();
|
|
198
|
+
|
|
199
|
+
render(
|
|
200
|
+
<label htmlFor="test-checkbox">
|
|
201
|
+
<input
|
|
202
|
+
id="test-checkbox"
|
|
203
|
+
type="checkbox"
|
|
204
|
+
onChange={onCheckboxChange}
|
|
205
|
+
/>
|
|
206
|
+
<Popover trigger={<button type="button">{triggerText}</button>}>
|
|
207
|
+
<span>{bodyText}</span>
|
|
208
|
+
</Popover>
|
|
209
|
+
</label>,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
fireEvent.click(screen.getByText(triggerText));
|
|
213
|
+
|
|
214
|
+
await waitFor(() => {
|
|
215
|
+
expect(screen.getByText(bodyText)).toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
fireEvent.click(screen.getByLabelText("Close"));
|
|
219
|
+
|
|
220
|
+
expect(onCheckboxChange).not.toHaveBeenCalled();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
130
224
|
describe("events", () => {
|
|
131
225
|
it("calls onOpen when Popover is opened", async () => {
|
|
132
226
|
const sampleText = "Sample Text";
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { cross } from "@simplybusiness/icons";
|
|
13
13
|
import classNames from "classnames/dedupe";
|
|
14
14
|
import type { ReactElement, ReactNode, RefAttributes } from "react";
|
|
15
|
-
import { cloneElement, useRef, useState } from "react";
|
|
15
|
+
import { cloneElement, useCallback, useEffect, useRef, useState } from "react";
|
|
16
16
|
import { useWindowEvent } from "@simplybusiness/mobius-hooks";
|
|
17
17
|
import type { DOMProps } from "../../types";
|
|
18
18
|
import { Button } from "../Button";
|
|
@@ -38,6 +38,7 @@ const OFFSET_FROM_CONTENT_DEFAULT = 10;
|
|
|
38
38
|
export const Popover = (props: PopoverProps) => {
|
|
39
39
|
const { trigger, children, onOpen, onClose, className } = props;
|
|
40
40
|
const arrowRef = useRef(null);
|
|
41
|
+
const floatingContainerRef = useRef<HTMLDivElement | null>(null);
|
|
41
42
|
const [isOpen, setIsOpen] = useState(false);
|
|
42
43
|
const { refs, floatingStyles, context } = useFloating({
|
|
43
44
|
open: isOpen,
|
|
@@ -72,6 +73,33 @@ export const Popover = (props: PopoverProps) => {
|
|
|
72
73
|
className,
|
|
73
74
|
);
|
|
74
75
|
|
|
76
|
+
const setFloatingRef = useCallback(
|
|
77
|
+
(node: HTMLDivElement | null) => {
|
|
78
|
+
refs.setFloating(node);
|
|
79
|
+
floatingContainerRef.current = node;
|
|
80
|
+
},
|
|
81
|
+
[refs],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Native listener to prevent clicks inside the popover from activating
|
|
85
|
+
// interactive ancestors. Must be native because React's onClick fires
|
|
86
|
+
// too late via delegation.
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const el = floatingContainerRef.current;
|
|
89
|
+
if (!el) return;
|
|
90
|
+
|
|
91
|
+
const preventLabelActivation = (e: Event) => {
|
|
92
|
+
const target = e.target as HTMLElement;
|
|
93
|
+
// Allow default behavior for interactive elements (links, inputs, etc.)
|
|
94
|
+
// so they remain functional inside the popover.
|
|
95
|
+
if (!target.closest("a[href], input, select, textarea")) {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
el.addEventListener("click", preventLabelActivation);
|
|
100
|
+
return () => el.removeEventListener("click", preventLabelActivation);
|
|
101
|
+
}, [isOpen]);
|
|
102
|
+
|
|
75
103
|
const toggleVisibility = () => {
|
|
76
104
|
if (isOpen) {
|
|
77
105
|
setIsOpen(false);
|
|
@@ -107,7 +135,7 @@ export const Popover = (props: PopoverProps) => {
|
|
|
107
135
|
{isOpen && (
|
|
108
136
|
<div
|
|
109
137
|
className={containerClasses}
|
|
110
|
-
ref={
|
|
138
|
+
ref={setFloatingRef}
|
|
111
139
|
style={floatingStyles}
|
|
112
140
|
{...getFloatingProps()}
|
|
113
141
|
>
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/components/Popover/Popover.tsx"],
|
|
4
|
-
"sourcesContent": ["import {\n FloatingArrow,\n arrow,\n autoUpdate,\n flip,\n offset,\n shift,\n useDismiss,\n useFloating,\n useInteractions,\n} from \"@floating-ui/react\";\nimport { cross } from \"@simplybusiness/icons\";\nimport classNames from \"classnames/dedupe\";\nimport type { ReactElement, ReactNode, RefAttributes } from \"react\";\nimport { cloneElement, useRef, useState } from \"react\";\nimport { useWindowEvent } from \"@simplybusiness/mobius-hooks\";\nimport type { DOMProps } from \"../../types\";\nimport { Button } from \"../Button\";\nimport { Icon } from \"../Icon\";\nimport \"./Popover.css\";\n\nexport type PopoverElementType = HTMLDivElement;\n\nexport interface PopoverProps\n extends DOMProps, RefAttributes<PopoverElementType> {\n children?: ReactNode;\n trigger: ReactElement;\n /** Callback that fires each time the accordion is opened */\n onOpen?: () => void;\n /** Callback that fires each time the accordion is closed */\n onClose?: () => void;\n /** Custom class name for setting specific CSS */\n className?: string;\n}\n\nconst OFFSET_FROM_CONTENT_DEFAULT = 10;\n\nexport const Popover = (props: PopoverProps) => {\n const { trigger, children, onOpen, onClose, className } = props;\n const arrowRef = useRef(null);\n const [isOpen, setIsOpen] = useState(false);\n const { refs, floatingStyles, context } = useFloating({\n open: isOpen,\n onOpenChange: setIsOpen,\n whileElementsMounted: autoUpdate,\n middleware: [\n arrow({\n element: arrowRef,\n }),\n offset(OFFSET_FROM_CONTENT_DEFAULT),\n shift(),\n flip(),\n ],\n });\n const dismiss = useDismiss(context, {\n bubbles: true,\n outsidePress: (event: MouseEvent) => {\n // Prevent 'onClose' from firing when clicking the toggle to close\n const toggle = refs.reference.current as HTMLElement;\n const isToggleClick = !toggle?.contains(event.target as HTMLElement);\n if (isToggleClick) {\n onClose?.();\n }\n return true;\n },\n });\n const { getReferenceProps, getFloatingProps } = useInteractions([dismiss]);\n\n const containerClasses = classNames(\n \"mobius\",\n \"mobius-popover__container\",\n className,\n );\n\n const toggleVisibility = () => {\n if (isOpen) {\n setIsOpen(false);\n onClose?.();\n return;\n }\n\n setIsOpen(true);\n onOpen?.();\n };\n\n const triggerComponent = cloneElement(trigger, {\n ref: refs.setReference,\n className: classNames(\n (trigger.props as { className?: string }).className,\n \"mobius-popover__toggle\",\n ),\n onClick: toggleVisibility,\n ...getReferenceProps(),\n } as Record<string, unknown>);\n\n useWindowEvent(\"keydown\", e => {\n if (e.key === \"Escape\") {\n onClose?.();\n e.preventDefault();\n e.stopPropagation();\n }\n });\n\n return (\n <>\n {triggerComponent}\n {isOpen && (\n <div\n className={containerClasses}\n ref={refs.setFloating}\n style={floatingStyles}\n {...getFloatingProps()}\n >\n <div className=\"mobius-popover\">\n <header className=\"mobius-popover__header\">\n <Button\n type=\"button\"\n className=\"mobius-popover__close-button\"\n onClick={toggleVisibility}\n aria-label=\"Close\"\n variant=\"ghost\"\n >\n <Icon\n icon={cross}\n size=\"md\"\n className=\"mobius-popover__close-icon\"\n />\n </Button>\n </header>\n <div className=\"mobius-popover__body\">{children}</div>\n </div>\n <FloatingArrow\n ref={arrowRef}\n context={context}\n width={20}\n className=\"mobius-popover__arrow-icon\"\n />\n </div>\n )}\n </>\n );\n};\n"],
|
|
5
|
-
"mappings": ";;;;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,OAAO,gBAAgB;AAEvB,SAAS,cAAc,QAAQ,gBAAgB;AAC/C,SAAS,sBAAsB;AAI/B,OAAO;AAqFH,mBAkBY,KATN,YATN;AArEJ,IAAM,8BAA8B;AAE7B,IAAM,UAAU,CAAC,UAAwB;AAC9C,QAAM,EAAE,SAAS,UAAU,QAAQ,SAAS,UAAU,IAAI;AAC1D,QAAM,WAAW,OAAO,IAAI;AAC5B,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,EAAE,MAAM,gBAAgB,QAAQ,IAAI,YAAY;AAAA,IACpD,MAAM;AAAA,IACN,cAAc;AAAA,IACd,sBAAsB;AAAA,IACtB,YAAY;AAAA,MACV,MAAM;AAAA,QACJ,SAAS;AAAA,MACX,CAAC;AAAA,MACD,OAAO,2BAA2B;AAAA,MAClC,MAAM;AAAA,MACN,KAAK;AAAA,IACP;AAAA,EACF,CAAC;AACD,QAAM,UAAU,WAAW,SAAS;AAAA,IAClC,SAAS;AAAA,IACT,cAAc,CAAC,UAAsB;AAEnC,YAAM,SAAS,KAAK,UAAU;AAC9B,YAAM,gBAAgB,CAAC,QAAQ,SAAS,MAAM,MAAqB;AACnE,UAAI,eAAe;AACjB,kBAAU;AAAA,MACZ;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AACD,QAAM,EAAE,mBAAmB,iBAAiB,IAAI,gBAAgB,CAAC,OAAO,CAAC;AAEzE,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,mBAAmB,MAAM;AAC7B,QAAI,QAAQ;AACV,gBAAU,KAAK;AACf,gBAAU;AACV;AAAA,IACF;AAEA,cAAU,IAAI;AACd,aAAS;AAAA,EACX;AAEA,QAAM,mBAAmB,aAAa,SAAS;AAAA,IAC7C,KAAK,KAAK;AAAA,IACV,WAAW;AAAA,MACR,QAAQ,MAAiC;AAAA,MAC1C;AAAA,IACF;AAAA,IACA,SAAS;AAAA,IACT,GAAG,kBAAkB;AAAA,EACvB,CAA4B;AAE5B,iBAAe,WAAW,OAAK;AAC7B,QAAI,EAAE,QAAQ,UAAU;AACtB,gBAAU;AACV,QAAE,eAAe;AACjB,QAAE,gBAAgB;AAAA,IACpB;AAAA,EACF,CAAC;AAED,SACE,iCACG;AAAA;AAAA,IACA,UACC;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,QACX,KAAK,KAAK;AAAA,QACV,OAAO;AAAA,QACN,GAAG,iBAAiB;AAAA,QAErB;AAAA,+BAAC,SAAI,WAAU,kBACb;AAAA,gCAAC,YAAO,WAAU,0BAChB;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS;AAAA,gBACT,cAAW;AAAA,gBACX,SAAQ;AAAA,gBAER;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAM;AAAA,oBACN,MAAK;AAAA,oBACL,WAAU;AAAA;AAAA,gBACZ;AAAA;AAAA,YACF,GACF;AAAA,YACA,oBAAC,SAAI,WAAU,wBAAwB,UAAS;AAAA,aAClD;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL;AAAA,cACA,OAAO;AAAA,cACP,WAAU;AAAA;AAAA,UACZ;AAAA;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
File without changes
|