@mario_hahn/jumpy-ui 0.14.0 → 0.14.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.
package/dist/index.js CHANGED
@@ -1,4 +1,83 @@
1
1
  "use client";
2
+ import {
3
+ JumpTokenInput
4
+ } from "./chunk-KYDYZC4E.js";
5
+ import {
6
+ BrandMark,
7
+ JumpAppShell,
8
+ JumpAvatar,
9
+ JumpBackground,
10
+ JumpBadge,
11
+ JumpBanner,
12
+ JumpBaseInput,
13
+ JumpButton,
14
+ JumpCard,
15
+ JumpCheckbox,
16
+ JumpChip,
17
+ JumpChoice,
18
+ JumpDatePicker,
19
+ JumpDialog,
20
+ JumpDivider,
21
+ JumpDropdown,
22
+ JumpExpandable,
23
+ JumpExpandableGroup,
24
+ JumpField,
25
+ JumpGrid,
26
+ JumpGridItem,
27
+ JumpHyperlink,
28
+ JumpIconButton,
29
+ JumpInput,
30
+ JumpLinkButton,
31
+ JumpList,
32
+ JumpListItem,
33
+ JumpLoader,
34
+ JumpMenu,
35
+ JumpMenuItem,
36
+ JumpModal,
37
+ JumpNotice,
38
+ JumpPageControl,
39
+ JumpPinInput,
40
+ JumpPopover,
41
+ JumpRadioGroup,
42
+ JumpSearchField,
43
+ JumpSectionFooter,
44
+ JumpSectionHeader,
45
+ JumpSheet,
46
+ JumpSideMenu,
47
+ JumpSideMenuItem,
48
+ JumpSkeletonLoader,
49
+ JumpSnackbarProvider,
50
+ JumpSpinner,
51
+ JumpSwitch,
52
+ JumpTabBar,
53
+ JumpTabBarItem,
54
+ JumpTable,
55
+ JumpTableBody,
56
+ JumpTableCell,
57
+ JumpTableFooter,
58
+ JumpTableHead,
59
+ JumpTableHeaderCell,
60
+ JumpTableRow,
61
+ JumpTabs,
62
+ JumpTag,
63
+ JumpTextarea,
64
+ JumpTimePicker,
65
+ JumpToggleButton,
66
+ JumpToggleButtonGroup,
67
+ JumpTooltip,
68
+ JumpTopBar,
69
+ JumpTypography,
70
+ KeyboardKeys,
71
+ applySubtreeSelection,
72
+ getHeaderCheckboxProps,
73
+ getNextSortOrder,
74
+ getRowCheckboxProps,
75
+ getSortActionLabel,
76
+ getSortMapping,
77
+ getSortOrder,
78
+ isRowFullySelected,
79
+ useJumpSnackbar
80
+ } from "./chunk-KUCMD6DR.js";
2
81
  import {
3
82
  JumpyAnimation,
4
83
  JumpyAnimationDelay,
@@ -11,7 +90,6 @@ import {
11
90
  JumpySpacing,
12
91
  JumpyTextStyle,
13
92
  JumpyThemeProvider,
14
- JumpyThemeScopeContext,
15
93
  animationDelayValue,
16
94
  animationDurationValue,
17
95
  animationEasingValue,
@@ -31,4557 +109,10 @@ import {
31
109
  themeToCSSVariables,
32
110
  useJumpyTheme
33
111
  } from "./chunk-KDFNGI6Y.js";
34
- import {
35
- cn
36
- } from "./chunk-3DBKYKP2.js";
112
+ import "./chunk-3DBKYKP2.js";
37
113
  import {
38
114
  jumpyThemeInventory
39
115
  } from "./chunk-XDTVYUWG.js";
40
-
41
- // index.js
42
- import { autoUpdate, flip, FloatingFocusManager, FloatingOverlay, FloatingPortal, offset, shift, size, useFloating } from "@floating-ui/react";
43
- import { Children, cloneElement, createContext, forwardRef, isValidElement, useContext, useEffect as useEffect2, useId, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState2 } from "react";
44
-
45
- // token-input.js
46
- import { useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
47
- import { $addUpdateTag, $createLineBreakNode, $createParagraphNode, $createTextNode, $getNodeByKey, $getRoot, $getSelection, $isElementNode, $isLineBreakNode, $isNodeSelection, $isRangeSelection, $isTextNode, $setSelection, CLEAR_HISTORY_COMMAND, COMMAND_PRIORITY_HIGH, COPY_COMMAND, CUT_COMMAND, DELETE_CHARACTER_COMMAND, DecoratorNode, HISTORY_PUSH_TAG, KEY_BACKSPACE_COMMAND, KEY_DELETE_COMMAND, KEY_ENTER_COMMAND, PASTE_COMMAND } from "lexical";
48
- import { $generateJSONFromSelectedNodes } from "@lexical/clipboard";
49
- import { LexicalComposer } from "@lexical/react/LexicalComposer";
50
- import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
51
- import { ContentEditable } from "@lexical/react/LexicalContentEditable";
52
- import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
53
- import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
54
- import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
55
- import { jsx, jsxs } from "react/jsx-runtime";
56
- var clipboardType = "application/x-jumpy-token-input";
57
- function normalizeTokenInputValue(value, multiline = false) {
58
- const result = [];
59
- const ids = /* @__PURE__ */ new Set();
60
- for (const part of value) {
61
- if (part.type === "text") {
62
- const text = multiline ? part.text.replace(/\r\n?/g, "\n") : part.text.replace(/[\r\n]+/g, " ");
63
- if (!text) continue;
64
- const previous = result[result.length - 1];
65
- if (previous?.type === "text") previous.text += text;
66
- else result.push({ type: "text", text });
67
- } else if (part.type === "token") {
68
- if (typeof part.id !== "string" || !part.id || ids.has(part.id) || typeof part.value !== "string" || typeof part.label !== "string" || !part.label) throw new Error("JumpTokenInput tokens require unique ids, string values and non-empty labels");
69
- ids.add(part.id);
70
- result.push({ type: "token", id: part.id, value: part.value, label: part.label });
71
- } else throw new Error("Unknown JumpTokenInput value part");
72
- }
73
- return result;
74
- }
75
- function tokenInputText(value) {
76
- return value.map((part) => part.type === "text" ? part.text : part.label).join("");
77
- }
78
- var JumpyTokenNode = class _JumpyTokenNode extends DecoratorNode {
79
- static getType() {
80
- return "jumpy-token";
81
- }
82
- static clone(node) {
83
- return new _JumpyTokenNode(node.__token, node.__key);
84
- }
85
- static importJSON(json) {
86
- return new _JumpyTokenNode(json.token);
87
- }
88
- constructor(token, key) {
89
- super(key);
90
- this.__token = { ...token };
91
- }
92
- exportJSON() {
93
- return { ...super.exportJSON(), type: "jumpy-token", version: 1, token: { ...this.__token } };
94
- }
95
- getTextContent() {
96
- return this.__token.label;
97
- }
98
- isInline() {
99
- return true;
100
- }
101
- isKeyboardSelectable() {
102
- return false;
103
- }
104
- createDOM() {
105
- const element = document.createElement("span");
106
- element.contentEditable = "false";
107
- element.dataset.jumpyTokenId = this.__token.id;
108
- element.style.display = "inline-block";
109
- return element;
110
- }
111
- updateDOM() {
112
- return false;
113
- }
114
- decorate(editor) {
115
- return /* @__PURE__ */ jsx(TokenChip, { editor, nodeKey: this.__key, token: this.__token });
116
- }
117
- };
118
- function TokenChip({ editor, nodeKey, token }) {
119
- const theme = useJumpyTheme();
120
- const [editable, setEditable] = useState(editor.isEditable());
121
- useEffect(() => editor.registerEditableListener(setEditable), [editor]);
122
- return /* @__PURE__ */ jsxs("span", { "data-jumpy-slot": "input-token", style: { display: "inline-flex", maxWidth: "100%", boxSizing: "border-box", alignItems: "center", verticalAlign: "baseline", borderRadius: theme.radius.small, border: `${theme.borderWidth.small}px solid var(--jumpy-color-feedback-info-border)`, paddingInline: theme.spacing.small3XS, gap: theme.spacing.small3XS, background: "var(--jumpy-color-feedback-info-surface)", color: "var(--jumpy-color-text-primary)", fontSize: theme.fontFamily.bodySmall.fontSize, lineHeight: "inherit", whiteSpace: "inherit" }, children: [
123
- /* @__PURE__ */ jsx("span", { style: { minWidth: 0 }, children: token.label }),
124
- editable && /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, "aria-label": `Remove ${token.label}`, onMouseDown: (event) => event.preventDefault(), onClick: (event) => {
125
- event.preventDefault();
126
- event.stopPropagation();
127
- if (!editor.isEditable()) return;
128
- editor.update(() => {
129
- const node = $getNodeByKey(nodeKey);
130
- if (node) {
131
- node.selectPrevious();
132
- node.remove();
133
- }
134
- }, { tag: HISTORY_PUSH_TAG });
135
- editor.focus();
136
- }, style: { display: "inline-flex", alignItems: "center", appearance: "none", border: 0, padding: 0, background: "transparent", color: "inherit", font: "inherit", cursor: "pointer" }, children: /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", focusable: "false", width: theme.fontFamily.bodySmall.fontSize, height: theme.fontFamily.bodySmall.fontSize, viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "m4 4 8 8M12 4l-8 8", stroke: "currentColor", strokeWidth: "1.5" }) }) })
137
- ] });
138
- }
139
- function $readValue(multiline = false) {
140
- const parts = [];
141
- function visit(node) {
142
- if (node instanceof JumpyTokenNode) parts.push({ ...node.__token });
143
- else if ($isTextNode(node)) parts.push({ type: "text", text: node.getTextContent() });
144
- else if ($isLineBreakNode(node)) parts.push({ type: "text", text: "\n" });
145
- else if ($isElementNode(node)) node.getChildren().forEach(visit);
146
- }
147
- $getRoot().getChildren().forEach((node, index) => {
148
- if (index > 0) parts.push({ type: "text", text: "\n" });
149
- visit(node);
150
- });
151
- return normalizeTokenInputValue(parts, multiline);
152
- }
153
- function $nodes(parts) {
154
- return parts.flatMap((part) => part.type === "token" ? [new JumpyTokenNode(part)] : part.text.split("\n").flatMap((text, index) => [
155
- ...index > 0 ? [$createLineBreakNode()] : [],
156
- ...text ? [$createTextNode(text)] : []
157
- ]));
158
- }
159
- function $replaceValue(parts) {
160
- $getRoot().clear().append($createParagraphNode().append(...$nodes(parts)));
161
- }
162
- function $adjacentToken(backward) {
163
- const selection = $getSelection();
164
- if (!$isRangeSelection(selection) || !selection.isCollapsed()) return null;
165
- const { offset: offset2, type } = selection.anchor;
166
- const node = selection.anchor.getNode();
167
- if (type === "element" && $isElementNode(node)) return node.getChildAtIndex(offset2 - (backward ? 1 : 0));
168
- if ($isTextNode(node) && (backward ? offset2 === 0 : offset2 === node.getTextContentSize())) return backward ? node.getPreviousSibling() : node.getNextSibling();
169
- return null;
170
- }
171
- function EditorBehavior({ model, multiline, editable, editorRef, onValueChange, onChange, onClear }) {
172
- const [editor] = useLexicalComposerContext();
173
- const savedSelection = useRef(null);
174
- const callbacks = useRef({ onValueChange, onChange, onClear });
175
- callbacks.current = { onValueChange, onChange, onClear };
176
- const previousMode = useRef(multiline);
177
- useLayoutEffect(() => {
178
- editor.setEditable(editable);
179
- }, [editor, editable]);
180
- useLayoutEffect(() => {
181
- const current = editor.getEditorState().read(() => $readValue(multiline));
182
- if (previousMode.current !== multiline || JSON.stringify(current) !== JSON.stringify(model)) {
183
- savedSelection.current = null;
184
- editor.update(() => {
185
- $replaceValue(model);
186
- $setSelection(null);
187
- }, { tag: "jumpy-external-value" });
188
- editor.dispatchCommand(CLEAR_HISTORY_COMMAND, void 0);
189
- }
190
- previousMode.current = multiline;
191
- }, [editor, model, multiline]);
192
- function insert(parts) {
193
- if (!editor.isEditable()) return;
194
- editor.update(() => {
195
- const selection = savedSelection.current?.clone() || $getSelection();
196
- if (selection) $setSelection(selection);
197
- else $getRoot().selectEnd();
198
- $getSelection()?.insertNodes($nodes(parts));
199
- }, { tag: HISTORY_PUSH_TAG });
200
- editor.focus();
201
- }
202
- useImperativeHandle(editorRef, () => ({
203
- focus: () => editor.focus(),
204
- insertText: (text) => insert(normalizeTokenInputValue([{ type: "text", text }], multiline)),
205
- insertToken: (token) => {
206
- const part = { ...token, type: "token", id: token.id || crypto.randomUUID() };
207
- const current = editor.getEditorState().read(() => $readValue(multiline));
208
- normalizeTokenInputValue([...current, part], multiline);
209
- insert([part]);
210
- },
211
- removeToken: (id) => {
212
- if (!editor.isEditable()) return;
213
- editor.update(() => {
214
- function remove(node) {
215
- if (node instanceof JumpyTokenNode && node.__token.id === id) {
216
- node.selectPrevious();
217
- node.remove();
218
- } else if ($isElementNode(node)) node.getChildren().forEach(remove);
219
- }
220
- remove($getRoot());
221
- }, { tag: HISTORY_PUSH_TAG });
222
- },
223
- clear: () => {
224
- if (!editor.isEditable()) return;
225
- if (editor.getEditorState().read(() => $readValue(multiline).length) === 0) return;
226
- editor.update(() => {
227
- if (!$getSelection()) $getRoot().selectEnd();
228
- }, { discrete: true });
229
- editor.update(() => {
230
- $replaceValue([]);
231
- $getRoot().selectStart();
232
- }, { tag: [HISTORY_PUSH_TAG, "jumpy-clear"] });
233
- editor.focus();
234
- }
235
- }));
236
- useEffect(() => {
237
- let previous = JSON.stringify(editor.getEditorState().read(() => $readValue(multiline)));
238
- const unregisterUpdate = editor.registerUpdateListener(({ editorState, tags }) => {
239
- editorState.read(() => {
240
- const selection = $getSelection();
241
- if (selection) savedSelection.current = selection.clone();
242
- const next = $readValue(multiline);
243
- const json = JSON.stringify(next);
244
- if (json === previous) return;
245
- previous = json;
246
- if (tags.has("jumpy-external-value")) return;
247
- callbacks.current.onChange?.({ target: { value: tokenInputText(next) }, currentTarget: { value: tokenInputText(next) } });
248
- callbacks.current.onValueChange(next);
249
- if (tags.has("jumpy-clear")) callbacks.current.onClear?.();
250
- });
251
- });
252
- const deleteToken = (event, backward) => {
253
- if (!editor.isEditable() || editor.isComposing()) return false;
254
- const selection = $getSelection();
255
- if ($isNodeSelection(selection)) {
256
- $addUpdateTag(HISTORY_PUSH_TAG);
257
- event?.preventDefault();
258
- for (const node2 of selection.getNodes()) {
259
- node2.selectPrevious();
260
- node2.remove();
261
- }
262
- return true;
263
- }
264
- const node = $adjacentToken(backward);
265
- if (!(node instanceof JumpyTokenNode)) return false;
266
- $addUpdateTag(HISTORY_PUSH_TAG);
267
- event?.preventDefault();
268
- node.selectPrevious();
269
- node.remove();
270
- return true;
271
- };
272
- const copy = (event, cut) => {
273
- if (!event?.clipboardData || cut && !editor.isEditable()) return false;
274
- const selection = $getSelection();
275
- if (!selection || $isRangeSelection(selection) && selection.isCollapsed()) return false;
276
- const { nodes } = $generateJSONFromSelectedNodes(editor, selection);
277
- const parts = [];
278
- const visit = (node) => {
279
- if (node.type === "jumpy-token") parts.push(node.token);
280
- else if (node.type === "text") parts.push({ type: "text", text: node.text });
281
- else if (node.type === "linebreak") parts.push({ type: "text", text: "\n" });
282
- else node.children?.forEach(visit);
283
- };
284
- nodes.forEach((node, index) => {
285
- if (index > 0 && node.type === "paragraph") parts.push({ type: "text", text: "\n" });
286
- visit(node);
287
- });
288
- event.preventDefault();
289
- event.clipboardData.setData("text/plain", tokenInputText(parts));
290
- event.clipboardData.setData(clipboardType, JSON.stringify(parts));
291
- if (cut) {
292
- $addUpdateTag(HISTORY_PUSH_TAG);
293
- if ($isNodeSelection(selection)) for (const node of selection.getNodes()) node.remove();
294
- else if ($isRangeSelection(selection)) selection.removeText();
295
- }
296
- return true;
297
- };
298
- const commands = [
299
- editor.registerCommand(DELETE_CHARACTER_COMMAND, (backward) => deleteToken(null, backward), COMMAND_PRIORITY_HIGH),
300
- editor.registerCommand(KEY_BACKSPACE_COMMAND, (event) => deleteToken(event, true), COMMAND_PRIORITY_HIGH),
301
- editor.registerCommand(KEY_DELETE_COMMAND, (event) => deleteToken(event, false), COMMAND_PRIORITY_HIGH),
302
- editor.registerCommand(KEY_ENTER_COMMAND, (event) => {
303
- if (editor.isComposing()) return false;
304
- event?.preventDefault();
305
- if (editable && multiline && !event?.metaKey && !event?.ctrlKey) {
306
- const selection = $getSelection();
307
- if ($isRangeSelection(selection)) {
308
- $addUpdateTag(HISTORY_PUSH_TAG);
309
- selection.insertLineBreak();
310
- }
311
- }
312
- return true;
313
- }, COMMAND_PRIORITY_HIGH),
314
- editor.registerCommand(COPY_COMMAND, (event) => copy(event, false), COMMAND_PRIORITY_HIGH),
315
- editor.registerCommand(CUT_COMMAND, (event) => copy(event, true), COMMAND_PRIORITY_HIGH),
316
- editor.registerCommand(PASTE_COMMAND, (event) => {
317
- if (!event?.clipboardData || !editor.isEditable()) return false;
318
- event.preventDefault();
319
- $addUpdateTag(HISTORY_PUSH_TAG);
320
- let parts;
321
- try {
322
- parts = normalizeTokenInputValue(JSON.parse(event.clipboardData.getData(clipboardType)).map((part) => part.type === "token" ? { ...part, id: crypto.randomUUID() } : part), multiline);
323
- } catch {
324
- parts = normalizeTokenInputValue([{ type: "text", text: event.clipboardData.getData("text/plain") }], multiline);
325
- }
326
- $getSelection()?.insertNodes($nodes(parts));
327
- return true;
328
- }, COMMAND_PRIORITY_HIGH)
329
- ];
330
- return () => {
331
- unregisterUpdate();
332
- commands.forEach((unregister) => unregister());
333
- };
334
- }, [editor, multiline, editable]);
335
- return null;
336
- }
337
- function TokenEditor({ model, multiline = false, minRows = 3, maxRows, editorRef, onValueChange, onChange, onClear, onFocus, onBlur, fieldContext, value: _value, placeholder, disabled, readOnly, style, ...props }) {
338
- const theme = useJumpyTheme();
339
- const initialConfig = useMemo(() => ({ namespace: "JumpyTokenInput", nodes: [JumpyTokenNode], editable: !disabled && !readOnly, onError: (error) => {
340
- throw error;
341
- }, editorState: () => $replaceValue(model) }), []);
342
- const offset2 = fieldContext?.hasLabel ? spacingValue(theme, JumpySpacing.large) : 0;
343
- const lineHeight = theme.fontFamily.bodyMedium.fontSize * theme.fontFamily.bodyMedium.lineHeight;
344
- return /* @__PURE__ */ jsx(LexicalComposer, { initialConfig, children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", minWidth: 0, marginTop: offset2 }, children: [
345
- /* @__PURE__ */ jsx(
346
- PlainTextPlugin,
347
- {
348
- contentEditable: /* @__PURE__ */ jsx(ContentEditable, { ...props, onFocusCapture: (event) => {
349
- props.onFocusCapture?.(event);
350
- onFocus?.(event);
351
- }, onBlurCapture: (event) => {
352
- props.onBlurCapture?.(event);
353
- onBlur?.(event);
354
- }, "data-jumpy-component": "token-input", role: "textbox", "aria-multiline": multiline, "aria-readonly": readOnly || void 0, "aria-disabled": disabled || void 0, tabIndex: disabled ? -1 : 0, style: { width: "100%", minHeight: lineHeight * (multiline ? minRows : 1), maxHeight: multiline && maxRows !== void 0 ? lineHeight * maxRows : void 0, padding: 0, outline: 0, overflowX: "auto", overflowY: multiline ? "auto" : void 0, whiteSpace: multiline ? "pre-wrap" : "pre", overflowWrap: multiline ? "anywhere" : void 0, color: "var(--jumpy-cmp-base-input-value-color)", fontFamily: theme.fontFamily.familyBase, fontSize: theme.fontFamily.bodyMedium.fontSize, lineHeight: theme.fontFamily.bodyMedium.lineHeight, ...style } }),
355
- placeholder: null,
356
- ErrorBoundary: LexicalErrorBoundary
357
- }
358
- ),
359
- model.length === 0 && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: { position: "absolute", top: 0, left: 0, pointerEvents: "none", color: "var(--jumpy-cmp-base-input-placeholder-color)", fontSize: theme.fontFamily.bodyMedium.fontSize, lineHeight: theme.fontFamily.bodyMedium.lineHeight }, children: placeholder }),
360
- /* @__PURE__ */ jsx(HistoryPlugin, {}),
361
- /* @__PURE__ */ jsx(EditorBehavior, { model, multiline, editable: !disabled && !readOnly, editorRef, onValueChange, onChange, onClear })
362
- ] }) });
363
- }
364
-
365
- // index.js
366
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
367
- var JumpyFieldContext = createContext(null);
368
- var JumpyTableContext = createContext(null);
369
- var JumpyExpandableContext = createContext(null);
370
- var JumpySideMenuContext = createContext(null);
371
- var JumpyToggleButtonGroupContext = createContext(null);
372
- var JumpySnackbarContext = createContext({ addSnackbar: () => "", removeSnackbar: () => {
373
- }, clearAll: () => {
374
- } });
375
- var KeyboardKeys = {
376
- ARROW_DOWN: "ArrowDown",
377
- ARROW_LEFT: "ArrowLeft",
378
- ARROW_RIGHT: "ArrowRight",
379
- ARROW_UP: "ArrowUp",
380
- END: "End",
381
- ENTER: "Enter",
382
- ESCAPE: "Escape",
383
- HOME: "Home",
384
- SPACE: " ",
385
- TAB: "Tab"
386
- };
387
- function ChevronDownIcon(props) {
388
- const theme = useJumpyTheme();
389
- return /* @__PURE__ */ jsx2("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, style: { display: "block", fontSize: sizeValue(theme, JumpySize.small), ...props.style || {} }, children: /* @__PURE__ */ jsx2("path", { fillRule: "evenodd", d: "M7.41 8.295 6 9.705l6 6 6-6-1.41-1.41-4.59 4.58z", clipRule: "evenodd", fill: "currentColor" }) });
390
- }
391
- function ChevronUpIcon(props) {
392
- const theme = useJumpyTheme();
393
- return /* @__PURE__ */ jsx2("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, style: { display: "block", fontSize: sizeValue(theme, JumpySize.small), ...props.style || {} }, children: /* @__PURE__ */ jsx2("path", { fillRule: "evenodd", d: "m16.59 15.705 1.41-1.41-6-6-6 6 1.41 1.41 4.59-4.58z", clipRule: "evenodd", fill: "currentColor" }) });
394
- }
395
- function CircleConfirmIcon(props) {
396
- return /* @__PURE__ */ jsx2("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, children: /* @__PURE__ */ jsx2("path", { fill: "currentColor", d: "M10 1.667a8.333 8.333 0 1 0 0 16.666 8.333 8.333 0 0 0 0-16.666Zm3.775 6.441-4.167 4.167a.833.833 0 0 1-1.178 0l-2.083-2.083a.833.833 0 1 1 1.178-1.179l1.494 1.494 3.577-3.577a.833.833 0 0 1 1.179 1.178Z" }) });
397
- }
398
- function CircleErrorIcon(props) {
399
- return /* @__PURE__ */ jsx2("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, children: /* @__PURE__ */ jsx2("path", { fill: "currentColor", d: "M10 1.667a8.333 8.333 0 1 0 0 16.666A8.333 8.333 0 0 0 10 1.667Zm0 4.166c.46 0 .833.373.833.834v3.75a.833.833 0 0 1-1.666 0v-3.75c0-.46.373-.834.833-.834Zm0 8.75a1.042 1.042 0 1 1 0-2.083 1.042 1.042 0 0 1 0 2.083Z" }) });
400
- }
401
- function CircleClearIcon(props) {
402
- return /* @__PURE__ */ jsxs2("svg", { "data-jumpy-stroke-icon": "true", width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", ...props, style: { display: "block", fill: "none", ...props.style || {} }, children: [
403
- /* @__PURE__ */ jsx2("path", { d: "M10 17.5a7.5 7.5 0 1 0 0-15 7.5 7.5 0 0 0 0 15Z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
404
- /* @__PURE__ */ jsx2("path", { d: "m7.5 7.5 5 5M12.5 7.5l-5 5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
405
- ] });
406
- }
407
- function supportsDisabledAttribute(tag) {
408
- return ["button", "input", "select", "textarea", "option", "optgroup", "fieldset"].includes(tag);
409
- }
410
- function responsiveValue(value, defaults) {
411
- if (value && typeof value === "object" && !Array.isArray(value)) {
412
- return {
413
- small: value.small ?? defaults.small,
414
- medium: value.medium ?? value.small ?? defaults.medium,
415
- large: value.large ?? value.medium ?? value.small ?? defaults.large,
416
- xLarge: value.xLarge ?? value.large ?? value.medium ?? value.small ?? defaults.xLarge
417
- };
418
- }
419
- return { small: value, medium: value, large: value, xLarge: value };
420
- }
421
- function gridSpacingValue(spacing, fallback = "1rem") {
422
- const length = (value2) => typeof value2 === "number" ? `${value2}px` : value2;
423
- if (Array.isArray(spacing)) return [length(spacing[0] ?? fallback), length(spacing[1] ?? fallback)];
424
- const value = length(spacing ?? fallback);
425
- return [value, value];
426
- }
427
- function responsiveGridSpacing(spacing) {
428
- const defaults = { small: ["1rem", "1rem"], medium: ["1rem", "1rem"], large: ["1.5rem", "1.5rem"], xLarge: ["1.5rem", "1.5rem"] };
429
- if (spacing == null || typeof spacing !== "object" || Array.isArray(spacing)) {
430
- return {
431
- small: gridSpacingValue(spacing ?? defaults.small, defaults.small[0]),
432
- medium: gridSpacingValue(spacing ?? defaults.medium, defaults.medium[0]),
433
- large: gridSpacingValue(spacing ?? defaults.large, defaults.large[0]),
434
- xLarge: gridSpacingValue(spacing ?? defaults.xLarge, defaults.xLarge[0])
435
- };
436
- }
437
- return {
438
- small: gridSpacingValue(spacing.small ?? defaults.small, defaults.small[0]),
439
- medium: gridSpacingValue(spacing.medium ?? spacing.small ?? defaults.medium, defaults.medium[0]),
440
- large: gridSpacingValue(spacing.large ?? spacing.medium ?? spacing.small ?? defaults.large, defaults.large[0]),
441
- xLarge: gridSpacingValue(spacing.xLarge ?? spacing.large ?? spacing.medium ?? spacing.small ?? defaults.xLarge, defaults.xLarge[0])
442
- };
443
- }
444
- function isValueSelected(selectedValues, value) {
445
- return value !== void 0 && Array.isArray(selectedValues) && selectedValues.includes(value);
446
- }
447
- function hasSelectedNestedItem(node, selectedValues) {
448
- if (!Array.isArray(selectedValues) || !node) return false;
449
- let found = false;
450
- Children.forEach(node, (child) => {
451
- if (found || !isValidElement(child)) return;
452
- if (isValueSelected(selectedValues, child.props.value)) {
453
- found = true;
454
- return;
455
- }
456
- const nestedMenu = child.props.slots?.nestedMenu ?? child.props.nestedMenu;
457
- if (hasSelectedNestedItem(nestedMenu ?? child.props.children, selectedValues)) found = true;
458
- });
459
- return found;
460
- }
461
- function focusSideMenuItem(itemRefs, direction) {
462
- const items = itemRefs.map((ref) => ref?.current).filter((item) => item && item.dataset.disabled !== "true");
463
- if (!items.length) return;
464
- const currentIndex = Math.max(0, items.findIndex((item) => item === document.activeElement || item.contains(document.activeElement)));
465
- const nextIndex = direction === "first" ? 0 : direction === "last" ? items.length - 1 : (currentIndex + direction + items.length) % items.length;
466
- items[nextIndex]?.focus();
467
- }
468
- function transitionTimeToMs(value) {
469
- return value.split(",").map((part) => part.trim()).reduce((max, part) => {
470
- if (!part) return max;
471
- const ms = part.endsWith("ms") ? Number.parseFloat(part) : Number.parseFloat(part) * 1e3;
472
- return Number.isFinite(ms) ? Math.max(max, ms) : max;
473
- }, 0);
474
- }
475
- function getWidthTransitionMs(element) {
476
- const styles = window.getComputedStyle(element);
477
- const properties = styles.transitionProperty.split(",").map((part) => part.trim());
478
- const widthIndex = properties.findIndex((property) => property === "width" || property === "all");
479
- if (widthIndex === -1) return 0;
480
- return transitionTimeToMs(styles.transitionDuration) + transitionTimeToMs(styles.transitionDelay);
481
- }
482
- function splitNavigationItemProps(props) {
483
- const linkKeys = /* @__PURE__ */ new Set(["href", "target", "rel", "download", "hrefLang", "ping", "referrerPolicy", "aria-current", "aria-disabled"]);
484
- const wrapperProps = {};
485
- const linkProps = {};
486
- Object.entries(props).forEach(([key, value]) => {
487
- if (linkKeys.has(key)) {
488
- linkProps[key] = value;
489
- return;
490
- }
491
- wrapperProps[key] = value;
492
- });
493
- return { wrapperProps, linkProps };
494
- }
495
- function clampNumber(value, min, max) {
496
- return Math.min(Math.max(value, min), max);
497
- }
498
- function getButtonVariantStyles(theme, variant, status = "regular") {
499
- const variantTokens = theme.actions[variant] || theme.actions.primary;
500
- const tokens = variantTokens[status] || variantTokens.regular;
501
- const elevated = variant === "primary";
502
- const token = (state, slot, fallback) => variant === "primary" && status === "regular" ? `var(--jumpy-actions-primary-regular-${state}-${slot}, ${fallback})` : fallback;
503
- return {
504
- "--jumpy-action-default-surface": token("default", "surface", tokens.default.surface),
505
- "--jumpy-action-default-text": token("default", "text", tokens.default.text),
506
- "--jumpy-action-default-border": token("default", "border", tokens.default.border),
507
- "--jumpy-action-default-icon": token("default", "icon", tokens.default.icon),
508
- "--jumpy-action-hover-surface": token("hover", "surface", tokens.hover.surface),
509
- "--jumpy-action-hover-text": token("hover", "text", tokens.hover.text),
510
- "--jumpy-action-hover-icon": token("hover", "icon", tokens.hover.icon),
511
- "--jumpy-action-hover-border": token("hover", "border", tokens.hover.border),
512
- "--jumpy-action-pressed-surface": token("pressed", "surface", tokens.pressed.surface),
513
- "--jumpy-action-pressed-text": token("pressed", "text", tokens.pressed.text),
514
- "--jumpy-action-pressed-icon": token("pressed", "icon", tokens.pressed.icon),
515
- "--jumpy-action-pressed-border": token("pressed", "border", tokens.pressed.border),
516
- "--jumpy-action-focus-surface": token("focus", "surface", tokens.focus.surface),
517
- "--jumpy-action-focus-text": token("focus", "text", tokens.focus.text),
518
- "--jumpy-action-focus-icon": token("focus", "icon", tokens.focus.icon),
519
- "--jumpy-action-focus-border": token("focus", "border", tokens.focus.border),
520
- "--jumpy-action-focus-ring": token("focus", "focus-ring", tokens.focus.focusRing),
521
- "--jumpy-action-disabled-surface": token("disabled", "surface", tokens.disabled.surface),
522
- "--jumpy-action-disabled-text": token("disabled", "text", tokens.disabled.text),
523
- "--jumpy-action-disabled-icon": token("disabled", "icon", tokens.disabled.icon),
524
- "--jumpy-action-disabled-border": token("disabled", "border", tokens.disabled.border),
525
- "--jumpy-action-hover-shadow": "none",
526
- "--jumpy-action-focus-shadow": elevated ? shadowValue(theme, JumpyShadowStyle.positiveSmall) : "none",
527
- "--jumpy-action-pressed-shadow": "none",
528
- "--jumpy-action-default-shadow": elevated ? shadowValue(theme, JumpyShadowStyle.positiveMedium) : "none"
529
- };
530
- }
531
- function getNoticeToneStyles(theme, tone) {
532
- switch (tone) {
533
- case "success":
534
- return {
535
- backgroundColor: theme.color.successSurface,
536
- borderColor: theme.color.successBorder,
537
- color: theme.color.successText
538
- };
539
- case "warning":
540
- return {
541
- backgroundColor: theme.color.warningSurface,
542
- borderColor: theme.color.warningBorder,
543
- color: theme.color.warningText
544
- };
545
- case "danger":
546
- return {
547
- backgroundColor: theme.color.dangerSurface,
548
- borderColor: theme.color.dangerBorder,
549
- color: theme.color.dangerText
550
- };
551
- default:
552
- return {
553
- backgroundColor: theme.color.infoSurface,
554
- borderColor: theme.color.infoBorder,
555
- color: theme.color.textPrimary
556
- };
557
- }
558
- }
559
- function getBannerStatusStyles(theme, status) {
560
- switch (status) {
561
- case "positive":
562
- return { indicator: theme.color.positiveBase, indicatorSurface: theme.color.positiveUltralight, inlineSurface: theme.color.positiveExtralight };
563
- case "warning":
564
- return { indicator: theme.color.warningBase, indicatorSurface: theme.color.warningUltralight, inlineSurface: theme.color.warningExtralight };
565
- case "alarm":
566
- return { indicator: theme.color.alarmBase, indicatorSurface: theme.color.alarmUltralight, inlineSurface: theme.color.alarmExtralight };
567
- case "info":
568
- return { indicator: theme.color.neutralBase, indicatorSurface: theme.color.neutralMegalight, inlineSurface: `rgba(244, 247, 245, ${opacityValue(theme, JumpyOpacity.medium)})` };
569
- default:
570
- return { indicator: theme.color.neutralBase, indicatorSurface: "transparent", inlineSurface: "transparent" };
571
- }
572
- }
573
- function JumpSeverityIcon({ status = "other", icon, style }) {
574
- if (status === "other" && icon) return icon;
575
- const common = {
576
- viewBox: "0 0 24 24",
577
- width: "100%",
578
- height: "100%",
579
- fill: "currentColor",
580
- xmlns: "http://www.w3.org/2000/svg",
581
- "aria-hidden": true,
582
- focusable: false,
583
- style
584
- };
585
- switch (status) {
586
- case "positive":
587
- return /* @__PURE__ */ jsx2("svg", { ...common, children: /* @__PURE__ */ jsx2("path", { d: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm4.3 7.8-5 5a1 1 0 0 1-1.4 0l-2.2-2.2a1 1 0 1 1 1.4-1.4l1.5 1.5 4.3-4.3a1 1 0 1 1 1.4 1.4Z" }) });
588
- case "warning":
589
- return /* @__PURE__ */ jsx2("svg", { ...common, children: /* @__PURE__ */ jsx2("path", { d: "M11.1 3.5a1 1 0 0 1 1.8 0l8.2 15A1 1 0 0 1 20.2 20H3.8a1 1 0 0 1-.9-1.5l8.2-15ZM12 8a1 1 0 0 0-1 1v4a1 1 0 1 0 2 0V9a1 1 0 0 0-1-1Zm0 9.25a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z" }) });
590
- case "alarm":
591
- return /* @__PURE__ */ jsx2("svg", { ...common, children: /* @__PURE__ */ jsx2("path", { d: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm1 13a1 1 0 1 1-2 0V7a1 1 0 1 1 2 0v8Zm-1 4a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5Z" }) });
592
- case "info":
593
- return /* @__PURE__ */ jsx2("svg", { ...common, children: /* @__PURE__ */ jsx2("path", { d: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20Zm1 15a1 1 0 1 1-2 0v-6a1 1 0 1 1 2 0v6Zm-1-8.5A1.25 1.25 0 1 1 12 6a1.25 1.25 0 0 1 0 2.5Z" }) });
594
- default:
595
- return /* @__PURE__ */ jsx2("svg", { ...common, children: /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "9" }) });
596
- }
597
- }
598
- function renderBannerSlot(slot, fallbackTag = "span", className, style) {
599
- if (!slot) return null;
600
- if (typeof slot === "string" || typeof slot === "number") {
601
- const Tag = fallbackTag;
602
- return /* @__PURE__ */ jsx2(Tag, { className, style, children: slot });
603
- }
604
- if (isValidElement(slot)) {
605
- return cloneElement(slot, {
606
- className: cn(className, slot.props.className),
607
- style: slot.props.style || style ? { ...style, ...slot.props.style } : void 0
608
- });
609
- }
610
- return slot;
611
- }
612
- function getBadgeStatusStyles(theme, status) {
613
- const alwaysLight = "var(--jumpy-color-non-color-always-ultralight, #FFFFFF)";
614
- switch (status) {
615
- case "positive":
616
- return { backgroundColor: theme.color.positiveBase, color: alwaysLight };
617
- case "warning":
618
- return { backgroundColor: theme.color.warningBase, color: theme.color.nonColorAlwaysUltradark };
619
- case "alarm":
620
- return { backgroundColor: theme.color.alarmBase, color: alwaysLight };
621
- case "active":
622
- return { backgroundColor: "var(--jumpy-color-primary-base)", color: theme.color.textOnPrimary };
623
- default:
624
- return { backgroundColor: theme.color.neutralBase, color: alwaysLight };
625
- }
626
- }
627
- function getBadgeSizeStyles(theme, size2) {
628
- const labelSmall = textStyleValue(theme, JumpyTextStyle.labelSmallEmphasized);
629
- const labelMedium = textStyleValue(theme, JumpyTextStyle.labelMediumEmphasized);
630
- switch (size2) {
631
- case "xSmall":
632
- return {
633
- width: sizeValue(theme, JumpySize.small3XS),
634
- height: sizeValue(theme, JumpySize.small3XS),
635
- minWidth: sizeValue(theme, JumpySize.small3XS),
636
- padding: spacingValue(theme, JumpySpacing.zero),
637
- fontSize: 0,
638
- lineHeight: 0
639
- };
640
- case "medium":
641
- return {
642
- minWidth: sizeValue(theme, JumpySize.small),
643
- height: sizeValue(theme, JumpySize.small),
644
- padding: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px`,
645
- fontFamily: theme.fontFamily.familyBase,
646
- fontSize: labelMedium.fontSize,
647
- // A line box filling the whole-pixel content height keeps the baseline from rounding the digits upward.
648
- lineHeight: `${sizeValue(theme, JumpySize.small) - spacingValue(theme, JumpySpacing.small3XS) * 2}px`,
649
- fontWeight: labelMedium.fontWeight
650
- };
651
- default:
652
- return {
653
- minWidth: sizeValue(theme, JumpySize.small2XS),
654
- height: sizeValue(theme, JumpySize.small2XS),
655
- padding: `${spacingValue(theme, JumpySpacing.zero)}px ${spacingValue(theme, JumpySpacing.small3XS)}px`,
656
- fontFamily: theme.fontFamily.familyBase,
657
- fontSize: labelSmall.fontSize,
658
- lineHeight: `${sizeValue(theme, JumpySize.small2XS)}px`,
659
- fontWeight: labelSmall.fontWeight
660
- };
661
- }
662
- }
663
- function getTagStatusStyles(theme, status) {
664
- switch (status) {
665
- case "positive":
666
- return { backgroundColor: theme.color.successSurface, color: theme.color.successText, borderColor: theme.color.successBorder };
667
- case "warning":
668
- return { backgroundColor: theme.color.warningSurface, color: theme.color.warningText, borderColor: theme.color.warningBorder };
669
- case "alarm":
670
- return { backgroundColor: theme.color.dangerSurface, color: theme.color.dangerText, borderColor: theme.color.dangerBorder };
671
- default:
672
- return { backgroundColor: theme.color.surfaceField, color: theme.color.textPrimary, borderColor: theme.color.borderSubtle };
673
- }
674
- }
675
- function getTypographyStyle(theme, variant = "bodyMedium") {
676
- const style = textStyleValue(theme, JumpyTextStyle[variant] || JumpyTextStyle.bodyMedium);
677
- return {
678
- fontFamily: theme.fontFamily.familyBase,
679
- fontSize: style.fontSize,
680
- lineHeight: style.lineHeight,
681
- fontWeight: style.fontWeight,
682
- letterSpacing: style.letterSpacing
683
- };
684
- }
685
- function getCardStateStyles(theme, selected = false) {
686
- const inactive = theme.card.inactive;
687
- const active = theme.card.active;
688
- const tokens = selected ? active : inactive;
689
- const state = selected ? "active" : "inactive";
690
- const cardToken = (interaction, slot, fallback) => `var(--jumpy-card-${state}-${interaction}-${slot}, ${fallback})`;
691
- return {
692
- "--jumpy-card-default-surface": cardToken("default", "surface", tokens.default.surface),
693
- "--jumpy-card-default-border": cardToken("default", "border", tokens.default.border),
694
- "--jumpy-card-default-text": cardToken("default", "text", tokens.default.text),
695
- "--jumpy-card-default-icon": cardToken("default", "icon", tokens.default.icon),
696
- "--jumpy-card-hover-surface": cardToken("hover", "surface", tokens.hover.surface),
697
- "--jumpy-card-hover-border": cardToken("hover", "border", tokens.hover.border),
698
- "--jumpy-card-hover-text": cardToken("hover", "text", tokens.hover.text),
699
- "--jumpy-card-hover-icon": cardToken("hover", "icon", tokens.hover.icon),
700
- "--jumpy-card-pressed-surface": cardToken("pressed", "surface", tokens.pressed.surface),
701
- "--jumpy-card-pressed-border": cardToken("pressed", "border", tokens.pressed.border),
702
- "--jumpy-card-pressed-text": cardToken("pressed", "text", tokens.pressed.text),
703
- "--jumpy-card-pressed-icon": cardToken("pressed", "icon", tokens.pressed.icon),
704
- "--jumpy-card-focus-surface": cardToken("focus", "surface", tokens.focus.surface),
705
- "--jumpy-card-focus-border": cardToken("focus", "border", tokens.focus.border),
706
- "--jumpy-card-focus-ring": cardToken("focus-focus", "ring", tokens.focus.focusRing),
707
- "--jumpy-card-focus-text": cardToken("focus", "text", tokens.focus.text),
708
- "--jumpy-card-focus-icon": cardToken("focus", "icon", tokens.focus.icon),
709
- "--jumpy-card-disabled-surface": cardToken("disabled", "surface", tokens.disabled.surface),
710
- "--jumpy-card-disabled-border": cardToken("disabled", "border", tokens.disabled.border),
711
- "--jumpy-card-disabled-text": cardToken("disabled", "text", tokens.disabled.text),
712
- "--jumpy-card-disabled-icon": cardToken("disabled", "icon", tokens.disabled.icon)
713
- };
714
- }
715
- function getListItemStateStyles(theme, selected = false) {
716
- const tokens = selected ? theme.listItem.active : theme.listItem.inactive;
717
- return {
718
- "--jumpy-list-item-default-surface": tokens.default.surface,
719
- "--jumpy-list-item-default-text": tokens.default.text,
720
- "--jumpy-list-item-default-subtext": tokens.default.subtext,
721
- "--jumpy-list-item-default-icon": tokens.default.icon,
722
- "--jumpy-list-item-hover-surface": tokens.hover.surface,
723
- "--jumpy-list-item-hover-text": tokens.hover.text,
724
- "--jumpy-list-item-hover-subtext": tokens.hover.subtext,
725
- "--jumpy-list-item-hover-icon": tokens.hover.icon,
726
- "--jumpy-list-item-pressed-surface": tokens.pressed.surface,
727
- "--jumpy-list-item-pressed-text": tokens.pressed.text,
728
- "--jumpy-list-item-pressed-subtext": tokens.pressed.subtext,
729
- "--jumpy-list-item-pressed-icon": tokens.pressed.icon,
730
- "--jumpy-list-item-focus-surface": tokens.focus.surface,
731
- "--jumpy-list-item-focus-text": tokens.focus.text,
732
- "--jumpy-list-item-focus-subtext": tokens.focus.subtext,
733
- "--jumpy-list-item-focus-icon": tokens.focus.icon,
734
- "--jumpy-list-item-focus-ring": tokens.focus.focusRing,
735
- "--jumpy-list-item-disabled-surface": tokens.disabled.surface,
736
- "--jumpy-list-item-disabled-text": tokens.disabled.text,
737
- "--jumpy-list-item-disabled-subtext": tokens.disabled.subtext,
738
- "--jumpy-list-item-disabled-icon": tokens.disabled.icon
739
- };
740
- }
741
- function getButtonSpacing(theme, variant, hasLeading, hasTrailing) {
742
- const gap = spacingValue(theme, JumpySpacing.smallXS);
743
- const verticalPadding = spacingValue(theme, JumpySpacing.small2XS);
744
- if (variant === "borderless") {
745
- return {
746
- gap,
747
- paddingLeft: spacingValue(theme, JumpySpacing.zero),
748
- paddingRight: spacingValue(theme, JumpySpacing.zero),
749
- paddingTop: verticalPadding,
750
- paddingBottom: verticalPadding
751
- };
752
- }
753
- return {
754
- gap,
755
- paddingLeft: hasLeading ? spacingValue(theme, JumpySpacing.medium) : spacingValue(theme, JumpySpacing.medium) + spacingValue(theme, JumpySpacing.small3XS),
756
- paddingRight: hasTrailing ? spacingValue(theme, JumpySpacing.medium) : spacingValue(theme, JumpySpacing.medium) + spacingValue(theme, JumpySpacing.small3XS),
757
- paddingTop: verticalPadding,
758
- paddingBottom: verticalPadding
759
- };
760
- }
761
- function getFieldInputStyles(theme, disabled) {
762
- const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
763
- return {
764
- display: "block",
765
- width: "100%",
766
- boxSizing: "border-box",
767
- color: "var(--jumpy-cmp-base-input-value-color)",
768
- padding: 0,
769
- margin: 0,
770
- border: 0,
771
- outline: 0,
772
- background: "transparent",
773
- opacity: disabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
774
- fontFamily: theme.fontFamily.familyBase,
775
- fontSize: bodyMedium.fontSize,
776
- lineHeight: `${bodyMedium.fontSize * bodyMedium.lineHeight}px`,
777
- transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${theme.animation.timeEasingRelease}`
778
- };
779
- }
780
- function getFieldShellStyles(theme, disabled, status = "regular", focused = false, readOnly = false) {
781
- const outlineWidth = borderWidthValue(theme, focused && !readOnly ? JumpyBorderWidth.medium : JumpyBorderWidth.small);
782
- const easing = focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease;
783
- return {
784
- display: "flex",
785
- alignItems: "center",
786
- width: "100%",
787
- minHeight: sizeValue(theme, JumpySize.largeXL),
788
- height: sizeValue(theme, JumpySize.largeXL),
789
- boxSizing: "border-box",
790
- borderRadius: radiusValue(theme, JumpyRadius.small),
791
- border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid transparent`,
792
- outline: readOnly ? "none" : `${outlineWidth}px solid var(--jumpy-cmp-base-input-border-color)`,
793
- outlineOffset: readOnly ? void 0 : -outlineWidth,
794
- backgroundColor: "var(--jumpy-cmp-base-input-surface-color)",
795
- paddingLeft: spacingValue(theme, JumpySpacing.large),
796
- paddingRight: spacingValue(theme, JumpySpacing.small3XS),
797
- paddingTop: spacingValue(theme, JumpySpacing.small3XS),
798
- paddingBottom: spacingValue(theme, JumpySpacing.small3XS),
799
- opacity: disabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
800
- gap: spacingValue(theme, JumpySpacing.medium),
801
- cursor: disabled || readOnly ? "default" : "text",
802
- pointerEvents: disabled ? "none" : void 0,
803
- transition: `background-color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${easing}, outline-color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${easing}, outline-width ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${easing}`
804
- };
805
- }
806
- function renderStandaloneField(theme, disabled, className, style, inputElement, status = "regular", readOnly = false) {
807
- return /* @__PURE__ */ jsx2(
808
- "div",
809
- {
810
- "data-jumpy-component": "field-control",
811
- "data-disabled": disabled ? "true" : "false",
812
- "data-readonly": readOnly ? "true" : "false",
813
- "data-status": status,
814
- className: cn(className),
815
- style: {
816
- ...getFieldShellStyles(theme, disabled, status, false, readOnly),
817
- ...style || {}
818
- },
819
- children: inputElement
820
- }
821
- );
822
- }
823
- function JumpBackground({ snow = true }) {
824
- const theme = useJumpyTheme();
825
- return /* @__PURE__ */ jsxs2("div", { className: "pointer-events-none fixed inset-0 z-0 overflow-hidden", style: { pointerEvents: "none" }, children: [
826
- /* @__PURE__ */ jsx2("div", { className: "absolute left-1/2 top-[-18rem] h-[34rem] w-[34rem] -translate-x-1/2 rounded-full blur-[120px]", style: { backgroundColor: theme.color.infoSurface } }),
827
- /* @__PURE__ */ jsx2("div", { className: "absolute bottom-24 right-[-10rem] h-[28rem] w-[28rem] rounded-full blur-[110px]", style: { backgroundColor: theme.color.borderSubtle } }),
828
- /* @__PURE__ */ jsx2("div", { className: "absolute inset-0 bg-[radial-gradient(rgba(26,28,31,0.16)_1px,transparent_1px)] bg-[size:22px_22px]", style: { opacity: theme.opacity.subtle } }),
829
- snow && /* @__PURE__ */ jsx2("div", { className: "snowfall", "aria-hidden": "true" })
830
- ] });
831
- }
832
- function JumpBadge({ as, size: size2 = "medium", status = "regular", hidden = false, children, className = "", ...props }) {
833
- const theme = useJumpyTheme();
834
- const visible = !hidden;
835
- const Root = as || "div";
836
- return /* @__PURE__ */ jsx2(
837
- Root,
838
- {
839
- ...props,
840
- "data-jumpy-component": "badge",
841
- "data-size": size2,
842
- "data-status": status,
843
- "data-visible": visible ? "true" : "false",
844
- role: props.role || "status",
845
- "aria-live": props["aria-live"] || "polite",
846
- "aria-hidden": hidden || void 0,
847
- className: cn("inline-flex items-center justify-center box-border", className),
848
- style: {
849
- display: "inline-flex",
850
- alignItems: "center",
851
- justifyContent: "center",
852
- boxSizing: "border-box",
853
- ...getBadgeSizeStyles(theme, size2),
854
- ...getBadgeStatusStyles(theme, status),
855
- borderRadius: radiusValue(theme, JumpyRadius.full),
856
- letterSpacing: "normal",
857
- opacity: visible ? opacityValue(theme, JumpyOpacity.solid) : opacityValue(theme, JumpyOpacity.transparent),
858
- scale: visible ? 1 : 0.25,
859
- width: size2 === "xSmall" ? sizeValue(theme, JumpySize.small3XS) : "fit-content",
860
- transition: `width ${animationDurationValue(theme, JumpyAnimationDuration.medium)}s ${theme.animation.timeEasingDrift}, opacity ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${visible ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}, scale ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${visible ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`,
861
- ...props.style || {}
862
- },
863
- children: size2 === "xSmall" ? null : /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "badge-content", style: { display: "inline-block", lineHeight: "inherit", fontVariantNumeric: "tabular-nums" }, children })
864
- }
865
- );
866
- }
867
- function BrandMark({ compact = false, label = "Jumpy UI" }) {
868
- const theme = useJumpyTheme();
869
- return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center gap-3", !compact && "justify-center"), children: [
870
- /* @__PURE__ */ jsx2("div", { className: "grid h-10 w-10 place-items-center rounded-xl text-xl font-black", style: { backgroundColor: theme.color.surfaceCard, color: theme.color.surfacePrimary, borderRadius: theme.radius.medium }, children: "J" }),
871
- /* @__PURE__ */ jsx2("span", { className: "block text-lg font-extrabold tracking-tight", style: { color: theme.color.textPrimary, fontFamily: theme.typography.fontFamily }, children: label })
872
- ] });
873
- }
874
- function JumpCard({ as, ref, children, className = "", variant = "filled", interactive = false, selected = false, disabled = false, dragged = false, innerClasses = {}, ...props }) {
875
- const theme = useJumpyTheme();
876
- const Component = as || "div";
877
- const cardRef = useRef2(null);
878
- const [pressScaleLimit, setPressScaleLimit] = useState2(1);
879
- const pressDistance = spacingValue(theme, JumpySpacing.small3XS);
880
- useLayoutEffect2(() => {
881
- const element = cardRef.current;
882
- if (!element || !interactive || disabled) return;
883
- const measure = () => {
884
- const longestSide = Math.max(element.offsetWidth, element.offsetHeight);
885
- setPressScaleLimit(longestSide > 0 ? Math.max(0, 1 - pressDistance / longestSide) : 1);
886
- };
887
- measure();
888
- const observer = new ResizeObserver(measure);
889
- observer.observe(element);
890
- return () => observer.disconnect();
891
- }, [Component, interactive, disabled, pressDistance]);
892
- const isButton = Component === "button";
893
- const activity = selected ? "active" : "inactive";
894
- const draggedStyles = dragged && !disabled ? {
895
- borderColor: `var(--jumpy-card-${activity}-dragged-border)`,
896
- backgroundColor: `var(--jumpy-card-${activity}-dragged-surface)`
897
- } : {};
898
- const hasPaddingUtility = /(?:^|\s)(?:[a-z]+:)*p(?:[trblxy])?-/.test(className);
899
- return /* @__PURE__ */ jsxs2(
900
- Component,
901
- {
902
- ...props,
903
- ref: (node) => {
904
- cardRef.current = node;
905
- if (typeof ref === "function") return ref(node);
906
- if (ref) ref.current = node;
907
- },
908
- type: isButton ? props.type || "button" : props.type,
909
- disabled: isButton ? disabled : void 0,
910
- "data-jumpy-component": "card",
911
- "data-variant": variant,
912
- "data-interactive": interactive ? "true" : "false",
913
- "data-selected": selected ? "true" : "false",
914
- "data-dragged": dragged ? "true" : "false",
915
- "data-disabled": disabled ? "true" : "false",
916
- tabIndex: interactive && !disabled ? props.tabIndex ?? 0 : props.tabIndex,
917
- "aria-disabled": disabled || void 0,
918
- className: cn(className),
919
- style: {
920
- ...getCardStateStyles(theme, selected),
921
- "--jumpy-card-press-scale-limit": pressScaleLimit,
922
- display: "block",
923
- position: "relative",
924
- boxSizing: "border-box",
925
- overflow: "hidden",
926
- appearance: isButton ? "none" : void 0,
927
- WebkitAppearance: isButton ? "none" : void 0,
928
- outline: "none",
929
- border: 0,
930
- borderRadius: theme.sizing.cardRadius,
931
- padding: props.style?.padding === void 0 && !hasPaddingUtility ? spacingValue(theme, JumpySpacing.large) : void 0,
932
- backgroundColor: "var(--jumpy-card-default-surface)",
933
- boxShadow: variant === "filled" ? theme.shadow.card : "none",
934
- color: "var(--jumpy-card-default-text, var(--jumpy-color-text-primary))",
935
- ...draggedStyles,
936
- ...props.style || {}
937
- },
938
- children: [
939
- interactive && selected ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "checkmark", className: innerClasses.checkmark?.className, style: { position: "absolute", right: "var(--jumpy-cmp-card-checkmark-right)", top: "var(--jumpy-cmp-card-checkmark-top)", display: "flex", color: "var(--jumpy-card-default-icon)", ...innerClasses.checkmark?.style || {} }, children: /* @__PURE__ */ jsx2(CircleConfirmIcon, { "data-jumpy-slot": "checkmark-icon", className: innerClasses.checkmarkIcon?.className, style: { width: sizeValue(theme, JumpySize.small), height: sizeValue(theme, JumpySize.small), ...innerClasses.checkmarkIcon?.style || {} } }) }) : null,
940
- children
941
- ]
942
- }
943
- );
944
- }
945
- function JumpGrid({ as, columns = { small: 8, medium: 12, large: 12, xLarge: 12 }, spacing = { small: ["1rem", "1rem"], medium: ["1rem", "1rem"], large: ["1.5rem", "1.5rem"], xLarge: ["1.5rem", "1.5rem"] }, children, className = "", ...props }) {
946
- const Component = as || "div";
947
- const resolvedColumns = responsiveValue(columns, { small: 8, medium: 12, large: 12, xLarge: 12 });
948
- const resolvedSpacing = responsiveGridSpacing(spacing);
949
- return /* @__PURE__ */ jsx2(
950
- Component,
951
- {
952
- ...props,
953
- "data-jumpy-component": "grid",
954
- className: cn(className),
955
- style: {
956
- "--jumpy-cmp-grid-columns-small": resolvedColumns.small,
957
- "--jumpy-cmp-grid-columns-medium": resolvedColumns.medium,
958
- "--jumpy-cmp-grid-columns-large": resolvedColumns.large,
959
- "--jumpy-cmp-grid-columns-xLarge": resolvedColumns.xLarge,
960
- "--jumpy-cmp-grid-spacing-row-small": resolvedSpacing.small[0],
961
- "--jumpy-cmp-grid-spacing-column-small": resolvedSpacing.small[1],
962
- "--jumpy-cmp-grid-spacing-row-medium": resolvedSpacing.medium[0],
963
- "--jumpy-cmp-grid-spacing-column-medium": resolvedSpacing.medium[1],
964
- "--jumpy-cmp-grid-spacing-row-large": resolvedSpacing.large[0],
965
- "--jumpy-cmp-grid-spacing-column-large": resolvedSpacing.large[1],
966
- "--jumpy-cmp-grid-spacing-row-xLarge": resolvedSpacing.xLarge[0],
967
- "--jumpy-cmp-grid-spacing-column-xLarge": resolvedSpacing.xLarge[1],
968
- ...props.style || {}
969
- },
970
- children
971
- }
972
- );
973
- }
974
- function JumpGridItem({ as, span = 1, horizontalPlacement = "stretch", verticalPlacement = "stretch", children, className = "", ...props }) {
975
- const Component = as || "div";
976
- const resolvedSpan = responsiveValue(span, { small: 1, medium: 1, large: 1, xLarge: 1 });
977
- return /* @__PURE__ */ jsx2(
978
- Component,
979
- {
980
- ...props,
981
- "data-jumpy-component": "grid-item",
982
- "data-horizontal-placement": horizontalPlacement,
983
- "data-vertical-placement": verticalPlacement,
984
- className: cn(className),
985
- style: {
986
- "--jumpy-cmp-grid-item-span-small": resolvedSpan.small,
987
- "--jumpy-cmp-grid-item-span-medium": resolvedSpan.medium,
988
- "--jumpy-cmp-grid-item-span-large": resolvedSpan.large,
989
- "--jumpy-cmp-grid-item-span-xLarge": resolvedSpan.xLarge,
990
- ...props.style || {}
991
- },
992
- children
993
- }
994
- );
995
- }
996
- function JumpList({ as, children, className = "", ...props }) {
997
- const Component = as || "div";
998
- return /* @__PURE__ */ jsx2(
999
- Component,
1000
- {
1001
- ...props,
1002
- "data-jumpy-component": "list",
1003
- role: props.role || "list",
1004
- className: cn(className),
1005
- style: {
1006
- display: "block",
1007
- width: "100%",
1008
- height: "fit-content",
1009
- overflowX: "hidden",
1010
- overflowY: "auto",
1011
- ...props.style || {}
1012
- },
1013
- children
1014
- }
1015
- );
1016
- }
1017
- function JumpListItem({ as, children, className = "", selected = false, disabled = false, readOnly = false, focusedView = false, leading, trailing, metadata, description, slots = {}, nestedCollapsed, defaultNestedCollapsed = true, onNestedCollapsedChange, truncateTexts = false, innerClasses = {}, ref, ...props }) {
1018
- const theme = useJumpyTheme();
1019
- const Component = as || "button";
1020
- const isButton = Component === "button";
1021
- const componentName = props["data-jumpy-component"] || "list-item";
1022
- const resolvedLeading = slots.leading ?? leading;
1023
- const resolvedTrailing = slots.trailing ?? trailing;
1024
- const resolvedMetadata = slots.metadata ?? metadata;
1025
- const resolvedDescription = slots.description ?? description;
1026
- const resolvedSelector = slots.selector;
1027
- const resolvedNestedList = slots.nestedList;
1028
- const [internalNestedCollapsed, setInternalNestedCollapsed] = useState2(defaultNestedCollapsed);
1029
- const isNestedCollapsed = nestedCollapsed ?? internalNestedCollapsed;
1030
- const hasNestedList = Boolean(resolvedNestedList);
1031
- const isDisabled = disabled || readOnly;
1032
- const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
1033
- const bodySmall = textStyleValue(theme, JumpyTextStyle.bodySmall);
1034
- const iconSize = sizeValue(theme, JumpySize.small2XS);
1035
- const handleExpandedChange = () => {
1036
- const nextCollapsed = !isNestedCollapsed;
1037
- if (nestedCollapsed === void 0) setInternalNestedCollapsed(nextCollapsed);
1038
- onNestedCollapsedChange?.(nextCollapsed);
1039
- };
1040
- const handleClick = (event) => {
1041
- if (isDisabled) return;
1042
- if (hasNestedList) handleExpandedChange();
1043
- props.onClick?.(event);
1044
- };
1045
- const handleKeyDown = (event) => {
1046
- if ((event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE) && hasNestedList && !isDisabled) {
1047
- event.preventDefault();
1048
- handleExpandedChange();
1049
- }
1050
- props.onKeyDown?.(event);
1051
- };
1052
- return /* @__PURE__ */ jsxs2(
1053
- Component,
1054
- {
1055
- ...props,
1056
- ref,
1057
- type: isButton ? props.type || "button" : props.type,
1058
- disabled: isButton ? disabled : void 0,
1059
- "aria-disabled": !isButton && isDisabled ? true : void 0,
1060
- "aria-current": selected ? props["aria-current"] || "true" : props["aria-current"],
1061
- "aria-selected": props.role === "option" || props.role === "treeitem" ? selected : props["aria-selected"],
1062
- "aria-expanded": hasNestedList ? !isNestedCollapsed : props["aria-expanded"],
1063
- onClick: handleClick,
1064
- onKeyDown: handleKeyDown,
1065
- "data-jumpy-component": componentName,
1066
- "data-selected": selected ? "true" : "false",
1067
- "data-disabled": disabled ? "true" : "false",
1068
- "data-readonly": readOnly ? "true" : "false",
1069
- "data-focused-view": focusedView ? "true" : void 0,
1070
- role: props.role || "listitem",
1071
- className: cn("text-left", className),
1072
- style: {
1073
- ...getListItemStateStyles(theme, selected),
1074
- display: "grid",
1075
- textAlign: "start",
1076
- gridTemplateColumns: `${resolvedSelector ? "min-content " : ""}${resolvedLeading ? "min-content " : ""}minmax(0, 1fr) ${resolvedMetadata ? "minmax(0, min-content) " : ""}${resolvedTrailing || hasNestedList ? "min-content" : ""}`,
1077
- gridTemplateRows: resolvedDescription ? "auto auto" : "auto",
1078
- alignItems: "center",
1079
- gap: spacingValue(theme, JumpySpacing.large),
1080
- width: "100%",
1081
- minHeight: sizeValue(theme, JumpySize.large) + spacingValue(theme, JumpySpacing.large),
1082
- boxSizing: "border-box",
1083
- padding: `${spacingValue(theme, JumpySpacing.medium)}px ${spacingValue(theme, JumpySpacing.large)}px`,
1084
- border: 0,
1085
- borderRadius: radiusValue(theme, JumpyRadius.small),
1086
- backgroundColor: componentName === "side-menu-item" ? "var(--jumpy-list-item-surface, var(--jumpy-list-item-default-surface))" : "transparent",
1087
- color: "var(--jumpy-list-item-color, var(--jumpy-list-item-default-text))",
1088
- cursor: disabled ? "not-allowed" : readOnly ? "default" : "pointer",
1089
- fontFamily: theme.fontFamily.familyBase,
1090
- fontSize: bodyMedium.fontSize,
1091
- lineHeight: bodyMedium.lineHeight,
1092
- fontWeight: bodyMedium.fontWeight,
1093
- opacity: disabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
1094
- pointerEvents: readOnly ? "none" : void 0,
1095
- position: "relative",
1096
- zIndex: 1,
1097
- ...props.style || {}
1098
- },
1099
- children: [
1100
- resolvedSelector ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "selector", "aria-hidden": props.role === "option" || props.role === "treeitem" ? "true" : void 0, className: innerClasses.selector?.className, style: { display: "inline-flex", pointerEvents: "none", color: "var(--jumpy-list-item-icon-color, var(--jumpy-list-item-default-icon))", ...innerClasses.selector?.style || {} }, children: resolvedSelector }) : null,
1101
- resolvedLeading ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "leading", "aria-hidden": "true", className: innerClasses.leading?.className, style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: iconSize, height: iconSize, color: "var(--jumpy-list-item-icon-color, var(--jumpy-list-item-default-icon))", ...innerClasses.leading?.style || {} }, children: resolvedLeading }) : null,
1102
- /* @__PURE__ */ jsxs2("span", { style: { display: "grid", minWidth: 0 }, children: [
1103
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "label", "data-truncate": truncateTexts ? "true" : void 0, "data-text": typeof children === "string" ? children : void 0, className: innerClasses.label?.className, style: { minWidth: 0, maxWidth: "100%", overflowWrap: "anywhere", wordBreak: "break-word", fontWeight: bodyMedium.fontWeight, ...innerClasses.label?.style || {} }, children }),
1104
- resolvedDescription ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "description", className: innerClasses.description?.className, style: { color: selected ? theme.listItem.active.default.subtext : theme.listItem.inactive.default.subtext, fontSize: bodySmall.fontSize, lineHeight: bodySmall.lineHeight, ...innerClasses.description?.style || {} }, children: resolvedDescription }) : null
1105
- ] }),
1106
- resolvedMetadata ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "metadata", className: innerClasses.metadata?.className, style: { color: selected ? theme.listItem.active.default.subtext : theme.listItem.inactive.default.subtext, whiteSpace: "nowrap", fontSize: bodySmall.fontSize, lineHeight: bodySmall.lineHeight, ...innerClasses.metadata?.style || {} }, children: resolvedMetadata }) : null,
1107
- resolvedTrailing || hasNestedList ? /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "trailing", "aria-hidden": "true", className: innerClasses.trailing?.className, style: { display: "inline-flex", color: "var(--jumpy-list-item-icon-color, var(--jumpy-list-item-default-icon))", ...innerClasses.trailing?.style || {} }, children: [
1108
- resolvedTrailing,
1109
- hasNestedList ? /* @__PURE__ */ jsx2(JumpIconButton, { as: "span", variant: "secondary", alternateHover: true, tabIndex: -1, "aria-hidden": "true", children: isNestedCollapsed ? /* @__PURE__ */ jsx2(ChevronDownIcon, {}) : /* @__PURE__ */ jsx2(ChevronUpIcon, {}) }) : null
1110
- ] }) : null,
1111
- hasNestedList ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "nested-list", "aria-hidden": isNestedCollapsed ? "true" : void 0, style: { gridColumn: "1 / -1", display: isNestedCollapsed ? "none" : "block" }, children: resolvedNestedList }) : null
1112
- ]
1113
- }
1114
- );
1115
- }
1116
- function JumpSideMenu({ as, children, className = "", collapsed = false, selected, disabled = false, disableKeyboardNavigation = false, renderMode, getItemProps, onChange, onKeyDown, onTransitionEnd, ...props }) {
1117
- const Component = as || "ul";
1118
- const theme = useJumpyTheme();
1119
- const elementRef = useRef2(null);
1120
- const itemRefs = useRef2([]);
1121
- const previousCollapsed = useRef2(collapsed);
1122
- const releaseTimer = useRef2(void 0);
1123
- const [isResizing, setIsResizing] = useState2(false);
1124
- useLayoutEffect2(() => {
1125
- if (previousCollapsed.current === collapsed) return void 0;
1126
- previousCollapsed.current = collapsed;
1127
- const element = elementRef.current;
1128
- if (!element) return void 0;
1129
- const transitionMs = getWidthTransitionMs(element);
1130
- if (!transitionMs) return void 0;
1131
- setIsResizing(true);
1132
- window.clearTimeout(releaseTimer.current);
1133
- releaseTimer.current = window.setTimeout(() => setIsResizing(false), transitionMs + 100);
1134
- return void 0;
1135
- }, [collapsed]);
1136
- useEffect2(() => () => window.clearTimeout(releaseTimer.current), []);
1137
- const selectItem = (value) => {
1138
- if (disabled || value === void 0) return;
1139
- onChange?.([value]);
1140
- };
1141
- const handleKeyDown = (event) => {
1142
- if (!disableKeyboardNavigation) {
1143
- if (event.key === KeyboardKeys.ARROW_DOWN || event.key === KeyboardKeys.ARROW_RIGHT) {
1144
- event.preventDefault();
1145
- focusSideMenuItem(itemRefs.current, 1);
1146
- } else if (event.key === KeyboardKeys.ARROW_UP || event.key === KeyboardKeys.ARROW_LEFT) {
1147
- event.preventDefault();
1148
- focusSideMenuItem(itemRefs.current, -1);
1149
- } else if (event.key === KeyboardKeys.HOME) {
1150
- event.preventDefault();
1151
- focusSideMenuItem(itemRefs.current, "first");
1152
- } else if (event.key === KeyboardKeys.END) {
1153
- event.preventDefault();
1154
- focusSideMenuItem(itemRefs.current, "last");
1155
- }
1156
- }
1157
- onKeyDown?.(event);
1158
- };
1159
- const handleTransitionEnd = (event) => {
1160
- onTransitionEnd?.(event);
1161
- if (event.target === event.currentTarget && event.propertyName === "width") {
1162
- window.clearTimeout(releaseTimer.current);
1163
- setIsResizing(false);
1164
- }
1165
- };
1166
- return /* @__PURE__ */ jsx2(JumpySideMenuContext.Provider, { value: { collapsed, disabled, selected, onSelect: selectItem, containerExpanded: isResizing || !collapsed, renderMode, getItemProps, disableKeyboardNavigation, itemRefs }, children: /* @__PURE__ */ jsx2(
1167
- Component,
1168
- {
1169
- ...props,
1170
- ref: elementRef,
1171
- "data-jumpy-component": "side-menu",
1172
- "data-menu-collapsed": collapsed ? "true" : void 0,
1173
- "data-menu-resizing": isResizing || props["data-menu-resizing"] === "true" ? "true" : void 0,
1174
- "aria-disabled": disabled || void 0,
1175
- onKeyDown: handleKeyDown,
1176
- onTransitionEnd: handleTransitionEnd,
1177
- className: cn(className),
1178
- style: {
1179
- display: "block",
1180
- width: "fit-content",
1181
- height: "fit-content",
1182
- overflowX: "hidden",
1183
- overflowY: "auto",
1184
- margin: 0,
1185
- padding: 0,
1186
- listStyle: "none",
1187
- transition: `width ${animationDurationValue(theme, collapsed ? JumpyAnimationDuration.small2XS : JumpyAnimationDuration.smallXS)}s ${theme.animation.timeEasingDrift}`,
1188
- ...collapsed && !isResizing ? { minWidth: "min-content" } : {},
1189
- ...props.style || {}
1190
- },
1191
- children
1192
- }
1193
- ) });
1194
- }
1195
- function JumpSideMenuItem({ as, children, value, selected = false, disabled = false, className = "", icon, badge, collapsed, slots, metadata, description, nestedMenu, nestedCollapsed, defaultNestedCollapsed = false, onNestedCollapsedChange, renderMode, focusedView, truncateTexts = false, tooltipProps, innerClasses = {}, wrapperProps = {}, expanderIconProps = {}, onClick, onKeyDown, ...props }) {
1196
- const theme = useJumpyTheme();
1197
- const sideMenu = useContext(JumpySideMenuContext);
1198
- const itemRef = useRef2(null);
1199
- const wrapperItemRef = useRef2(null);
1200
- const isCollapsed = sideMenu ? !sideMenu.containerExpanded : collapsed ?? false;
1201
- const resolvedRenderMode = sideMenu?.renderMode ?? renderMode ?? "navigation";
1202
- const selectedValues = sideMenu?.selected;
1203
- const resolvedNestedMenu = slots?.nestedMenu ?? nestedMenu;
1204
- const resolvedMetadata = slots?.metadata ?? metadata;
1205
- const isSelected = isValueSelected(selectedValues, value) || selected;
1206
- const isDisabled = disabled || sideMenu?.disabled;
1207
- const resolvedTabIndex = focusedView ? 0 : sideMenu && !sideMenu.disableKeyboardNavigation && Array.isArray(selectedValues) ? isSelected ? 0 : -1 : props.tabIndex;
1208
- const hasNested = Boolean(resolvedNestedMenu);
1209
- const hasSelectedChildren = hasSelectedNestedItem(resolvedNestedMenu, selectedValues);
1210
- const [internalNestedCollapsed, setInternalNestedCollapsed] = useState2(defaultNestedCollapsed);
1211
- const isNestedCollapsed = nestedCollapsed ?? internalNestedCollapsed;
1212
- const iconNode = typeof icon === "function" ? icon({ selected: isSelected }) : icon;
1213
- const badgeNode = typeof badge === "function" ? badge({ selected: isSelected, collapsed: isCollapsed }) : badge;
1214
- const handleItemClick = (event) => {
1215
- if (isDisabled) return;
1216
- if (hasNested) {
1217
- const nextCollapsed = !isNestedCollapsed;
1218
- onNestedCollapsedChange?.(nextCollapsed);
1219
- if (nestedCollapsed === void 0) setInternalNestedCollapsed(nextCollapsed);
1220
- } else {
1221
- sideMenu?.onSelect?.(value);
1222
- }
1223
- onClick?.(event);
1224
- };
1225
- const handleItemKeyDown = (event) => {
1226
- if ((event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE) && !isDisabled) {
1227
- event.preventDefault();
1228
- handleItemClick(event);
1229
- }
1230
- onKeyDown?.(event);
1231
- };
1232
- const itemInteractionProps = sideMenu?.getItemProps?.({ onClick: handleItemClick, onKeyDown: handleItemKeyDown }) ?? {};
1233
- const { onClick: interactionOnClick, onKeyDown: interactionOnKeyDown, ...restItemInteractionProps } = itemInteractionProps;
1234
- const { wrapperProps: splitWrapperProps, linkProps } = resolvedRenderMode === "navigation" ? splitNavigationItemProps(props) : { wrapperProps: {}, linkProps: props };
1235
- useEffect2(() => {
1236
- if (!sideMenu?.itemRefs) return void 0;
1237
- const registryItem = isCollapsed && resolvedRenderMode === "navigation" ? wrapperItemRef : itemRef;
1238
- sideMenu.itemRefs.current.push(registryItem);
1239
- return () => {
1240
- sideMenu.itemRefs.current = sideMenu.itemRefs.current.filter((item) => item !== registryItem);
1241
- };
1242
- }, [isCollapsed, resolvedRenderMode, sideMenu?.itemRefs]);
1243
- const withTooltip = (node) => {
1244
- if (!tooltipProps?.content) return node;
1245
- const { content, ...restTooltipProps } = tooltipProps;
1246
- return /* @__PURE__ */ jsx2(JumpTooltip, { ...restTooltipProps, content, children: node });
1247
- };
1248
- const trailingNode = hasNested ? /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "expander", "aria-hidden": "true", className: innerClasses.expander?.className, style: { display: "inline-flex", alignItems: "center", gap: spacingValue(theme, JumpySpacing.small), ...innerClasses.expander?.style || {} }, children: [
1249
- badgeNode,
1250
- /* @__PURE__ */ jsx2("span", { ...expanderIconProps, "data-jumpy-slot": "expander-icon", className: cn(innerClasses.expanderIcon?.className, expanderIconProps.className), style: { display: "inline-flex", transform: isNestedCollapsed ? "rotate(0deg)" : "rotate(180deg)", transition: `transform ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${theme.animation.timeEasingDrift}`, ...innerClasses.expanderIcon?.style || {}, ...expanderIconProps.style || {} }, children: /* @__PURE__ */ jsx2(ChevronDownIcon, {}) })
1251
- ] }) : badgeNode;
1252
- if (isCollapsed) {
1253
- const Component = as || (resolvedRenderMode === "navigation" ? "a" : "button");
1254
- const isButton = Component === "button";
1255
- const tokens = isSelected ? theme.listItem.active.default : theme.listItem.inactive.default;
1256
- const labelStyle = textStyleValue(theme, isSelected ? JumpyTextStyle.labelMediumEmphasized : JumpyTextStyle.labelMedium);
1257
- const collapsedWrapperInteractiveProps = resolvedRenderMode === "navigation" ? {
1258
- ref: wrapperItemRef,
1259
- tabIndex: resolvedTabIndex,
1260
- onClick: (event) => {
1261
- splitWrapperProps.onClick?.(event);
1262
- wrapperProps.onClick?.(event);
1263
- if (!event.defaultPrevented && event.target === event.currentTarget) itemRef.current?.click();
1264
- },
1265
- onKeyDown: (event) => {
1266
- splitWrapperProps.onKeyDown?.(event);
1267
- wrapperProps.onKeyDown?.(event);
1268
- if (!event.defaultPrevented && (event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE)) {
1269
- event.preventDefault();
1270
- itemRef.current?.click();
1271
- }
1272
- }
1273
- } : {};
1274
- return /* @__PURE__ */ jsxs2("li", { ...splitWrapperProps, ...wrapperProps, ...collapsedWrapperInteractiveProps, "data-jumpy-component": "side-menu-item-wrapper", "aria-expanded": hasNested ? !isNestedCollapsed : void 0, className: cn(innerClasses.selector?.className, splitWrapperProps.className, wrapperProps.className), style: { display: "block", margin: 0, padding: 0, ...splitWrapperProps.style || {}, ...innerClasses.selector?.style || {}, ...wrapperProps.style || {} }, children: [
1275
- withTooltip(
1276
- /* @__PURE__ */ jsxs2(
1277
- Component,
1278
- {
1279
- ...linkProps,
1280
- ref: itemRef,
1281
- ...restItemInteractionProps,
1282
- onClick: interactionOnClick ?? handleItemClick,
1283
- onKeyDown: interactionOnKeyDown ?? handleItemKeyDown,
1284
- type: isButton ? linkProps.type || "button" : linkProps.type,
1285
- disabled: isButton ? isDisabled : void 0,
1286
- "aria-disabled": !isButton && isDisabled ? true : void 0,
1287
- "aria-current": isSelected ? linkProps["aria-current"] || "page" : linkProps["aria-current"],
1288
- role: !isButton && !linkProps.href ? linkProps.role || "link" : linkProps.role,
1289
- tabIndex: resolvedRenderMode === "navigation" ? -1 : resolvedTabIndex,
1290
- "data-jumpy-component": "side-menu-item",
1291
- "data-collapsed": "true",
1292
- "data-selected": isSelected ? "true" : "false",
1293
- "data-disabled": isDisabled ? "true" : "false",
1294
- "data-value": !hasNested ? value : void 0,
1295
- "data-has-selected-children": hasSelectedChildren ? "true" : void 0,
1296
- "data-focused-view": focusedView ? "true" : void 0,
1297
- className: cn(className, innerClasses.collapsedLabel?.className),
1298
- style: {
1299
- position: "relative",
1300
- display: "flex",
1301
- flexDirection: "column",
1302
- alignItems: "center",
1303
- width: "100%",
1304
- minWidth: sizeValue(theme, JumpySize.large8XL) - spacingValue(theme, JumpySpacing.large3XL),
1305
- boxSizing: "border-box",
1306
- border: 0,
1307
- borderRadius: 0,
1308
- backgroundColor: "transparent",
1309
- color: tokens.text,
1310
- cursor: isDisabled ? "not-allowed" : "pointer",
1311
- padding: `${spacingValue(theme, JumpySpacing.smallXS)}px 0`,
1312
- opacity: isDisabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
1313
- ...linkProps.style || {}
1314
- },
1315
- children: [
1316
- /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "side-menu-decorator", className: innerClasses.decoratorContainer?.className, style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large2XL)}px ${spacingValue(theme, JumpySpacing.small3XS)}px`, color: tokens.icon, ...innerClasses.decoratorContainer?.style || {} }, children: [
1317
- iconNode,
1318
- badgeNode ? /* @__PURE__ */ jsx2("span", { style: { position: "absolute", top: 0, right: spacingValue(theme, JumpySpacing.large), display: "inline-flex" }, children: badgeNode }) : null
1319
- ] }),
1320
- children ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "label", "data-text": typeof children === "string" ? children : void 0, className: innerClasses.label?.className, style: { boxSizing: "border-box", paddingInline: spacingValue(theme, JumpySpacing.large), overflowWrap: "anywhere", textAlign: "center", fontFamily: theme.fontFamily.familyBase, fontSize: labelStyle.fontSize, lineHeight: labelStyle.lineHeight, fontWeight: labelStyle.fontWeight, ...truncateTexts ? { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: "100%" } : {}, ...innerClasses.label?.style || {} }, children }) : null
1321
- ]
1322
- }
1323
- )
1324
- ),
1325
- hasNested && !isNestedCollapsed ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "nested-menu", children: resolvedNestedMenu }) : null
1326
- ] });
1327
- }
1328
- const expandedItem = /* @__PURE__ */ jsx2(
1329
- JumpListItem,
1330
- {
1331
- ...linkProps,
1332
- ref: itemRef,
1333
- ...restItemInteractionProps,
1334
- as: as || (resolvedRenderMode === "navigation" ? "a" : "li"),
1335
- onClick: interactionOnClick ?? handleItemClick,
1336
- onKeyDown: interactionOnKeyDown ?? handleItemKeyDown,
1337
- selected: isSelected,
1338
- disabled: isDisabled,
1339
- leading: iconNode,
1340
- trailing: trailingNode,
1341
- metadata: resolvedMetadata,
1342
- description,
1343
- truncateTexts,
1344
- innerClasses,
1345
- "aria-expanded": hasNested ? !isNestedCollapsed : void 0,
1346
- role: linkProps.role || (resolvedRenderMode === "navigation" ? "link" : void 0),
1347
- tabIndex: resolvedTabIndex,
1348
- "data-jumpy-component": "side-menu-item",
1349
- "data-value": !hasNested ? value : void 0,
1350
- "data-has-selected-children": hasSelectedChildren ? "true" : void 0,
1351
- "data-focused-view": focusedView ? "true" : void 0,
1352
- className: cn(className, innerClasses.selector?.className),
1353
- style: {
1354
- "--jumpy-side-menu-item-indicator-scale": isSelected ? 1 : 0.5,
1355
- paddingLeft: spacingValue(theme, JumpySpacing.large2XL),
1356
- paddingRight: spacingValue(theme, JumpySpacing.large2XL),
1357
- paddingTop: spacingValue(theme, JumpySpacing.large),
1358
- paddingBottom: spacingValue(theme, JumpySpacing.large),
1359
- ...truncateTexts ? { overflow: "hidden" } : {},
1360
- ...innerClasses.selector?.style || {},
1361
- ...linkProps.style || {}
1362
- },
1363
- children
1364
- }
1365
- );
1366
- const expandedNestedMenu = hasNested && !isNestedCollapsed ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "nested-menu", children: resolvedNestedMenu }) : null;
1367
- if (resolvedRenderMode === "navigation") {
1368
- return withTooltip(
1369
- /* @__PURE__ */ jsxs2("li", { ...splitWrapperProps, ...wrapperProps, "data-jumpy-component": "side-menu-item-wrapper", "aria-expanded": hasNested ? !isNestedCollapsed : void 0, className: cn(splitWrapperProps.className, wrapperProps.className), style: { display: "block", margin: 0, padding: 0, ...splitWrapperProps.style || {}, ...wrapperProps.style || {} }, children: [
1370
- withTooltip(expandedItem),
1371
- expandedNestedMenu
1372
- ] })
1373
- );
1374
- }
1375
- return withTooltip(
1376
- /* @__PURE__ */ jsxs2(Fragment, { children: [
1377
- expandedItem,
1378
- expandedNestedMenu
1379
- ] })
1380
- );
1381
- }
1382
- function JumpTypography({ as, variant = "bodyMedium", color = "primary", className = "", children, ...props }) {
1383
- const theme = useJumpyTheme();
1384
- const Component = as || "p";
1385
- const colorValue = color === "secondary" ? theme.color.textSecondary : color === "primary" ? theme.color.textPrimary : color;
1386
- return /* @__PURE__ */ jsx2(
1387
- Component,
1388
- {
1389
- ...props,
1390
- "data-jumpy-component": "typography",
1391
- className: cn(className),
1392
- style: {
1393
- ...getTypographyStyle(theme, variant),
1394
- color: colorValue,
1395
- margin: 0,
1396
- ...props.style || {}
1397
- },
1398
- children
1399
- }
1400
- );
1401
- }
1402
- function JumpHyperlink({ as, variant = "inline", disabled = false, className = "", children, ...props }) {
1403
- const theme = useJumpyTheme();
1404
- const Component = as || "a";
1405
- if (variant !== "inline") {
1406
- return /* @__PURE__ */ jsx2(JumpLinkButton, { ...props, href: props.href || "#", variant: "borderless", className, children });
1407
- }
1408
- return /* @__PURE__ */ jsx2(
1409
- Component,
1410
- {
1411
- ...props,
1412
- "data-jumpy-component": "hyperlink",
1413
- "aria-disabled": disabled || void 0,
1414
- tabIndex: disabled ? -1 : props.tabIndex,
1415
- className: cn(className),
1416
- style: {
1417
- display: "inline",
1418
- borderRadius: radiusValue(theme, JumpyRadius.small),
1419
- backgroundColor: "transparent",
1420
- color: disabled ? theme.actions.tertiary.regular.disabled.text : theme.actions.tertiary.regular.default.text,
1421
- font: "inherit",
1422
- textDecoration: "none",
1423
- cursor: disabled ? "default" : "pointer",
1424
- pointerEvents: disabled ? "none" : void 0,
1425
- ...props.style || {}
1426
- },
1427
- children
1428
- }
1429
- );
1430
- }
1431
- function JumpDivider({ as, direction = "horizontal", captionPosition = "center", className = "", children, ...props }) {
1432
- const theme = useJumpyTheme();
1433
- const Component = as || "div";
1434
- const isVertical = direction === "vertical";
1435
- return /* @__PURE__ */ jsx2(
1436
- Component,
1437
- {
1438
- ...props,
1439
- "data-jumpy-component": "divider",
1440
- role: "separator",
1441
- "aria-orientation": direction,
1442
- "aria-label": children ? String(children) : props["aria-label"],
1443
- className: cn(className),
1444
- style: {
1445
- display: isVertical ? "inline-flex" : "flex",
1446
- alignItems: "center",
1447
- justifyContent: captionPosition === "start" ? "flex-start" : captionPosition === "end" ? "flex-end" : "center",
1448
- alignSelf: isVertical ? "stretch" : void 0,
1449
- width: isVertical ? borderWidthValue(theme, JumpyBorderWidth.small) : "100%",
1450
- minHeight: isVertical ? sizeValue(theme, JumpySize.large) : void 0,
1451
- border: 0,
1452
- borderTop: isVertical ? 0 : `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}`,
1453
- borderLeft: isVertical ? `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}` : 0,
1454
- color: theme.color.textSecondary,
1455
- ...props.style || {}
1456
- },
1457
- children: children ? /* @__PURE__ */ jsx2("span", { style: { backgroundColor: theme.color.surfaceCard, padding: `0 ${spacingValue(theme, JumpySpacing.smallXS)}px`, transform: isVertical ? "rotate(-90deg)" : void 0 }, children }) : null
1458
- }
1459
- );
1460
- }
1461
- function JumpAvatar({ as, size: size2 = "medium", disableInteraction = false, disabled = false, className = "", children, ...props }) {
1462
- const theme = useJumpyTheme();
1463
- const Component = as || (disableInteraction ? "span" : "button");
1464
- const isButton = Component === "button";
1465
- const sizes = { small: JumpySize.large, medium: JumpySize.large2XL, large: JumpySize.large4XL };
1466
- const avatarSize = sizeValue(theme, sizes[size2] || JumpySize.large2XL);
1467
- return /* @__PURE__ */ jsx2(
1468
- Component,
1469
- {
1470
- ...props,
1471
- type: isButton ? props.type || "button" : props.type,
1472
- disabled: isButton ? disabled : void 0,
1473
- "aria-disabled": !isButton && disabled ? true : void 0,
1474
- "data-jumpy-component": "avatar",
1475
- className: cn(className),
1476
- style: {
1477
- display: "inline-grid",
1478
- placeItems: "center",
1479
- width: avatarSize,
1480
- height: avatarSize,
1481
- borderRadius: radiusValue(theme, JumpyRadius.full),
1482
- border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}`,
1483
- backgroundColor: theme.color.infoSurface,
1484
- color: theme.color.surfacePrimary,
1485
- fontFamily: theme.fontFamily.familyBase,
1486
- fontWeight: theme.fontFamily.bodyMediumEmphasized.fontWeight,
1487
- opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
1488
- cursor: disableInteraction || disabled ? "default" : "pointer",
1489
- overflow: "hidden",
1490
- ...props.style || {}
1491
- },
1492
- children
1493
- }
1494
- );
1495
- }
1496
- function JumpBanner({ as, status: statusProp = "other", variant = "default", direction = "horizontal", hideStatus = false, floating = false, title, actions, slots, innerClasses = {}, className = "", children, ...props }) {
1497
- const theme = useJumpyTheme();
1498
- const Component = as || "div";
1499
- const status = variant === "inline" && statusProp === "other" ? "info" : statusProp;
1500
- const statusTone = getBannerStatusStyles(theme, status);
1501
- const urgent = status === "alarm";
1502
- const bannerTitle = slots?.title ?? title;
1503
- const bannerActions = slots?.actions ?? actions;
1504
- const bannerIcon = slots?.icon;
1505
- const contentContainerVertical = direction === "vertical";
1506
- const contentTitle = textStyleValue(theme, JumpyTextStyle.titleMediumEmphasized);
1507
- const contentDescription = textStyleValue(theme, JumpyTextStyle.bodyMedium);
1508
- return /* @__PURE__ */ jsxs2(
1509
- Component,
1510
- {
1511
- ...props,
1512
- "data-jumpy-component": "banner",
1513
- "data-variant": variant,
1514
- "data-status": status,
1515
- "data-floating": floating ? "true" : "false",
1516
- role: urgent ? "alert" : "status",
1517
- "aria-live": urgent ? "assertive" : "polite",
1518
- className: cn(className),
1519
- style: {
1520
- "--jumpy-banner-status-icon": statusTone.indicator,
1521
- "--jumpy-banner-status-surface": statusTone.indicatorSurface,
1522
- "--jumpy-banner-inline-surface": statusTone.inlineSurface,
1523
- ...props.style || {}
1524
- },
1525
- children: [
1526
- !hideStatus ? /* @__PURE__ */ jsx2(
1527
- "span",
1528
- {
1529
- "data-jumpy-slot": "banner-status",
1530
- className: innerClasses.status?.className,
1531
- "aria-hidden": "true",
1532
- style: {
1533
- ...innerClasses.status?.style || {}
1534
- },
1535
- children: /* @__PURE__ */ jsx2(JumpSeverityIcon, { status, icon: bannerIcon })
1536
- }
1537
- ) : null,
1538
- /* @__PURE__ */ jsxs2(
1539
- "div",
1540
- {
1541
- "data-jumpy-slot": "banner-content-container",
1542
- "data-direction": direction,
1543
- className: innerClasses.contentContainer?.className,
1544
- style: {
1545
- ...innerClasses.contentContainer?.style || {}
1546
- },
1547
- children: [
1548
- /* @__PURE__ */ jsxs2(
1549
- "div",
1550
- {
1551
- "data-jumpy-slot": "banner-content",
1552
- className: innerClasses.content?.className,
1553
- style: {
1554
- ...innerClasses.content?.style || {}
1555
- },
1556
- children: [
1557
- renderBannerSlot(bannerTitle, "span", innerClasses.contentTitle?.className, {
1558
- margin: 0,
1559
- fontFamily: theme.fontFamily.familyBase,
1560
- fontSize: contentTitle.fontSize,
1561
- lineHeight: contentTitle.lineHeight,
1562
- fontWeight: contentTitle.fontWeight,
1563
- letterSpacing: contentTitle.letterSpacing,
1564
- color: "inherit",
1565
- ...innerClasses.contentTitle?.style || {}
1566
- }),
1567
- renderBannerSlot(children, "p", innerClasses.contentDescription?.className, {
1568
- margin: 0,
1569
- fontFamily: theme.fontFamily.familyBase,
1570
- fontSize: contentDescription.fontSize,
1571
- lineHeight: contentDescription.lineHeight,
1572
- fontWeight: contentDescription.fontWeight,
1573
- letterSpacing: contentDescription.letterSpacing,
1574
- color: "inherit",
1575
- ...innerClasses.contentDescription?.style || {}
1576
- })
1577
- ]
1578
- }
1579
- ),
1580
- contentContainerVertical && bannerActions ? /* @__PURE__ */ jsx2(
1581
- "div",
1582
- {
1583
- "data-jumpy-slot": "banner-actions-container",
1584
- className: innerClasses.actionsContainer?.className,
1585
- style: {
1586
- ...innerClasses.actionsContainer?.style || {}
1587
- },
1588
- children: bannerActions
1589
- }
1590
- ) : bannerActions
1591
- ]
1592
- }
1593
- )
1594
- ]
1595
- }
1596
- );
1597
- }
1598
- function JumpSkeletonLoader({ as, variant = "text", lines = 1, width, height, disableAnimation = false, className = "", children, ...props }) {
1599
- const theme = useJumpyTheme();
1600
- const Component = as || "div";
1601
- if (children) return children;
1602
- const defaultHeight = variant === "text" ? theme.fontFamily.bodyMedium.fontSize * theme.fontFamily.bodyMedium.lineHeight : variant === "icon" || variant === "avatar" ? sizeValue(theme, JumpySize.large) : sizeValue(theme, JumpySize.large2XL);
1603
- const radius = variant === "avatar" ? radiusValue(theme, JumpyRadius.full) : radiusValue(theme, JumpyRadius.small);
1604
- const items = Array.from({ length: variant === "text" ? lines : 1 });
1605
- return /* @__PURE__ */ jsx2("div", { "data-jumpy-component": "skeleton-loader-group", className: cn(className), style: { display: "grid", gap: spacingValue(theme, JumpySpacing.smallXS), width: width || "100%", ...props.style || {} }, children: items.map((_, index) => /* @__PURE__ */ jsx2(
1606
- Component,
1607
- {
1608
- "aria-hidden": "true",
1609
- "data-jumpy-component": "skeleton-loader",
1610
- style: {
1611
- width: index === items.length - 1 && items.length > 1 ? "72%" : width || "100%",
1612
- height: height || defaultHeight,
1613
- borderRadius: radius,
1614
- backgroundColor: theme.color.borderSubtle,
1615
- opacity: disableAnimation ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.medium)
1616
- }
1617
- },
1618
- `skeleton-${lines}-${index}`
1619
- )) });
1620
- }
1621
- var JumpSearchField = forwardRef(function JumpSearchField2({ label = "Search", placeholder = "Search", className = "", ...props }, ref) {
1622
- const ariaLabelledBy = props["aria-labelledby"];
1623
- const ariaLabel = ariaLabelledBy ? void 0 : props["aria-label"] || label || placeholder;
1624
- return /* @__PURE__ */ jsx2(JumpBaseInput, { className: cn("jumpy-search-field", className), leading: /* @__PURE__ */ jsx2("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "currentColor", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", children: /* @__PURE__ */ jsx2("path", { d: "M16.071 14.5q1.509-1.95 1.508-4.5c0-4.1-3.418-7.5-7.54-7.5C5.919 2.5 2.5 5.9 2.5 10s3.418 7.5 7.54 7.5c1.709 0 3.317-.6 4.524-1.5l5.529 5.5 1.407-1.4zm-6.031 1c-3.016 0-5.53-2.5-5.53-5.5s2.515-5.5 5.53-5.5S15.569 7 15.569 10s-2.513 5.5-5.53 5.5z" }) }), children: /* @__PURE__ */ jsx2(JumpInput, { ...props, ref, type: "search", placeholder, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy }) });
1625
- });
1626
- function JumpTooltip({ content, children, className = "", ...props }) {
1627
- const theme = useJumpyTheme();
1628
- const [visible, setVisible] = useState2(false);
1629
- return /* @__PURE__ */ jsxs2("span", { ...props, "data-jumpy-component": "tooltip", className: cn(className), style: { position: "relative", display: "inline-flex", ...props.style || {} }, onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false), onFocus: () => setVisible(true), onBlur: () => setVisible(false), children: [
1630
- children,
1631
- visible ? /* @__PURE__ */ jsx2("span", { role: "tooltip", style: { position: "absolute", left: "50%", bottom: `calc(100% + ${spacingValue(theme, JumpySpacing.smallXS)}px)`, transform: "translateX(-50%)", zIndex: 30, width: "max-content", maxWidth: 240, borderRadius: radiusValue(theme, JumpyRadius.small), backgroundColor: theme.color.textPrimary, color: theme.color.surfaceCard, padding: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px`, boxShadow: theme.shadow.positiveSmall, fontSize: theme.fontFamily.bodySmall.fontSize, lineHeight: theme.fontFamily.bodySmall.lineHeight }, children: content }) : null
1632
- ] });
1633
- }
1634
- function JumpPopover({ open, defaultOpen = false, onOpenChange, trigger, children, className = "", ...props }) {
1635
- const theme = useJumpyTheme();
1636
- const [internalOpen, setInternalOpen] = useState2(defaultOpen);
1637
- const visible = open ?? internalOpen;
1638
- const setVisible = (next) => {
1639
- setInternalOpen(next);
1640
- onOpenChange?.(next);
1641
- };
1642
- return /* @__PURE__ */ jsxs2("span", { "data-jumpy-component": "popover-root", className: cn(className), style: { position: "relative", display: "inline-flex", ...props.style || {} }, children: [
1643
- /* @__PURE__ */ jsx2("span", { onClick: () => setVisible(!visible), children: trigger }),
1644
- visible ? /* @__PURE__ */ jsx2("div", { role: "dialog", "data-jumpy-component": "popover", style: { position: "absolute", top: `calc(100% + ${spacingValue(theme, JumpySpacing.smallXS)}px)`, left: 0, zIndex: 30, minWidth: 220, borderRadius: radiusValue(theme, JumpyRadius.large), border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}`, backgroundColor: theme.color.surfaceCard, color: theme.color.textPrimary, boxShadow: theme.shadow.positiveMedium, padding: spacingValue(theme, JumpySpacing.large) }, children }) : null
1645
- ] });
1646
- }
1647
- function JumpMenu({ trigger, triggerProps = {}, open, defaultOpen = false, onOpenChange, header, footer, children, className = "", placement = "bottom-start", closeOnSelect = true, ...props }) {
1648
- const theme = useJumpyTheme();
1649
- const [internalOpen, setInternalOpen] = useState2(defaultOpen);
1650
- const isOpen = open ?? internalOpen;
1651
- const menuRef = useRef2(null);
1652
- const insideEventRef = useRef2(null);
1653
- const menuOffset = spacingValue(theme, JumpySpacing.small3XS);
1654
- const menuPadding = spacingValue(theme, JumpySpacing.medium);
1655
- const menuMinWidth = sizeValue(theme, JumpySize.large8XL);
1656
- const { refs, floatingStyles } = useFloating({
1657
- open: isOpen,
1658
- onOpenChange: setOpen,
1659
- placement,
1660
- strategy: "fixed",
1661
- whileElementsMounted: autoUpdate,
1662
- middleware: [
1663
- offset(menuOffset),
1664
- flip({ padding: menuPadding }),
1665
- size({
1666
- padding: menuPadding,
1667
- apply({ rects, availableHeight, elements }) {
1668
- Object.assign(elements.floating.style, {
1669
- minWidth: `${Math.max(menuMinWidth, rects.reference.width)}px`,
1670
- maxHeight: `${Math.max(sizeValue(theme, JumpySize.large2XL), availableHeight)}px`
1671
- });
1672
- }
1673
- })
1674
- ]
1675
- });
1676
- function setOpen(next) {
1677
- setInternalOpen(next);
1678
- onOpenChange?.(next);
1679
- }
1680
- function getMenuItems() {
1681
- return Array.from(menuRef.current?.querySelectorAll('[role="menuitem"]') || []).filter((item) => item.getAttribute("aria-disabled") !== "true" && !item.disabled);
1682
- }
1683
- function focusMenuItem(direction) {
1684
- const menuItems = getMenuItems();
1685
- if (menuItems.length === 0) return;
1686
- const activeIndex = menuItems.indexOf(document.activeElement);
1687
- const nextIndex = direction === "first" ? 0 : direction === "last" ? menuItems.length - 1 : (activeIndex + direction + menuItems.length) % menuItems.length;
1688
- menuItems[nextIndex]?.focus();
1689
- }
1690
- function openAndFocusFirst() {
1691
- setOpen(true);
1692
- window.requestAnimationFrame(() => focusMenuItem("first"));
1693
- }
1694
- function handleTriggerKeyDown(event) {
1695
- if (event.key === KeyboardKeys.ARROW_DOWN || event.key === KeyboardKeys.ENTER || event.key === KeyboardKeys.SPACE) {
1696
- event.preventDefault();
1697
- openAndFocusFirst();
1698
- }
1699
- if (event.key === KeyboardKeys.ARROW_UP) {
1700
- event.preventDefault();
1701
- setOpen(true);
1702
- window.requestAnimationFrame(() => focusMenuItem("last"));
1703
- }
1704
- }
1705
- function handleMenuKeyDown(event) {
1706
- if (event.key === KeyboardKeys.ESCAPE) {
1707
- event.preventDefault();
1708
- setOpen(false);
1709
- refs.reference.current?.focus?.();
1710
- }
1711
- if (event.key === KeyboardKeys.ARROW_DOWN) {
1712
- event.preventDefault();
1713
- focusMenuItem(1);
1714
- }
1715
- if (event.key === KeyboardKeys.ARROW_UP) {
1716
- event.preventDefault();
1717
- focusMenuItem(-1);
1718
- }
1719
- if (event.key === KeyboardKeys.HOME) {
1720
- event.preventDefault();
1721
- focusMenuItem("first");
1722
- }
1723
- if (event.key === KeyboardKeys.END) {
1724
- event.preventDefault();
1725
- focusMenuItem("last");
1726
- }
1727
- props.onKeyDown?.(event);
1728
- }
1729
- useEffect2(() => {
1730
- if (!isOpen) return void 0;
1731
- function dismissOutside(event) {
1732
- if (insideEventRef.current !== event) setOpen(false);
1733
- }
1734
- document.addEventListener("pointerdown", dismissOutside);
1735
- document.addEventListener("focusin", dismissOutside);
1736
- return () => {
1737
- document.removeEventListener("pointerdown", dismissOutside);
1738
- document.removeEventListener("focusin", dismissOutside);
1739
- };
1740
- });
1741
- function markInteractionInside(event) {
1742
- insideEventRef.current = event.nativeEvent;
1743
- }
1744
- function handleMenuClick(event) {
1745
- props.onClick?.(event);
1746
- if (!closeOnSelect || event.defaultPrevented) return;
1747
- const item = event.target.closest?.('[role="menuitem"], [role="menuitemradio"], [role="menuitemcheckbox"]');
1748
- if (!item || item.disabled || item.getAttribute("aria-disabled") === "true" || item.closest("[aria-haspopup]")) return;
1749
- setOpen(false);
1750
- refs.domReference.current?.focus?.();
1751
- }
1752
- return /* @__PURE__ */ jsxs2("span", { "data-jumpy-component": "menu-root", className: cn(className), onPointerDown: markInteractionInside, onFocus: markInteractionInside, style: { display: "inline-flex", ...props.style || {} }, children: [
1753
- /* @__PURE__ */ jsx2("span", { ...triggerProps, ref: refs.setReference, "aria-haspopup": "menu", "aria-expanded": isOpen, tabIndex: 0, onClick: () => setOpen(!isOpen), onKeyDown: handleTriggerKeyDown, children: trigger }),
1754
- isOpen ? /* @__PURE__ */ jsx2(JumpyPortal, { children: /* @__PURE__ */ jsxs2(
1755
- "div",
1756
- {
1757
- ...props,
1758
- ref: (node) => {
1759
- refs.setFloating(node);
1760
- menuRef.current = node;
1761
- },
1762
- "data-jumpy-component": "menu",
1763
- role: "menu",
1764
- onClick: handleMenuClick,
1765
- onKeyDown: handleMenuKeyDown,
1766
- style: {
1767
- ...floatingStyles,
1768
- zIndex: JUMPY_POPUP_Z_INDEX,
1769
- maxWidth: "min(420px, calc(100vw - 32px))",
1770
- overflowY: "auto",
1771
- ...props.style || {}
1772
- },
1773
- children: [
1774
- header ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "menu-header", children: header }) : null,
1775
- children,
1776
- footer ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "menu-footer", children: footer }) : null
1777
- ]
1778
- }
1779
- ) }) : null
1780
- ] });
1781
- }
1782
- function JumpMenuItem({ as, children, selected = false, disabled = false, readOnly = false, focusedView = false, leading, trailing, metadata, description, slots = {}, nestedMenu, onClick, ...props }) {
1783
- const Component = as || "button";
1784
- const resolvedSlots = {
1785
- ...slots,
1786
- leading: slots.leading ?? leading,
1787
- trailing: nestedMenu ? /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", children: "\u203A" }) : slots.trailing ?? trailing,
1788
- metadata: slots.metadata ?? metadata,
1789
- description: slots.description ?? description
1790
- };
1791
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
1792
- /* @__PURE__ */ jsx2(
1793
- JumpListItem,
1794
- {
1795
- ...props,
1796
- as: Component,
1797
- role: props.role || "menuitem",
1798
- selected,
1799
- disabled,
1800
- readOnly,
1801
- focusedView,
1802
- slots: resolvedSlots,
1803
- "data-jumpy-component": "menu-item",
1804
- onClick: (event) => {
1805
- if (disabled || readOnly) return;
1806
- onClick?.(event);
1807
- },
1808
- children
1809
- }
1810
- ),
1811
- nestedMenu || null
1812
- ] });
1813
- }
1814
- function JumpToggleButton({ as, selected, value, disabled = false, onChange, leading, children, className = "", ...props }) {
1815
- const theme = useJumpyTheme();
1816
- const group = useContext(JumpyToggleButtonGroupContext);
1817
- const Component = as || "button";
1818
- const isButton = Component === "button";
1819
- const isDisabled = disabled || group?.disabled || false;
1820
- const isSelected = group && value !== void 0 ? group.value.includes(value) : Boolean(selected);
1821
- const textStyle = textStyleValue(theme, JumpyTextStyle.bodyMediumEmphasized);
1822
- function handleClick(event) {
1823
- if (isDisabled) return;
1824
- if (group && value !== void 0) group.onChange(value);
1825
- onChange?.(!isSelected);
1826
- props.onClick?.(event);
1827
- }
1828
- return /* @__PURE__ */ jsxs2(
1829
- Component,
1830
- {
1831
- ...props,
1832
- type: isButton ? props.type || "button" : props.type,
1833
- disabled: isButton ? isDisabled : void 0,
1834
- "aria-disabled": !isButton && isDisabled ? true : void 0,
1835
- "aria-pressed": isSelected,
1836
- "data-jumpy-component": "toggle-button",
1837
- "data-selected": isSelected ? "true" : "false",
1838
- "data-disabled": isDisabled ? "true" : "false",
1839
- className: cn(className),
1840
- onClick: handleClick,
1841
- style: {
1842
- boxSizing: "border-box",
1843
- position: "relative",
1844
- display: "inline-flex",
1845
- alignItems: "center",
1846
- justifyContent: "center",
1847
- width: "fit-content",
1848
- height: sizeValue(theme, JumpySize.large),
1849
- gap: spacingValue(theme, JumpySpacing.small3XS),
1850
- padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large)}px`,
1851
- outline: "none",
1852
- border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-cmp-toggle-button-border-color)`,
1853
- borderRadius: "var(--jumpy-cmp-toggle-button-border-radius)",
1854
- backgroundColor: "var(--jumpy-cmp-toggle-button-surface)",
1855
- color: "var(--jumpy-cmp-toggle-button-text)",
1856
- cursor: isDisabled ? "not-allowed" : "pointer",
1857
- userSelect: "none",
1858
- fontFamily: theme.fontFamily.familyBase,
1859
- fontSize: textStyle.fontSize,
1860
- lineHeight: textStyle.lineHeight,
1861
- fontWeight: textStyle.fontWeight,
1862
- opacity: isDisabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid),
1863
- ...props.style || {}
1864
- },
1865
- children: [
1866
- leading ? /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", style: { display: "inline-flex", color: "var(--jumpy-cmp-toggle-button-icon-fill)" }, children: typeof leading === "function" ? leading({ selected: isSelected }) : leading }) : null,
1867
- children ? /* @__PURE__ */ jsx2("span", { style: { display: "inline-flex", alignItems: "center", justifyContent: "center", margin: `0 ${spacingValue(theme, JumpySpacing.small3XS)}px`, whiteSpace: "nowrap" }, children }) : null
1868
- ]
1869
- }
1870
- );
1871
- }
1872
- function JumpToggleButtonGroup({ as, selected, defaultSelected = [], onChange, exclusive = false, disabled = false, children, className = "", ...props }) {
1873
- const theme = useJumpyTheme();
1874
- const Component = as || "div";
1875
- const [internalSelected, setInternalSelected] = useState2(defaultSelected);
1876
- const currentSelected = selected ?? internalSelected;
1877
- function handleToggle(toggledValue) {
1878
- const nextSelected = currentSelected.includes(toggledValue) ? currentSelected.filter((item) => item !== toggledValue) : exclusive ? [toggledValue] : [...currentSelected, toggledValue];
1879
- setInternalSelected(nextSelected);
1880
- onChange?.(nextSelected);
1881
- }
1882
- return /* @__PURE__ */ jsx2(JumpyToggleButtonGroupContext.Provider, { value: { value: currentSelected, onChange: handleToggle, exclusive, disabled }, children: /* @__PURE__ */ jsx2(
1883
- Component,
1884
- {
1885
- ...props,
1886
- "data-jumpy-component": "toggle-button-group",
1887
- role: props.role || "toolbar",
1888
- className: cn(className),
1889
- style: { display: "inline-flex", flexDirection: "row", alignItems: "center", width: "fit-content", gap: spacingValue(theme, JumpySpacing.small3XS), flexWrap: "wrap", userSelect: "none", ...props.style || {} },
1890
- children
1891
- }
1892
- ) });
1893
- }
1894
- function JumpPinInput({ length = 6, value, defaultValue = "", onChange, onComplete, name, mask = false, type = "numeric", error = false, disabled = false, helperText, ariaLabels = {}, className = "", innerClasses = {}, ...props }) {
1895
- const theme = useJumpyTheme();
1896
- const normalizedLength = clampNumber(length, 4, 8);
1897
- const [internalValue, setInternalValue] = useState2(defaultValue);
1898
- const currentValue = String(value ?? internalValue).slice(0, normalizedLength);
1899
- const refs = useRef2([]);
1900
- const pattern = type === "alphanumeric" ? /^[a-z0-9]$/i : /^\d$/;
1901
- const helperId = helperText ? `${name || props.id || "jumpy-pin-input"}-helper` : void 0;
1902
- function commit(nextValue, focusIndex) {
1903
- const compact = nextValue.slice(0, normalizedLength);
1904
- setInternalValue(compact);
1905
- onChange?.(compact);
1906
- if (compact.length === normalizedLength) onComplete?.(compact);
1907
- if (focusIndex !== void 0) refs.current[clampNumber(focusIndex, 0, normalizedLength - 1)]?.focus();
1908
- }
1909
- function replaceAt(index, char) {
1910
- const cells = Array.from({ length: normalizedLength }, (_, cellIndex) => currentValue[cellIndex] || "");
1911
- cells[index] = char;
1912
- return cells.join("");
1913
- }
1914
- const supportingColor = disabled ? theme.color["jumpy-input-regular-disabled-supporting"] : error ? theme.color["jumpy-input-alarm-default-supporting"] : theme.color["jumpy-input-regular-default-supporting"];
1915
- const textStyle = textStyleValue(theme, JumpyTextStyle.bodyLarge);
1916
- return /* @__PURE__ */ jsxs2("fieldset", { ...props, disabled, "data-jumpy-component": "pin-input", className: cn(className), style: { display: "inline-flex", flexDirection: "column", alignItems: "flex-start", border: 0, margin: 0, padding: 0, ...props.style || {} }, children: [
1917
- name ? /* @__PURE__ */ jsx2("input", { type: "hidden", name, value: currentValue }) : null,
1918
- /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "pin-input-cells", className: cn(innerClasses.cells?.className), style: { display: "flex", gap: spacingValue(theme, JumpySpacing.small2XS), cursor: disabled ? "not-allowed" : "text", ...innerClasses.cells?.style || {} }, onClick: (event) => {
1919
- if (event.target === event.currentTarget) refs.current[Math.min(currentValue.length, normalizedLength - 1)]?.focus();
1920
- }, children: Array.from({ length: normalizedLength }, (_, index) => /* @__PURE__ */ jsx2(
1921
- "input",
1922
- {
1923
- ref: (node) => {
1924
- refs.current[index] = node;
1925
- },
1926
- type: mask ? "password" : "text",
1927
- inputMode: type === "alphanumeric" ? "text" : "numeric",
1928
- autoComplete: index === 0 ? "one-time-code" : "off",
1929
- autoCapitalize: "characters",
1930
- "aria-label": ariaLabels.cell ? ariaLabels.cell(index + 1, normalizedLength) : `PIN digit ${index + 1} of ${normalizedLength}`,
1931
- "aria-invalid": error || void 0,
1932
- "aria-describedby": helperId,
1933
- disabled,
1934
- "data-jumpy-slot": "pin-input-cell",
1935
- "data-status": error ? "alarm" : "regular",
1936
- "data-disabled": disabled ? "true" : "false",
1937
- maxLength: 1,
1938
- value: currentValue[index] || "",
1939
- onChange: (event) => {
1940
- const nextRaw = event.target.value.slice(-1);
1941
- if (!nextRaw) return;
1942
- if (!pattern.test(nextRaw)) return;
1943
- commit(replaceAt(index, nextRaw.toUpperCase()), index + 1);
1944
- },
1945
- onKeyDown: (event) => {
1946
- if (event.key === "Backspace") {
1947
- event.preventDefault();
1948
- if (currentValue[index]) commit(replaceAt(index, ""), index);
1949
- else commit(replaceAt(Math.max(index - 1, 0), ""), index - 1);
1950
- }
1951
- if (event.key === "ArrowLeft") refs.current[Math.max(index - 1, 0)]?.focus();
1952
- if (event.key === "ArrowRight") refs.current[Math.min(index + 1, normalizedLength - 1)]?.focus();
1953
- },
1954
- onPaste: (event) => {
1955
- event.preventDefault();
1956
- const pasted = event.clipboardData.getData("text").split("").filter((char) => pattern.test(char)).join("").slice(0, normalizedLength).toUpperCase();
1957
- if (pasted) commit(pasted, pasted.length - 1);
1958
- },
1959
- className: cn(innerClasses.cell?.className),
1960
- style: { width: sizeValue(theme, JumpySize.largeXL), height: sizeValue(theme, JumpySize.largeXL), boxSizing: "border-box", appearance: "none", WebkitAppearance: "none", border: 0, outlineWidth: "var(--jumpy-cmp-pin-input-cell-outline-width)", outlineStyle: "solid", outlineColor: "var(--jumpy-cmp-pin-input-cell-border)", outlineOffset: `calc(var(--jumpy-cmp-pin-input-cell-outline-width) * -1)`, borderRadius: radiusValue(theme, JumpyRadius.small), backgroundColor: "var(--jumpy-cmp-pin-input-cell-surface)", color: "var(--jumpy-cmp-pin-input-cell-value)", textAlign: "center", overflow: "hidden", fontFamily: theme.fontFamily.familyBase, fontSize: textStyle.fontSize, lineHeight: textStyle.lineHeight, fontWeight: textStyle.fontWeight, ...innerClasses.cell?.style || {} }
1961
- },
1962
- index
1963
- )) }),
1964
- helperText ? /* @__PURE__ */ jsx2("div", { id: helperId, "data-jumpy-slot": "pin-input-helper", className: cn(innerClasses.supportingText?.className), style: { paddingTop: spacingValue(theme, JumpySpacing.small3XS), color: supportingColor, fontFamily: theme.fontFamily.familyBase, fontSize: textStyleValue(theme, JumpyTextStyle.labelSmall).fontSize, ...innerClasses.supportingText?.style || {} }, children: helperText }) : null
1965
- ] });
1966
- }
1967
- function JumpPageControl({ totalPages = 1, currentPage, defaultPage = 0, maxVisibleDots = 7, disabled = false, disabledPages = [], disableInteraction = false, onPageChange, pageAriaLabels = [], className = "", ...props }) {
1968
- const theme = useJumpyTheme();
1969
- const [internalPage, setInternalPage] = useState2(defaultPage);
1970
- const selectedPage = clampNumber(currentPage ?? internalPage, 0, Math.max(totalPages - 1, 0));
1971
- const visibleCount = clampNumber(maxVisibleDots, 3, Math.max(totalPages, 3));
1972
- const half = Math.floor(visibleCount / 2);
1973
- const start = clampNumber(selectedPage - half, 0, Math.max(totalPages - visibleCount, 0));
1974
- const pages = Array.from({ length: Math.min(totalPages, visibleCount) }, (_, index) => start + index);
1975
- const showLeft = start > 0;
1976
- const showRight = start + visibleCount < totalPages;
1977
- function selectPage(page, pageDisabled) {
1978
- if (disabled || disableInteraction || pageDisabled) return;
1979
- setInternalPage(page);
1980
- onPageChange?.(page);
1981
- }
1982
- function renderDot(page, truncation = false) {
1983
- const selected = page === selectedPage;
1984
- const pageDisabled = disabledPages.includes(page);
1985
- const dotSize = truncation ? sizeValue(theme, JumpySize.small5XS) : sizeValue(theme, JumpySize.small4XS);
1986
- return /* @__PURE__ */ jsx2("button", { type: "button", disabled: disabled || pageDisabled || truncation || disableInteraction, "aria-label": truncation ? "More pages" : pageAriaLabels[page] || `Page ${page + 1}`, "aria-current": selected ? "page" : void 0, "data-jumpy-component": "page-control-dot", "data-selected": selected ? "true" : "false", "data-truncation": truncation ? "true" : "false", onClick: () => selectPage(page, pageDisabled), style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: truncation ? sizeValue(theme, JumpySize.small3XS) : sizeValue(theme, JumpySize.small), height: truncation ? sizeValue(theme, JumpySize.small3XS) : sizeValue(theme, JumpySize.small), border: `${borderWidthValue(theme, JumpyBorderWidth.medium)}px solid transparent`, borderRadius: radiusValue(theme, JumpyRadius.small), background: "transparent", cursor: disabled || pageDisabled || truncation || disableInteraction ? "default" : "pointer", opacity: disabled || pageDisabled ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid) }, children: /* @__PURE__ */ jsx2("span", { style: { display: "block", width: dotSize, height: dotSize, boxSizing: "border-box", borderRadius: selected || truncation ? radiusValue(theme, JumpyRadius.full) : radiusValue(theme, JumpyRadius.small), border: truncation ? 0 : `${borderWidthValue(theme, JumpyBorderWidth.medium)}px solid ${selected ? theme.listItem.active.default.icon : theme.color.borderStrong}`, backgroundColor: selected ? theme.listItem.active.default.icon : truncation ? theme.color.borderStrong : "transparent" } }) }, truncation ? `truncate-${page}` : page);
1987
- }
1988
- return /* @__PURE__ */ jsxs2("div", { ...props, "data-jumpy-component": "page-control", className: cn(className), style: { position: "relative", overflow: "clip", width: "100%", height: sizeValue(theme, JumpySize.small), display: "flex", justifyContent: "center", alignItems: "center", boxSizing: "border-box", ...props.style || {} }, children: [
1989
- showLeft ? renderDot(start - 1, true) : null,
1990
- pages.map((page) => renderDot(page)),
1991
- showRight ? renderDot(start + visibleCount, true) : null
1992
- ] });
1993
- }
1994
- function textFromNode(node) {
1995
- if (typeof node === "string" || typeof node === "number") return String(node);
1996
- if (Array.isArray(node)) return node.map(textFromNode).filter(Boolean).join(" ");
1997
- if (isValidElement(node)) return textFromNode(node.props.children);
1998
- return "";
1999
- }
2000
- function useJumpSnackbar() {
2001
- return useContext(JumpySnackbarContext);
2002
- }
2003
- function JumpSnackbarProvider({ children, position = "bottom-center", maxSnackbars = 3, offset: offset2 = {}, className = "", ...props }) {
2004
- const theme = useJumpyTheme();
2005
- const [snackbars, setSnackbars] = useState2([]);
2006
- const top = position.startsWith("top");
2007
- const align = position.endsWith("start") ? "flex-start" : position.endsWith("end") ? "flex-end" : "center";
2008
- const horizontalOffset = offset2.left ?? offset2.right ?? spacingValue(theme, JumpySpacing.large);
2009
- const verticalOffset = top ? offset2.top ?? spacingValue(theme, JumpySpacing.smallXS) : offset2.bottom ?? spacingValue(theme, JumpySpacing.smallXS);
2010
- function removeSnackbar(id) {
2011
- setSnackbars((items) => items.filter((item) => item.id !== id));
2012
- }
2013
- function addSnackbar(contentOrData, options = {}) {
2014
- const id = options.id || `jumpy-snackbar-${Date.now()}-${Math.random().toString(36).slice(2)}`;
2015
- const isData = contentOrData && typeof contentOrData === "object" && !isValidElement(contentOrData) && ("title" in contentOrData || "description" in contentOrData || "status" in contentOrData);
2016
- const duration = options.persistent || contentOrData?.persistent ? null : options.duration ?? contentOrData?.duration ?? 4e3;
2017
- const snackbar = { id, content: contentOrData, isData, createdAt: Date.now() };
2018
- setSnackbars((items) => [snackbar, ...items.filter((item) => item.id !== id)].slice(0, maxSnackbars));
2019
- if (duration) window.setTimeout(() => removeSnackbar(id), duration);
2020
- return id;
2021
- }
2022
- const contextValue = { addSnackbar, removeSnackbar, clearAll: () => setSnackbars([]), snackbars };
2023
- return /* @__PURE__ */ jsxs2(JumpySnackbarContext.Provider, { value: contextValue, children: [
2024
- children,
2025
- /* @__PURE__ */ jsx2("output", { "aria-live": "assertive", "aria-atomic": "true", style: { position: "absolute", width: 1, height: 1, padding: 0, margin: -1, overflow: "hidden", clip: "rect(0, 0, 0, 0)", whiteSpace: "nowrap", border: 0 }, children: snackbars[0] ? textFromNode(snackbars[0].content?.title || snackbars[0].content?.description || snackbars[0].content) : "" }),
2026
- /* @__PURE__ */ jsx2("div", { ...props, "data-jumpy-component": "snackbar-provider", className: cn(className), style: { position: "fixed", zIndex: 1100, left: position.endsWith("start") ? horizontalOffset : position.endsWith("end") ? "auto" : "50%", right: position.endsWith("end") ? horizontalOffset : "auto", top: top ? verticalOffset : "auto", bottom: top ? "auto" : verticalOffset, transform: position.endsWith("center") ? "translateX(-50%)" : void 0, display: "flex", flexDirection: "column", alignItems: align, gap: spacingValue(theme, JumpySpacing.smallXS), width: "min(487px, calc(100vw - 32px))", maxWidth: "50vw", pointerEvents: "none", ...props.style || {} }, children: snackbars.map((snackbar) => snackbar.isData ? /* @__PURE__ */ jsx2(JumpBanner, { floating: true, status: snackbar.content.status || "info", title: snackbar.content.title, actions: snackbar.content.slots?.actions, style: { width: "100%", pointerEvents: "auto", boxShadow: theme.shadow.positiveLarge }, children: snackbar.content.description }, snackbar.id) : /* @__PURE__ */ jsx2("div", { "data-jumpy-component": "snackbar", style: { width: "100%", pointerEvents: "auto", borderRadius: radiusValue(theme, JumpyRadius.small), boxShadow: theme.shadow.positiveLarge }, children: snackbar.content }, snackbar.id)) })
2027
- ] });
2028
- }
2029
- function JumpTable({ children, showHorizontalBorders = false, disabled = false, stickyHeader = false, density = "regular", containerProps, innerClasses = {}, className = "", ...props }) {
2030
- const { className: containerClassName, style: containerStyle, ...restContainerProps } = containerProps || {};
2031
- const contextValue = { showHorizontalBorders, disabled };
2032
- return /* @__PURE__ */ jsx2(JumpyTableContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "table-wrapper", className: innerClasses.wrapper?.className, style: innerClasses.wrapper?.style, children: /* @__PURE__ */ jsx2("div", { ...restContainerProps, "data-jumpy-slot": "table-container", "data-sticky-header": stickyHeader ? "true" : "false", className: cn(containerClassName), style: containerStyle, children: /* @__PURE__ */ jsx2("table", { ...props, "data-jumpy-component": "table", "data-horizontal-borders": showHorizontalBorders ? "true" : "false", "data-density": density === "condensed" ? "condensed" : void 0, "data-disabled": disabled ? "true" : void 0, role: props.role || "grid", className: cn(className), children }) }) }) });
2033
- }
2034
- function JumpTableHead({ as, children, className = "", ...props }) {
2035
- const Component = as || "thead";
2036
- return /* @__PURE__ */ jsx2(Component, { ...props, "data-jumpy-component": "table-head", role: props.role || "rowgroup", className: cn(className), children });
2037
- }
2038
- function JumpTableBody({ as, children, className = "", ...props }) {
2039
- const Component = as || "tbody";
2040
- return /* @__PURE__ */ jsx2(Component, { ...props, "data-jumpy-component": "table-body", role: props.role || "rowgroup", className: cn(className), children });
2041
- }
2042
- function JumpTableFooter({ as, children, className = "", ...props }) {
2043
- const Component = as || "tfoot";
2044
- return /* @__PURE__ */ jsx2(Component, { ...props, "data-jumpy-component": "table-footer", role: props.role || "rowgroup", className: cn(className), children });
2045
- }
2046
- function JumpTableRow({ as, children, selected = false, disabled = false, disableHover = false, verticalAlign = "middle", dragging = false, className = "", onClick, ...props }) {
2047
- const Component = as || "tr";
2048
- const tableContext = useContext(JumpyTableContext);
2049
- const isDisabled = disabled || tableContext?.disabled;
2050
- const isInteractive = !disableHover && !isDisabled && Boolean(onClick);
2051
- return /* @__PURE__ */ jsx2(
2052
- Component,
2053
- {
2054
- ...props,
2055
- "data-jumpy-component": "table-row",
2056
- "data-selectable": isInteractive ? "true" : void 0,
2057
- "data-disable-hover": disableHover ? "true" : void 0,
2058
- "data-disabled": isDisabled ? "true" : void 0,
2059
- "data-state": selected ? "selected" : void 0,
2060
- "data-vertical-align": verticalAlign === "top" ? "top" : void 0,
2061
- "data-dragging": dragging ? "true" : void 0,
2062
- role: props.role || "row",
2063
- "aria-selected": selected || void 0,
2064
- "aria-disabled": isDisabled || void 0,
2065
- tabIndex: isInteractive ? props.tabIndex ?? 0 : props.tabIndex,
2066
- className: cn(className),
2067
- onClick: isDisabled ? void 0 : onClick,
2068
- children
2069
- }
2070
- );
2071
- }
2072
- function getTableCellTypography(theme, emphasized = false) {
2073
- return textStyleValue(theme, emphasized ? JumpyTextStyle.bodyMediumEmphasized : JumpyTextStyle.bodyMedium);
2074
- }
2075
- function tableCellTextStyle(theme, emphasized = false, style) {
2076
- const textStyle = getTableCellTypography(theme, emphasized);
2077
- return {
2078
- fontFamily: theme.fontFamily.familyBase,
2079
- fontSize: textStyle.fontSize,
2080
- lineHeight: textStyle.lineHeight,
2081
- fontWeight: textStyle.fontWeight,
2082
- letterSpacing: textStyle.letterSpacing,
2083
- ...style
2084
- };
2085
- }
2086
- function JumpTableCell({ as, children, selected = false, disabled = false, align, variant = "default", padding = "default", showHorizontalBorder, pinned, pinnedEdge = false, leading, trailing, description, slots, innerClasses = {}, className = "", onClick, ...props }) {
2087
- const theme = useJumpyTheme();
2088
- const tableContext = useContext(JumpyTableContext);
2089
- const Component = as || "td";
2090
- const isControl = variant === "control" || padding === "checkbox";
2091
- const resolvedAlign = align || (isControl ? "center" : "start");
2092
- const cellLeading = slots?.leading ?? leading;
2093
- const cellTrailing = slots?.trailing ?? trailing;
2094
- const cellDescription = slots?.description ?? description;
2095
- const isDisabled = disabled || tableContext?.disabled;
2096
- const resolvedHorizontalBorder = showHorizontalBorder ?? tableContext?.showHorizontalBorders ?? false;
2097
- const isInteractive = !isDisabled && Boolean(onClick);
2098
- return /* @__PURE__ */ jsxs2(
2099
- Component,
2100
- {
2101
- ...props,
2102
- "data-jumpy-component": "table-cell",
2103
- "data-pinned": pinned || void 0,
2104
- "data-pinned-edge": pinnedEdge ? "true" : void 0,
2105
- "data-horizontal-border": resolvedHorizontalBorder ? "true" : "false",
2106
- "data-state": selected ? "selected" : "default",
2107
- "data-disabled": isDisabled ? "true" : void 0,
2108
- "data-align": resolvedAlign,
2109
- "data-padding": padding,
2110
- "data-variant": isControl ? "control" : void 0,
2111
- "data-interactive": isInteractive ? "true" : void 0,
2112
- role: props.role || "gridcell",
2113
- "aria-disabled": isDisabled || void 0,
2114
- "aria-selected": selected || void 0,
2115
- tabIndex: props.tabIndex ?? (isInteractive ? 0 : void 0),
2116
- className: cn(className),
2117
- onClick: isDisabled ? void 0 : onClick,
2118
- children: [
2119
- /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", "data-jumpy-slot": "table-cell-surface" }),
2120
- /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "table-cell-inner", "data-align": resolvedAlign, className: innerClasses.inner?.className, style: innerClasses.inner?.style, children: [
2121
- cellLeading ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "table-cell-leading", className: innerClasses.leading?.className, style: innerClasses.leading?.style, children: cellLeading }) : null,
2122
- /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "table-cell-content", className: innerClasses.content?.className, style: innerClasses.content?.style, children: [
2123
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "table-cell-text", className: innerClasses.text?.className, style: tableCellTextStyle(theme, false, innerClasses.text?.style), children }),
2124
- cellDescription ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "table-cell-description", className: innerClasses.description?.className, style: tableCellTextStyle(theme, false, innerClasses.description?.style), children: cellDescription }) : null
2125
- ] }),
2126
- cellTrailing ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "table-cell-trailing", className: innerClasses.trailing?.className, style: innerClasses.trailing?.style, children: cellTrailing }) : null
2127
- ] })
2128
- ]
2129
- }
2130
- );
2131
- }
2132
- function getSortMapping(currentOrder, ascValue, descValue, defaultValue) {
2133
- if (currentOrder === "ascending" || currentOrder === "asc") return ascValue;
2134
- if (currentOrder === "descending" || currentOrder === "desc") return descValue;
2135
- return defaultValue;
2136
- }
2137
- function getNextSortOrder(order) {
2138
- if (order === "ascending" || order === "asc") return "desc";
2139
- if (order === "descending" || order === "desc") return "default";
2140
- return "asc";
2141
- }
2142
- function getSortOrder(sortDirection) {
2143
- return getSortMapping(sortDirection, "asc", "desc", "default");
2144
- }
2145
- function getSortActionLabel(currentOrder, columnName) {
2146
- if (columnName) {
2147
- return getSortMapping(currentOrder, `Sort ${columnName} descending`, `Clear sort on ${columnName}`, `Sort ${columnName} ascending`);
2148
- }
2149
- return getSortMapping(currentOrder, "Sort descending", "Clear sort", "Sort ascending");
2150
- }
2151
- function participatesInSelection(row) {
2152
- return row.getCanSelect() || (row.subRows?.some(participatesInSelection) ?? false);
2153
- }
2154
- function getSelectionChildren(row) {
2155
- const children = row.subRows?.filter(participatesInSelection);
2156
- return children?.length ? children : void 0;
2157
- }
2158
- function isRowFullySelected(row) {
2159
- const children = getSelectionChildren(row);
2160
- if (children) return children.every(isRowFullySelected);
2161
- return row.getCanSelect() && row.getIsSelected();
2162
- }
2163
- function applySubtreeSelection(row, selected) {
2164
- if (row.getCanSelect() && row.getIsSelected() !== selected) {
2165
- row.toggleSelected(selected, { selectChildren: false });
2166
- }
2167
- row.subRows?.forEach((subRow) => applySubtreeSelection(subRow, selected));
2168
- }
2169
- function getRowCheckboxProps(row, disabled) {
2170
- const checked = isRowFullySelected(row);
2171
- return {
2172
- checked,
2173
- indeterminate: !checked && row.getIsSomeSelected(),
2174
- disabled: disabled || !row.getCanSelect(),
2175
- "aria-label": `Select row ${row.id}`,
2176
- onChange: () => applySubtreeSelection(row, !isRowFullySelected(row))
2177
- };
2178
- }
2179
- function getHeaderCheckboxProps(table, disabled) {
2180
- const topRows = table.getRowModel?.().rows.filter((row) => (row.depth ?? 0) === 0 && participatesInSelection(row));
2181
- const isTree = topRows?.some((row) => getSelectionChildren(row)) ?? false;
2182
- const checked = isTree && topRows ? topRows.every(isRowFullySelected) : table.getIsAllRowsSelected();
2183
- return {
2184
- checked,
2185
- indeterminate: !checked && table.getIsSomeRowsSelected(),
2186
- disabled,
2187
- "aria-label": "Select all rows",
2188
- onChange: table.getToggleAllRowsSelectedHandler()
2189
- };
2190
- }
2191
- function sortSymbol(order) {
2192
- if (order === "ascending" || order === "asc") return "\u2191";
2193
- if (order === "descending" || order === "desc") return "\u2193";
2194
- return "\u2195";
2195
- }
2196
- function ariaSortOrder(order) {
2197
- if (order === "ascending" || order === "asc") return "ascending";
2198
- if (order === "descending" || order === "desc") return "descending";
2199
- if (order === "default" || order === "none" || order === false) return "none";
2200
- return void 0;
2201
- }
2202
- function JumpTableHeaderCell({ as, children, selected = false, disabled = false, align, variant = "default", padding = "default", showHorizontalBorder, pinned, pinnedEdge = false, sortable = false, sortOrder = "default", onSortChange, ariaLabels, leading, trailing, slots, innerClasses = {}, className = "", ...props }) {
2203
- const theme = useJumpyTheme();
2204
- const tableContext = useContext(JumpyTableContext);
2205
- const Component = as || "th";
2206
- const isControl = variant === "control" || padding === "checkbox";
2207
- const resolvedAlign = align || (isControl ? "center" : "start");
2208
- const cellLeading = slots?.leading ?? leading;
2209
- const cellTrailing = slots?.trailing ?? trailing;
2210
- const isDisabled = disabled || tableContext?.disabled;
2211
- const resolvedHorizontalBorder = showHorizontalBorder ?? tableContext?.showHorizontalBorders ?? false;
2212
- const participatesInSort = sortable || sortOrder !== "default" && sortOrder !== "none";
2213
- const ariaSort = participatesInSort ? ariaSortOrder(sortOrder) : void 0;
2214
- return /* @__PURE__ */ jsxs2(
2215
- Component,
2216
- {
2217
- ...props,
2218
- "data-jumpy-component": "table-header-cell",
2219
- "data-pinned": pinned || void 0,
2220
- "data-pinned-edge": pinnedEdge ? "true" : void 0,
2221
- "data-horizontal-border": resolvedHorizontalBorder ? "true" : "false",
2222
- "data-sortable": sortable ? "true" : "false",
2223
- "data-sort-order": sortOrder,
2224
- "data-disabled": isDisabled ? "true" : void 0,
2225
- "data-state": selected ? "selected" : "default",
2226
- "data-align": resolvedAlign,
2227
- "data-padding": padding,
2228
- "data-variant": isControl ? "control" : void 0,
2229
- role: props.role || "columnheader",
2230
- "aria-sort": ariaSort,
2231
- className: cn(className),
2232
- children: [
2233
- /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", "data-jumpy-slot": "table-cell-surface" }),
2234
- /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "table-cell-inner", "data-align": resolvedAlign, className: innerClasses.inner?.className, style: innerClasses.inner?.style, children: [
2235
- cellLeading ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "table-cell-leading", className: innerClasses.leading?.className, style: innerClasses.leading?.style, children: cellLeading }) : null,
2236
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "table-cell-content", className: innerClasses.label?.className, style: innerClasses.label?.style, children: /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "table-cell-text", className: innerClasses.labelText?.className, style: tableCellTextStyle(theme, true, innerClasses.labelText?.style), children }) }),
2237
- cellTrailing || sortable ? /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "table-cell-trailing", className: innerClasses.trailing?.className, style: innerClasses.trailing?.style, children: [
2238
- sortable ? /* @__PURE__ */ jsx2(JumpIconButton, { variant: "secondary", disabled: isDisabled, "aria-label": typeof ariaLabels?.sort === "function" ? ariaLabels.sort(sortOrder) : ariaLabels?.sort || getSortActionLabel(sortOrder, typeof children === "string" ? children : void 0), onClick: () => onSortChange?.(getNextSortOrder(sortOrder)), children: sortSymbol(sortOrder) }) : null,
2239
- cellTrailing
2240
- ] }) : null
2241
- ] })
2242
- ]
2243
- }
2244
- );
2245
- }
2246
- function ChevronIcon({ expanded }) {
2247
- return /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", width: "24", height: "24", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx2("path", { d: expanded ? "M7.4 15.4 12 10.8l4.6 4.6 1.4-1.4-6-6-6 6 1.4 1.4Z" : "M7.4 8.6 12 13.2l4.6-4.6 1.4 1.4-6 6-6-6 1.4-1.4Z" }) });
2248
- }
2249
- function JumpExpandable({ as, variant = "filled", disabled = false, expanded, defaultExpanded = false, onChange, slots, innerClasses = {}, className = "", children, ...props }) {
2250
- const Component = as || "div";
2251
- const context = useContext(JumpyExpandableContext);
2252
- const panelId = useId();
2253
- const isDisabled = disabled || context?.disabled;
2254
- const isControlled = expanded !== void 0 || context?.expanded !== void 0;
2255
- const [uncontrolledExpanded, setUncontrolledExpanded] = useState2(defaultExpanded);
2256
- const isExpanded = isControlled ? Boolean(expanded ?? context?.expanded) : uncontrolledExpanded;
2257
- const label = slots?.label;
2258
- const description = slots?.description;
2259
- const leading = slots?.leading;
2260
- const trailing = slots?.trailing;
2261
- function handleToggle() {
2262
- if (isDisabled) return;
2263
- const nextExpanded = !isExpanded;
2264
- context?.onExpandedChange?.(nextExpanded);
2265
- if (!isControlled) setUncontrolledExpanded(nextExpanded);
2266
- onChange?.(nextExpanded);
2267
- }
2268
- const header = /* @__PURE__ */ jsxs2(
2269
- "button",
2270
- {
2271
- ref: context?.buttonRef,
2272
- type: "button",
2273
- "data-jumpy-slot": "expandable-header",
2274
- className: innerClasses.header?.className,
2275
- style: innerClasses.header?.style,
2276
- onClick: handleToggle,
2277
- disabled: isDisabled,
2278
- "aria-controls": panelId,
2279
- "aria-expanded": isExpanded,
2280
- children: [
2281
- /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "expandable-slots-container", children: [
2282
- leading,
2283
- label || description ? /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "expandable-header-content", className: innerClasses.headerContent?.className, style: innerClasses.headerContent?.style, children: [
2284
- label ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "expandable-label", className: innerClasses.label?.className, style: innerClasses.label?.style, children: label }) : null,
2285
- description ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "expandable-description", className: innerClasses.description?.className, style: innerClasses.description?.style, children: description }) : null
2286
- ] }) : null
2287
- ] }),
2288
- /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "expandable-slots-container", children: [
2289
- trailing,
2290
- !isDisabled ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "expandable-icon", className: innerClasses.icon?.className, style: innerClasses.icon?.style, children: /* @__PURE__ */ jsx2(ChevronIcon, { expanded: isExpanded }) }) : null
2291
- ] })
2292
- ]
2293
- }
2294
- );
2295
- return /* @__PURE__ */ jsxs2(Component, { ...props, "data-jumpy-component": "expandable", "data-variant": variant, className: cn(className), children: [
2296
- context?.headingLevel ? /* @__PURE__ */ jsx2(context.headingLevel, { "data-jumpy-slot": "expandable-header-wrapper", children: header }) : header,
2297
- /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "expandable-content-wrapper", "data-expanded": isExpanded ? "true" : "false", children: /* @__PURE__ */ jsx2("div", { id: panelId, "data-jumpy-slot": "expandable-panel", className: innerClasses.panel?.className, style: innerClasses.panel?.style, children }) })
2298
- ] });
2299
- }
2300
- function headingTag(level) {
2301
- if (!level) return void 0;
2302
- return `h${Math.min(6, Math.max(1, Number(level)))}`;
2303
- }
2304
- function focusExpandableButton(buttonRefs, index) {
2305
- const nextButton = buttonRefs.current[index];
2306
- nextButton?.focus();
2307
- }
2308
- function JumpExpandableGroup({ as, multiple = false, disabled = false, headingLevel, onChange, innerClasses = {}, className = "", children, ...props }) {
2309
- const Component = as || "div";
2310
- const buttonRefs = useRef2([]);
2311
- const childArray = Children.toArray(children);
2312
- const [expandedIndices, setExpandedIndices] = useState2(() => {
2313
- const initial = /* @__PURE__ */ new Set();
2314
- childArray.forEach((child, index) => {
2315
- if (isValidElement(child) && (child.props.expanded || child.props.defaultExpanded)) initial.add(index);
2316
- });
2317
- return initial;
2318
- });
2319
- function updateExpanded(index, nextExpanded) {
2320
- setExpandedIndices((current) => {
2321
- const next = new Set(current);
2322
- if (nextExpanded) {
2323
- if (!multiple) next.clear();
2324
- next.add(index);
2325
- } else {
2326
- next.delete(index);
2327
- }
2328
- onChange?.(Array.from(next).sort((a, b) => a - b));
2329
- return next;
2330
- });
2331
- }
2332
- function handleKeyDown(event) {
2333
- const currentIndex = buttonRefs.current.findIndex((button) => button === event.target);
2334
- if (currentIndex === -1) {
2335
- props.onKeyDown?.(event);
2336
- return;
2337
- }
2338
- if (event.key === KeyboardKeys.ARROW_DOWN) {
2339
- event.preventDefault();
2340
- focusExpandableButton(buttonRefs, (currentIndex + 1) % buttonRefs.current.length);
2341
- } else if (event.key === KeyboardKeys.ARROW_UP) {
2342
- event.preventDefault();
2343
- focusExpandableButton(buttonRefs, (currentIndex - 1 + buttonRefs.current.length) % buttonRefs.current.length);
2344
- } else if (event.key === KeyboardKeys.HOME) {
2345
- event.preventDefault();
2346
- focusExpandableButton(buttonRefs, 0);
2347
- } else if (event.key === KeyboardKeys.END) {
2348
- event.preventDefault();
2349
- focusExpandableButton(buttonRefs, buttonRefs.current.length - 1);
2350
- }
2351
- props.onKeyDown?.(event);
2352
- }
2353
- return /* @__PURE__ */ jsx2(Component, { ...props, "data-jumpy-component": "expandable-group", "aria-disabled": disabled || void 0, className: cn(className), onKeyDown: handleKeyDown, children: childArray.map((child, index) => {
2354
- const contextValue = {
2355
- headingLevel: headingTag(headingLevel),
2356
- disabled,
2357
- expanded: expandedIndices.has(index),
2358
- onExpandedChange: (nextExpanded) => updateExpanded(index, nextExpanded),
2359
- buttonRef: (element) => {
2360
- buttonRefs.current[index] = element;
2361
- }
2362
- };
2363
- return /* @__PURE__ */ jsxs2(JumpyExpandableContext.Provider, { value: contextValue, children: [
2364
- child,
2365
- index < childArray.length - 1 ? /* @__PURE__ */ jsx2(JumpDivider, { direction: "horizontal", "data-jumpy-slot": "expandable-group-divider", className: innerClasses.divider?.className, style: innerClasses.divider?.style }) : null
2366
- ] }, child.key || index);
2367
- }) });
2368
- }
2369
- var JUMPY_POPUP_Z_INDEX = 1200;
2370
- function JumpyPortal({ children }) {
2371
- const theme = useJumpyTheme();
2372
- const scope = useContext(JumpyThemeScopeContext);
2373
- return /* @__PURE__ */ jsx2(FloatingPortal, { children: /* @__PURE__ */ jsx2("div", { "data-jumpy-theme": "true", "data-jumpy-scope": scope || void 0, "data-jumpy-portal": "true", style: { ...!scope ? themeToCSSVariables(theme) : {}, fontFamily: theme.fontFamily.familyBase }, children }) });
2374
- }
2375
- function JumpyModalLayer({ children, onVisibleChange, style, onPointerDown, ...props }) {
2376
- const theme = useJumpyTheme();
2377
- const { refs, context } = useFloating({ open: true, onOpenChange: onVisibleChange });
2378
- return /* @__PURE__ */ jsx2(JumpyPortal, { children: /* @__PURE__ */ jsx2(FloatingOverlay, { ...props, lockScroll: true, onPointerDown: (event) => {
2379
- onPointerDown?.(event);
2380
- if (event.target === event.currentTarget) event.preventDefault();
2381
- }, style, children: /* @__PURE__ */ jsx2(FloatingFocusManager, { context, outsideElementsInert: true, restoreFocus: true, children: cloneElement(children, {
2382
- ref: refs.setFloating,
2383
- tabIndex: -1,
2384
- onKeyDown(event) {
2385
- children.props.onKeyDown?.(event);
2386
- const nestedPopup = !event.currentTarget.contains(event.target) || event.target.closest('[aria-haspopup][aria-expanded="true"]');
2387
- if (event.key === KeyboardKeys.ESCAPE && !event.defaultPrevented && !nestedPopup) {
2388
- event.preventDefault();
2389
- event.stopPropagation();
2390
- onVisibleChange?.(false);
2391
- }
2392
- }
2393
- }) }) }) });
2394
- }
2395
- function JumpSheet({ visible = false, onVisibleChange, position = "right", title, subtitle, footer, children, className = "", ...props }) {
2396
- const theme = useJumpyTheme();
2397
- const vertical = position === "left" || position === "right";
2398
- if (!visible) return null;
2399
- return /* @__PURE__ */ jsx2(JumpyModalLayer, { onVisibleChange, "data-jumpy-component": "sheet-overlay", role: "presentation", onClick: (event) => {
2400
- if (event.target === event.currentTarget) onVisibleChange?.(false);
2401
- }, style: { position: "fixed", inset: 0, zIndex: 50, backgroundColor: theme.color.surfaceOverlay }, children: /* @__PURE__ */ jsxs2("aside", { ...props, "data-jumpy-component": "sheet", className: cn(className), role: "dialog", "aria-modal": "true", "aria-label": title || props["aria-label"], tabIndex: -1, onClick: (event) => {
2402
- event.stopPropagation();
2403
- props.onClick?.(event);
2404
- }, style: { position: "fixed", top: position === "bottom" ? "auto" : 0, bottom: position === "top" ? "auto" : 0, left: position === "right" ? "auto" : 0, right: position === "left" ? "auto" : 0, width: vertical ? "min(420px, 100vw)" : "100vw", height: vertical ? "100dvh" : "min(420px, 100dvh)", backgroundColor: theme.color.surfaceCard, color: theme.color.textPrimary, boxShadow: theme.shadow.positiveMedium, display: "flex", flexDirection: "column", ...props.style || {} }, children: [
2405
- /* @__PURE__ */ jsxs2("div", { style: { padding: spacingValue(theme, JumpySpacing.large), borderBottom: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}` }, children: [
2406
- title ? /* @__PURE__ */ jsx2(JumpTypography, { variant: "titleMediumEmphasized", children: title }) : null,
2407
- subtitle ? /* @__PURE__ */ jsx2(JumpTypography, { variant: "bodySmall", color: "secondary", children: subtitle }) : null
2408
- ] }),
2409
- /* @__PURE__ */ jsx2("div", { style: { padding: spacingValue(theme, JumpySpacing.large), flex: 1, overflow: "auto" }, children }),
2410
- footer ? /* @__PURE__ */ jsx2("div", { style: { padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid ${theme.color.borderSubtle}` }, children: footer }) : null
2411
- ] }) });
2412
- }
2413
- function JumpyNavigationIcon({ type, size: size2 = 24 }) {
2414
- const paths = {
2415
- menu: "M4 6h16M4 12h16M4 18h16",
2416
- close: "M6 6l12 12M18 6 6 18",
2417
- back: "M15 18l-6-6 6-6",
2418
- more: "M5 12h.01M12 12h.01M19 12h.01"
2419
- };
2420
- return /* @__PURE__ */ jsx2("svg", { width: size2, height: size2, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: type === "more" ? 3 : 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", "data-jumpy-stroke-icon": "true", style: { display: "block" }, children: /* @__PURE__ */ jsx2("path", { d: paths[type] }) });
2421
- }
2422
- function JumpTopBar({ leading, onMenu, onBack, menuOpen = false, title, subtitle, trailing, sticky = true, titleAlign = "start", titleHidden = false, ariaLabels = {}, className = "", ...props }) {
2423
- const theme = useJumpyTheme();
2424
- const [scrolled, setScrolled] = useState2(false);
2425
- const titleStyle = textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized);
2426
- const subtitleStyle = textStyleValue(theme, JumpyTextStyle.bodySmall);
2427
- useEffect2(() => {
2428
- if (!sticky) return void 0;
2429
- const update = () => setScrolled(window.scrollY > 0);
2430
- update();
2431
- window.addEventListener("scroll", update, { passive: true });
2432
- return () => window.removeEventListener("scroll", update);
2433
- }, [sticky]);
2434
- const leadingType = leading === "menu" || leading === "back" ? leading : void 0;
2435
- const leadingNode = leading === "menu" ? /* @__PURE__ */ jsx2(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.menu || "Open navigation", "aria-haspopup": "dialog", "aria-expanded": menuOpen, onClick: onMenu, children: /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "menu" }) }) : leading === "back" ? /* @__PURE__ */ jsx2(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.back || "Back", onClick: onBack, children: /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "back" }) }) : leading;
2436
- return /* @__PURE__ */ jsxs2(
2437
- "header",
2438
- {
2439
- ...props,
2440
- "data-jumpy-component": "top-bar",
2441
- "data-sticky": sticky ? "true" : "false",
2442
- "data-scrolled": scrolled ? "true" : "false",
2443
- "data-title-align": titleAlign,
2444
- "data-title-hidden": titleHidden ? "true" : "false",
2445
- className: cn(className),
2446
- style: {
2447
- boxSizing: "border-box",
2448
- position: sticky ? "sticky" : "relative",
2449
- top: 0,
2450
- zIndex: 40,
2451
- alignItems: "center",
2452
- gap: spacingValue(theme, JumpySpacing.medium),
2453
- minHeight: `calc(${sizeValue(theme, JumpySize.large2XL)}px + env(safe-area-inset-top, 0px))`,
2454
- padding: `calc(${spacingValue(theme, JumpySpacing.smallXS)}px + env(safe-area-inset-top, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-right, 0px)) ${spacingValue(theme, JumpySpacing.smallXS)}px calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-left, 0px))`,
2455
- color: "var(--jumpy-color-text-primary)",
2456
- fontFamily: theme.fontFamily.familyBase,
2457
- ...props.style || {}
2458
- },
2459
- children: [
2460
- leadingNode ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "top-bar-leading", "data-leading": leadingType, style: { minWidth: 0, maxWidth: "100%", marginLeft: -spacingValue(theme, JumpySpacing.smallXS) }, children: leadingNode }) : null,
2461
- /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "top-bar-title", "aria-hidden": titleHidden || void 0, style: titleAlign === "center" ? {
2462
- display: "flex",
2463
- flexDirection: "column",
2464
- alignItems: "center",
2465
- textAlign: "center",
2466
- minWidth: 0,
2467
- opacity: titleHidden ? 0 : 1
2468
- } : { display: "flex", flexDirection: "column", flex: "1 1 auto", minWidth: 0, opacity: titleHidden ? 0 : 1 }, children: [
2469
- title ? /* @__PURE__ */ jsx2("span", { style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: titleStyle.fontSize, lineHeight: titleStyle.lineHeight, fontWeight: titleStyle.fontWeight }, children: title }) : null,
2470
- subtitle ? /* @__PURE__ */ jsx2("span", { style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "var(--jumpy-color-text-secondary)", fontSize: subtitleStyle.fontSize, lineHeight: subtitleStyle.lineHeight }, children: subtitle }) : null
2471
- ] }),
2472
- trailing ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "top-bar-trailing", style: { display: "inline-flex", flex: "0 0 auto", marginLeft: "auto", alignItems: "center", gap: spacingValue(theme, JumpySpacing.smallXS) }, children: trailing }) : null
2473
- ]
2474
- }
2475
- );
2476
- }
2477
- function JumpTabBar({ children, className = "", "aria-label": ariaLabel = "Main navigation", ...props }) {
2478
- const theme = useJumpyTheme();
2479
- return /* @__PURE__ */ jsx2(
2480
- "div",
2481
- {
2482
- "data-jumpy-component": "tab-bar",
2483
- className: cn(className),
2484
- style: {
2485
- boxSizing: "border-box",
2486
- position: "sticky",
2487
- bottom: 0,
2488
- zIndex: 40,
2489
- padding: `0 calc(${spacingValue(theme, JumpySpacing.medium)}px + env(safe-area-inset-right, 0px)) calc(${spacingValue(theme, JumpySpacing.medium)}px + env(safe-area-inset-bottom, 0px)) calc(${spacingValue(theme, JumpySpacing.medium)}px + env(safe-area-inset-left, 0px))`,
2490
- pointerEvents: "none"
2491
- },
2492
- children: /* @__PURE__ */ jsx2(
2493
- "nav",
2494
- {
2495
- ...props,
2496
- "aria-label": ariaLabel,
2497
- "data-jumpy-slot": "tab-bar-surface",
2498
- style: {
2499
- boxSizing: "border-box",
2500
- display: "flex",
2501
- gap: spacingValue(theme, JumpySpacing.small4XS),
2502
- padding: spacingValue(theme, JumpySpacing.small3XS),
2503
- borderRadius: radiusValue(theme, JumpyRadius.large2XL),
2504
- border: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`,
2505
- boxShadow: theme.shadow.positiveMedium,
2506
- pointerEvents: "auto",
2507
- fontFamily: theme.fontFamily.familyBase,
2508
- ...props.style || {}
2509
- },
2510
- children
2511
- }
2512
- )
2513
- }
2514
- );
2515
- }
2516
- function JumpTabBarItem({ as, icon, badge, selected = false, children, className = "", ...props }) {
2517
- const theme = useJumpyTheme();
2518
- const Component = as || (props.href ? "a" : "button");
2519
- const labelStyle = textStyleValue(theme, JumpyTextStyle.labelSmall);
2520
- const badgeNode = typeof badge === "number" ? badge > 0 ? /* @__PURE__ */ jsx2(JumpBadge, { size: "small", status: "active", "aria-label": void 0, role: "presentation", "aria-live": "off", children: badge > 99 ? "99+" : badge }) : null : badge;
2521
- return /* @__PURE__ */ jsxs2(
2522
- Component,
2523
- {
2524
- ...props,
2525
- type: Component === "button" ? props.type || "button" : props.type,
2526
- "aria-current": selected ? props["aria-current"] || "page" : props["aria-current"],
2527
- "data-jumpy-component": "tab-bar-item",
2528
- "data-selected": selected ? "true" : "false",
2529
- className: cn(className),
2530
- style: {
2531
- boxSizing: "border-box",
2532
- flex: "1 1 0",
2533
- minWidth: 0,
2534
- display: "flex",
2535
- flexDirection: "column",
2536
- alignItems: "center",
2537
- justifyContent: "center",
2538
- gap: spacingValue(theme, JumpySpacing.small4XS),
2539
- minHeight: sizeValue(theme, JumpySize.large2XL),
2540
- padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.small3XS)}px`,
2541
- border: 0,
2542
- borderRadius: radiusValue(theme, JumpyRadius.largeXL),
2543
- backgroundColor: selected ? "var(--jumpy-interactive-active-regular-default-surface)" : "transparent",
2544
- color: selected ? "var(--jumpy-interactive-active-regular-default-text)" : "var(--jumpy-color-text-secondary)",
2545
- textDecoration: "none",
2546
- cursor: "pointer",
2547
- fontFamily: theme.fontFamily.familyBase,
2548
- fontSize: labelStyle.fontSize,
2549
- lineHeight: labelStyle.lineHeight,
2550
- fontWeight: labelStyle.fontWeight,
2551
- ...props.style || {}
2552
- },
2553
- children: [
2554
- icon ? /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "tab-bar-item-icon", "aria-hidden": "true", style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: sizeValue(theme, JumpySize.smallXS), height: sizeValue(theme, JumpySize.smallXS) }, children: [
2555
- icon,
2556
- badgeNode ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "tab-bar-item-badge", style: { position: "absolute", top: -spacingValue(theme, JumpySpacing.smallXS), left: "50%", marginLeft: spacingValue(theme, JumpySpacing.small3XS), display: "inline-flex" }, children: badgeNode }) : null
2557
- ] }) : null,
2558
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "tab-bar-item-label", style: { maxWidth: "100%", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children })
2559
- ]
2560
- }
2561
- );
2562
- }
2563
- var jumpyLocations = { current: null, previous: null };
2564
- function jumpyNormalizeUrl(url) {
2565
- const resolved = new URL(url, window.location.href);
2566
- const path = resolved.pathname.length > 1 ? resolved.pathname.replace(/\/+$/, "") : resolved.pathname;
2567
- return `${resolved.origin}${path}${resolved.search === "?" ? "" : resolved.search}`;
2568
- }
2569
- function jumpyPreviousEntryIs(href) {
2570
- const target = jumpyNormalizeUrl(href);
2571
- const navigation = window.navigation;
2572
- if (navigation && typeof navigation.entries === "function" && navigation.currentEntry) {
2573
- if (!navigation.canGoBack) return false;
2574
- const previous = navigation.entries()[navigation.currentEntry.index - 1];
2575
- return Boolean(previous?.url) && jumpyNormalizeUrl(previous.url) === target;
2576
- }
2577
- return Boolean(jumpyLocations.previous) && jumpyNormalizeUrl(jumpyLocations.previous) === target && window.history.length > 1;
2578
- }
2579
- function JumpyAppShellBack({ back, variant, ariaLabels }) {
2580
- const theme = useJumpyTheme();
2581
- const Component = back.as || (back.href ? "a" : "button");
2582
- const textStyle = textStyleValue(theme, variant === "inline" ? JumpyTextStyle.bodySmallEmphasized : JumpyTextStyle.bodyMedium);
2583
- const accessibleName = ariaLabels.backTo ? ariaLabels.backTo(back.label) : `Back to ${back.label}`;
2584
- function handleClick(event) {
2585
- back.onClick?.(event);
2586
- if (event.defaultPrevented || !back.href || back.history === false) return;
2587
- if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
2588
- if (!jumpyPreviousEntryIs(back.href)) return;
2589
- event.preventDefault();
2590
- window.history.back();
2591
- }
2592
- return /* @__PURE__ */ jsxs2(
2593
- Component,
2594
- {
2595
- href: back.href,
2596
- type: Component === "button" ? "button" : void 0,
2597
- "aria-label": accessibleName,
2598
- "data-jumpy-slot": "app-shell-back",
2599
- "data-variant": variant,
2600
- onClick: handleClick,
2601
- style: {
2602
- boxSizing: "border-box",
2603
- alignItems: "center",
2604
- gap: spacingValue(theme, JumpySpacing.small4XS),
2605
- maxWidth: "100%",
2606
- minWidth: 0,
2607
- minHeight: variant === "inline" ? void 0 : sizeValue(theme, JumpySize.largeXL),
2608
- margin: variant === "inline" ? `0 0 ${spacingValue(theme, JumpySpacing.small3XS)}px ${-spacingValue(theme, JumpySpacing.small3XS)}px` : 0,
2609
- padding: variant === "inline" ? 0 : `0 ${spacingValue(theme, JumpySpacing.smallXS)}px 0 0`,
2610
- border: 0,
2611
- borderRadius: radiusValue(theme, JumpyRadius.small),
2612
- background: "transparent",
2613
- color: "var(--jumpy-interactive-active-regular-default-text)",
2614
- textDecoration: "none",
2615
- cursor: "pointer",
2616
- fontFamily: theme.fontFamily.familyBase,
2617
- fontSize: textStyle.fontSize,
2618
- lineHeight: textStyle.lineHeight,
2619
- fontWeight: textStyle.fontWeight
2620
- },
2621
- children: [
2622
- /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "back", size: variant === "inline" ? 18 : 24 }),
2623
- /* @__PURE__ */ jsx2("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: back.label })
2624
- ]
2625
- }
2626
- );
2627
- }
2628
- function JumpyAppShellActions({ actions, variant, ariaLabels }) {
2629
- const theme = useJumpyTheme();
2630
- if (!actions.length) return null;
2631
- const gap = spacingValue(theme, JumpySpacing.smallXS);
2632
- if (variant === "full") {
2633
- return /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "app-shell-actions", "data-variant": "full", style: { alignItems: "center", gap, flexWrap: "wrap", justifyContent: "flex-end" }, children: actions.map((action, index) => /* @__PURE__ */ jsx2(JumpButton, { as: action.href ? "a" : void 0, href: action.href, variant: action.variant || "secondary", leadingIcon: action.icon, disabled: action.disabled, onClick: action.onClick, children: action.label }, action.id ?? index)) });
2634
- }
2635
- const visible = actions.length > 2 ? actions.slice(0, 1) : actions;
2636
- const overflow = actions.length > 2 ? actions.slice(1) : [];
2637
- return /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "app-shell-actions", "data-variant": "compact", style: { alignItems: "center", gap }, children: [
2638
- visible.map((action, index) => /* @__PURE__ */ jsx2(JumpIconButton, { as: action.href ? "a" : void 0, href: action.href, variant: action.variant === "primary" ? "primary" : "borderless", "aria-label": action.label, disabled: action.disabled, onClick: action.onClick, children: action.icon }, action.id ?? index)),
2639
- overflow.length ? /* @__PURE__ */ jsx2(
2640
- JumpMenu,
2641
- {
2642
- placement: "bottom-end",
2643
- triggerProps: { role: "button", "aria-label": ariaLabels.moreActions || "More actions", "data-jumpy-slot": "app-shell-actions-more" },
2644
- trigger: /* @__PURE__ */ jsx2(JumpIconButton, { as: "span", variant: "borderless", tabIndex: -1, "aria-hidden": "true", children: /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "more" }) }),
2645
- children: overflow.map((action, index) => /* @__PURE__ */ jsx2(JumpMenuItem, { as: action.href ? "a" : void 0, href: action.href, leading: action.icon, disabled: action.disabled, onClick: action.onClick, children: action.label }, action.id ?? index))
2646
- }
2647
- ) : null
2648
- ] });
2649
- }
2650
- function JumpAppShell({ brand, navigation, sidebarFooter, topBar, tabBar, hideTabBar = false, breakpoint = 1024, sidebarWidth = 256, navigationOpen, defaultNavigationOpen = false, onNavigationOpenChange, ariaLabels = {}, children, className = "", ...props }) {
2651
- const theme = useJumpyTheme();
2652
- const shellId = useId();
2653
- const [internalOpen, setInternalOpen] = useState2(defaultNavigationOpen);
2654
- const isOpen = Boolean(navigation) && (navigationOpen ?? internalOpen);
2655
- const swipeStart = useRef2(null);
2656
- const rootRef = useRef2(null);
2657
- const [drawerMounted, setDrawerMounted] = useState2(isOpen);
2658
- const showDrawer = isOpen || drawerMounted;
2659
- const closing = drawerMounted && !isOpen;
2660
- const wasShown = useRef2(showDrawer);
2661
- const { desktop: topBarDesktop = false, leading, largeTitle = false, actions = [], back, ...topBarProps } = topBar || {};
2662
- const showLargeTitle = largeTitle && Boolean(topBarProps.title);
2663
- const hasBack = Boolean(back?.label);
2664
- const topBarOnDesktop = topBarDesktop || !showLargeTitle && (actions.length > 0 || hasBack);
2665
- const largeTitleRef = useRef2(null);
2666
- const [largeTitleCovered, setLargeTitleCovered] = useState2(false);
2667
- const resolvedLeading = hasBack ? /* @__PURE__ */ jsx2(JumpyAppShellBack, { back, variant: "bar", ariaLabels }) : leading ?? (navigation ? "menu" : void 0);
2668
- useEffect2(() => {
2669
- const href = window.location.href;
2670
- if (jumpyLocations.current === href) return;
2671
- jumpyLocations.previous = jumpyLocations.current;
2672
- jumpyLocations.current = href;
2673
- });
2674
- const shell = `[data-jumpy-app-shell="${shellId}"]`;
2675
- const width = typeof sidebarWidth === "number" ? `${sidebarWidth}px` : sidebarWidth;
2676
- function setOpen(next) {
2677
- if (navigationOpen === void 0) setInternalOpen(next);
2678
- onNavigationOpenChange?.(next);
2679
- }
2680
- useEffect2(() => {
2681
- if (!isOpen) return void 0;
2682
- const desktop = window.matchMedia(`(min-width: ${breakpoint}px)`);
2683
- const closeOnDesktop = () => {
2684
- if (desktop.matches) setOpen(false);
2685
- };
2686
- closeOnDesktop();
2687
- desktop.addEventListener("change", closeOnDesktop);
2688
- return () => desktop.removeEventListener("change", closeOnDesktop);
2689
- });
2690
- useEffect2(() => {
2691
- if (isOpen) {
2692
- setDrawerMounted(true);
2693
- return void 0;
2694
- }
2695
- if (!drawerMounted) return void 0;
2696
- if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
2697
- setDrawerMounted(false);
2698
- return void 0;
2699
- }
2700
- const timer = window.setTimeout(() => setDrawerMounted(false), animationDurationValue(theme, JumpyAnimationDuration.small) * 1e3 + 100);
2701
- return () => window.clearTimeout(timer);
2702
- }, [isOpen, drawerMounted]);
2703
- useEffect2(() => {
2704
- const closed = wasShown.current && !showDrawer;
2705
- wasShown.current = showDrawer;
2706
- if (!closed) return void 0;
2707
- const frame = window.requestAnimationFrame(() => {
2708
- if (document.activeElement && document.activeElement !== document.body) return;
2709
- rootRef.current?.querySelector(':scope > [data-jumpy-slot="app-shell-body"] > [data-jumpy-component="top-bar"] > [data-leading="menu"] button')?.focus();
2710
- });
2711
- return () => window.cancelAnimationFrame(frame);
2712
- }, [showDrawer]);
2713
- useEffect2(() => {
2714
- const element = largeTitleRef.current;
2715
- if (!showLargeTitle || !element || typeof IntersectionObserver === "undefined") return void 0;
2716
- const bar = rootRef.current?.querySelector(':scope > [data-jumpy-slot="app-shell-body"] > [data-jumpy-component="top-bar"]');
2717
- const barHeight = bar && bar.getBoundingClientRect().height > 0 ? bar.getBoundingClientRect().height : 0;
2718
- const observer = new IntersectionObserver(([entry]) => setLargeTitleCovered(!entry.isIntersecting), { rootMargin: `-${Math.round(barHeight)}px 0px 0px 0px` });
2719
- observer.observe(element);
2720
- return () => observer.disconnect();
2721
- }, [showLargeTitle, topBarProps.title]);
2722
- function closeOnNavigate(event) {
2723
- const item = event.target.closest?.('a[href], [data-jumpy-component="side-menu-item"]');
2724
- if (!item || item.hasAttribute("aria-expanded") || item.getAttribute("aria-disabled") === "true" || item.disabled) return;
2725
- setOpen(false);
2726
- }
2727
- const shellStyles = `
2728
- ${shell} { grid-template-columns: minmax(0, 1fr); }
2729
- ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: none; }
2730
- ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: inline-flex; }
2731
- ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: none; }
2732
- ${shell} [data-jumpy-slot='app-shell-back'][data-variant='inline'] { display: none; }
2733
- @media (min-width: ${breakpoint}px) {
2734
- ${shell} { grid-template-columns: ${width} minmax(0, 1fr); }
2735
- ${shell} > [data-jumpy-slot='app-shell-sidebar'] { display: flex; }
2736
- ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='compact'] { display: none; }
2737
- ${shell} [data-jumpy-slot='app-shell-actions'][data-variant='full'] { display: inline-flex; }
2738
- ${shell} [data-jumpy-slot='app-shell-back'][data-variant='inline'] { display: flex; width: fit-content; }
2739
- ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='tab-bar'] { display: none; }
2740
- ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'][data-mobile-only='true'] { display: none; }
2741
- ${shell} > [data-jumpy-slot='app-shell-body'] > [data-jumpy-component='top-bar'] > [data-leading='menu'] { display: none; }
2742
- }`;
2743
- const topBarNode = /* @__PURE__ */ jsx2(
2744
- JumpTopBar,
2745
- {
2746
- ...topBarProps,
2747
- "data-mobile-only": topBarOnDesktop ? "false" : "true",
2748
- leading: resolvedLeading,
2749
- menuOpen: isOpen,
2750
- onMenu: () => setOpen(true),
2751
- title: topBarProps.title ?? brand,
2752
- subtitle: showLargeTitle ? void 0 : topBarProps.subtitle,
2753
- titleAlign: showLargeTitle || hasBack ? "center" : topBarProps.titleAlign,
2754
- titleHidden: showLargeTitle ? !largeTitleCovered : topBarProps.titleHidden,
2755
- "data-large-title": showLargeTitle ? "true" : void 0,
2756
- trailing: actions.length || topBarProps.trailing ? /* @__PURE__ */ jsxs2(Fragment, { children: [
2757
- /* @__PURE__ */ jsx2(JumpyAppShellActions, { actions, variant: "compact", ariaLabels }),
2758
- showLargeTitle ? null : /* @__PURE__ */ jsx2(JumpyAppShellActions, { actions, variant: "full", ariaLabels }),
2759
- topBarProps.trailing
2760
- ] }) : void 0,
2761
- ariaLabels: { menu: ariaLabels.openNavigation, back: ariaLabels.back, ...topBarProps.ariaLabels || {} }
2762
- }
2763
- );
2764
- return /* @__PURE__ */ jsxs2(
2765
- "div",
2766
- {
2767
- ...props,
2768
- ref: rootRef,
2769
- "data-jumpy-component": "app-shell",
2770
- "data-jumpy-app-shell": shellId,
2771
- "data-navigation-open": isOpen ? "true" : "false",
2772
- className: cn(className),
2773
- style: {
2774
- display: "grid",
2775
- minHeight: "100dvh",
2776
- backgroundColor: "var(--jumpy-color-surface-canvas)",
2777
- color: "var(--jumpy-color-text-primary)",
2778
- fontFamily: theme.fontFamily.familyBase,
2779
- ...props.style || {}
2780
- },
2781
- children: [
2782
- /* @__PURE__ */ jsx2("style", { children: shellStyles }),
2783
- /* @__PURE__ */ jsxs2(
2784
- "aside",
2785
- {
2786
- "data-jumpy-slot": "app-shell-sidebar",
2787
- "aria-label": ariaLabels.navigation || "Navigation",
2788
- style: {
2789
- boxSizing: "border-box",
2790
- position: "sticky",
2791
- top: 0,
2792
- height: "100dvh",
2793
- flexDirection: "column",
2794
- overflow: "hidden",
2795
- borderRight: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`,
2796
- backgroundColor: "var(--jumpy-color-surface-card)"
2797
- },
2798
- children: [
2799
- brand ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-brand", style: { flex: "0 0 auto", padding: spacingValue(theme, JumpySpacing.large2XL), borderBottom: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: brand }) : null,
2800
- navigation ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-navigation", style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", overscrollBehavior: "contain", padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }) : null,
2801
- sidebarFooter ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-sidebar-footer", style: { flex: "0 0 auto", padding: spacingValue(theme, JumpySpacing.large), borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)` }, children: sidebarFooter }) : null
2802
- ]
2803
- }
2804
- ),
2805
- /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "app-shell-body", style: { display: "flex", flexDirection: "column", minWidth: 0, minHeight: "100dvh" }, children: [
2806
- topBar !== null ? topBarNode : null,
2807
- /* @__PURE__ */ jsxs2("main", { "data-jumpy-slot": "app-shell-content", style: { flex: 1, minWidth: 0 }, children: [
2808
- showLargeTitle ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-large-title", style: { padding: `${spacingValue(theme, JumpySpacing.smallXS)}px calc(var(--jumpy-app-shell-gutter, ${spacingValue(theme, JumpySpacing.large)}px) + env(safe-area-inset-right, 0px)) 0 calc(var(--jumpy-app-shell-gutter, ${spacingValue(theme, JumpySpacing.large)}px) + env(safe-area-inset-left, 0px))` }, children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: spacingValue(theme, JumpySpacing.large), flexWrap: "wrap" }, children: [
2809
- /* @__PURE__ */ jsxs2("div", { style: { minWidth: 0 }, children: [
2810
- hasBack ? /* @__PURE__ */ jsx2(JumpyAppShellBack, { back, variant: "inline", ariaLabels }) : null,
2811
- /* @__PURE__ */ jsx2("h1", { ref: largeTitleRef, style: { margin: 0, ...textStyleValue(theme, JumpyTextStyle.displaySmallEmphasized), overflowWrap: "anywhere" }, children: topBarProps.title }),
2812
- topBarProps.subtitle ? /* @__PURE__ */ jsx2("p", { style: { margin: `${spacingValue(theme, JumpySpacing.small4XS)}px 0 0`, color: "var(--jumpy-color-text-secondary)", ...textStyleValue(theme, JumpyTextStyle.bodyMedium) }, children: topBarProps.subtitle }) : null
2813
- ] }),
2814
- /* @__PURE__ */ jsx2(JumpyAppShellActions, { actions, variant: "full", ariaLabels })
2815
- ] }) }) : null,
2816
- children
2817
- ] }),
2818
- tabBar && !hideTabBar ? tabBar : null
2819
- ] }),
2820
- showDrawer ? /* @__PURE__ */ jsx2(JumpyModalLayer, { onVisibleChange: setOpen, "data-jumpy-component": "app-shell-drawer-overlay", "data-state": closing ? "closing" : "open", style: { position: "fixed", inset: 0, zIndex: 60, backgroundColor: "var(--jumpy-color-surface-overlay)" }, children: /* @__PURE__ */ jsxs2(
2821
- "div",
2822
- {
2823
- role: "dialog",
2824
- "aria-modal": "true",
2825
- "aria-label": ariaLabels.navigation || "Navigation",
2826
- "data-jumpy-component": "app-shell-drawer",
2827
- "data-state": closing ? "closing" : "open",
2828
- onAnimationEnd: (event) => {
2829
- if (closing && event.target === event.currentTarget) setDrawerMounted(false);
2830
- },
2831
- onClick: closeOnNavigate,
2832
- onTouchStart: (event) => {
2833
- const touch = event.touches[0];
2834
- swipeStart.current = touch ? { x: touch.clientX, y: touch.clientY } : null;
2835
- },
2836
- onTouchEnd: (event) => {
2837
- const start = swipeStart.current;
2838
- const touch = event.changedTouches[0];
2839
- swipeStart.current = null;
2840
- if (!start || !touch) return;
2841
- const dx = touch.clientX - start.x;
2842
- const dy = touch.clientY - start.y;
2843
- if (dx < -spacingValue(theme, JumpySpacing.large4XL) && Math.abs(dx) > Math.abs(dy) * 1.5) setOpen(false);
2844
- },
2845
- style: {
2846
- boxSizing: "border-box",
2847
- position: "fixed",
2848
- inset: 0,
2849
- display: "flex",
2850
- flexDirection: "column",
2851
- width: "100vw",
2852
- height: "100dvh",
2853
- overflow: "hidden",
2854
- outline: "none",
2855
- backgroundColor: "var(--jumpy-color-surface-card)",
2856
- color: "var(--jumpy-color-text-primary)",
2857
- fontFamily: theme.fontFamily.familyBase
2858
- },
2859
- children: [
2860
- /* @__PURE__ */ jsx2(
2861
- JumpTopBar,
2862
- {
2863
- "data-jumpy-slot": "app-shell-drawer-header",
2864
- sticky: false,
2865
- style: { flex: "0 0 auto" },
2866
- leading: /* @__PURE__ */ jsx2(JumpIconButton, { variant: "borderless", "aria-label": ariaLabels.closeNavigation || "Close navigation", onClick: () => setOpen(false), children: /* @__PURE__ */ jsx2(JumpyNavigationIcon, { type: "close" }) }),
2867
- title: brand
2868
- }
2869
- ),
2870
- /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-drawer-navigation", style: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", overscrollBehavior: "contain", padding: spacingValue(theme, JumpySpacing.large) }, children: navigation }),
2871
- sidebarFooter ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "app-shell-drawer-footer", style: { flex: "0 0 auto", padding: `${spacingValue(theme, JumpySpacing.large)}px calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-right, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-bottom, 0px)) calc(${spacingValue(theme, JumpySpacing.large)}px + env(safe-area-inset-left, 0px))`, borderTop: `${borderWidthValue(theme, JumpyBorderWidth.small)}px solid var(--jumpy-color-border-subtle)`, backgroundColor: "var(--jumpy-color-surface-card)" }, children: sidebarFooter }) : null
2872
- ]
2873
- }
2874
- ) }) : null
2875
- ]
2876
- }
2877
- );
2878
- }
2879
- function JumpSectionHeader({ as, variant = "regular", description, className = "", children, ...props }) {
2880
- const theme = useJumpyTheme();
2881
- const Component = as || "li";
2882
- return /* @__PURE__ */ jsxs2(Component, { ...props, "data-jumpy-component": "section-header", className: cn(className), style: { display: "grid", gap: variant === "prominent" ? spacingValue(theme, JumpySpacing.small3XS) : 0, padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large)}px`, color: theme.color.textSecondary, ...props.style || {} }, children: [
2883
- /* @__PURE__ */ jsx2(JumpTypography, { variant: variant === "prominent" ? "bodyMediumEmphasized" : "labelSmallEmphasized", color: "secondary", children }),
2884
- variant === "prominent" && description ? /* @__PURE__ */ jsx2(JumpTypography, { variant: "bodySmall", color: "secondary", children: description }) : null
2885
- ] });
2886
- }
2887
- function JumpSectionFooter({ as, className = "", children, ...props }) {
2888
- const theme = useJumpyTheme();
2889
- const Component = as || "li";
2890
- return /* @__PURE__ */ jsx2(Component, { ...props, "data-jumpy-component": "section-footer", className: cn(className), style: { padding: `${spacingValue(theme, JumpySpacing.smallXS)}px ${spacingValue(theme, JumpySpacing.large)}px`, color: theme.color.textSecondary, ...props.style || {} }, children: /* @__PURE__ */ jsx2(JumpTypography, { variant: "bodySmall", color: "secondary", children }) });
2891
- }
2892
- function JumpTag({ size: size2 = "small", status = "regular", disabled = false, className = "", children, ...props }) {
2893
- const theme = useJumpyTheme();
2894
- const labelStyle = textStyleValue(theme, size2 === "medium" ? JumpyTextStyle.labelMediumEmphasized : JumpyTextStyle.labelSmallEmphasized);
2895
- return /* @__PURE__ */ jsx2(
2896
- "span",
2897
- {
2898
- ...props,
2899
- "data-jumpy-component": "tag",
2900
- "data-size": size2,
2901
- "data-status": status,
2902
- "data-disabled": disabled ? "true" : "false",
2903
- className: cn(className),
2904
- style: {
2905
- display: "inline-flex",
2906
- alignItems: "center",
2907
- width: "fit-content",
2908
- minHeight: size2 === "medium" ? sizeValue(theme, JumpySize.small) : sizeValue(theme, JumpySize.small2XS),
2909
- padding: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px`,
2910
- borderRadius: radiusValue(theme, JumpyRadius.full),
2911
- borderWidth: borderWidthValue(theme, JumpyBorderWidth.small),
2912
- borderStyle: "solid",
2913
- opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
2914
- fontFamily: theme.fontFamily.familyBase,
2915
- fontSize: labelStyle.fontSize,
2916
- lineHeight: labelStyle.lineHeight,
2917
- fontWeight: labelStyle.fontWeight,
2918
- letterSpacing: labelStyle.letterSpacing,
2919
- ...getTagStatusStyles(theme, status),
2920
- ...props.style || {}
2921
- },
2922
- children
2923
- }
2924
- );
2925
- }
2926
- function JumpChip({ selected = false, disabled = false, className = "", children, ...props }) {
2927
- const theme = useJumpyTheme();
2928
- const bodyMediumEmphasized = textStyleValue(theme, JumpyTextStyle.bodyMediumEmphasized);
2929
- return /* @__PURE__ */ jsx2(
2930
- "button",
2931
- {
2932
- ...props,
2933
- type: props.type || "button",
2934
- "data-jumpy-component": "chip",
2935
- "data-selected": selected ? "true" : "false",
2936
- disabled,
2937
- className: cn(className),
2938
- style: {
2939
- display: "inline-flex",
2940
- alignItems: "center",
2941
- justifyContent: "center",
2942
- minHeight: sizeValue(theme, JumpySize.large),
2943
- padding: `${spacingValue(theme, JumpySpacing.small2XS)}px ${spacingValue(theme, JumpySpacing.large)}px`,
2944
- borderRadius: radiusValue(theme, JumpyRadius.full),
2945
- borderWidth: borderWidthValue(theme, JumpyBorderWidth.small),
2946
- borderStyle: "solid",
2947
- borderColor: selected ? theme.color.surfacePrimary : theme.color.borderStrong,
2948
- backgroundColor: selected ? theme.color.infoSurface : theme.color.surfaceField,
2949
- color: selected ? theme.color.surfacePrimary : theme.color.textPrimary,
2950
- opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
2951
- fontFamily: theme.fontFamily.familyBase,
2952
- fontSize: bodyMediumEmphasized.fontSize,
2953
- lineHeight: bodyMediumEmphasized.lineHeight,
2954
- fontWeight: bodyMediumEmphasized.fontWeight,
2955
- letterSpacing: bodyMediumEmphasized.letterSpacing,
2956
- cursor: disabled ? "not-allowed" : "pointer",
2957
- ...props.style || {}
2958
- },
2959
- children
2960
- }
2961
- );
2962
- }
2963
- function JumpCheckbox({ label, children, checked, defaultChecked = false, indeterminate = false, onCheckedChange, onIndeterminateChange, labelPlacement = "end", innerClasses = {}, className = "", disabled = false, readOnly = false, style, ...props }) {
2964
- const theme = useJumpyTheme();
2965
- const labelLarge = textStyleValue(theme, JumpyTextStyle.labelLarge);
2966
- const inputRef = useRef2(null);
2967
- const [uncontrolledChecked, setUncontrolledChecked] = useState2(defaultChecked);
2968
- const isControlled = checked !== void 0;
2969
- const isChecked = Boolean(isControlled ? checked : uncontrolledChecked);
2970
- const isReadonly = readOnly && !disabled;
2971
- const activeDefault = theme.selectors.active.default;
2972
- const activeDisabled = theme.selectors.active.disabled;
2973
- const activeReadOnly = theme.selectors.active.readOnly;
2974
- const inactiveDefault = theme.selectors.inactive.default;
2975
- const inactiveDisabled = theme.selectors.inactive.disabled;
2976
- const inactiveReadOnly = theme.selectors.inactive.readOnly;
2977
- const labelContent = children ?? label;
2978
- useEffect2(() => {
2979
- if (inputRef.current) inputRef.current.indeterminate = indeterminate;
2980
- }, [indeterminate]);
2981
- const stateTokens = isChecked || indeterminate ? disabled ? activeDisabled : isReadonly ? activeReadOnly : activeDefault : disabled ? inactiveDisabled : isReadonly ? inactiveReadOnly : inactiveDefault;
2982
- function handleChange(event) {
2983
- if (isReadonly) {
2984
- if (inputRef.current) inputRef.current.indeterminate = indeterminate;
2985
- return;
2986
- }
2987
- if (indeterminate) onIndeterminateChange?.(false);
2988
- if (!isControlled) setUncontrolledChecked(event.target.checked);
2989
- onCheckedChange?.(event.target.checked);
2990
- props.onChange?.(event);
2991
- }
2992
- return /* @__PURE__ */ jsxs2(
2993
- "label",
2994
- {
2995
- "data-jumpy-component": "checkbox",
2996
- "data-checked": isChecked || indeterminate ? "true" : "false",
2997
- "data-disabled": disabled ? "true" : void 0,
2998
- "data-readonly": isReadonly ? "true" : void 0,
2999
- "data-label-placement": labelPlacement,
3000
- className: cn(className),
3001
- style: {
3002
- "--jumpy-cmp-checkbox-container-size": `${sizeValue(theme, JumpySize.large)}px`,
3003
- "--jumpy-cmp-checkbox-container-radius": `${radiusValue(theme, JumpyRadius.medium)}px`,
3004
- "--jumpy-cmp-checkbox-input-size": `${sizeValue(theme, JumpySize.smallXS)}px`,
3005
- "--jumpy-cmp-checkbox-input-radius": `${radiusValue(theme, JumpyRadius.small2XS)}px`,
3006
- "--jumpy-cmp-checkbox-label-gap-size": `${spacingValue(theme, JumpySpacing.smallXS)}px`,
3007
- "--jumpy-cmp-checkbox-label-color": stateTokens.text,
3008
- "--jumpy-cmp-checkbox-container-color": stateTokens.surface || activeDefault.surface,
3009
- "--jumpy-cmp-checkbox-container-bg": "transparent",
3010
- "--jumpy-cmp-checkbox-icon-fill": stateTokens.icon || activeDefault.icon || theme.color.textOnPrimary,
3011
- "--jumpy-cmp-checkbox-input-border-color": stateTokens.border || inactiveDefault.border,
3012
- "--jumpy-cmp-checkbox-hover-ring-color": "var(--jumpy-selectors-inactive-hover-hover-ring, var(--jumpy-color-feedback-info-surface))",
3013
- "--jumpy-cmp-checkbox-hover-border-color": "var(--jumpy-selectors-inactive-hover-border, var(--jumpy-color-focus-border))",
3014
- "--jumpy-cmp-checkbox-hover-surface-color": "var(--jumpy-selectors-active-hover-surface, var(--jumpy-color-surface-primary))",
3015
- "--jumpy-cmp-checkbox-focus-border-color": "var(--jumpy-selectors-inactive-focus-border, var(--jumpy-color-focus-border))",
3016
- "--jumpy-cmp-checkbox-focus-ring-color": "var(--jumpy-selectors-inactive-focus-focus-ring, var(--jumpy-color-focus-ring))",
3017
- "--jumpy-cmp-checkbox-active-focus-ring-color": "var(--jumpy-selectors-active-focus-focus-ring, var(--jumpy-color-focus-ring))",
3018
- fontFamily: theme.fontFamily.familyBase,
3019
- fontSize: labelLarge.fontSize,
3020
- lineHeight: labelLarge.lineHeight,
3021
- fontWeight: labelLarge.fontWeight,
3022
- letterSpacing: labelLarge.letterSpacing,
3023
- ...style || {}
3024
- },
3025
- children: [
3026
- /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "checkbox-container", className: innerClasses.container?.className, style: innerClasses.container?.style, children: [
3027
- /* @__PURE__ */ jsx2(
3028
- "input",
3029
- {
3030
- ...props,
3031
- ref: (node) => {
3032
- inputRef.current = node;
3033
- if (typeof props.ref === "function") props.ref(node);
3034
- else if (props.ref) props.ref.current = node;
3035
- },
3036
- type: "checkbox",
3037
- "data-jumpy-slot": "checkbox-input",
3038
- className: innerClasses.input?.className,
3039
- checked: isChecked,
3040
- disabled,
3041
- readOnly: isReadonly,
3042
- "aria-readonly": isReadonly || void 0,
3043
- onChange: handleChange
3044
- }
3045
- ),
3046
- isChecked || indeterminate ? /* @__PURE__ */ jsx2("svg", { "data-jumpy-slot": "checkbox-icon", className: innerClasses.icons?.className, style: innerClasses.icons?.style, viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: indeterminate ? /* @__PURE__ */ jsx2("path", { d: "M6 11h12v2H6z" }) : /* @__PURE__ */ jsx2("path", { d: "M9.4 16.6 4.8 12l1.4-1.4 3.2 3.2 8.4-8.4L19.2 6.8z" }) }) : null
3047
- ] }),
3048
- labelContent ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "checkbox-label", children: labelContent }) : null
3049
- ]
3050
- }
3051
- );
3052
- }
3053
- function JumpSwitch({ label, children, checked, defaultChecked = false, onCheckedChange, labelPlacement = "end", innerClasses = {}, className = "", disabled = false, readOnly = false, style, ...props }) {
3054
- const theme = useJumpyTheme();
3055
- const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
3056
- const [uncontrolledChecked, setUncontrolledChecked] = useState2(Boolean(defaultChecked));
3057
- const isControlled = checked !== void 0;
3058
- const isChecked = Boolean(isControlled ? checked : uncontrolledChecked);
3059
- const isReadonly = readOnly && !disabled;
3060
- const selectorState = disabled ? "disabled" : isReadonly ? "readOnly" : "default";
3061
- const selectorTokens = isChecked ? theme.selectors.active[selectorState] : theme.selectors.inactive[selectorState];
3062
- const labelContent = children ?? label;
3063
- return /* @__PURE__ */ jsxs2(
3064
- "label",
3065
- {
3066
- "data-jumpy-component": "switch",
3067
- "data-checked": isChecked ? "true" : "false",
3068
- "data-disabled": disabled ? "true" : void 0,
3069
- "data-readonly": isReadonly ? "true" : void 0,
3070
- "data-label-placement": labelPlacement,
3071
- className: cn(className),
3072
- style: {
3073
- "--jumpy-cmp-switch-thumb-size": `${sizeValue(theme, JumpySize.small2XS)}px`,
3074
- "--jumpy-cmp-switch-thumb-size-checked": `${sizeValue(theme, JumpySize.small)}px`,
3075
- "--jumpy-cmp-switch-track-width": `${sizeValue(theme, JumpySize.large2XL)}px`,
3076
- "--jumpy-cmp-switch-track-bg": selectorTokens.surface,
3077
- "--jumpy-cmp-switch-track-border": selectorTokens.border,
3078
- "--jumpy-cmp-switch-thumb-bg": selectorTokens.handle || selectorTokens.icon || theme.color.textOnPrimary,
3079
- "--jumpy-cmp-switch-icon-fill": selectorTokens.surface,
3080
- "--jumpy-cmp-switch-label-color": selectorTokens.text,
3081
- "--jumpy-cmp-switch-hover-track-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-hover-surface)`,
3082
- "--jumpy-cmp-switch-hover-track-border": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-hover-border)`,
3083
- "--jumpy-cmp-switch-hover-thumb-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-hover-handle)`,
3084
- "--jumpy-cmp-switch-focus-ring": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-focus-ring)`,
3085
- "--jumpy-cmp-switch-focus-track-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-surface)`,
3086
- "--jumpy-cmp-switch-focus-track-border": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-border)`,
3087
- "--jumpy-cmp-switch-focus-thumb-bg": `var(--jumpy-selectors-${isChecked ? "active" : "inactive"}-focus-handle)`,
3088
- fontFamily: theme.fontFamily.familyBase,
3089
- fontSize: bodyMedium.fontSize,
3090
- lineHeight: bodyMedium.lineHeight,
3091
- fontWeight: bodyMedium.fontWeight,
3092
- ...style || {}
3093
- },
3094
- children: [
3095
- /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "switch-container", className: innerClasses.container?.className, style: innerClasses.container?.style, children: [
3096
- /* @__PURE__ */ jsx2(
3097
- "input",
3098
- {
3099
- ...props,
3100
- type: "checkbox",
3101
- role: "switch",
3102
- checked: isChecked,
3103
- disabled,
3104
- readOnly: isReadonly,
3105
- "aria-checked": isChecked,
3106
- "aria-readonly": isReadonly || void 0,
3107
- "data-jumpy-slot": "switch-input",
3108
- className: innerClasses.input?.className,
3109
- onChange: (event) => {
3110
- if (isReadonly) return;
3111
- if (!isControlled) setUncontrolledChecked(event.target.checked);
3112
- onCheckedChange?.(event.target.checked);
3113
- props.onChange?.(event);
3114
- }
3115
- }
3116
- ),
3117
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "switch-track", className: innerClasses.track?.className, style: innerClasses.track?.style, children: /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "switch-thumb", className: innerClasses.thumb?.className, style: innerClasses.thumb?.style, children: /* @__PURE__ */ jsx2("svg", { "data-jumpy-slot": "switch-icon", className: innerClasses.icon?.className, style: innerClasses.icon?.style, viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx2("path", { d: "M6 10.25 8.6 12.85 14 7.45", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }) })
3118
- ] }),
3119
- labelContent ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "switch-label", children: labelContent }) : null
3120
- ]
3121
- }
3122
- );
3123
- }
3124
- function JumpRadioGroup({ name, value, defaultValue, onValueChange, options, className = "", disabled = false, readOnly = false, direction = "vertical", innerClasses = {}, ...props }) {
3125
- const theme = useJumpyTheme();
3126
- const labelLarge = textStyleValue(theme, JumpyTextStyle.labelLarge);
3127
- const isReadonly = readOnly && !disabled;
3128
- const generatedName = useId();
3129
- const validName = name || generatedName;
3130
- const [uncontrolledValue, setUncontrolledValue] = useState2(defaultValue ?? "");
3131
- const isControlled = value !== void 0;
3132
- const selectedValue = isControlled ? value : uncontrolledValue;
3133
- return /* @__PURE__ */ jsx2("div", { ...props, role: "radiogroup", "aria-readonly": isReadonly || void 0, "aria-orientation": direction === "horizontal" ? "horizontal" : "vertical", "data-jumpy-component": "radio-group", "data-direction": direction, className: cn(className), style: { display: "flex", flexDirection: direction === "horizontal" ? "row" : "column", gap: spacingValue(theme, JumpySpacing.medium), ...props.style || {} }, onChange: (event) => {
3134
- if (isReadonly || event.defaultPrevented) return;
3135
- const target = event.target;
3136
- if (target?.type === "radio" && target.name === validName) {
3137
- if (!isControlled) setUncontrolledValue(target.value);
3138
- onValueChange?.(target.value);
3139
- }
3140
- props.onChange?.(event);
3141
- }, children: options.map((option) => {
3142
- const optionDisabled = disabled || option.disabled;
3143
- const optionSelected = selectedValue === option.value;
3144
- const selectorState = optionDisabled ? "disabled" : isReadonly ? "readOnly" : "default";
3145
- const selectorTokens = optionSelected ? theme.selectors.active[selectorState] : theme.selectors.inactive[selectorState];
3146
- return /* @__PURE__ */ jsxs2(
3147
- "label",
3148
- {
3149
- "data-jumpy-component": "radio",
3150
- "data-checked": optionSelected ? "true" : "false",
3151
- "data-disabled": optionDisabled ? "true" : void 0,
3152
- "data-readonly": isReadonly ? "true" : void 0,
3153
- style: {
3154
- "--jumpy-cmp-radio-label-color": selectorTokens.text,
3155
- "--jumpy-cmp-radio-input-border-color": selectorTokens.border,
3156
- "--jumpy-cmp-radio-dot-color": optionSelected ? selectorTokens.surface : "transparent",
3157
- "--jumpy-cmp-radio-container-bg": "transparent",
3158
- "--jumpy-cmp-radio-hover-ring-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-hover-hover-ring)`,
3159
- "--jumpy-cmp-radio-hover-border-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-hover-border)`,
3160
- "--jumpy-cmp-radio-hover-dot-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-hover-surface)`,
3161
- "--jumpy-cmp-radio-focus-ring-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-focus-focus-ring)`,
3162
- "--jumpy-cmp-radio-focus-border-color": `var(--jumpy-selectors-${optionSelected ? "active" : "inactive"}-focus-border)`,
3163
- fontFamily: theme.fontFamily.familyBase,
3164
- fontSize: labelLarge.fontSize,
3165
- lineHeight: labelLarge.lineHeight,
3166
- fontWeight: labelLarge.fontWeight,
3167
- letterSpacing: labelLarge.letterSpacing
3168
- },
3169
- children: [
3170
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "radio-container", className: innerClasses.container?.className, style: innerClasses.container?.style, children: /* @__PURE__ */ jsx2("input", { type: "radio", name: validName, value: option.value, checked: optionSelected, disabled: optionDisabled, "aria-readonly": isReadonly || void 0, readOnly: isReadonly || void 0, "data-jumpy-slot": "radio-input", className: innerClasses.input?.className, style: innerClasses.input?.style, onChange: () => {
3171
- } }) }),
3172
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "radio-label", className: innerClasses.label?.className, style: innerClasses.label?.style, children: option.label })
3173
- ]
3174
- },
3175
- option.value
3176
- );
3177
- }) });
3178
- }
3179
- function toDateValue(value) {
3180
- if (value === null || value === void 0 || value === "") return null;
3181
- const date = value instanceof Date ? value : new Date(value);
3182
- return Number.isNaN(date.getTime()) ? null : date;
3183
- }
3184
- function sameDay(a, b) {
3185
- if (!a || !b) return false;
3186
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
3187
- }
3188
- function formatDateValue(value, format = "MM/dd/yyyy") {
3189
- const date = toDateValue(value);
3190
- if (!date) return "";
3191
- const parts = {
3192
- dd: String(date.getDate()).padStart(2, "0"),
3193
- MM: String(date.getMonth() + 1).padStart(2, "0"),
3194
- yyyy: String(date.getFullYear())
3195
- };
3196
- return format.replace(/yyyy|MM|dd/g, (token) => parts[token]);
3197
- }
3198
- function formatTimeValue(value, format = "HH:mm") {
3199
- const date = toDateValue(value);
3200
- if (!date) return "";
3201
- const hours24 = date.getHours();
3202
- const minutes = String(date.getMinutes()).padStart(2, "0");
3203
- const period = hours24 >= 12 ? "PM" : "AM";
3204
- const hours12 = hours24 % 12 || 12;
3205
- return format.includes("aa") ? format.replace("hh", String(hours12).padStart(2, "0")).replace("mm", minutes).replace("aa", period) : format.replace("HH", String(hours24).padStart(2, "0")).replace("mm", minutes);
3206
- }
3207
- function parseDateValue(text, format = "MM/dd/yyyy") {
3208
- const value = text.trim();
3209
- if (!value) return null;
3210
- const activeFormat = Array.isArray(format) ? format[0] : format;
3211
- const escaped = activeFormat.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3212
- const pattern = escaped.replace("dd", "(?<dd>\\d{1,2})").replace("MM", "(?<MM>\\d{1,2})").replace("yyyy", "(?<yyyy>\\d{4})");
3213
- const match = value.match(new RegExp(`^${pattern}$`));
3214
- if (!match?.groups) return null;
3215
- const day = Number(match.groups.dd);
3216
- const month = Number(match.groups.MM) - 1;
3217
- const year = Number(match.groups.yyyy);
3218
- const date = new Date(year, month, day);
3219
- return date.getFullYear() === year && date.getMonth() === month && date.getDate() === day ? date : null;
3220
- }
3221
- function parseTimeValue(text, format = "HH:mm") {
3222
- const value = text.trim();
3223
- if (!value) return null;
3224
- const usesPeriod = format.includes("aa");
3225
- const match = usesPeriod ? value.match(/^(\d{1,2}):(\d{2})\s*(AM|PM)$/i) : value.match(/^(\d{1,2}):(\d{2})$/);
3226
- if (!match) return null;
3227
- let hour = Number(match[1]);
3228
- const minute = Number(match[2]);
3229
- if (usesPeriod) {
3230
- if (hour < 1 || hour > 12) return null;
3231
- const period = match[3].toUpperCase();
3232
- hour = period === "PM" ? hour % 12 + 12 : hour % 12;
3233
- }
3234
- if (hour > 23 || minute > 59) return null;
3235
- const date = /* @__PURE__ */ new Date();
3236
- date.setHours(hour, minute, 0, 0);
3237
- return date;
3238
- }
3239
- function coerceDateOutput(date, sample) {
3240
- return typeof sample === "number" ? date?.getTime() ?? null : date;
3241
- }
3242
- function PickerTrailingIcon({ type, open }) {
3243
- if (type === "time") {
3244
- return /* @__PURE__ */ jsxs2("svg", { "data-jumpy-stroke-icon": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", style: { display: "block", fill: "none" }, children: [
3245
- /* @__PURE__ */ jsx2("path", { d: "M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12Z", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
3246
- /* @__PURE__ */ jsx2("path", { d: "M8 4.75V8l2.25 1.5", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
3247
- ] });
3248
- }
3249
- return /* @__PURE__ */ jsxs2("svg", { width: "16", height: "16", viewBox: "0 0 16 16", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", style: { display: "block" }, children: [
3250
- /* @__PURE__ */ jsx2("path", { d: "M4.25 2.5v2M11.75 2.5v2M3.25 6.25h9.5M3.75 3.75h8.5c.69 0 1.25.56 1.25 1.25v7.25c0 .69-.56 1.25-1.25 1.25h-8.5c-.69 0-1.25-.56-1.25-1.25V5c0-.69.56-1.25 1.25-1.25Z", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }),
3251
- open ? /* @__PURE__ */ jsx2("path", { d: "M6 10.5 7.25 11.75 10.25 8.75", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) : null
3252
- ] });
3253
- }
3254
- function getMonthDays(viewDate) {
3255
- const year = viewDate.getFullYear();
3256
- const month = viewDate.getMonth();
3257
- const firstDay = new Date(year, month, 1);
3258
- const leading = (firstDay.getDay() + 6) % 7;
3259
- const start = new Date(year, month, 1 - leading);
3260
- return Array.from({ length: 42 }, (_, index) => new Date(start.getFullYear(), start.getMonth(), start.getDate() + index));
3261
- }
3262
- function dateIsUnavailable(date, { minDate, maxDate, disabledDates = [], isDateDisabled }) {
3263
- const min = toDateValue(minDate);
3264
- const max = toDateValue(maxDate);
3265
- if (min && date < new Date(min.getFullYear(), min.getMonth(), min.getDate())) return true;
3266
- if (max && date > new Date(max.getFullYear(), max.getMonth(), max.getDate())) return true;
3267
- if (disabledDates.some((disabledDate) => sameDay(date, toDateValue(disabledDate)))) return true;
3268
- return Boolean(isDateDisabled?.(date));
3269
- }
3270
- function JumpDatePicker({ label = "Date", hint, value, defaultValue = null, onChange, open, onOpenChange, format = "MM/dd/yyyy", minDate, maxDate, disabledDates = [], isDateDisabled, labels = {}, disabled = false, readOnly = false, className = "", status = "regular", error = false, hideClearIcon = false, onClear, ...props }) {
3271
- const theme = useJumpyTheme();
3272
- const [internalValue, setInternalValue] = useState2(defaultValue);
3273
- const [internalOpen, setInternalOpen] = useState2(false);
3274
- const isControlled = value !== void 0;
3275
- const selectedDate = toDateValue(isControlled ? value : internalValue);
3276
- const selectedTime = selectedDate?.getTime() ?? null;
3277
- const [viewDate, setViewDate] = useState2(selectedDate || /* @__PURE__ */ new Date());
3278
- const isOpen = open ?? internalOpen;
3279
- const resolvedFormat = Array.isArray(format) ? format[0] : format;
3280
- const inputValue = formatDateValue(selectedDate, resolvedFormat);
3281
- const [draft, setDraft] = useState2(inputValue);
3282
- const typedValue = useRef2(void 0);
3283
- useEffect2(() => {
3284
- if (typedValue.current !== selectedTime) setDraft(inputValue);
3285
- typedValue.current = void 0;
3286
- if (selectedTime !== null) setViewDate(new Date(selectedTime));
3287
- }, [selectedTime, inputValue, resolvedFormat]);
3288
- const weekdays = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
3289
- const dayCells = getMonthDays(viewDate);
3290
- const { refs, floatingStyles } = useFloating({
3291
- open: isOpen,
3292
- onOpenChange: setOpen,
3293
- placement: "bottom-start",
3294
- strategy: "fixed",
3295
- whileElementsMounted: autoUpdate,
3296
- middleware: [offset(spacingValue(theme, JumpySpacing.smallXS)), flip({ padding: spacingValue(theme, JumpySpacing.medium) })]
3297
- });
3298
- function setOpen(next) {
3299
- setInternalOpen(next);
3300
- onOpenChange?.(next);
3301
- }
3302
- function commit(date) {
3303
- if (date && dateIsUnavailable(date, { minDate, maxDate, disabledDates, isDateDisabled })) return;
3304
- const next = coerceDateOutput(date, value ?? defaultValue);
3305
- typedValue.current = void 0;
3306
- if (!isControlled) setInternalValue(next);
3307
- setDraft(isControlled ? inputValue : formatDateValue(date, resolvedFormat));
3308
- onChange?.(next);
3309
- setOpen(false);
3310
- }
3311
- const trailing = readOnly ? void 0 : /* @__PURE__ */ jsx2(PickerTrailingIcon, { type: "date", open: isOpen });
3312
- return /* @__PURE__ */ jsxs2("div", { ref: refs.setReference, "data-jumpy-component": "date-picker", style: { position: "relative", width: "100%", maxWidth: "100%" }, children: [
3313
- /* @__PURE__ */ jsx2(JumpField, { label, hint, status, error, hideClearIcon, onClear, className, trailing, children: /* @__PURE__ */ jsx2(
3314
- JumpInput,
3315
- {
3316
- ...props,
3317
- role: "combobox",
3318
- "aria-expanded": isOpen,
3319
- "aria-haspopup": "dialog",
3320
- readOnly,
3321
- disabled,
3322
- "data-has-value": selectedTime !== null,
3323
- value: draft,
3324
- placeholder: props.placeholder || (Array.isArray(format) ? format[0] : format),
3325
- autoComplete: "off",
3326
- inputMode: "numeric",
3327
- onChange: (event) => {
3328
- const text = event.target.value;
3329
- setDraft(text);
3330
- const parsedDate = parseDateValue(text, format);
3331
- typedValue.current = void 0;
3332
- if (!text.trim() || parsedDate && !dateIsUnavailable(parsedDate, { minDate, maxDate, disabledDates, isDateDisabled })) {
3333
- const next = coerceDateOutput(parsedDate, value ?? defaultValue);
3334
- const nextTime = parsedDate?.getTime() ?? null;
3335
- if (nextTime !== selectedTime) typedValue.current = nextTime;
3336
- if (!isControlled) setInternalValue(next);
3337
- onChange?.(next);
3338
- }
3339
- },
3340
- onBlur: (event) => {
3341
- typedValue.current = void 0;
3342
- setDraft(inputValue);
3343
- props.onBlur?.(event);
3344
- },
3345
- onClick: (event) => {
3346
- if (!readOnly) setOpen(!isOpen);
3347
- props.onClick?.(event);
3348
- },
3349
- onKeyDown: (event) => {
3350
- if ((event.key === "ArrowDown" || event.key === "Enter") && !readOnly) {
3351
- event.preventDefault();
3352
- setOpen(true);
3353
- }
3354
- if (event.key === "Escape") setOpen(false);
3355
- props.onKeyDown?.(event);
3356
- }
3357
- }
3358
- ) }),
3359
- isOpen && !disabled && !readOnly ? /* @__PURE__ */ jsx2(JumpyPortal, { children: /* @__PURE__ */ jsxs2("div", { ref: refs.setFloating, role: "dialog", "aria-label": label, style: { ...floatingStyles, zIndex: JUMPY_POPUP_Z_INDEX, width: sizeValue(theme, JumpySize.large8XL) * 2.5, maxWidth: "calc(100vw - 32px)", overflow: "visible", borderRadius: radiusValue(theme, JumpyRadius.smallXS), backgroundColor: "var(--jumpy-card-inactive-default-surface, var(--jumpy-color-surface-card))", color: "var(--jumpy-card-inactive-default-text, var(--jumpy-color-text-primary))", boxShadow: theme.shadow.positiveMedium, padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0` }, children: [
3360
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: spacingValue(theme, JumpySpacing.medium), padding: spacingValue(theme, JumpySpacing.small3XS) }, children: [
3361
- /* @__PURE__ */ jsx2(JumpIconButton, { variant: "borderless", "aria-label": "Previous", onClick: () => setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() - 1, 1)), children: "\u2039" }),
3362
- /* @__PURE__ */ jsx2("strong", { style: { fontFamily: theme.fontFamily.familyBase, color: "inherit" }, children: viewDate.toLocaleDateString(void 0, { month: "long", year: "numeric" }) }),
3363
- /* @__PURE__ */ jsx2(JumpIconButton, { variant: "borderless", "aria-label": "Next", onClick: () => setViewDate(new Date(viewDate.getFullYear(), viewDate.getMonth() + 1, 1)), children: "\u203A" })
3364
- ] }),
3365
- /* @__PURE__ */ jsxs2("div", { style: { display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: spacingValue(theme, JumpySpacing.small3XS), padding: `0 ${spacingValue(theme, JumpySpacing.small3XS)}px` }, children: [
3366
- weekdays.map((weekday) => /* @__PURE__ */ jsx2("span", { style: { color: theme.color.textSecondary, textAlign: "center", fontSize: textStyleValue(theme, JumpyTextStyle.labelSmall).fontSize, fontWeight: theme.fontFamily.labelSmall.fontWeight }, children: weekday }, weekday)),
3367
- dayCells.map((date) => {
3368
- const outside = date.getMonth() !== viewDate.getMonth();
3369
- const selected = sameDay(date, selectedDate);
3370
- const unavailable = dateIsUnavailable(date, { minDate, maxDate, disabledDates, isDateDisabled });
3371
- return /* @__PURE__ */ jsx2("button", { type: "button", disabled: unavailable, onClick: () => commit(date), style: { minHeight: sizeValue(theme, JumpySize.large), border: 0, borderRadius: radiusValue(theme, JumpyRadius.small), backgroundColor: selected ? "var(--jumpy-interactive-active-regular-default-surface, var(--jumpy-color-surface-primary))" : "transparent", color: selected ? "var(--jumpy-interactive-active-regular-default-text, var(--jumpy-color-text-on-primary))" : outside ? "var(--jumpy-card-inactive-default-subtext, var(--jumpy-color-text-secondary))" : "var(--jumpy-card-inactive-default-text, var(--jumpy-color-text-primary))", opacity: unavailable ? opacityValue(theme, JumpyOpacity.medium) : outside ? opacityValue(theme, JumpyOpacity.strong) : opacityValue(theme, JumpyOpacity.solid), cursor: unavailable ? "not-allowed" : "pointer", fontFamily: theme.fontFamily.familyBase }, children: date.getDate() }, date.toISOString());
3372
- })
3373
- ] }),
3374
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "flex-end", gap: spacingValue(theme, JumpySpacing.smallXS), padding: spacingValue(theme, JumpySpacing.large) }, children: [
3375
- /* @__PURE__ */ jsx2(JumpButton, { variant: "tertiary", onClick: () => setOpen(false), children: labels.cancelButton || "Cancel" }),
3376
- /* @__PURE__ */ jsx2(JumpButton, { variant: "borderless", onClick: () => commit(/* @__PURE__ */ new Date()), children: "Today" })
3377
- ] })
3378
- ] }) }) : null
3379
- ] });
3380
- }
3381
- function JumpTimePicker({ label = "Time", hint, value, defaultValue = null, onChange, open, onOpenChange, format, twentyFourHours = true, labels = {}, disabled = false, readOnly = false, className = "", status = "regular", error = false, hideClearIcon = false, onClear, ...props }) {
3382
- const theme = useJumpyTheme();
3383
- const resolvedFormat = format || (twentyFourHours ? "HH:mm" : "hh:mm aa");
3384
- const [internalValue, setInternalValue] = useState2(defaultValue);
3385
- const [internalOpen, setInternalOpen] = useState2(false);
3386
- const isControlled = value !== void 0;
3387
- const selectedDate = toDateValue(isControlled ? value : internalValue);
3388
- const selectedTime = selectedDate?.getTime() ?? null;
3389
- const inputValue = formatTimeValue(selectedDate, resolvedFormat);
3390
- const [draft, setDraft] = useState2(inputValue);
3391
- const typedValue = useRef2(void 0);
3392
- useEffect2(() => {
3393
- if (typedValue.current !== selectedTime) setDraft(inputValue);
3394
- typedValue.current = void 0;
3395
- }, [selectedTime, inputValue, resolvedFormat]);
3396
- const isOpen = open ?? internalOpen;
3397
- const hours = Array.from({ length: twentyFourHours ? 24 : 12 }, (_, index) => String(twentyFourHours ? index : index + 1).padStart(2, "0"));
3398
- const minutes = Array.from({ length: 60 }, (_, index) => String(index).padStart(2, "0"));
3399
- const currentHour = selectedDate ? selectedDate.getHours() : 0;
3400
- const currentMinute = selectedDate ? selectedDate.getMinutes() : 0;
3401
- const displayHour = twentyFourHours ? currentHour : currentHour % 12 || 12;
3402
- const currentPeriod = currentHour >= 12 ? "PM" : "AM";
3403
- const { refs, floatingStyles } = useFloating({
3404
- open: isOpen,
3405
- onOpenChange: setOpen,
3406
- placement: "bottom-start",
3407
- strategy: "fixed",
3408
- whileElementsMounted: autoUpdate,
3409
- middleware: [offset(spacingValue(theme, JumpySpacing.smallXS)), flip({ padding: spacingValue(theme, JumpySpacing.medium) })]
3410
- });
3411
- function setOpen(next) {
3412
- setInternalOpen(next);
3413
- onOpenChange?.(next);
3414
- }
3415
- function commit(part, rawValue) {
3416
- const next = selectedDate ? new Date(selectedDate) : /* @__PURE__ */ new Date();
3417
- if (part === "hour") {
3418
- const hour = Number(rawValue);
3419
- next.setHours(twentyFourHours ? hour : currentPeriod === "PM" ? hour % 12 + 12 : hour % 12);
3420
- }
3421
- if (part === "minute") next.setMinutes(Number(rawValue));
3422
- if (part === "period") {
3423
- const base = next.getHours() % 12;
3424
- next.setHours(rawValue === "PM" ? base + 12 : base);
3425
- }
3426
- next.setSeconds(0, 0);
3427
- const output = coerceDateOutput(next, value ?? defaultValue);
3428
- typedValue.current = void 0;
3429
- if (!isControlled) setInternalValue(output);
3430
- setDraft(isControlled ? inputValue : formatTimeValue(next, resolvedFormat));
3431
- onChange?.(output);
3432
- }
3433
- const trailing = readOnly ? void 0 : /* @__PURE__ */ jsx2(PickerTrailingIcon, { type: "time", open: isOpen });
3434
- return /* @__PURE__ */ jsxs2("div", { ref: refs.setReference, "data-jumpy-component": "time-picker", style: { position: "relative", width: "100%", maxWidth: "100%" }, children: [
3435
- /* @__PURE__ */ jsx2(JumpField, { label, hint, status, error, hideClearIcon, onClear, className, trailing, children: /* @__PURE__ */ jsx2(
3436
- JumpInput,
3437
- {
3438
- ...props,
3439
- role: "combobox",
3440
- "aria-expanded": isOpen,
3441
- "aria-haspopup": "dialog",
3442
- readOnly,
3443
- disabled,
3444
- "data-has-value": selectedTime !== null,
3445
- value: draft,
3446
- placeholder: props.placeholder || resolvedFormat,
3447
- autoComplete: "off",
3448
- inputMode: resolvedFormat.includes("aa") ? "text" : "numeric",
3449
- onChange: (event) => {
3450
- const text = event.target.value;
3451
- setDraft(text);
3452
- const parsedDate = parseTimeValue(text, resolvedFormat);
3453
- typedValue.current = void 0;
3454
- if (!text.trim() || parsedDate) {
3455
- const output = coerceDateOutput(parsedDate, value ?? defaultValue);
3456
- const nextTime = parsedDate?.getTime() ?? null;
3457
- if (nextTime !== selectedTime) typedValue.current = nextTime;
3458
- if (!isControlled) setInternalValue(output);
3459
- onChange?.(output);
3460
- }
3461
- },
3462
- onBlur: (event) => {
3463
- typedValue.current = void 0;
3464
- setDraft(inputValue);
3465
- props.onBlur?.(event);
3466
- },
3467
- onClick: (event) => {
3468
- if (!readOnly) setOpen(!isOpen);
3469
- props.onClick?.(event);
3470
- },
3471
- onKeyDown: (event) => {
3472
- if ((event.key === "ArrowDown" || event.key === "Enter") && !readOnly) {
3473
- event.preventDefault();
3474
- setOpen(true);
3475
- }
3476
- if (event.key === "Escape") setOpen(false);
3477
- props.onKeyDown?.(event);
3478
- }
3479
- }
3480
- ) }),
3481
- isOpen && !disabled && !readOnly ? /* @__PURE__ */ jsx2(JumpyPortal, { children: /* @__PURE__ */ jsxs2("div", { ref: refs.setFloating, role: "dialog", "aria-label": label, style: { ...floatingStyles, zIndex: JUMPY_POPUP_Z_INDEX, overflow: "visible", borderRadius: radiusValue(theme, JumpyRadius.smallXS), backgroundColor: `var(--jumpy-non-interactive-background-alternative, var(--jumpy-color-non-interactive-background-alternative, ${theme.color.surfaceCard}))`, color: "var(--jumpy-card-inactive-default-text, var(--jumpy-color-text-primary))", boxShadow: theme.shadow.positiveMedium, padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`, minWidth: 260 }, children: [
3482
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", position: "relative", justifyContent: "center", alignItems: "center", gap: spacingValue(theme, JumpySpacing.small), padding: `${spacingValue(theme, JumpySpacing.zero)}px ${spacingValue(theme, JumpySpacing.zero)}px`, overflow: "hidden" }, children: [
3483
- /* @__PURE__ */ jsx2("div", { "aria-hidden": "true", style: { position: "absolute", left: 0, right: 0, top: "50%", height: sizeValue(theme, JumpySize.medium), transform: "translateY(-50%)", backgroundColor: "var(--jumpy-interactive-active-regular-default-surface)", pointerEvents: "none" } }),
3484
- /* @__PURE__ */ jsx2(TimeColumn, { label: "Hour", values: hours, selected: String(displayHour).padStart(2, "0"), onSelect: (next) => commit("hour", next), theme }),
3485
- /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", style: { zIndex: 1, display: "flex", alignItems: "center", justifyContent: "center", transform: "translateY(-0.1em)", width: sizeValue(theme, JumpySize.smallXS), height: sizeValue(theme, JumpySize.large), padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`, color: "var(--jumpy-interactive-active-regular-default-text)", fontFamily: theme.fontFamily.familyBase, fontSize: textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized).fontSize, fontWeight: textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized).fontWeight }, children: ":" }),
3486
- /* @__PURE__ */ jsx2(TimeColumn, { label: "Minute", values: minutes, selected: String(currentMinute).padStart(2, "0"), onSelect: (next) => commit("minute", next), theme }),
3487
- !twentyFourHours ? /* @__PURE__ */ jsx2(TimeColumn, { label: "Period", values: ["AM", "PM"], selected: currentPeriod, onSelect: (next) => commit("period", next), theme }) : null
3488
- ] }),
3489
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", gap: spacingValue(theme, JumpySpacing.smallXS), padding: spacingValue(theme, JumpySpacing.large) }, children: [
3490
- /* @__PURE__ */ jsx2(JumpButton, { variant: "tertiary", onClick: () => setOpen(false), children: labels.cancelButton || "Cancel" }),
3491
- /* @__PURE__ */ jsx2(JumpButton, { onClick: () => setOpen(false), children: labels.applyButton || "Apply" })
3492
- ] })
3493
- ] }) }) : null
3494
- ] });
3495
- }
3496
- function TimeColumn({ label, values, selected, onSelect, theme }) {
3497
- const selectedIndex = Math.max(0, values.indexOf(selected));
3498
- const wraps = values.length >= 5;
3499
- const visibleValues = Array.from({ length: 5 }, (_, offsetIndex) => {
3500
- const index = selectedIndex + offsetIndex - 2;
3501
- if (wraps) return values[(index + values.length) % values.length];
3502
- return index >= 0 && index < values.length ? values[index] : null;
3503
- });
3504
- const rowHeight = sizeValue(theme, JumpySize.large) + spacingValue(theme, JumpySpacing.small3XS) * 2;
3505
- const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
3506
- const bodyLargeEmphasized = textStyleValue(theme, JumpyTextStyle.bodyLargeEmphasized);
3507
- return /* @__PURE__ */ jsx2("div", { "aria-label": label, style: { position: "relative", zIndex: 1, display: "flex", flexDirection: "column", overflow: "hidden", padding: `0 ${spacingValue(theme, JumpySpacing.small3XS)}px`, margin: `0 ${-spacingValue(theme, JumpySpacing.small3XS)}px`, height: rowHeight * 5 }, children: /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexDirection: "column" }, children: visibleValues.map((value, offsetIndex) => {
3508
- if (value === null) return /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", style: { flex: `0 0 ${rowHeight}px` } }, `${label}-empty-${offsetIndex}`);
3509
- const active = value === selected;
3510
- return /* @__PURE__ */ jsx2("button", { type: "button", "data-jumpy-component": "time-picker-wheel-button", onClick: () => onSelect(value), "data-selected": active ? "true" : "false", style: { border: 0, width: sizeValue(theme, JumpySize.large), height: rowHeight, flex: `0 0 ${rowHeight}px`, backgroundColor: "transparent", color: active ? "var(--jumpy-interactive-active-regular-default-text, var(--jumpy-color-text-on-primary))" : "var(--jumpy-interactive-inactive-regular-default-text, var(--jumpy-color-text-primary))", padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`, cursor: "pointer", fontFamily: theme.fontFamily.familyBase, fontSize: active ? bodyLargeEmphasized.fontSize : bodyMedium.fontSize, lineHeight: active ? bodyLargeEmphasized.lineHeight : bodyMedium.lineHeight, fontWeight: active ? bodyLargeEmphasized.fontWeight : bodyMedium.fontWeight }, children: value }, `${label}-${value}`);
3511
- }) }) });
3512
- }
3513
- function flattenDropdownItems(items) {
3514
- return items.flatMap((item) => [item, ...flattenDropdownItems(item.children || [])]);
3515
- }
3516
- function JumpDropdown({ label, hint, value, defaultValue = [], onChange, open, onOpenChange, items = [], placeholder, popupMinWidth = 0, disabled = false, readOnly = false, multiple = false, searchable = false, renderTrigger, renderItem, inputRef, className = "", status = "regular", error = false, hideClearIcon = false, onClear, ...props }) {
3517
- const theme = useJumpyTheme();
3518
- const fieldContext = useContext(JumpyFieldContext);
3519
- const generatedId = useId();
3520
- const triggerId = props.id || `jumpy-dropdown-${generatedId}`;
3521
- const [internalValue, setInternalValue] = useState2(defaultValue);
3522
- const [internalOpen, setInternalOpen] = useState2(false);
3523
- const [searchValue, setSearchValue] = useState2("");
3524
- const [focusedIndex, setFocusedIndex] = useState2(-1);
3525
- const dropdownRef = useRef2(null);
3526
- const triggerRef = useRef2(null);
3527
- const selectedValues = value ?? internalValue;
3528
- const isOpen = open ?? internalOpen;
3529
- const flattenedItems = flattenDropdownItems(items);
3530
- const visibleItems = searchValue ? flattenedItems.filter((item) => item.label.toLowerCase().includes(searchValue.toLowerCase())) : flattenedItems;
3531
- const enabledVisibleItems = visibleItems.filter((item) => !item.disabled);
3532
- const selectedItems = flattenedItems.filter((item) => selectedValues.includes(item.value));
3533
- const displayText = selectedItems.map((item) => item.label).join(", ");
3534
- const dropdownOffset = spacingValue(theme, JumpySpacing.small3XS);
3535
- const dropdownPadding = spacingValue(theme, JumpySpacing.medium);
3536
- const dropdownMinHeight = sizeValue(theme, JumpySize.large2XL);
3537
- const dropdownMaxHeight = sizeValue(theme, JumpySize.large8XL) * 2;
3538
- const { refs, floatingStyles } = useFloating({
3539
- open: isOpen,
3540
- onOpenChange: setOpen,
3541
- placement: "bottom-start",
3542
- strategy: "fixed",
3543
- whileElementsMounted: autoUpdate,
3544
- middleware: [
3545
- offset(dropdownOffset),
3546
- flip({ padding: dropdownPadding }),
3547
- shift({ padding: dropdownPadding }),
3548
- size({
3549
- padding: dropdownPadding,
3550
- apply({ rects, availableWidth, availableHeight, elements }) {
3551
- Object.assign(elements.floating.style, {
3552
- width: `${Math.max(0, Math.min(Math.max(rects.reference.width, popupMinWidth), availableWidth))}px`,
3553
- maxHeight: `${Math.max(dropdownMinHeight, Math.min(dropdownMaxHeight, availableHeight))}px`
3554
- });
3555
- }
3556
- })
3557
- ]
3558
- });
3559
- const triggerStyle = fieldContext && !label && !hint ? {
3560
- paddingRight: sizeValue(theme, JumpySize.small2XS) + spacingValue(theme, JumpySpacing.medium),
3561
- ...props.style || {}
3562
- } : props.style;
3563
- const triggerInputStyle = {
3564
- ...triggerStyle || {},
3565
- caretColor: "transparent"
3566
- };
3567
- function setOpen(next) {
3568
- setInternalOpen(next);
3569
- onOpenChange?.(next);
3570
- if (!next) {
3571
- setSearchValue("");
3572
- setFocusedIndex(-1);
3573
- } else if (enabledVisibleItems.length > 0 && focusedIndex < 0) {
3574
- setFocusedIndex(visibleItems.findIndex((item) => !item.disabled));
3575
- }
3576
- }
3577
- function commit(nextValues) {
3578
- setInternalValue(nextValues);
3579
- onChange?.(nextValues);
3580
- if (!multiple) {
3581
- setOpen(false);
3582
- triggerRef.current?.focus();
3583
- }
3584
- }
3585
- useEffect2(() => {
3586
- if (!isOpen) return void 0;
3587
- function dismissOutside(event) {
3588
- if (!dropdownRef.current?.contains(event.target) && !refs.floating.current?.contains(event.target)) setOpen(false);
3589
- }
3590
- document.addEventListener("pointerdown", dismissOutside);
3591
- document.addEventListener("focusin", dismissOutside);
3592
- return () => {
3593
- document.removeEventListener("pointerdown", dismissOutside);
3594
- document.removeEventListener("focusin", dismissOutside);
3595
- };
3596
- });
3597
- function selectItem(item) {
3598
- if (!item || item.disabled) return;
3599
- if (multiple) {
3600
- commit(selectedValues.includes(item.value) ? selectedValues.filter((v) => v !== item.value) : [...selectedValues, item.value]);
3601
- return;
3602
- }
3603
- commit([item.value]);
3604
- }
3605
- function moveFocus(direction) {
3606
- if (enabledVisibleItems.length === 0) return;
3607
- const currentEnabledIndex = enabledVisibleItems.findIndex((item) => item.value === visibleItems[focusedIndex]?.value);
3608
- const nextEnabledIndex = direction === "start" ? 0 : direction === "end" ? enabledVisibleItems.length - 1 : (currentEnabledIndex + direction + enabledVisibleItems.length) % enabledVisibleItems.length;
3609
- setFocusedIndex(visibleItems.findIndex((item) => item.value === enabledVisibleItems[nextEnabledIndex].value));
3610
- }
3611
- const triggerInput = /* @__PURE__ */ jsx2(
3612
- JumpInput,
3613
- {
3614
- ...props,
3615
- ref: (node) => {
3616
- triggerRef.current = node;
3617
- refs.setReference(node?.closest('[data-jumpy-component="field-control"]') || node);
3618
- if (typeof inputRef === "function") inputRef(node);
3619
- else if (inputRef) inputRef.current = node;
3620
- },
3621
- id: triggerId,
3622
- style: triggerInputStyle,
3623
- "data-jumpy-dropdown-trigger": "true",
3624
- onChange: () => {
3625
- },
3626
- "data-has-value": Boolean(displayText),
3627
- role: "combobox",
3628
- "aria-expanded": isOpen,
3629
- "aria-haspopup": "listbox",
3630
- "aria-controls": isOpen ? `${triggerId}-listbox` : void 0,
3631
- "aria-activedescendant": isOpen && focusedIndex >= 0 ? `${triggerId}-option-${focusedIndex}` : void 0,
3632
- readOnly,
3633
- disabled,
3634
- value: displayText,
3635
- placeholder: displayText ? void 0 : placeholder,
3636
- autoComplete: "off",
3637
- spellCheck: false,
3638
- inputMode: "none",
3639
- onClick: (event) => {
3640
- if (!readOnly) setOpen(!isOpen);
3641
- props.onClick?.(event);
3642
- },
3643
- onBlur: (event) => {
3644
- props.onBlur?.(event);
3645
- },
3646
- onKeyDown: (event) => {
3647
- if (event.key !== "Tab" && event.key !== "Escape") event.preventDefault();
3648
- if (readOnly) {
3649
- props.onKeyDown?.(event);
3650
- return;
3651
- }
3652
- if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
3653
- setOpen(true);
3654
- }
3655
- if (event.key === "ArrowDown") moveFocus(1);
3656
- if (event.key === "ArrowUp") moveFocus(-1);
3657
- if (event.key === "Home") moveFocus("start");
3658
- if (event.key === "End") moveFocus("end");
3659
- if ((event.key === "Enter" || event.key === " ") && isOpen) selectItem(visibleItems[focusedIndex]);
3660
- props.onKeyDown?.(event);
3661
- },
3662
- onPaste: (event) => event.preventDefault()
3663
- }
3664
- );
3665
- const trailing = readOnly ? void 0 : /* @__PURE__ */ jsx2("svg", { width: "1em", height: "1em", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", style: { display: "block", fontSize: sizeValue(theme, JumpySize.small) }, children: /* @__PURE__ */ jsx2("path", { fillRule: "evenodd", d: isOpen ? "m16.59 15.705 1.41-1.41-6-6-6 6 1.41 1.41 4.59-4.58z" : "M7.41 8.295 6 9.705l6 6 6-6-1.41-1.41-4.59 4.58z", clipRule: "evenodd", fill: "currentColor" }) });
3666
- const trigger = renderTrigger ? renderTrigger(selectedValues, isOpen) : fieldContext && !label && !hint ? /* @__PURE__ */ jsxs2(Fragment, { children: [
3667
- triggerInput,
3668
- /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", style: { pointerEvents: "none", position: "absolute", right: spacingValue(theme, JumpySpacing.medium), top: "50%", transform: "translateY(-50%)", color: theme.color.textSecondary, display: "inline-flex", alignItems: "center", justifyContent: "center", lineHeight: 1 }, children: trailing })
3669
- ] }) : /* @__PURE__ */ jsx2(JumpField, { label, hint, status, error, hideClearIcon, onClear: () => {
3670
- commit([]);
3671
- setSearchValue("");
3672
- onClear?.();
3673
- }, className, trailing, children: triggerInput });
3674
- const listbox = isOpen && !disabled ? /* @__PURE__ */ jsx2(JumpyPortal, { children: /* @__PURE__ */ jsxs2(
3675
- "div",
3676
- {
3677
- ref: refs.setFloating,
3678
- id: `${triggerId}-listbox`,
3679
- role: "listbox",
3680
- "aria-multiselectable": multiple || void 0,
3681
- "data-jumpy-component": "dropdown-popover",
3682
- style: {
3683
- ...floatingStyles,
3684
- zIndex: JUMPY_POPUP_Z_INDEX,
3685
- overflowX: "hidden",
3686
- overflowY: "auto",
3687
- borderRadius: radiusValue(theme, JumpyRadius.large),
3688
- backgroundColor: `var(--jumpy-non-interactive-background-alternative, var(--jumpy-color-non-interactive-background-alternative, ${theme.color.surfaceCard}))`,
3689
- boxShadow: theme.shadow.positiveMedium,
3690
- padding: `${spacingValue(theme, JumpySpacing.small3XS)}px 0`
3691
- },
3692
- children: [
3693
- searchable ? /* @__PURE__ */ jsx2("div", { style: { margin: `${spacingValue(theme, JumpySpacing.small3XS)}px ${spacingValue(theme, JumpySpacing.smallXS)}px` }, children: /* @__PURE__ */ jsx2(JumpSearchField, { label: "Search", placeholder: "Search", value: searchValue, onChange: (event) => {
3694
- setSearchValue(event.target.value);
3695
- setFocusedIndex(-1);
3696
- } }) }) : null,
3697
- visibleItems.length === 0 ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "dropdown-empty-state", style: { padding: `${spacingValue(theme, JumpySpacing.medium)}px ${spacingValue(theme, JumpySpacing.large)}px`, color: "var(--jumpy-interactive-inactive-regular-default-text)", fontFamily: theme.fontFamily.familyBase, fontSize: textStyleValue(theme, JumpyTextStyle.bodyMedium).fontSize }, children: "No options available" }) : null,
3698
- visibleItems.map((item, index) => {
3699
- const selected = selectedValues.includes(item.value);
3700
- const listItemProps = {
3701
- id: `${triggerId}-option-${index}`,
3702
- role: "option",
3703
- "aria-selected": selected,
3704
- disabled: item.disabled,
3705
- onMouseDown: (event) => event.preventDefault(),
3706
- onMouseEnter: () => setFocusedIndex(index),
3707
- onClick: () => {
3708
- selectItem(item);
3709
- }
3710
- };
3711
- return renderItem ? renderItem(item, selected, listItemProps) : /* @__PURE__ */ jsx2(JumpListItem, { selected, ...listItemProps, children: item.label }, String(item.value));
3712
- })
3713
- ]
3714
- }
3715
- ) }) : null;
3716
- return /* @__PURE__ */ jsxs2("div", { ref: dropdownRef, "data-jumpy-component": "dropdown", onKeyDown: (event) => {
3717
- if (event.key === KeyboardKeys.ESCAPE && isOpen && !event.defaultPrevented) {
3718
- event.preventDefault();
3719
- event.stopPropagation();
3720
- setOpen(false);
3721
- triggerRef.current?.focus();
3722
- }
3723
- }, style: { position: "relative", width: "100%" }, children: [
3724
- trigger,
3725
- listbox
3726
- ] });
3727
- }
3728
- function JumpSpinner({ size: size2 = "medium", mode = "indeterminate", value = 0, showTrack = true, inverse = false, className = "", ...props }) {
3729
- const theme = useJumpyTheme();
3730
- const sizeMap = {
3731
- xSmall: { diameter: 20, strokeWidth: 2 },
3732
- small: { diameter: 24, strokeWidth: 2 },
3733
- medium: { diameter: 32, strokeWidth: 2 },
3734
- large: { diameter: 48, strokeWidth: 4 },
3735
- xLarge: { diameter: 56, strokeWidth: 4 },
3736
- xxLarge: { diameter: 72, strokeWidth: 4 }
3737
- };
3738
- const { diameter, strokeWidth } = sizeMap[size2] || sizeMap.medium;
3739
- const radius = (diameter - strokeWidth) / 2;
3740
- const center = diameter / 2;
3741
- const circumference = 2 * Math.PI * radius;
3742
- const clampedValue = Math.min(Math.max(value, 0), 100);
3743
- const dash = mode === "determinate" ? clampedValue / 100 * circumference : circumference * 0.72;
3744
- const gap = circumference - dash;
3745
- return /* @__PURE__ */ jsxs2("svg", { ...props, "data-jumpy-component": "spinner", className: cn(className), width: diameter, height: diameter, viewBox: `0 0 ${diameter} ${diameter}`, role: "progressbar", "aria-valuemin": mode === "determinate" ? 0 : void 0, "aria-valuemax": mode === "determinate" ? 100 : void 0, "aria-valuenow": mode === "determinate" ? clampedValue : void 0, "aria-label": props["aria-label"] || (mode === "determinate" ? `Loading progress: ${clampedValue}%` : "Loading in progress"), style: { color: inverse ? theme.color.textOnPrimary : theme.color.surfacePrimary, ...props.style || {} }, children: [
3746
- showTrack ? /* @__PURE__ */ jsx2("circle", { cx: center, cy: center, r: radius, fill: "none", stroke: theme.color.borderSubtle, strokeWidth }) : null,
3747
- /* @__PURE__ */ jsx2("circle", { cx: center, cy: center, r: radius, fill: "none", stroke: "currentColor", strokeWidth, strokeLinecap: "round", strokeDasharray: `${dash} ${gap}`, transform: `rotate(-90 ${center} ${center})`, children: mode === "indeterminate" ? /* @__PURE__ */ jsx2("animateTransform", { attributeName: "transform", type: "rotate", from: `0 ${center} ${center}`, to: `360 ${center} ${center}`, dur: "1s", repeatCount: "indefinite" }) : null })
3748
- ] });
3749
- }
3750
- function JumpDialog({ as, visible = false, onVisibleChange, title, description, footer, children, className = "", disableBackdropClick = false, disableContentScroll = false, fullScreen = false, overlayProps, slots, innerClasses = {}, ...props }) {
3751
- const theme = useJumpyTheme();
3752
- const Component = as || "div";
3753
- const labelId = useId();
3754
- const descriptionId = useId();
3755
- const dialogTitle = slots?.title ?? title;
3756
- const dialogDescription = slots?.description ?? description;
3757
- const dialogFooter = slots?.footer ?? footer;
3758
- const dialogIcon = slots?.icon;
3759
- const dialogIllustration = slots?.illustration;
3760
- const titleStyle = textStyleValue(theme, JumpyTextStyle.titleLarge);
3761
- const descriptionStyle = textStyleValue(theme, JumpyTextStyle.bodyMedium);
3762
- const { className: overlayClassName, style: overlayStyle, onClick: overlayOnClick, ...restOverlayProps } = overlayProps || {};
3763
- if (!visible) return null;
3764
- return /* @__PURE__ */ jsx2(
3765
- JumpyModalLayer,
3766
- {
3767
- ...restOverlayProps,
3768
- onVisibleChange,
3769
- "data-jumpy-component": "dialog-overlay",
3770
- "data-disable-content-scroll": disableContentScroll ? "true" : "false",
3771
- role: "presentation",
3772
- className: cn(overlayClassName),
3773
- onClick: (event) => {
3774
- overlayOnClick?.(event);
3775
- if (event.target === event.currentTarget && !event.defaultPrevented && !disableBackdropClick) onVisibleChange?.(false);
3776
- },
3777
- style: overlayStyle,
3778
- children: /* @__PURE__ */ jsxs2(
3779
- Component,
3780
- {
3781
- ...props,
3782
- "data-jumpy-component": "dialog",
3783
- "data-full-screen": fullScreen ? "true" : "false",
3784
- "data-no-footer": dialogFooter ? "false" : "true",
3785
- role: "dialog",
3786
- "aria-modal": "true",
3787
- "aria-labelledby": dialogTitle ? labelId : void 0,
3788
- "aria-describedby": dialogDescription ? descriptionId : void 0,
3789
- "aria-label": !dialogTitle ? props["aria-label"] || title : props["aria-label"],
3790
- className: cn(className),
3791
- onClick: (event) => {
3792
- event.stopPropagation();
3793
- props.onClick?.(event);
3794
- },
3795
- style: {
3796
- "--jumpy-dialog-width": props.style?.width || "472px",
3797
- ...props.style || {}
3798
- },
3799
- children: [
3800
- dialogIllustration ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "dialog-illustration", className: innerClasses.illustration?.className, style: innerClasses.illustration?.style, children: dialogIllustration }) : null,
3801
- /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "dialog-body", className: innerClasses.body?.className, style: innerClasses.body?.style, children: [
3802
- dialogIcon ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "dialog-icon", className: innerClasses.icon?.className, style: innerClasses.icon?.style, children: dialogIcon }) : null,
3803
- dialogTitle ? /* @__PURE__ */ jsx2(
3804
- "div",
3805
- {
3806
- id: labelId,
3807
- "data-jumpy-slot": "dialog-title",
3808
- className: innerClasses.title?.className,
3809
- style: {
3810
- margin: 0,
3811
- fontFamily: theme.fontFamily.familyBase,
3812
- fontSize: titleStyle.fontSize,
3813
- lineHeight: titleStyle.lineHeight,
3814
- fontWeight: titleStyle.fontWeight,
3815
- letterSpacing: titleStyle.letterSpacing,
3816
- ...innerClasses.title?.style || {}
3817
- },
3818
- children: dialogTitle
3819
- }
3820
- ) : null,
3821
- /* @__PURE__ */ jsxs2("div", { "data-jumpy-slot": "dialog-scrollable-content", className: innerClasses.scrollableContent?.className, style: innerClasses.scrollableContent?.style, children: [
3822
- dialogDescription ? /* @__PURE__ */ jsx2(
3823
- "div",
3824
- {
3825
- id: descriptionId,
3826
- "data-jumpy-slot": "dialog-description",
3827
- className: innerClasses.description?.className,
3828
- style: {
3829
- margin: 0,
3830
- fontFamily: theme.fontFamily.familyBase,
3831
- fontSize: descriptionStyle.fontSize,
3832
- lineHeight: descriptionStyle.lineHeight,
3833
- fontWeight: descriptionStyle.fontWeight,
3834
- letterSpacing: descriptionStyle.letterSpacing,
3835
- ...innerClasses.description?.style || {}
3836
- },
3837
- children: dialogDescription
3838
- }
3839
- ) : null,
3840
- children
3841
- ] })
3842
- ] }),
3843
- dialogFooter ? /* @__PURE__ */ jsx2("div", { "data-jumpy-slot": "dialog-footer", className: innerClasses.footer?.className, style: innerClasses.footer?.style, children: dialogFooter }) : null
3844
- ]
3845
- }
3846
- )
3847
- }
3848
- );
3849
- }
3850
- function JumpLoader({ label = "Jumpy UI wird geladen", fullScreen = false, className = "", decorativeBackground = false }) {
3851
- const theme = useJumpyTheme();
3852
- const content = /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col items-center text-center", className), role: "status", "aria-live": "polite", children: [
3853
- /* @__PURE__ */ jsxs2("div", { className: "relative flex items-center justify-center", style: { height: theme.sizing.loaderSize, width: theme.sizing.loaderSize }, children: [
3854
- /* @__PURE__ */ jsx2("div", { className: "absolute inset-0", style: { borderRadius: theme.radius.large2XL, backgroundColor: theme.color.infoSurface } }),
3855
- /* @__PURE__ */ jsx2("div", { className: "absolute inset-2", style: { borderRadius: theme.radius.largeXL, border: `1px solid ${theme.color.infoBorder}`, backgroundColor: theme.color.surfaceCard, boxShadow: theme.shadow.loader } }),
3856
- /* @__PURE__ */ jsx2("span", { className: "relative grid place-items-center", style: { color: theme.color.surfacePrimary }, children: /* @__PURE__ */ jsx2(JumpSpinner, { size: "large", "aria-label": label }) })
3857
- ] }),
3858
- /* @__PURE__ */ jsx2("p", { className: "mt-5 text-[11px] font-bold uppercase tracking-[0.18em]", style: { color: theme.color.surfacePrimary }, children: label }),
3859
- /* @__PURE__ */ jsxs2("div", { className: "mt-3 flex items-center gap-1.5", "aria-hidden": "true", children: [
3860
- /* @__PURE__ */ jsx2("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.2s]", style: { backgroundColor: theme.color.surfacePrimary } }),
3861
- /* @__PURE__ */ jsx2("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full [animation-delay:-0.1s]", style: { backgroundColor: theme.color.successBorder } }),
3862
- /* @__PURE__ */ jsx2("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full", style: { backgroundColor: theme.color.infoBorder } })
3863
- ] }),
3864
- /* @__PURE__ */ jsx2("span", { className: "sr-only", children: "Laden" })
3865
- ] });
3866
- if (!fullScreen) return content;
3867
- return /* @__PURE__ */ jsxs2("div", { className: "relative flex min-h-screen items-center justify-center overflow-hidden p-6", style: { backgroundColor: theme.color.surfaceCanvas, color: theme.color.textPrimary }, children: [
3868
- decorativeBackground && /* @__PURE__ */ jsx2(JumpBackground, { snow: false }),
3869
- /* @__PURE__ */ jsx2(JumpCard, { className: cn("px-10 py-9", decorativeBackground && "relative z-10"), children: content })
3870
- ] });
3871
- }
3872
- function JumpButton({ as, size: size2, variant = "primary", status = "regular", className = "", isLoading = false, disabled, elevated = false, slots, leadingIcon, trailingIcon, children, ...props }) {
3873
- const theme = useJumpyTheme();
3874
- const variantStyles = getButtonVariantStyles(theme, variant, status);
3875
- const height = sizeValue(theme, size2 ?? JumpySize.large);
3876
- if (!Number.isFinite(height) || height < 0) throw new Error(`Invalid JumpButton size token: ${size2}`);
3877
- const compact = height < sizeValue(theme, JumpySize.large);
3878
- const textStyle = textStyleValue(theme, compact ? JumpyTextStyle.bodySmallEmphasized : JumpyTextStyle.bodyMediumEmphasized);
3879
- const iconSize = sizeValue(theme, compact ? JumpySize.small3XS : JumpySize.small2XS);
3880
- const [pressed, setPressed] = useState2(false);
3881
- const leading = slots?.leading ?? leadingIcon;
3882
- const trailing = slots?.trailing ?? trailingIcon;
3883
- const Component = as || "button";
3884
- const isButton = Component === "button";
3885
- const isDisabled = disabled || isLoading;
3886
- const displayedLeading = isLoading ? /* @__PURE__ */ jsx2(JumpSpinner, { size: "xSmall", showTrack: false, "aria-hidden": "true", style: { display: "block", width: iconSize, height: iconSize, color: "inherit" } }) : leading;
3887
- const spacing = getButtonSpacing(theme, variant, Boolean(displayedLeading), Boolean(trailing));
3888
- const hasBorder = variant === "secondary" || variant === "tertiary";
3889
- if (size2 !== void 0) {
3890
- const border = hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0;
3891
- const contentHeight = Math.max(textStyle.fontSize * textStyle.lineHeight, displayedLeading || trailing ? iconSize : 0);
3892
- spacing.paddingTop = spacing.paddingBottom = Math.max(0, (height - contentHeight - border * 2) / 2);
3893
- if (compact) {
3894
- spacing.gap = spacingValue(theme, JumpySpacing.small3XS);
3895
- spacing.paddingLeft = spacing.paddingRight = spacingValue(theme, variant === "borderless" ? JumpySpacing.zero : JumpySpacing.medium);
3896
- }
3897
- }
3898
- const content = /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.gap, whiteSpace: "nowrap" }, children: [
3899
- displayedLeading ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "button-leading", "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: displayedLeading }) : null,
3900
- /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "button-label", children }),
3901
- trailing ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "button-trailing", "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", justifyContent: "center", flexShrink: 0, width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: trailing }) : null
3902
- ] });
3903
- return /* @__PURE__ */ jsx2(
3904
- Component,
3905
- {
3906
- ...props,
3907
- type: isButton ? props.type || "button" : props.type,
3908
- "data-jumpy-component": "icon-button",
3909
- "data-variant": variant,
3910
- "data-status": status,
3911
- "data-elevated": elevated ? "true" : "false",
3912
- "data-loading": isLoading ? "true" : "false",
3913
- "data-size": size2,
3914
- "data-pressed": pressed ? "true" : "false",
3915
- "aria-busy": isLoading || void 0,
3916
- "aria-disabled": !isButton && isDisabled ? true : props["aria-disabled"],
3917
- tabIndex: !isButton && isDisabled ? -1 : props.tabIndex,
3918
- disabled: isButton ? isDisabled : void 0,
3919
- onClick: (event) => {
3920
- if (isDisabled) {
3921
- event.preventDefault();
3922
- return;
3923
- }
3924
- props.onClick?.(event);
3925
- },
3926
- onPointerDown: (event) => {
3927
- setPressed(true);
3928
- props.onPointerDown?.(event);
3929
- },
3930
- onPointerUp: (event) => {
3931
- setPressed(false);
3932
- props.onPointerUp?.(event);
3933
- },
3934
- onPointerLeave: (event) => {
3935
- setPressed(false);
3936
- props.onPointerLeave?.(event);
3937
- },
3938
- onBlur: (event) => {
3939
- setPressed(false);
3940
- props.onBlur?.(event);
3941
- },
3942
- className: cn("disabled:cursor-not-allowed", className),
3943
- style: {
3944
- display: "inline-flex",
3945
- alignItems: "center",
3946
- justifyContent: "center",
3947
- minHeight: height,
3948
- appearance: "none",
3949
- WebkitAppearance: "none",
3950
- boxSizing: "border-box",
3951
- paddingLeft: spacing.paddingLeft,
3952
- paddingRight: spacing.paddingRight,
3953
- paddingTop: spacing.paddingTop,
3954
- paddingBottom: spacing.paddingBottom,
3955
- borderRadius: radiusValue(theme, JumpyRadius.small),
3956
- letterSpacing: textStyle.letterSpacing,
3957
- fontFamily: theme.fontFamily.familyBase,
3958
- fontSize: textStyle.fontSize,
3959
- lineHeight: textStyle.lineHeight,
3960
- fontWeight: textStyle.fontWeight,
3961
- // Primary regular disabled tokens already encode the subdued appearance.
3962
- opacity: isDisabled && !(variant === "primary" && status === "regular") ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
3963
- borderWidth: hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0,
3964
- borderStyle: hasBorder ? "solid" : "none",
3965
- ...variantStyles,
3966
- ...elevated && !isDisabled ? { "--jumpy-action-default-shadow": shadowValue(theme, JumpyShadowStyle.positiveMedium) } : {},
3967
- ...props.style || {}
3968
- },
3969
- children: /* @__PURE__ */ jsx2("span", { style: { position: "relative", display: "inline-flex", alignItems: "center", justifyContent: "center", whiteSpace: "nowrap" }, children: content })
3970
- }
3971
- );
3972
- }
3973
- function JumpIconButton({ as, variant = "primary", status = "regular", alternateHover = false, elevated = false, className = "", disabled, children, "aria-label": ariaLabel, ...props }) {
3974
- const theme = useJumpyTheme();
3975
- const actionVariant = variant === "primary" ? "secondary" : variant === "secondary" ? "tertiary" : variant;
3976
- const variantStyles = getButtonVariantStyles(theme, actionVariant, status);
3977
- const actionTokenPrefix = `--jumpy-actions-${actionVariant}-${status}`;
3978
- const [pressed, setPressed] = useState2(false);
3979
- const Component = as || "button";
3980
- const isButton = Component === "button";
3981
- const supportsDisabled = typeof Component === "string" && supportsDisabledAttribute(Component);
3982
- const hasBorder = variant === "primary";
3983
- return /* @__PURE__ */ jsx2(
3984
- Component,
3985
- {
3986
- ...props,
3987
- type: isButton ? props.type || "button" : props.type,
3988
- "aria-label": ariaLabel,
3989
- "data-jumpy-component": "button",
3990
- "data-variant": variant,
3991
- "data-status": status,
3992
- "data-elevated": elevated ? "true" : "false",
3993
- "data-pressed": pressed ? "true" : "false",
3994
- "aria-disabled": !supportsDisabled && disabled ? true : props["aria-disabled"],
3995
- tabIndex: !supportsDisabled && disabled ? -1 : props.tabIndex,
3996
- disabled: supportsDisabled ? disabled : void 0,
3997
- onPointerDown: (event) => {
3998
- setPressed(true);
3999
- props.onPointerDown?.(event);
4000
- },
4001
- onPointerUp: (event) => {
4002
- setPressed(false);
4003
- props.onPointerUp?.(event);
4004
- },
4005
- onPointerLeave: (event) => {
4006
- setPressed(false);
4007
- props.onPointerLeave?.(event);
4008
- },
4009
- onBlur: (event) => {
4010
- setPressed(false);
4011
- props.onBlur?.(event);
4012
- },
4013
- className: cn("disabled:cursor-not-allowed", className),
4014
- style: {
4015
- display: "inline-grid",
4016
- placeItems: "center",
4017
- width: sizeValue(theme, JumpySize.large),
4018
- height: sizeValue(theme, JumpySize.large),
4019
- minWidth: sizeValue(theme, JumpySize.large),
4020
- minHeight: sizeValue(theme, JumpySize.large),
4021
- appearance: "none",
4022
- WebkitAppearance: "none",
4023
- boxSizing: "border-box",
4024
- padding: spacingValue(theme, JumpySpacing.small2XS),
4025
- borderRadius: radiusValue(theme, JumpyRadius.small),
4026
- borderWidth: hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0,
4027
- borderStyle: hasBorder ? "solid" : "none",
4028
- opacity: disabled ? opacityValue(theme, JumpyOpacity.medium) : opacityValue(theme, JumpyOpacity.solid),
4029
- ...variantStyles,
4030
- "--jumpy-action-default-icon": `var(${actionTokenPrefix}-default-action, ${variantStyles["--jumpy-action-default-icon"]})`,
4031
- "--jumpy-action-hover-icon": `var(${actionTokenPrefix}-hover-action, ${variantStyles["--jumpy-action-hover-icon"]})`,
4032
- "--jumpy-action-focus-icon": `var(${actionTokenPrefix}-focus-action, ${variantStyles["--jumpy-action-focus-icon"]})`,
4033
- "--jumpy-action-pressed-icon": `var(${actionTokenPrefix}-pressed-action, ${variantStyles["--jumpy-action-pressed-icon"]})`,
4034
- "--jumpy-action-disabled-icon": `var(${actionTokenPrefix}-disabled-action, ${variantStyles["--jumpy-action-disabled-icon"]})`,
4035
- ...elevated && !disabled ? { "--jumpy-action-default-shadow": shadowValue(theme, JumpyShadowStyle.positiveMedium) } : {},
4036
- ...alternateHover ? {
4037
- "--jumpy-action-hover-surface": `var(--jumpy-interactive-inactive-${status}-hover-surface-alternate, ${variantStyles["--jumpy-action-hover-surface"]})`,
4038
- "--jumpy-action-pressed-surface": `var(--jumpy-interactive-inactive-${status}-pressed-surface-alternate, ${variantStyles["--jumpy-action-pressed-surface"]})`
4039
- } : {},
4040
- ...props.style || {}
4041
- },
4042
- children: /* @__PURE__ */ jsx2("span", { style: { display: "grid", placeItems: "center", width: sizeValue(theme, JumpySize.small), height: sizeValue(theme, JumpySize.small), lineHeight: 0, fontSize: sizeValue(theme, JumpySize.small) }, "aria-hidden": "true", children })
4043
- }
4044
- );
4045
- }
4046
- function JumpLinkButton({ href, variant = "borderless", status = "regular", className = "", leadingIcon, trailingIcon, children, ...props }) {
4047
- const theme = useJumpyTheme();
4048
- const variantStyles = getButtonVariantStyles(theme, variant, status);
4049
- const bodyMediumEmphasized = textStyleValue(theme, JumpyTextStyle.bodyMediumEmphasized);
4050
- const iconSize = sizeValue(theme, JumpySize.small2XS);
4051
- const [pressed, setPressed] = useState2(false);
4052
- const spacing = getButtonSpacing(theme, variant, Boolean(leadingIcon), Boolean(trailingIcon));
4053
- const hasBorder = variant === "secondary" || variant === "tertiary";
4054
- return /* @__PURE__ */ jsx2(
4055
- "a",
4056
- {
4057
- href,
4058
- ...props,
4059
- "data-jumpy-component": "link-button",
4060
- "data-variant": variant,
4061
- "data-status": status,
4062
- "data-pressed": pressed ? "true" : "false",
4063
- onPointerDown: (event) => {
4064
- setPressed(true);
4065
- props.onPointerDown?.(event);
4066
- },
4067
- onPointerUp: (event) => {
4068
- setPressed(false);
4069
- props.onPointerUp?.(event);
4070
- },
4071
- onPointerLeave: (event) => {
4072
- setPressed(false);
4073
- props.onPointerLeave?.(event);
4074
- },
4075
- onBlur: (event) => {
4076
- setPressed(false);
4077
- props.onBlur?.(event);
4078
- },
4079
- className: cn(className),
4080
- style: {
4081
- display: "inline-flex",
4082
- alignItems: "center",
4083
- justifyContent: "center",
4084
- minHeight: sizeValue(theme, JumpySize.large),
4085
- appearance: "none",
4086
- WebkitAppearance: "none",
4087
- boxSizing: "border-box",
4088
- paddingLeft: spacing.paddingLeft,
4089
- paddingRight: spacing.paddingRight,
4090
- paddingTop: spacing.paddingTop,
4091
- paddingBottom: spacing.paddingBottom,
4092
- borderRadius: radiusValue(theme, JumpyRadius.small),
4093
- letterSpacing: bodyMediumEmphasized.letterSpacing,
4094
- fontFamily: theme.fontFamily.familyBase,
4095
- fontSize: bodyMediumEmphasized.fontSize,
4096
- lineHeight: bodyMediumEmphasized.lineHeight,
4097
- fontWeight: bodyMediumEmphasized.fontWeight,
4098
- borderWidth: hasBorder ? borderWidthValue(theme, JumpyBorderWidth.small) : 0,
4099
- borderStyle: hasBorder ? "solid" : "none",
4100
- ...variantStyles,
4101
- ...props.style || {}
4102
- },
4103
- children: /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", flexDirection: "row", alignItems: "center", justifyContent: "center", gap: spacing.gap, whiteSpace: "nowrap" }, children: [
4104
- leadingIcon ? /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "inline-flex shrink-0 items-center justify-center", style: { width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: leadingIcon }) : null,
4105
- /* @__PURE__ */ jsx2("span", { children }),
4106
- trailingIcon ? /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "inline-flex shrink-0 items-center justify-center", style: { width: iconSize, height: iconSize, lineHeight: 0, fontSize: iconSize }, children: trailingIcon }) : null
4107
- ] })
4108
- }
4109
- );
4110
- }
4111
- var JumpInput = forwardRef(function JumpInput2({ className = "", ...props }, ref) {
4112
- const theme = useJumpyTheme();
4113
- const fieldContext = useContext(JumpyFieldContext);
4114
- const inputOffset = fieldContext?.hasLabel ? spacingValue(theme, JumpySpacing.large) : spacingValue(theme, JumpySpacing.zero);
4115
- const inputMinHeight = fieldContext ? void 0 : sizeValue(theme, JumpySize.largeXL) - spacingValue(theme, JumpySpacing.small3XS) * 2;
4116
- const inputElement = /* @__PURE__ */ jsx2(
4117
- "input",
4118
- {
4119
- ...props,
4120
- ref,
4121
- "data-jumpy-component": "input",
4122
- className: cn("w-full text-sm disabled:cursor-not-allowed", className),
4123
- style: {
4124
- ...getFieldInputStyles(theme, props.disabled),
4125
- minHeight: inputMinHeight,
4126
- marginTop: inputOffset,
4127
- ...props.style || {}
4128
- }
4129
- }
4130
- );
4131
- if (fieldContext) {
4132
- return inputElement;
4133
- }
4134
- return renderStandaloneField(theme, props.disabled, "", null, inputElement, props.status || "regular", props.readOnly);
4135
- });
4136
- function TokenEditorField(props) {
4137
- const fieldContext = useContext(JumpyFieldContext);
4138
- return /* @__PURE__ */ jsx2(TokenEditor, { ...props, fieldContext });
4139
- }
4140
- function JumpTokenInput({ ref, value, defaultValue = [], onChange, onTokenInsert, onTokenRemove, multiline = false, minRows = 3, maxRows, hideClearIcon = false, onClear, label, hint, status = "regular", error = false, disabled = false, readOnly = false, className = "", ...props }) {
4141
- const editorHandle = useRef2(null);
4142
- const [internalValue, setInternalValue] = useState2(() => normalizeTokenInputValue(defaultValue, multiline));
4143
- const [, refresh] = useState2(0);
4144
- if (multiline && (!Number.isInteger(minRows) || minRows < 1 || maxRows !== void 0 && (!Number.isInteger(maxRows) || maxRows < minRows))) throw new Error("JumpTokenInput requires minRows >= 1 and maxRows >= minRows");
4145
- const model = normalizeTokenInputValue(value === void 0 ? internalValue : value, multiline);
4146
- const change = (next) => {
4147
- if (disabled || readOnly) return;
4148
- if (value === void 0) setInternalValue(next);
4149
- else refresh((revision) => revision + 1);
4150
- onChange?.(next);
4151
- const previousTokens = model.filter((part) => part.type === "token");
4152
- const nextTokens = next.filter((part) => part.type === "token");
4153
- for (const token of nextTokens) if (!previousTokens.some((previous) => previous.id === token.id)) onTokenInsert?.(token);
4154
- for (const token of previousTokens) if (!nextTokens.some((current) => current.id === token.id)) onTokenRemove?.(token);
4155
- };
4156
- return /* @__PURE__ */ jsx2(JumpField, { label, hint, status, error, hideClearIcon, onClear: () => editorHandle.current?.clear(), className, children: /* @__PURE__ */ jsx2(TokenEditorField, { ...props, editorRef: (handle) => {
4157
- editorHandle.current = handle;
4158
- if (typeof ref === "function") return ref(handle);
4159
- if (ref) ref.current = handle;
4160
- }, model, multiline, minRows, maxRows, value: tokenInputText(model), "data-has-value": model.length > 0, disabled, readOnly, onValueChange: change, onClear }) });
4161
- }
4162
- function JumpTextarea({ className = "", ...props }) {
4163
- const theme = useJumpyTheme();
4164
- const fieldContext = useContext(JumpyFieldContext);
4165
- const inputOffset = fieldContext?.hasLabel ? spacingValue(theme, JumpySpacing.large) : spacingValue(theme, JumpySpacing.zero);
4166
- const textareaElement = /* @__PURE__ */ jsx2(
4167
- "textarea",
4168
- {
4169
- ...props,
4170
- "data-jumpy-component": "textarea",
4171
- className: cn("w-full text-sm disabled:cursor-not-allowed", className),
4172
- style: {
4173
- ...getFieldInputStyles(theme, props.disabled),
4174
- minHeight: props.rows ? Number(props.rows) * 24 : sizeValue(theme, JumpySize.large2XL),
4175
- marginTop: inputOffset,
4176
- resize: "none",
4177
- ...props.style || {}
4178
- }
4179
- }
4180
- );
4181
- if (fieldContext) {
4182
- return textareaElement;
4183
- }
4184
- return renderStandaloneField(theme, props.disabled, "", { alignItems: "flex-start", height: "auto", maxHeight: "none", paddingRight: spacingValue(theme, JumpySpacing.small3XS) }, textareaElement, props.status || "regular", props.readOnly);
4185
- }
4186
- function JumpBaseInput({ label, hint, children, className = "", error = false, hideClearIcon = false, showCharCount = false, onClear, ...props }) {
4187
- const theme = useJumpyTheme();
4188
- const fieldId = useId();
4189
- const labelId = useId();
4190
- const helperId = useId();
4191
- const counterId = useId();
4192
- const child = Children.only(children);
4193
- const childValue = isValidElement(child) ? child.props.value ?? child.props.defaultValue ?? "" : "";
4194
- const childHasValue = isValidElement(child) && (child.props["data-has-value"] === true || child.props["data-has-value"] === "true");
4195
- const [focused, setFocused] = useState2(false);
4196
- const [hasValue, setHasValue] = useState2(childHasValue || Boolean(String(childValue).length));
4197
- const [inputValue, setInputValue] = useState2(String(childValue ?? ""));
4198
- const bodySmall = textStyleValue(theme, JumpyTextStyle.bodySmall);
4199
- const bodyMedium = textStyleValue(theme, JumpyTextStyle.bodyMedium);
4200
- const labelSmall = textStyleValue(theme, JumpyTextStyle.labelSmall);
4201
- useEffect2(() => {
4202
- setHasValue(childHasValue || Boolean(String(childValue).length));
4203
- setInputValue(String(childValue ?? ""));
4204
- }, [childHasValue, childValue]);
4205
- if (!isValidElement(child)) {
4206
- return /* @__PURE__ */ jsx2("div", { className: cn("block w-full", className), children });
4207
- }
4208
- const originalOnFocus = child.props.onFocus;
4209
- const originalOnBlur = child.props.onBlur;
4210
- const originalOnChange = child.props.onChange;
4211
- const disabled = Boolean(child.props.disabled);
4212
- const isDropdownTrigger = child.props["data-jumpy-dropdown-trigger"] === true || child.props["data-jumpy-dropdown-trigger"] === "true";
4213
- const readOnly = Boolean(child.props.readOnly) && !isDropdownTrigger;
4214
- const floating = focused && !readOnly || hasValue || Boolean(props.slots?.content);
4215
- const isTextarea = child.type === JumpTextarea || child.type === TokenEditorField && child.props.multiline;
4216
- const isDropdown = isDropdownTrigger || child.props["data-jumpy-component"] === "dropdown";
4217
- const status = error ? "alarm" : props.status || "regular";
4218
- const leading = props.slots?.leading ?? props.leading;
4219
- const trailing = props.slots?.trailing ?? props.trailing;
4220
- const content = props.slots?.content;
4221
- const helperText = props.slots?.helperText ?? hint;
4222
- const hasHelperText = Boolean(helperText);
4223
- const maxLength = child.props.maxLength;
4224
- const hasCounter = showCharCount && maxLength !== void 0 && maxLength !== null;
4225
- const showClearButton = hasValue && !hideClearIcon && !disabled && !readOnly;
4226
- const showErrorIcon = Boolean(error) && !disabled && !readOnly;
4227
- const showTrailing = showClearButton || trailing || showErrorIcon;
4228
- const describedBy = [child.props["aria-describedby"], hasHelperText ? helperId : void 0, hasCounter ? counterId : void 0].filter(Boolean).join(" ") || void 0;
4229
- const labelScale = labelSmall.fontSize / bodyMedium.fontSize;
4230
- const fieldInnerHeight = sizeValue(theme, JumpySize.largeXL) - spacingValue(theme, JumpySpacing.small3XS) * 2;
4231
- const labelRestingY = fieldInnerHeight / 2;
4232
- const labelFloatingY = (labelSmall.fontSize * labelSmall.lineHeight - labelSmall.fontSize) / 2;
4233
- const labelTiming = focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease;
4234
- const enhancedChild = cloneElement(child, {
4235
- id: child.props.id || fieldId,
4236
- "aria-labelledby": child.props["aria-labelledby"] || (!child.props["aria-label"] && label ? labelId : void 0),
4237
- "aria-invalid": error ? true : child.props["aria-invalid"],
4238
- "aria-describedby": describedBy,
4239
- placeholder: !label || isDropdown || floating ? child.props.placeholder : " ",
4240
- style: content ? {
4241
- ...child.props.style || {},
4242
- position: "absolute",
4243
- inset: 0,
4244
- zIndex: 2,
4245
- width: "100%",
4246
- height: "100%",
4247
- margin: 0,
4248
- opacity: 0,
4249
- cursor: disabled || readOnly ? "default" : "pointer"
4250
- } : child.props.style,
4251
- onFocus: (event) => {
4252
- setFocused(true);
4253
- originalOnFocus?.(event);
4254
- },
4255
- onBlur: (event) => {
4256
- setFocused(false);
4257
- setHasValue(childHasValue || Boolean(String(event.target?.value ?? "").length));
4258
- originalOnBlur?.(event);
4259
- },
4260
- onChange: (event) => {
4261
- setHasValue(childHasValue || Boolean(String(event.target?.value ?? "").length));
4262
- setInputValue(String(event.target?.value ?? ""));
4263
- originalOnChange?.(event);
4264
- }
4265
- });
4266
- const Container = label ? "label" : "div";
4267
- const containerProps = label ? { htmlFor: child.props.id || fieldId } : {
4268
- onClick: () => {
4269
- if (!disabled) document.getElementById(child.props.id || fieldId)?.focus();
4270
- }
4271
- };
4272
- return /* @__PURE__ */ jsxs2("div", { "data-jumpy-component": "base-input-root", className: cn("inline-flex w-full max-w-full flex-col", className), style: { color: "var(--jumpy-cmp-base-input-helper-color)" }, children: [
4273
- /* @__PURE__ */ jsx2(
4274
- Container,
4275
- {
4276
- ...containerProps,
4277
- "data-jumpy-component": "base-input",
4278
- "data-disabled": disabled ? "true" : "false",
4279
- "data-readonly": readOnly ? "true" : "false",
4280
- "data-status": status,
4281
- style: { color: "var(--jumpy-cmp-base-input-helper-color)" },
4282
- children: /* @__PURE__ */ jsxs2(
4283
- "div",
4284
- {
4285
- "data-jumpy-component": "field-control",
4286
- "data-disabled": disabled ? "true" : "false",
4287
- "data-readonly": readOnly ? "true" : "false",
4288
- "data-status": status,
4289
- "data-testid": props["data-testid"],
4290
- onMouseDown: (event) => {
4291
- const control = document.getElementById(child.props.id || fieldId);
4292
- const interactive = event.target.closest("input, textarea, select, button, a, [tabindex]");
4293
- if (disabled || !control || event.button !== 0 || interactive && event.currentTarget.contains(interactive)) return;
4294
- event.preventDefault();
4295
- control.focus();
4296
- },
4297
- style: {
4298
- ...getFieldShellStyles(theme, disabled, status, focused, readOnly),
4299
- alignItems: isTextarea ? "flex-start" : "center",
4300
- position: "relative",
4301
- height: isTextarea ? "auto" : sizeValue(theme, JumpySize.largeXL),
4302
- minHeight: isTextarea ? sizeValue(theme, JumpySize.large2XL) : sizeValue(theme, JumpySize.largeXL)
4303
- },
4304
- children: [
4305
- leading ? /* @__PURE__ */ jsx2(
4306
- "span",
4307
- {
4308
- "aria-hidden": "true",
4309
- className: "inline-flex shrink-0 items-center justify-center",
4310
- style: {
4311
- width: sizeValue(theme, JumpySize.small),
4312
- height: sizeValue(theme, JumpySize.small),
4313
- color: "var(--jumpy-cmp-base-input-icon-fill)",
4314
- transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`
4315
- },
4316
- children: leading
4317
- }
4318
- ) : null,
4319
- /* @__PURE__ */ jsxs2(
4320
- "div",
4321
- {
4322
- style: {
4323
- position: "relative",
4324
- display: "flex",
4325
- alignItems: isTextarea ? "initial" : "center",
4326
- alignSelf: "stretch",
4327
- flex: 1,
4328
- minWidth: 0,
4329
- overflow: "hidden"
4330
- },
4331
- children: [
4332
- label && /* @__PURE__ */ jsx2(
4333
- "span",
4334
- {
4335
- id: labelId,
4336
- "data-testid": props.labelTestId,
4337
- style: {
4338
- position: "absolute",
4339
- left: spacingValue(theme, JumpySpacing.zero),
4340
- top: spacingValue(theme, JumpySpacing.zero),
4341
- transform: floating ? `translateY(${labelFloatingY}px) scale(${labelScale})` : `translateY(calc(${labelRestingY}px - 50%)) scale(1)`,
4342
- transformOrigin: "left top",
4343
- color: "var(--jumpy-cmp-base-input-label-color)",
4344
- fontFamily: theme.fontFamily.familyBase,
4345
- fontSize: bodyMedium.fontSize,
4346
- lineHeight: floating ? labelSmall.lineHeight : bodyMedium.lineHeight,
4347
- fontWeight: floating ? labelSmall.fontWeight : bodyMedium.fontWeight,
4348
- letterSpacing: floating ? labelSmall.letterSpacing : bodyMedium.letterSpacing,
4349
- pointerEvents: "none",
4350
- transition: `transform ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${labelTiming}, color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${labelTiming}`,
4351
- maxWidth: "100%",
4352
- overflow: "hidden",
4353
- textOverflow: "ellipsis",
4354
- whiteSpace: "nowrap"
4355
- },
4356
- children: label
4357
- }
4358
- ),
4359
- /* @__PURE__ */ jsx2(JumpyFieldContext.Provider, { value: { fieldId, hasLabel: Boolean(label), floating }, children: enhancedChild }),
4360
- content ? /* @__PURE__ */ jsx2(
4361
- "div",
4362
- {
4363
- "data-jumpy-slot": "content",
4364
- style: {
4365
- width: "100%",
4366
- minWidth: 0,
4367
- paddingTop: label ? spacingValue(theme, JumpySpacing.large) : spacingValue(theme, JumpySpacing.zero),
4368
- color: "var(--jumpy-cmp-base-input-value-color)",
4369
- fontFamily: theme.fontFamily.familyBase,
4370
- fontSize: bodyMedium.fontSize,
4371
- lineHeight: `${bodyMedium.fontSize * bodyMedium.lineHeight}px`,
4372
- overflow: "hidden",
4373
- textOverflow: "ellipsis",
4374
- whiteSpace: "nowrap"
4375
- },
4376
- children: content
4377
- }
4378
- ) : null
4379
- ]
4380
- }
4381
- ),
4382
- showTrailing ? /* @__PURE__ */ jsxs2(
4383
- "span",
4384
- {
4385
- "data-jumpy-slot": "trailing-container",
4386
- className: "inline-flex shrink-0 items-center justify-center",
4387
- style: {
4388
- display: "inline-flex",
4389
- alignItems: isTextarea ? "flex-start" : "center",
4390
- justifyContent: "center",
4391
- alignSelf: isTextarea ? "flex-start" : void 0,
4392
- minHeight: isTextarea ? sizeValue(theme, JumpySize.large) : void 0,
4393
- flexShrink: 0,
4394
- gap: spacingValue(theme, JumpySpacing.smallXS),
4395
- color: "var(--jumpy-cmp-base-input-trailing-color)",
4396
- transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`
4397
- },
4398
- children: [
4399
- showClearButton ? /* @__PURE__ */ jsx2(
4400
- JumpIconButton,
4401
- {
4402
- variant: "secondary",
4403
- "aria-label": "Clear",
4404
- "data-jumpy-slot": "clear-button",
4405
- onClick: (event) => {
4406
- event.preventDefault();
4407
- event.stopPropagation();
4408
- const input = document.getElementById(child.props.id || fieldId);
4409
- if (input && child.props.value === void 0) input.value = "";
4410
- setHasValue(false);
4411
- setInputValue("");
4412
- originalOnChange?.({ target: { value: "" }, currentTarget: { value: "" } });
4413
- onClear?.();
4414
- input?.focus();
4415
- },
4416
- style: {
4417
- width: sizeValue(theme, JumpySize.large),
4418
- height: sizeValue(theme, JumpySize.large),
4419
- minWidth: sizeValue(theme, JumpySize.large),
4420
- minHeight: sizeValue(theme, JumpySize.large),
4421
- padding: spacingValue(theme, JumpySpacing.small2XS),
4422
- borderRadius: radiusValue(theme, JumpyRadius.small)
4423
- },
4424
- children: /* @__PURE__ */ jsx2(CircleClearIcon, {})
4425
- }
4426
- ) : null,
4427
- showErrorIcon || trailing ? /* @__PURE__ */ jsxs2("span", { "data-jumpy-slot": "trailing", "aria-hidden": "true", style: { display: "inline-flex", alignItems: "center", justifyContent: "center", gap: spacingValue(theme, JumpySpacing.smallXS), paddingRight: spacingValue(theme, JumpySpacing.medium), color: "var(--jumpy-cmp-base-input-trailing-color)", fontSize: bodyMedium.fontSize, lineHeight: bodyMedium.lineHeight }, children: [
4428
- showErrorIcon ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "trailing-error", style: { display: "inline-flex", alignItems: "center", color: "var(--jumpy-cmp-base-input-trailing-error-fill, var(--jumpy-cmp-base-input-icon-fill))", lineHeight: 1 }, children: /* @__PURE__ */ jsx2(CircleErrorIcon, { style: { width: sizeValue(theme, JumpySize.small2XS), height: sizeValue(theme, JumpySize.small2XS) } }) }) : null,
4429
- trailing ? /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "trailing-text", style: { display: "inline-flex", alignItems: "center", maxWidth: `calc(30 * ${spacingValue(theme, JumpySpacing.smallXS)}px)`, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", lineHeight: 1 }, children: trailing }) : null
4430
- ] }) : null
4431
- ]
4432
- }
4433
- ) : null
4434
- ]
4435
- }
4436
- )
4437
- }
4438
- ),
4439
- (hasHelperText || hasCounter) && /* @__PURE__ */ jsxs2(
4440
- "div",
4441
- {
4442
- className: "flex justify-end",
4443
- style: {
4444
- gap: spacingValue(theme, JumpySpacing.smallXS),
4445
- paddingTop: spacingValue(theme, JumpySpacing.small3XS),
4446
- paddingLeft: spacingValue(theme, JumpySpacing.large),
4447
- paddingRight: spacingValue(theme, JumpySpacing.large),
4448
- color: "var(--jumpy-cmp-base-input-helper-color)",
4449
- fontSize: bodySmall.fontSize,
4450
- lineHeight: `${bodySmall.fontSize * bodySmall.lineHeight}px`,
4451
- transition: `color ${animationDurationValue(theme, JumpyAnimationDuration.smallXS)}s ${focused && !readOnly ? theme.animation.timeEasingImpulse : theme.animation.timeEasingRelease}`
4452
- },
4453
- children: [
4454
- hasHelperText ? /* @__PURE__ */ jsx2("span", { id: helperId, style: { flex: 1 }, children: helperText }) : null,
4455
- hasCounter ? /* @__PURE__ */ jsxs2("span", { id: counterId, style: { flex: 0, whiteSpace: "nowrap" }, children: [
4456
- inputValue.length,
4457
- "/",
4458
- maxLength
4459
- ] }) : null
4460
- ]
4461
- }
4462
- )
4463
- ] });
4464
- }
4465
- function JumpField(props) {
4466
- return /* @__PURE__ */ jsx2(JumpBaseInput, { ...props });
4467
- }
4468
- function JumpChoice({ selected = false, title, description, className = "", ...props }) {
4469
- const theme = useJumpyTheme();
4470
- return /* @__PURE__ */ jsxs2(
4471
- "button",
4472
- {
4473
- type: "button",
4474
- ...props,
4475
- "data-jumpy-component": "choice",
4476
- className: cn("w-full p-4 text-left transition active:scale-[0.99] disabled:cursor-not-allowed", className),
4477
- style: {
4478
- borderRadius: theme.sizing.choiceRadius,
4479
- border: `1px solid ${selected ? theme.color.surfacePrimary : theme.color.borderSubtle}`,
4480
- backgroundColor: selected ? theme.color.infoSurface : theme.color.surfaceCard,
4481
- color: theme.color.textPrimary,
4482
- opacity: props.disabled ? theme.opacity.disabled : 1
4483
- },
4484
- children: [
4485
- /* @__PURE__ */ jsx2("span", { className: "block text-sm font-bold", children: title }),
4486
- description && /* @__PURE__ */ jsx2("span", { className: "mt-1 block text-xs leading-5", style: { color: theme.color.textSecondary }, children: description })
4487
- ]
4488
- }
4489
- );
4490
- }
4491
- function JumpNotice({ tone = "info", title, children, className = "" }) {
4492
- const theme = useJumpyTheme();
4493
- const toneStyles = getNoticeToneStyles(theme, tone);
4494
- return /* @__PURE__ */ jsxs2("div", { className: cn("p-4", className), style: { borderRadius: theme.sizing.choiceRadius, border: `1px solid ${toneStyles.borderColor}`, backgroundColor: toneStyles.backgroundColor, color: toneStyles.color }, children: [
4495
- title && /* @__PURE__ */ jsx2("p", { className: "text-sm font-bold", children: title }),
4496
- /* @__PURE__ */ jsx2("div", { className: cn("text-xs leading-5", title && "mt-1"), children })
4497
- ] });
4498
- }
4499
- function JumpTabs({ items, value, onChange, className = "" }) {
4500
- const scrollRef = useRef2(null);
4501
- const indicatorRef = useRef2(null);
4502
- const tabRefs = useRef2(/* @__PURE__ */ new Map());
4503
- const placeIndicatorRef = useRef2(null);
4504
- placeIndicatorRef.current = (animate) => {
4505
- const selectedTab = tabRefs.current.get(value);
4506
- const scrollContainer = scrollRef.current;
4507
- const indicator = indicatorRef.current;
4508
- if (!selectedTab || !scrollContainer || !indicator) return;
4509
- const tabBounds = selectedTab.getBoundingClientRect();
4510
- const containerBounds = scrollContainer.getBoundingClientRect();
4511
- const tabStyles = getComputedStyle(selectedTab);
4512
- const content = selectedTab.firstElementChild;
4513
- const contentStyles = content ? getComputedStyle(content) : null;
4514
- const insetLeft = Number.parseFloat(tabStyles.paddingLeft) + (contentStyles ? Number.parseFloat(contentStyles.paddingLeft) : 0);
4515
- const insetRight = Number.parseFloat(tabStyles.paddingRight) + (contentStyles ? Number.parseFloat(contentStyles.paddingRight) : 0);
4516
- const x = tabBounds.left - containerBounds.left + scrollContainer.scrollLeft + insetLeft;
4517
- const width = tabBounds.width - insetLeft - insetRight;
4518
- if (!animate) indicator.style.transition = "none";
4519
- indicator.style.setProperty("--jumpy-cmp-tabs-indicator-x", `${x}px`);
4520
- indicator.style.setProperty("--jumpy-cmp-tabs-indicator-width", `${width}px`);
4521
- indicator.style.setProperty("--jumpy-cmp-tabs-indicator-opacity", "1");
4522
- if (!animate) {
4523
- indicator.getBoundingClientRect();
4524
- indicator.style.transition = "";
4525
- }
4526
- };
4527
- const indicatorPlacedRef = useRef2(false);
4528
- useLayoutEffect2(() => {
4529
- placeIndicatorRef.current(indicatorPlacedRef.current);
4530
- indicatorPlacedRef.current = true;
4531
- }, [items, value]);
4532
- useEffect2(() => {
4533
- const scrollContainer = scrollRef.current;
4534
- if (!scrollContainer || typeof ResizeObserver === "undefined") return void 0;
4535
- const sizes = /* @__PURE__ */ new WeakMap();
4536
- const observer = new ResizeObserver((entries) => {
4537
- let resized = false;
4538
- entries.forEach(({ target, contentRect }) => {
4539
- const size2 = `${contentRect.width}x${contentRect.height}`;
4540
- if (sizes.has(target) && sizes.get(target) !== size2) resized = true;
4541
- sizes.set(target, size2);
4542
- });
4543
- if (resized) placeIndicatorRef.current(false);
4544
- });
4545
- observer.observe(scrollContainer);
4546
- tabRefs.current.forEach((tab) => observer.observe(tab));
4547
- return () => observer.disconnect();
4548
- }, [items]);
4549
- return /* @__PURE__ */ jsx2("div", { className, "data-jumpy-component": "tabs", children: /* @__PURE__ */ jsxs2("div", { ref: scrollRef, "data-jumpy-slot": "tabs-scroll-container", role: "tablist", "aria-orientation": "horizontal", tabIndex: -1, children: [
4550
- items.map((item) => {
4551
- const active = item.key === value;
4552
- return /* @__PURE__ */ jsx2(
4553
- "button",
4554
- {
4555
- ref: (node) => {
4556
- if (node) tabRefs.current.set(item.key, node);
4557
- else tabRefs.current.delete(item.key);
4558
- },
4559
- type: "button",
4560
- role: "tab",
4561
- "aria-selected": active,
4562
- onClick: (event) => {
4563
- onChange(item.key);
4564
- event.currentTarget.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
4565
- },
4566
- "data-jumpy-component": "tab",
4567
- children: /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "tab-content", children: /* @__PURE__ */ jsx2("span", { "data-jumpy-slot": "tab-label", "data-text": item.label, children: item.label }) })
4568
- },
4569
- item.key
4570
- );
4571
- }),
4572
- /* @__PURE__ */ jsx2(
4573
- "div",
4574
- {
4575
- ref: indicatorRef,
4576
- "aria-hidden": "true",
4577
- "data-jumpy-slot": "tabs-indicator"
4578
- }
4579
- )
4580
- ] }) });
4581
- }
4582
- function JumpModal({ children }) {
4583
- return /* @__PURE__ */ jsx2(JumpDialog, { visible: true, children });
4584
- }
4585
116
  export {
4586
117
  BrandMark,
4587
118
  JumpAppShell,