@pk-design/react-tailwind 1.8.1 → 1.10.0
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/cjs/components/Banner/Banner.js +1 -1
- package/dist/cjs/components/Banner/Banner.js.map +1 -1
- package/dist/cjs/components/BottomNavigation/BottomNavigation.js +14 -5
- package/dist/cjs/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/cjs/components/Button/Button.js +88 -60
- package/dist/cjs/components/Button/Button.js.map +1 -1
- package/dist/cjs/components/Drawer/Drawer.js +6 -2
- package/dist/cjs/components/Drawer/Drawer.js.map +1 -1
- package/dist/cjs/components/MobileHeader/MobileHeader.js +21 -3
- package/dist/cjs/components/MobileHeader/MobileHeader.js.map +1 -1
- package/dist/cjs/components/PullToRefresh/PullToRefresh.js +145 -57
- package/dist/cjs/components/PullToRefresh/PullToRefresh.js.map +1 -1
- package/dist/cjs/components/SelectBox/SelectBox.js +25 -4
- package/dist/cjs/components/SelectBox/SelectBox.js.map +1 -1
- package/dist/cjs/components/StickyActionBar/StickyActionBar.js +2 -2
- package/dist/cjs/components/StickyActionBar/StickyActionBar.js.map +1 -1
- package/dist/cjs/components/Toast/Toast.js +1 -1
- package/dist/cjs/components/Toast/Toast.js.map +1 -1
- package/dist/cjs/hooks/use-breakpoint.js +48 -0
- package/dist/cjs/hooks/use-breakpoint.js.map +1 -0
- package/dist/cjs/index.js +5 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/components/Banner/Banner.js +1 -1
- package/dist/es/components/Banner/Banner.js.map +1 -1
- package/dist/es/components/BottomNavigation/BottomNavigation.js +14 -5
- package/dist/es/components/BottomNavigation/BottomNavigation.js.map +1 -1
- package/dist/es/components/Button/Button.js +89 -61
- package/dist/es/components/Button/Button.js.map +1 -1
- package/dist/es/components/Drawer/Drawer.js +6 -2
- package/dist/es/components/Drawer/Drawer.js.map +1 -1
- package/dist/es/components/MobileHeader/MobileHeader.js +21 -3
- package/dist/es/components/MobileHeader/MobileHeader.js.map +1 -1
- package/dist/es/components/PullToRefresh/PullToRefresh.js +146 -58
- package/dist/es/components/PullToRefresh/PullToRefresh.js.map +1 -1
- package/dist/es/components/SelectBox/SelectBox.js +26 -5
- package/dist/es/components/SelectBox/SelectBox.js.map +1 -1
- package/dist/es/components/StickyActionBar/StickyActionBar.js +2 -2
- package/dist/es/components/StickyActionBar/StickyActionBar.js.map +1 -1
- package/dist/es/components/Toast/Toast.js +1 -1
- package/dist/es/components/Toast/Toast.js.map +1 -1
- package/dist/es/hooks/use-breakpoint.js +43 -0
- package/dist/es/hooks/use-breakpoint.js.map +1 -0
- package/dist/es/index.js +1 -0
- package/dist/es/index.js.map +1 -1
- package/dist/react-tailwind.cjs.js +344 -135
- package/dist/react-tailwind.cjs.js.map +1 -1
- package/dist/react-tailwind.es.js +342 -137
- package/dist/react-tailwind.es.js.map +1 -1
- package/dist/types/components/BottomNavigation/BottomNavigation.d.ts +1 -1
- package/dist/types/components/BottomNavigation/BottomNavigation.types.d.ts +28 -3
- package/dist/types/components/Button/Button.types.d.ts +12 -6
- package/dist/types/components/Drawer/Drawer.types.d.ts +8 -4
- package/dist/types/components/MobileHeader/MobileHeader.d.ts +1 -1
- package/dist/types/components/MobileHeader/MobileHeader.types.d.ts +13 -0
- package/dist/types/components/PullToRefresh/PullToRefresh.d.ts +1 -1
- package/dist/types/components/PullToRefresh/PullToRefresh.types.d.ts +45 -6
- package/dist/types/components/SelectBox/SelectBox.types.d.ts +18 -1
- package/dist/types/components/StickyActionBar/StickyActionBar.types.d.ts +12 -0
- package/dist/types/hooks/use-breakpoint.d.ts +27 -0
- package/dist/types/hooks/use-breakpoint.test.d.ts +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/test-utils/mock-viewport.d.ts +7 -0
- package/package.json +1 -1
|
@@ -151,59 +151,35 @@ TextContent.displayName = 'TextContent';
|
|
|
151
151
|
|
|
152
152
|
/**
|
|
153
153
|
* Tailwind CSS classes for theme + variant combinations.
|
|
154
|
+
*
|
|
155
|
+
* Only colours live here. Focus outline width/offset and disabled styling are
|
|
156
|
+
* applied uniformly in the component so they can be toggled in one place.
|
|
154
157
|
*/
|
|
155
158
|
const THEMED_BTN_CLASS = {
|
|
156
159
|
primary: {
|
|
157
|
-
default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:
|
|
158
|
-
outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus-visible:
|
|
159
|
-
plain: 'text-[var(--ui-primary-text)] hover:bg-
|
|
160
|
-
},
|
|
161
|
-
danger: {
|
|
162
|
-
default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:bg-[var(--ui-danger-disabled)]',
|
|
163
|
-
outlined: 'text-[var(--ui-danger-text)] hover:text-white border border-[var(--ui-danger)] hover:bg-[var(--ui-danger)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)] disabled:border-[var(--ui-danger-disabled)]',
|
|
164
|
-
plain: 'text-[var(--ui-danger-text)] hover:bg-red-50/80 dark:hover:bg-red-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)]',
|
|
165
|
-
},
|
|
166
|
-
secondary: {
|
|
167
|
-
default: 'text-gray-700 bg-white border border-[var(--ui-border)] hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:bg-gray-200 dark:disabled:bg-gray-700',
|
|
168
|
-
outlined: 'text-gray-700 hover:text-white border border-[var(--ui-border)] hover:bg-gray-700 dark:text-gray-200 dark:hover:text-white dark:hover:bg-gray-600 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400 disabled:border-[var(--ui-border-muted)]',
|
|
169
|
-
plain: 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-800 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400',
|
|
170
|
-
},
|
|
171
|
-
success: {
|
|
172
|
-
default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-emerald-500 disabled:bg-emerald-300 dark:disabled:bg-emerald-900',
|
|
173
|
-
outlined: 'text-emerald-600 hover:text-white border border-emerald-600 hover:bg-emerald-600 dark:text-emerald-400 dark:border-emerald-500 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-emerald-500 disabled:opacity-50',
|
|
174
|
-
plain: 'text-emerald-600 hover:bg-emerald-50 dark:text-emerald-400 dark:hover:bg-emerald-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-emerald-500 disabled:opacity-50',
|
|
175
|
-
},
|
|
176
|
-
warning: {
|
|
177
|
-
default: 'text-white bg-amber-500 hover:bg-amber-600 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-amber-500 disabled:bg-amber-300 dark:disabled:bg-amber-900',
|
|
178
|
-
outlined: 'text-amber-600 hover:text-white border border-amber-500 hover:bg-amber-500 dark:text-amber-400 dark:border-amber-500 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-amber-500 disabled:opacity-50',
|
|
179
|
-
plain: 'text-amber-600 hover:bg-amber-50 dark:text-amber-400 dark:hover:bg-amber-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-amber-500 disabled:opacity-50',
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
const LOADER_TEXT_CLASS = {
|
|
183
|
-
primary: {
|
|
184
|
-
default: 'text-white',
|
|
185
|
-
outlined: 'text-[var(--ui-primary-text)]',
|
|
186
|
-
plain: 'text-[var(--ui-primary-text)]',
|
|
160
|
+
default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:outline-[var(--ui-primary-ring)]',
|
|
161
|
+
outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus-visible:outline-[var(--ui-primary-ring)]',
|
|
162
|
+
plain: 'text-[var(--ui-primary-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-primary-ring)]',
|
|
187
163
|
},
|
|
188
164
|
danger: {
|
|
189
|
-
default: 'text-white',
|
|
190
|
-
outlined: 'text-[var(--ui-danger-text)]',
|
|
191
|
-
plain: 'text-[var(--ui-danger-text)]',
|
|
165
|
+
default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:outline-[var(--ui-danger-ring)]',
|
|
166
|
+
outlined: 'text-[var(--ui-danger-text)] hover:text-white border border-[var(--ui-danger)] hover:bg-[var(--ui-danger)] focus-visible:outline-[var(--ui-danger-ring)]',
|
|
167
|
+
plain: 'text-[var(--ui-danger-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-danger-ring)]',
|
|
192
168
|
},
|
|
193
169
|
secondary: {
|
|
194
|
-
default: 'text-gray-700 dark:text-white',
|
|
195
|
-
outlined: 'text-gray-700 dark:text-gray-200',
|
|
196
|
-
plain: 'text-gray-700 dark:text-gray-200',
|
|
170
|
+
default: 'text-gray-700 bg-white border border-[var(--ui-border)] hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 focus-visible:outline-[var(--ui-focus-ring)]',
|
|
171
|
+
outlined: 'text-gray-700 hover:text-white border border-[var(--ui-border)] hover:bg-gray-700 dark:text-gray-200 dark:hover:text-white dark:hover:bg-gray-600 focus-visible:outline-[var(--ui-focus-ring)]',
|
|
172
|
+
plain: 'text-gray-700 hover:bg-current/10 active:bg-current/15 dark:text-gray-200 focus-visible:outline-[var(--ui-focus-ring)]',
|
|
197
173
|
},
|
|
198
174
|
success: {
|
|
199
|
-
default: 'text-white',
|
|
200
|
-
outlined: 'text-emerald-600 dark:text-emerald-400',
|
|
201
|
-
plain: 'text-emerald-
|
|
175
|
+
default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:outline-emerald-500',
|
|
176
|
+
outlined: 'text-emerald-700 hover:text-white border border-emerald-600 hover:bg-emerald-600 dark:text-emerald-400 dark:border-emerald-500 focus-visible:outline-emerald-500',
|
|
177
|
+
plain: 'text-emerald-700 hover:bg-current/10 active:bg-current/15 dark:text-emerald-400 focus-visible:outline-emerald-500',
|
|
202
178
|
},
|
|
203
179
|
warning: {
|
|
204
|
-
default: 'text-white',
|
|
205
|
-
outlined: 'text-amber-
|
|
206
|
-
plain: 'text-amber-
|
|
180
|
+
default: 'text-white bg-amber-500 hover:bg-amber-600 focus-visible:outline-amber-500',
|
|
181
|
+
outlined: 'text-amber-700 hover:text-white border border-amber-500 hover:bg-amber-500 dark:text-amber-400 dark:border-amber-500 focus-visible:outline-amber-500',
|
|
182
|
+
plain: 'text-amber-700 hover:bg-current/10 active:bg-current/15 dark:text-amber-400 focus-visible:outline-amber-500',
|
|
207
183
|
},
|
|
208
184
|
};
|
|
209
185
|
const SIZE_CLASSES = {
|
|
@@ -223,22 +199,32 @@ const LOADER_SIZE = {
|
|
|
223
199
|
};
|
|
224
200
|
/**
|
|
225
201
|
* Base shared tailwind classes.
|
|
202
|
+
*
|
|
203
|
+
* Disabled styling is keyed off both the native `disabled` attribute and
|
|
204
|
+
* `aria-disabled`, so non-button elements rendered via `as` look disabled too.
|
|
226
205
|
*/
|
|
227
206
|
const BASE_BTN_CLASS = 'font-medium inline-flex items-center justify-center shrink-0 cursor-pointer select-none ' +
|
|
228
|
-
'
|
|
229
|
-
'active:scale-[0.98]
|
|
207
|
+
'transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-150 ease-out ' +
|
|
208
|
+
'active:scale-[0.98] focus:outline-none ' +
|
|
209
|
+
'disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none ' +
|
|
210
|
+
'aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:shadow-none';
|
|
230
211
|
const Button = React.forwardRef(({ as, type, ...props }, ref) => {
|
|
231
|
-
var _a, _b, _c, _d;
|
|
232
212
|
const { className = '', children, theme = 'primary', variant = 'default', size = 'md', leftIcon, rightIcon, fullWidth = false, loading = false, loadingText = '', rounded = false, disabled, iconOnly = false, noOutlineOnFocus = false, active, onClick, ...restProps } = props;
|
|
233
213
|
const Component = as || 'button';
|
|
234
214
|
const isNativeButton = Component === 'button';
|
|
215
|
+
// Clean invalid props when not rendering a real button
|
|
216
|
+
const { type: _ignoreType, disabled: _ignoreDisabled, role, tabIndex, onKeyDown, onKeyUp, ...domProps } = restProps;
|
|
217
|
+
const ariaLabel = domProps['aria-label'];
|
|
218
|
+
const ariaLabelledBy = domProps['aria-labelledby'];
|
|
235
219
|
// Accessibility guard for icon-only buttons
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
220
|
+
React.useEffect(() => {
|
|
221
|
+
if (iconOnly && !ariaLabel && !ariaLabelledBy) {
|
|
222
|
+
console.warn('[ui] <Button iconOnly> requires aria-label or aria-labelledby for accessibility.');
|
|
223
|
+
}
|
|
224
|
+
}, [iconOnly, ariaLabel, ariaLabelledBy]);
|
|
239
225
|
const resolvedDisabled = Boolean(disabled || loading);
|
|
240
|
-
//
|
|
241
|
-
const
|
|
226
|
+
// Links (`href`, or `to` for router Link components) keep their native semantics.
|
|
227
|
+
const isLink = !isNativeButton && (domProps.href != null || domProps.to != null);
|
|
242
228
|
const handleClick = (e) => {
|
|
243
229
|
if (resolvedDisabled) {
|
|
244
230
|
e.preventDefault();
|
|
@@ -247,31 +233,73 @@ const Button = React.forwardRef(({ as, type, ...props }, ref) => {
|
|
|
247
233
|
}
|
|
248
234
|
onClick === null || onClick === void 0 ? void 0 : onClick(e);
|
|
249
235
|
};
|
|
236
|
+
// Mirror native <button> keyboard activation for non-button elements:
|
|
237
|
+
// Enter activates on keydown, Space on keyup (and must not scroll the page).
|
|
238
|
+
const handleKeyDown = (e) => {
|
|
239
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
|
|
240
|
+
if (e.defaultPrevented || e.target !== e.currentTarget)
|
|
241
|
+
return;
|
|
242
|
+
if (e.key === 'Enter') {
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
e.currentTarget.click();
|
|
245
|
+
}
|
|
246
|
+
else if (e.key === ' ') {
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
const handleKeyUp = (e) => {
|
|
251
|
+
onKeyUp === null || onKeyUp === void 0 ? void 0 : onKeyUp(e);
|
|
252
|
+
if (e.defaultPrevented || e.target !== e.currentTarget)
|
|
253
|
+
return;
|
|
254
|
+
if (e.key === ' ') {
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
e.currentTarget.click();
|
|
257
|
+
}
|
|
258
|
+
};
|
|
250
259
|
const elementProps = isNativeButton
|
|
251
260
|
? {
|
|
252
261
|
type: type !== null && type !== void 0 ? type : 'button',
|
|
253
262
|
disabled: resolvedDisabled,
|
|
263
|
+
role,
|
|
264
|
+
tabIndex,
|
|
254
265
|
onClick: handleClick,
|
|
266
|
+
onKeyDown,
|
|
267
|
+
onKeyUp,
|
|
255
268
|
}
|
|
256
|
-
:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
269
|
+
: isLink
|
|
270
|
+
? {
|
|
271
|
+
role,
|
|
272
|
+
tabIndex: resolvedDisabled ? -1 : tabIndex,
|
|
273
|
+
'aria-disabled': resolvedDisabled ? true : undefined,
|
|
274
|
+
onClick: handleClick,
|
|
275
|
+
onKeyDown,
|
|
276
|
+
onKeyUp,
|
|
277
|
+
}
|
|
278
|
+
: {
|
|
279
|
+
role: role !== null && role !== void 0 ? role : 'button',
|
|
280
|
+
tabIndex: resolvedDisabled ? -1 : (tabIndex !== null && tabIndex !== void 0 ? tabIndex : 0),
|
|
281
|
+
'aria-disabled': resolvedDisabled ? true : undefined,
|
|
282
|
+
onClick: handleClick,
|
|
283
|
+
onKeyDown: handleKeyDown,
|
|
284
|
+
onKeyUp: handleKeyUp,
|
|
285
|
+
};
|
|
262
286
|
const sizeObj = SIZE_CLASSES[size] || SIZE_CLASSES.md;
|
|
263
287
|
const classes = React.useMemo(() => {
|
|
264
288
|
var _a;
|
|
265
|
-
return buildClassName(BASE_BTN_CLASS, ((_a = THEMED_BTN_CLASS[theme]) === null || _a === void 0 ? void 0 : _a[variant]) || THEMED_BTN_CLASS.primary.default, iconOnly ? sizeObj.iconOnly : sizeObj.normal,
|
|
289
|
+
return buildClassName(BASE_BTN_CLASS, ((_a = THEMED_BTN_CLASS[theme]) === null || _a === void 0 ? void 0 : _a[variant]) || THEMED_BTN_CLASS.primary.default, iconOnly ? sizeObj.iconOnly : sizeObj.normal,
|
|
290
|
+
// An outline (not a ring) so the offset gap stays transparent on any surface/mode.
|
|
291
|
+
!noOutlineOnFocus &&
|
|
292
|
+
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid', rounded && 'rounded-full', fullWidth && 'w-full', variant === 'default' && 'shadow-xs hover:shadow-none', active && 'ring-2 ring-current', className);
|
|
266
293
|
}, [theme, variant, sizeObj, iconOnly, noOutlineOnFocus, rounded, fullWidth, active, className]);
|
|
267
|
-
|
|
294
|
+
// The spinner and loading text inherit the button's text colour.
|
|
295
|
+
const loaderIcon = (jsxRuntime.jsx(Loader, { "data-testid": "btn-loader-icon", size: LOADER_SIZE[size] || 'xs', className: "text-current" }));
|
|
268
296
|
/**
|
|
269
297
|
* SPECIAL CASE: iconOnly + loading → show spinner only
|
|
270
298
|
*/
|
|
271
299
|
if (loading && iconOnly) {
|
|
272
|
-
return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...
|
|
300
|
+
return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loaderIcon }));
|
|
273
301
|
}
|
|
274
|
-
return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...
|
|
302
|
+
return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loading ? (jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, loadingText ? (jsxRuntime.jsx(TextContent, { className: "text-current", children: loadingText })) : (children)] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [leftIcon && jsxRuntime.jsx("span", { className: "shrink-0 inline-flex items-center", children: leftIcon }), children, rightIcon && jsxRuntime.jsx("span", { className: "shrink-0 inline-flex items-center", children: rightIcon })] })) }));
|
|
275
303
|
});
|
|
276
304
|
Button.displayName = 'Button';
|
|
277
305
|
|
|
@@ -313,6 +341,45 @@ var SvgX = function SvgX(props) {
|
|
|
313
341
|
})));
|
|
314
342
|
};
|
|
315
343
|
|
|
344
|
+
const BREAKPOINT_QUERIES = {
|
|
345
|
+
medium: '(min-width: 768px)',
|
|
346
|
+
expanded: '(min-width: 1024px)',
|
|
347
|
+
};
|
|
348
|
+
// jsdom and some embedded webviews have no matchMedia; treat every query as
|
|
349
|
+
// unmatched there (the compact layout) rather than throwing.
|
|
350
|
+
const hasMatchMedia = () => typeof window !== 'undefined' && typeof window.matchMedia === 'function';
|
|
351
|
+
/** Live result of a CSS media query. `false` during server rendering or without `matchMedia`. */
|
|
352
|
+
const useMediaQuery = (query) => {
|
|
353
|
+
const subscribe = React.useCallback((onChange) => {
|
|
354
|
+
if (!hasMatchMedia())
|
|
355
|
+
return () => { };
|
|
356
|
+
const list = window.matchMedia(query);
|
|
357
|
+
list.addEventListener('change', onChange);
|
|
358
|
+
return () => list.removeEventListener('change', onChange);
|
|
359
|
+
}, [query]);
|
|
360
|
+
return React.useSyncExternalStore(subscribe, () => hasMatchMedia() && window.matchMedia(query).matches, () => false);
|
|
361
|
+
};
|
|
362
|
+
/** Current layout range; `compact` during server rendering. */
|
|
363
|
+
const useBreakpoint = () => {
|
|
364
|
+
const isMedium = useMediaQuery(BREAKPOINT_QUERIES.medium);
|
|
365
|
+
const isExpanded = useMediaQuery(BREAKPOINT_QUERIES.expanded);
|
|
366
|
+
if (isExpanded)
|
|
367
|
+
return 'expanded';
|
|
368
|
+
return isMedium ? 'medium' : 'compact';
|
|
369
|
+
};
|
|
370
|
+
/** Resolves a `ResponsiveValue` for the current viewport. */
|
|
371
|
+
const useResponsiveValue = (value) => {
|
|
372
|
+
var _a, _b, _c;
|
|
373
|
+
const breakpoint = useBreakpoint();
|
|
374
|
+
if (typeof value !== 'object' || value === null || !('base' in value))
|
|
375
|
+
return value;
|
|
376
|
+
if (breakpoint === 'expanded')
|
|
377
|
+
return (_b = (_a = value.lg) !== null && _a !== void 0 ? _a : value.md) !== null && _b !== void 0 ? _b : value.base;
|
|
378
|
+
if (breakpoint === 'medium')
|
|
379
|
+
return (_c = value.md) !== null && _c !== void 0 ? _c : value.base;
|
|
380
|
+
return value.base;
|
|
381
|
+
};
|
|
382
|
+
|
|
316
383
|
const alignmentMap = {
|
|
317
384
|
center: 'items-center justify-center h-full',
|
|
318
385
|
start: 'justify-start h-full',
|
|
@@ -325,7 +392,8 @@ const panelAlignmentMap = {
|
|
|
325
392
|
start: 'data-[closed]:-translate-x-1/2 rounded-r-2xl',
|
|
326
393
|
end: 'data-[closed]:translate-x-1/2 rounded-l-2xl',
|
|
327
394
|
top: 'data-[closed]:-translate-y-1/2 rounded-b-2xl',
|
|
328
|
-
|
|
395
|
+
// Bottom sheets clear the home indicator on notched phones (0 elsewhere).
|
|
396
|
+
bottom: 'data-[closed]:translate-y-full rounded-t-2xl md:rounded-t-3xl max-h-[88vh] max-h-[88dvh] pb-[env(safe-area-inset-bottom,0px)]',
|
|
329
397
|
};
|
|
330
398
|
const panelSizeMap = {
|
|
331
399
|
xs: 'max-w-xs',
|
|
@@ -336,7 +404,9 @@ const panelSizeMap = {
|
|
|
336
404
|
'2xl': 'max-w-2xl',
|
|
337
405
|
full: 'w-full',
|
|
338
406
|
};
|
|
339
|
-
const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align = 'end', size = 'md', backdrop = true, title, description, children, footer, footerSticky = false, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, showCloseButton = true, dragHandle, }) => {
|
|
407
|
+
const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align: alignProp = 'end', size: sizeProp = 'md', backdrop = true, title, description, children, footer, footerSticky = false, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, showCloseButton = true, dragHandle, }) => {
|
|
408
|
+
const align = useResponsiveValue(alignProp);
|
|
409
|
+
const size = useResponsiveValue(sizeProp);
|
|
340
410
|
const showTitleSection = Boolean(title || description || showBackButton || showCloseButton);
|
|
341
411
|
const shouldShowDragHandle = dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom';
|
|
342
412
|
const [viewportHeight, setViewportHeight] = React.useState(null);
|
|
@@ -595,7 +665,7 @@ const Banner = React.forwardRef((props, ref) => {
|
|
|
595
665
|
const { type = 'info', iconSize = 'sm', children, icon, removable = false, onRemove, className, ...restProps } = props;
|
|
596
666
|
const DefaultIcon = IconMap[type];
|
|
597
667
|
const iconNode = icon !== undefined ? (icon) : (jsxRuntime.jsx(DefaultIcon, { "data-testid": `banner-${type}-icon`, className: buildClassName('shrink-0', sizeMap[iconSize]) }));
|
|
598
|
-
return (jsxRuntime.jsxs("div", { ref: ref, className: buildClassName('text-sm rounded-md flex w-full p-3 items-center gap-3 ring-1 ring-inset', TypeClassMap[type], className), "data-testid": `banner-${type}`, ...restProps, children: [iconNode, jsxRuntime.jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (jsxRuntime.jsx(Button, { iconOnly: true, "aria-label": "Dismiss banner", size: "xs", variant: "plain",
|
|
668
|
+
return (jsxRuntime.jsxs("div", { ref: ref, className: buildClassName('text-sm rounded-md flex w-full p-3 items-center gap-3 ring-1 ring-inset', TypeClassMap[type], className), "data-testid": `banner-${type}`, ...restProps, children: [iconNode, jsxRuntime.jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (jsxRuntime.jsx(Button, { iconOnly: true, "aria-label": "Dismiss banner", size: "xs", variant: "plain", className: "text-current focus-visible:outline-current shrink-0", onClick: () => onRemove === null || onRemove === void 0 ? void 0 : onRemove(), "data-testid": "banner-remove-btn", children: jsxRuntime.jsx(SvgX, { className: "size-4.5", "data-testid": "banner-remove-icon" }) }))] }));
|
|
599
669
|
});
|
|
600
670
|
Banner.displayName = 'Banner';
|
|
601
671
|
|
|
@@ -651,20 +721,23 @@ const BodyText = React.forwardRef((props, ref) => {
|
|
|
651
721
|
});
|
|
652
722
|
BodyText.displayName = 'BodyText';
|
|
653
723
|
|
|
654
|
-
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', }) {
|
|
724
|
+
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', linkAs = 'a', linkHrefProp = 'href', ariaLabel = 'Bottom Navigation', hidden = false, }) {
|
|
655
725
|
const handleTabClick = (item) => {
|
|
656
726
|
var _a;
|
|
657
727
|
if (item.disabled)
|
|
658
728
|
return;
|
|
659
729
|
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
|
|
660
|
-
onChange(item.id);
|
|
730
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(item.id);
|
|
661
731
|
};
|
|
732
|
+
// Removed from the tree, not just covered, so hidden tabs can't take focus.
|
|
733
|
+
if (hidden)
|
|
734
|
+
return null;
|
|
662
735
|
const themeStyles = {
|
|
663
736
|
light: 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',
|
|
664
737
|
dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',
|
|
665
738
|
primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',
|
|
666
739
|
}[theme];
|
|
667
|
-
return (jsxRuntime.jsx("nav", { "aria-label":
|
|
740
|
+
return (jsxRuntime.jsx("nav", { "aria-label": ariaLabel, className: buildClassName('w-full flex items-center justify-around select-none shadow-lg transition-colors relative', fixed && 'fixed bottom-0 left-0 right-0 z-40', 'pb-[env(safe-area-inset-bottom,6px)] px-2', themeStyles, containerClass), "data-testid": "bottom-navigation", children: items.map((item) => {
|
|
668
741
|
const isActive = item.id === activeId;
|
|
669
742
|
const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive);
|
|
670
743
|
const activeItemColor = {
|
|
@@ -687,11 +760,17 @@ function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels
|
|
|
687
760
|
info: 'bg-blue-500 text-white',
|
|
688
761
|
success: 'bg-emerald-500 text-white',
|
|
689
762
|
}[item.badgeVariant || 'danger'];
|
|
690
|
-
|
|
763
|
+
// A disabled item stays a button: a link can't be disabled.
|
|
764
|
+
const isLink = Boolean(item.href) && !item.disabled;
|
|
765
|
+
const ItemElement = isLink ? linkAs : 'button';
|
|
766
|
+
const elementProps = isLink
|
|
767
|
+
? { [linkHrefProp]: item.href }
|
|
768
|
+
: { type: 'button', disabled: item.disabled };
|
|
769
|
+
return (jsxRuntime.jsxs(ItemElement, { ...elementProps, onClick: () => handleTabClick(item), className: buildClassName('flex-1 flex flex-col items-center justify-center pt-2 pb-1.5 px-1 transition-all duration-200 relative cursor-pointer group', item.disabled && 'opacity-40 cursor-not-allowed pointer-events-none', isActive ? activeItemColor : inactiveItemColor, itemClass), "data-testid": `bottom-nav-item-${item.id}`, "aria-current": isActive ? 'page' : undefined, "aria-label": !showLabelText && typeof item.label === 'string' ? item.label : undefined, children: [activeIndicatorStyle === 'line' && isActive && (jsxRuntime.jsx("span", { className: buildClassName('absolute -top-[1px] h-0.75 w-9 rounded-full transition-all duration-200 z-10', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') })), jsxRuntime.jsxs("div", { className: buildClassName('relative inline-flex items-center justify-center transition-all duration-200', activeIndicatorStyle === 'pill'
|
|
691
770
|
? buildClassName('px-4 py-1 rounded-full', isActive
|
|
692
771
|
? pillActiveStyles
|
|
693
772
|
: 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40')
|
|
694
|
-
: 'px-1 py-0.5'), children: [jsxRuntime.jsx("span", { className: "text-xl transition-transform duration-150 group-active:scale-95", children: isActive && item.activeIcon ? item.activeIcon : item.icon }), item.badge !== undefined && item.badge !== null && (jsxRuntime.jsx("span", { className: buildClassName('absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10', badgeColor), "data-testid": `bottom-nav-badge-${item.id}`, children: item.badge }))] }), showLabelText && (jsxRuntime.jsx("span", { className: buildClassName('text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150', isActive ? 'font-bold' : 'font-medium opacity-90'), children: item.label })), activeIndicatorStyle === 'dot' && isActive && (jsxRuntime.jsx("span", { className: buildClassName('size-1.5 rounded-full mt-1 transition-all duration-200', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') }))] }, item.id));
|
|
773
|
+
: 'px-1 py-0.5'), children: [jsxRuntime.jsx("span", { className: "text-xl transition-transform duration-150 group-active:scale-95", "aria-hidden": "true", children: isActive && item.activeIcon ? item.activeIcon : item.icon }), item.badge !== undefined && item.badge !== null && (jsxRuntime.jsx("span", { className: buildClassName('absolute -top-1 -right-1 min-w-[16px] h-4 px-1 rounded-full text-[10px] font-bold flex items-center justify-center shadow-xs z-10', badgeColor), "data-testid": `bottom-nav-badge-${item.id}`, children: item.badge }))] }), showLabelText && (jsxRuntime.jsx("span", { className: buildClassName('text-[11px] leading-tight mt-1 truncate max-w-full transition-colors duration-150', isActive ? 'font-bold' : 'font-medium opacity-90'), children: item.label })), activeIndicatorStyle === 'dot' && isActive && (jsxRuntime.jsx("span", { className: buildClassName('size-1.5 rounded-full mt-1 transition-all duration-200', theme === 'primary' ? 'bg-white' : 'bg-blue-600 dark:bg-blue-400') }))] }, item.id));
|
|
695
774
|
}) }));
|
|
696
775
|
}
|
|
697
776
|
BottomNavigation.displayName = 'BottomNavigation';
|
|
@@ -2335,77 +2414,165 @@ function PinInput({ length = 4, value: externalValue, onChange, onComplete, mask
|
|
|
2335
2414
|
}
|
|
2336
2415
|
PinInput.displayName = 'PinInput';
|
|
2337
2416
|
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2417
|
+
// Resistance applied to the finger's travel, so the pull feels elastic.
|
|
2418
|
+
const RESISTANCE = 0.45;
|
|
2419
|
+
// Finger travel (px) before deciding whether a gesture is a pull or a sideways swipe.
|
|
2420
|
+
const AXIS_LOCK_SLOP = 8;
|
|
2421
|
+
// Overlay badge size and the gap it rests at below the container's top edge.
|
|
2422
|
+
const BADGE_SIZE = 40;
|
|
2423
|
+
const BADGE_TOP_GAP = 10;
|
|
2424
|
+
function PullArrowIcon({ progress, className }) {
|
|
2425
|
+
return (jsxRuntime.jsx("svg", { className: className, style: { transform: `rotate(${Math.min(180, progress * 180)}deg)` }, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", "aria-hidden": "true", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 14l-7 7m0 0l-7-7m7 7V3" }) }));
|
|
2341
2426
|
}
|
|
2342
|
-
function PullToRefresh({ onRefresh, pullThreshold = 70, maxPullDistance = 120, disabled = false, refreshing: externalRefreshing, pullingContent, refreshingContent, children, className = '', contentClass = '', }) {
|
|
2427
|
+
function PullToRefresh({ onRefresh, onError, variant = 'push', pullThreshold = 70, maxPullDistance = 120, disabled = false, refreshing: externalRefreshing, pullingContent, refreshingContent, pullLabel = 'Pull down to refresh', releaseLabel = 'Release to refresh', refreshingLabel = 'Refreshing...', refreshedLabel = 'Content refreshed', children, className = '', contentClass = '', }) {
|
|
2343
2428
|
const containerRef = React.useRef(null);
|
|
2344
|
-
const startYRef = React.useRef(0);
|
|
2345
|
-
const isPullingRef = React.useRef(false);
|
|
2346
2429
|
const [pullDistance, setPullDistance] = React.useState(0);
|
|
2430
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
2347
2431
|
const [internalRefreshing, setInternalRefreshing] = React.useState(false);
|
|
2432
|
+
const [announcement, setAnnouncement] = React.useState('');
|
|
2348
2433
|
const isRefreshing = externalRefreshing !== undefined ? externalRefreshing : internalRefreshing;
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2434
|
+
// The touch listeners are attached once, natively (see below), so they read
|
|
2435
|
+
// the latest props and state through this ref instead of stale closures.
|
|
2436
|
+
const latest = React.useRef({
|
|
2437
|
+
disabled,
|
|
2438
|
+
isRefreshing,
|
|
2439
|
+
pullThreshold,
|
|
2440
|
+
maxPullDistance,
|
|
2441
|
+
onRefresh,
|
|
2442
|
+
onError,
|
|
2443
|
+
refreshedLabel,
|
|
2444
|
+
});
|
|
2445
|
+
React.useLayoutEffect(() => {
|
|
2446
|
+
latest.current = {
|
|
2447
|
+
disabled,
|
|
2448
|
+
isRefreshing,
|
|
2449
|
+
pullThreshold,
|
|
2450
|
+
maxPullDistance,
|
|
2451
|
+
onRefresh,
|
|
2452
|
+
onError,
|
|
2453
|
+
refreshedLabel,
|
|
2454
|
+
};
|
|
2455
|
+
});
|
|
2456
|
+
React.useEffect(() => {
|
|
2361
2457
|
const container = containerRef.current;
|
|
2362
|
-
if (!container
|
|
2458
|
+
if (!container)
|
|
2363
2459
|
return;
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2460
|
+
let mounted = true;
|
|
2461
|
+
let pulling = false;
|
|
2462
|
+
let axis = 'pending';
|
|
2463
|
+
let startX = 0;
|
|
2464
|
+
let startY = 0;
|
|
2465
|
+
let distance = 0;
|
|
2466
|
+
const setDistance = (next) => {
|
|
2467
|
+
distance = next;
|
|
2468
|
+
setPullDistance(next);
|
|
2469
|
+
};
|
|
2470
|
+
// Ends a gesture without refreshing: a cancelled touch, a sideways swipe,
|
|
2471
|
+
// or content that scrolled away from the top.
|
|
2472
|
+
const reset = () => {
|
|
2473
|
+
pulling = false;
|
|
2474
|
+
setIsDragging(false);
|
|
2475
|
+
setDistance(0);
|
|
2476
|
+
};
|
|
2477
|
+
const refresh = async () => {
|
|
2478
|
+
const { pullThreshold: threshold, onRefresh: run, onError: report } = latest.current;
|
|
2479
|
+
setDistance(threshold);
|
|
2384
2480
|
setInternalRefreshing(true);
|
|
2385
2481
|
try {
|
|
2386
|
-
await
|
|
2482
|
+
await run();
|
|
2483
|
+
if (mounted)
|
|
2484
|
+
setAnnouncement(latest.current.refreshedLabel);
|
|
2485
|
+
}
|
|
2486
|
+
catch (error) {
|
|
2487
|
+
report === null || report === void 0 ? void 0 : report(error);
|
|
2387
2488
|
}
|
|
2388
2489
|
finally {
|
|
2389
|
-
|
|
2390
|
-
|
|
2490
|
+
if (mounted) {
|
|
2491
|
+
setInternalRefreshing(false);
|
|
2492
|
+
setDistance(0);
|
|
2493
|
+
}
|
|
2391
2494
|
}
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2495
|
+
};
|
|
2496
|
+
const handleTouchStart = (e) => {
|
|
2497
|
+
var _a, _b;
|
|
2498
|
+
const { disabled: isDisabled, isRefreshing: busy } = latest.current;
|
|
2499
|
+
if (isDisabled || busy || container.scrollTop > 0 || e.touches.length !== 1)
|
|
2500
|
+
return;
|
|
2501
|
+
startX = (_a = e.touches[0].clientX) !== null && _a !== void 0 ? _a : 0;
|
|
2502
|
+
startY = (_b = e.touches[0].clientY) !== null && _b !== void 0 ? _b : 0;
|
|
2503
|
+
axis = 'pending';
|
|
2504
|
+
pulling = true;
|
|
2505
|
+
setAnnouncement('');
|
|
2506
|
+
setIsDragging(true);
|
|
2507
|
+
setDistance(0);
|
|
2508
|
+
};
|
|
2509
|
+
const handleTouchMove = (e) => {
|
|
2510
|
+
var _a, _b, _c, _d;
|
|
2511
|
+
if (!pulling)
|
|
2512
|
+
return;
|
|
2513
|
+
if (latest.current.disabled || latest.current.isRefreshing || container.scrollTop > 0) {
|
|
2514
|
+
reset();
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
const dx = ((_b = (_a = e.touches[0]) === null || _a === void 0 ? void 0 : _a.clientX) !== null && _b !== void 0 ? _b : 0) - startX;
|
|
2518
|
+
const dy = ((_d = (_c = e.touches[0]) === null || _c === void 0 ? void 0 : _c.clientY) !== null && _d !== void 0 ? _d : 0) - startY;
|
|
2519
|
+
if (axis === 'pending') {
|
|
2520
|
+
if (Math.abs(dx) < AXIS_LOCK_SLOP && Math.abs(dy) < AXIS_LOCK_SLOP)
|
|
2521
|
+
return;
|
|
2522
|
+
axis = dy > 0 && Math.abs(dy) > Math.abs(dx) ? 'vertical' : 'horizontal';
|
|
2523
|
+
// A sideways swipe or an upward scroll belongs to the content, not to us.
|
|
2524
|
+
if (axis === 'horizontal') {
|
|
2525
|
+
reset();
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
const next = Math.min(latest.current.maxPullDistance, Math.max(0, dy) * RESISTANCE);
|
|
2530
|
+
setDistance(next);
|
|
2531
|
+
// Needs the non-passive listener: stops the browser's own pull-to-refresh/overscroll.
|
|
2532
|
+
if (next > 0 && e.cancelable)
|
|
2533
|
+
e.preventDefault();
|
|
2534
|
+
};
|
|
2535
|
+
const handleTouchEnd = () => {
|
|
2536
|
+
if (!pulling)
|
|
2537
|
+
return;
|
|
2538
|
+
pulling = false;
|
|
2539
|
+
setIsDragging(false);
|
|
2540
|
+
if (distance >= latest.current.pullThreshold)
|
|
2541
|
+
void refresh();
|
|
2542
|
+
else
|
|
2543
|
+
setDistance(0);
|
|
2544
|
+
};
|
|
2545
|
+
// React registers touch listeners as passive, where preventDefault() is
|
|
2546
|
+
// ignored, so these are attached natively with touchmove non-passive.
|
|
2547
|
+
container.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
2548
|
+
container.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
2549
|
+
container.addEventListener('touchend', handleTouchEnd);
|
|
2550
|
+
container.addEventListener('touchcancel', reset);
|
|
2551
|
+
return () => {
|
|
2552
|
+
mounted = false;
|
|
2553
|
+
container.removeEventListener('touchstart', handleTouchStart);
|
|
2554
|
+
container.removeEventListener('touchmove', handleTouchMove);
|
|
2555
|
+
container.removeEventListener('touchend', handleTouchEnd);
|
|
2556
|
+
container.removeEventListener('touchcancel', reset);
|
|
2557
|
+
};
|
|
2558
|
+
}, []);
|
|
2397
2559
|
const progress = Math.min(1, pullDistance / pullThreshold);
|
|
2398
2560
|
const isReady = pullDistance >= pullThreshold;
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2561
|
+
const offset = isRefreshing ? pullThreshold : pullDistance;
|
|
2562
|
+
// Follow the finger exactly while dragging; animate only the settle afterwards.
|
|
2563
|
+
const motionClass = isDragging
|
|
2564
|
+
? 'transition-none'
|
|
2565
|
+
: 'transition-transform duration-200 ease-out motion-reduce:transition-none';
|
|
2566
|
+
return (jsxRuntime.jsxs("div", { ref: containerRef, className: buildClassName('relative overflow-y-auto overscroll-none', className), "data-testid": "pull-to-refresh-container", children: [jsxRuntime.jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: isRefreshing ? refreshingLabel : announcement }), variant === 'overlay' ? (jsxRuntime.jsx("div", { className: buildClassName('pointer-events-none absolute inset-x-0 top-0 z-20 flex justify-center', motionClass), style: {
|
|
2567
|
+
transform: `translateY(${offset - BADGE_SIZE + BADGE_TOP_GAP}px)`,
|
|
2568
|
+
opacity: offset > 0 ? 1 : 0,
|
|
2569
|
+
}, "aria-hidden": "true", "data-testid": "pull-to-refresh-indicator", children: jsxRuntime.jsx("div", { className: "grid size-10 place-items-center rounded-full bg-[var(--ui-primary)] text-white shadow-lg", children: isRefreshing
|
|
2570
|
+
? (refreshingContent !== null && refreshingContent !== void 0 ? refreshingContent : jsxRuntime.jsx(Loader, { size: "xs", className: "text-white" }))
|
|
2571
|
+
: (pullingContent !== null && pullingContent !== void 0 ? pullingContent : jsxRuntime.jsx(PullArrowIcon, { progress: progress, className: "size-4" })) }) })) : (
|
|
2572
|
+
// Absolutely positioned, so the content's own translate is the only shift.
|
|
2573
|
+
jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-0 top-0 z-20 flex items-center justify-center overflow-hidden", style: { height: `${offset}px`, opacity: offset > 0 ? 1 : 0 }, "aria-hidden": "true", "data-testid": "pull-to-refresh-indicator", children: jsxRuntime.jsx("div", { className: "flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-white dark:bg-gray-800 border border-[var(--ui-border)] shadow-md text-xs font-semibold text-gray-700 dark:text-gray-200", children: isRefreshing
|
|
2574
|
+
? (refreshingContent !== null && refreshingContent !== void 0 ? refreshingContent : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Loader, { size: "sm" }), jsxRuntime.jsx("span", { children: refreshingLabel })] })))
|
|
2575
|
+
: (pullingContent !== null && pullingContent !== void 0 ? pullingContent : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(PullArrowIcon, { progress: progress, className: "size-4 text-[var(--ui-primary)] dark:text-blue-400" }), jsxRuntime.jsx("span", { children: isReady ? releaseLabel : pullLabel })] }))) }) })), jsxRuntime.jsx("div", { className: buildClassName(variant === 'push' && motionClass, contentClass), style: variant === 'push' ? { transform: `translateY(${offset}px)` } : undefined, "data-testid": "pull-to-refresh-content", children: children })] }));
|
|
2409
2576
|
}
|
|
2410
2577
|
PullToRefresh.displayName = 'PullToRefresh';
|
|
2411
2578
|
|
|
@@ -3318,12 +3485,23 @@ function ChevronLeftIcon$1() {
|
|
|
3318
3485
|
function SearchIcon$1() {
|
|
3319
3486
|
return (jsxRuntime.jsx("svg", { className: "size-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }));
|
|
3320
3487
|
}
|
|
3488
|
+
function MoreIcon() {
|
|
3489
|
+
return (jsxRuntime.jsxs("svg", { className: "size-5", fill: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: [jsxRuntime.jsx("circle", { cx: "12", cy: "5", r: "1.75" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "1.75" }), jsxRuntime.jsx("circle", { cx: "12", cy: "19", r: "1.75" })] }));
|
|
3490
|
+
}
|
|
3321
3491
|
function ClearIcon() {
|
|
3322
3492
|
return (jsxRuntime.jsx("svg", { className: "size-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }));
|
|
3323
3493
|
}
|
|
3324
|
-
function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabel = 'Back', leading, actions, searchable = false, searchValue: externalSearchValue, onSearchChange, searchPlaceholder = 'Search...', sticky = true, transparent = false, bottomSlot, className = '', }) {
|
|
3494
|
+
function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabel = 'Back', leading, actions, maxVisibleActions, overflowLabel = 'More actions', trailing, searchable = false, searchValue: externalSearchValue, onSearchChange, searchPlaceholder = 'Search...', sticky = true, transparent = false, bottomSlot, className = '', }) {
|
|
3325
3495
|
const [isSearchActive, setIsSearchActive] = React.useState(false);
|
|
3326
3496
|
const [internalSearchValue, setInternalSearchValue] = React.useState('');
|
|
3497
|
+
const [isOverflowOpen, setIsOverflowOpen] = React.useState(false);
|
|
3498
|
+
// The overflow button takes one of the visible slots.
|
|
3499
|
+
const allActions = actions !== null && actions !== void 0 ? actions : [];
|
|
3500
|
+
const shouldOverflow = maxVisibleActions !== undefined && allActions.length > maxVisibleActions;
|
|
3501
|
+
const visibleActions = shouldOverflow
|
|
3502
|
+
? allActions.slice(0, Math.max(maxVisibleActions - 1, 0))
|
|
3503
|
+
: allActions;
|
|
3504
|
+
const overflowActions = shouldOverflow ? allActions.slice(visibleActions.length) : [];
|
|
3327
3505
|
const searchValue = externalSearchValue !== undefined ? externalSearchValue : internalSearchValue;
|
|
3328
3506
|
const handleSearchInput = (e) => {
|
|
3329
3507
|
const val = e.target.value;
|
|
@@ -3338,14 +3516,20 @@ function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabe
|
|
|
3338
3516
|
? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-[var(--ui-border-muted)]'
|
|
3339
3517
|
: 'bg-white dark:bg-gray-900 border-b border-[var(--ui-border)] shadow-xs', 'pt-[env(safe-area-inset-top,0px)]', className), "data-testid": "mobile-header", children: [jsxRuntime.jsx("div", { className: "h-14 px-3.5 flex items-center justify-between gap-3 relative", children: searchable && isSearchActive ? (jsxRuntime.jsxs("div", { className: "flex-1 flex items-center gap-2", "data-testid": "mobile-header-search-container", children: [jsxRuntime.jsxs("div", { className: "relative flex-1 flex items-center", children: [jsxRuntime.jsx("span", { className: "absolute left-3 pointer-events-none", children: jsxRuntime.jsx(SearchIcon$1, {}) }), jsxRuntime.jsx("input", { type: "text", value: searchValue, onChange: handleSearchInput, placeholder: searchPlaceholder, autoFocus: true, className: "w-full pl-9 pr-8 py-1.5 text-xs bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white rounded-full border border-transparent focus:border-blue-500 focus:outline-hidden transition-all", "data-testid": "mobile-header-search-input" }), searchValue && (jsxRuntime.jsx("button", { type: "button", onClick: handleClearSearch, "aria-label": "Clear search", className: "absolute right-2.5 p-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer", children: jsxRuntime.jsx(ClearIcon, {}) }))] }), jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(false), className: "text-xs font-semibold text-blue-600 dark:text-blue-400 px-2 py-1.5 rounded-lg hover:bg-blue-50 dark:hover:bg-blue-950/40 cursor-pointer", children: "Cancel" })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 z-10", children: onBack ? (jsxRuntime.jsxs("button", { type: "button", onClick: onBack, "aria-label": backLabel, className: "flex items-center gap-0.5 text-blue-600 dark:text-blue-400 hover:text-blue-700 p-1 -ml-1 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-back-btn", children: [jsxRuntime.jsx(ChevronLeftIcon$1, {}), jsxRuntime.jsx("span", { className: "text-xs font-semibold hidden sm:inline", children: backLabel })] })) : (leading) }), jsxRuntime.jsxs("div", { className: buildClassName('flex-1 min-w-0 px-2', titleAlign === 'center'
|
|
3340
3518
|
? 'absolute inset-x-12 text-center pointer-events-none flex flex-col items-center justify-center'
|
|
3341
|
-
: 'text-left'), children: [title && (jsxRuntime.jsx("div", { className: "text-sm font-bold text-gray-900 dark:text-white truncate pointer-events-auto", "data-testid": "mobile-header-title", children: title })), subtitle && (jsxRuntime.jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(true), "aria-label": "Search", className: "p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-search-toggle", children: jsxRuntime.jsx(SearchIcon$1, {}) })),
|
|
3519
|
+
: 'text-left'), children: [title && (jsxRuntime.jsx("div", { className: "text-sm font-bold text-gray-900 dark:text-white truncate pointer-events-auto", "data-testid": "mobile-header-title", children: title })), subtitle && (jsxRuntime.jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (jsxRuntime.jsx("button", { type: "button", onClick: () => setIsSearchActive(true), "aria-label": "Search", className: "p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-search-toggle", children: jsxRuntime.jsx(SearchIcon$1, {}) })), visibleActions.map((action) => {
|
|
3342
3520
|
const badgeColor = {
|
|
3343
3521
|
danger: 'bg-red-500 text-white',
|
|
3344
3522
|
info: 'bg-blue-500 text-white',
|
|
3345
3523
|
success: 'bg-emerald-500 text-white',
|
|
3346
3524
|
}[action.badgeVariant || 'danger'];
|
|
3347
3525
|
return (jsxRuntime.jsxs("button", { type: "button", disabled: action.disabled, onClick: action.onClick, "aria-label": action.label, className: buildClassName('p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors relative cursor-pointer', action.disabled && 'opacity-40 cursor-not-allowed pointer-events-none'), "data-testid": `mobile-header-action-${action.id}`, children: [jsxRuntime.jsx("span", { className: "text-base", children: action.icon }), action.badge !== undefined && action.badge !== null && (jsxRuntime.jsx("span", { className: buildClassName('absolute top-1 right-1 min-w-[14px] h-3.5 px-0.5 rounded-full text-[9px] font-bold flex items-center justify-center shadow-xs', badgeColor), "data-testid": `mobile-header-badge-${action.id}`, children: action.badge }))] }, action.id));
|
|
3348
|
-
})
|
|
3526
|
+
}), overflowActions.length > 0 && (jsxRuntime.jsx("button", { type: "button", onClick: () => setIsOverflowOpen(true), "aria-label": overflowLabel, "aria-haspopup": "dialog", "aria-expanded": isOverflowOpen, className: "p-2 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-xl transition-colors cursor-pointer", "data-testid": "mobile-header-overflow", children: jsxRuntime.jsx(MoreIcon, {}) })), trailing] })] })) }), overflowActions.length > 0 && (jsxRuntime.jsx(ActionSheet, { isOpen: isOverflowOpen, onClose: () => setIsOverflowOpen(false), actions: overflowActions.map((action) => ({
|
|
3527
|
+
id: action.id,
|
|
3528
|
+
label: action.label,
|
|
3529
|
+
icon: action.icon,
|
|
3530
|
+
disabled: action.disabled,
|
|
3531
|
+
onClick: action.onClick,
|
|
3532
|
+
})) })), bottomSlot && (jsxRuntime.jsx("div", { className: "px-3 py-2 border-t border-[var(--ui-border-muted)]", "data-testid": "mobile-header-bottom-slot", children: bottomSlot }))] }));
|
|
3349
3533
|
}
|
|
3350
3534
|
MobileHeader.displayName = 'MobileHeader';
|
|
3351
3535
|
|
|
@@ -3848,9 +4032,18 @@ const sizeClasses$1 = {
|
|
|
3848
4032
|
},
|
|
3849
4033
|
};
|
|
3850
4034
|
const SelectBox = React.forwardRef((props, forwardedRef) => {
|
|
3851
|
-
const { labelKey = 'label', valueKey = 'value', name, id = name, options = EMPTY_ARRAY, groups, selected, size = 'md', label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange = NOOP, placeholder, dropdownContainerClass = 'z-20', multiple = false, maxSelection, showSelectAll = false, immediate = true, showErrorMessage = true, error = null, helperText, leftGroup, renderOption, async = false, searchable = true, required = false, allowAdd = false, addNewText = 'Create', onAdd = NOOP, allowClear = false, allowFreeText = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
|
|
4035
|
+
const { labelKey = 'label', valueKey = 'value', name, id = name, options = EMPTY_ARRAY, groups, selected: selectedProp, defaultSelected, size = 'md', label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange: onChangeProp = NOOP, placeholder, dropdownContainerClass = 'z-20', dropdownMinWidth, multiple = false, maxSelection, showSelectAll = false, immediate = true, showErrorMessage = true, error = null, helperText, leftGroup, renderOption, async = false, searchable = true, required = false, allowAdd = false, addNewText = 'Create', onAdd = NOOP, allowClear = false, allowFreeText = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
|
|
4036
|
+
// Uncontrolled when `selected` is not passed: keep the selection internally.
|
|
4037
|
+
const isControlled = selectedProp !== undefined;
|
|
4038
|
+
const [internalSelected, setInternalSelected] = React.useState(defaultSelected !== null && defaultSelected !== void 0 ? defaultSelected : null);
|
|
4039
|
+
const selected = isControlled ? selectedProp : internalSelected;
|
|
4040
|
+
const onChange = React.useCallback((value) => {
|
|
4041
|
+
if (!isControlled)
|
|
4042
|
+
setInternalSelected(value);
|
|
4043
|
+
onChangeProp(value);
|
|
4044
|
+
}, [isControlled, onChangeProp]);
|
|
3852
4045
|
if (props.asBottomSheet || props.mobileMode === 'sheet') {
|
|
3853
|
-
return jsxRuntime.jsx(MobilePicker, { ...props });
|
|
4046
|
+
return jsxRuntime.jsx(MobilePicker, { ...props, selected: selected, onChange: onChange });
|
|
3854
4047
|
}
|
|
3855
4048
|
const hasError = Boolean(error);
|
|
3856
4049
|
const [query, setQuery] = React.useState('');
|
|
@@ -4048,6 +4241,16 @@ const SelectBox = React.forwardRef((props, forwardedRef) => {
|
|
|
4048
4241
|
}
|
|
4049
4242
|
}, [isAllSelected, onChange, allOptions]);
|
|
4050
4243
|
const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
|
|
4244
|
+
// Reserve room for the chevron (and the clear button when shown) so long values
|
|
4245
|
+
// don't run underneath them.
|
|
4246
|
+
const showClearButton = allowClear && !multiple && !isEmpty(normalizedSelected);
|
|
4247
|
+
const rightPaddingClass = showClearButton
|
|
4248
|
+
? 'pe-16'
|
|
4249
|
+
: size === 'lg'
|
|
4250
|
+
? 'pe-11'
|
|
4251
|
+
: size === 'sm'
|
|
4252
|
+
? 'pe-9'
|
|
4253
|
+
: 'pe-10';
|
|
4051
4254
|
const hasFreeTextOption = Boolean(allowFreeText &&
|
|
4052
4255
|
query.trim() &&
|
|
4053
4256
|
!allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }));
|
|
@@ -4060,7 +4263,7 @@ const SelectBox = React.forwardRef((props, forwardedRef) => {
|
|
|
4060
4263
|
const isOptSelected = selectedValueSet.has(optVal);
|
|
4061
4264
|
return (jsxRuntime.jsx(react.ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-gray-100 dark:data-[focus]:bg-white/10 data-disabled:opacity-50 cursor-pointer data-disabled:pointer-events-none", children: renderOption ? (renderOption(option, Boolean(isOptSelected))) : (jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 text-gray-900 dark:text-white group-data-[selected]:text-blue-600", children: [jsxRuntime.jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsxRuntime.jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] })) }, optVal));
|
|
4062
4265
|
};
|
|
4063
|
-
return (jsxRuntime.jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxRuntime.jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsxRuntime.jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsxRuntime.jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxRuntime.jsxs(react.Combobox, { multiple: multiple, immediate: immediate, name: name, value: normalizedSelected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, by: (a, b) => (a && b ? getValue(a) === getValue(b) : a === b), children: [multiple ? (jsxRuntime.jsxs("div", { className: buildClassName(baseInputClass, 'flex flex-wrap gap-2 peer relative', hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), ref: forwardedRef, children: [hasLeftGroup && (jsxRuntime.jsx("span", { className: buildClassName('absolute left-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), isArray(normalizedSelected) &&
|
|
4266
|
+
return (jsxRuntime.jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxRuntime.jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsxRuntime.jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsxRuntime.jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxRuntime.jsxs(react.Combobox, { multiple: multiple, immediate: immediate, name: name, value: normalizedSelected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, by: (a, b) => (a && b ? getValue(a) === getValue(b) : a === b), children: [multiple ? (jsxRuntime.jsxs("div", { className: buildClassName(baseInputClass, 'flex flex-wrap gap-2 peer relative', rightPaddingClass, hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), ref: forwardedRef, children: [hasLeftGroup && (jsxRuntime.jsx("span", { className: buildClassName('absolute left-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), isArray(normalizedSelected) &&
|
|
4064
4267
|
(normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) > 0 &&
|
|
4065
4268
|
normalizedSelected.map((option, idx) => (jsxRuntime.jsx(Badge, { theme: "secondary", size: currentSize.badgeSize, removable: !disabled, onRemove: () => removeSelection(idx), children: getDisplayValue(option) }, `${getValue(option)}-${idx}`))), jsxRuntime.jsx(react.ComboboxInput, { className: "outline-none flex-1 dark:placeholder-gray-400 placeholder-gray-400 bg-transparent", "aria-label": name, value: query, autoComplete: "off", placeholder: (normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) ? '' : placeholder, onChange: (e) => {
|
|
4066
4269
|
const val = e.target.value;
|
|
@@ -4100,7 +4303,9 @@ const SelectBox = React.forwardRef((props, forwardedRef) => {
|
|
|
4100
4303
|
}
|
|
4101
4304
|
}, onFocus: () => {
|
|
4102
4305
|
setQuery('');
|
|
4103
|
-
}, className: buildClassName(baseInputClass, hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), disabled: disabled, id: id, readOnly: !searchable, ref: inputRef, "data-testid": "combobox-input" }),
|
|
4306
|
+
}, className: buildClassName(baseInputClass, 'text-ellipsis', rightPaddingClass, hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), disabled: disabled, id: id, readOnly: !searchable, ref: inputRef, "data-testid": "combobox-input" }), showClearButton && (jsxRuntime.jsx(Button, { size: "xs", iconOnly: true, "aria-label": "Clear selection", theme: "secondary", variant: "plain", className: "group absolute inset-y-0 right-8 h-6 top-2", onClick: () => handleOnChangeInternal(multiple ? [] : null), disabled: disabled, "data-testid": "combobox-clear-button", children: jsxRuntime.jsx(SvgX, { className: "size-4 stroke-gray-500 dark:stroke-gray-300" }) })), jsxRuntime.jsx(react.ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5 flex items-center justify-center", "data-testid": "combobox-button", children: jsxRuntime.jsx(SvgChevronDown, { className: chevronIconClass }) })] })), jsxRuntime.jsxs(react.ComboboxOptions, { anchor: { to: 'bottom start', gap: 5, padding: 8 }, as: "div", modal: modalDropdown,
|
|
4307
|
+
// Matches the input width by default; min-width lets the panel grow wider than a narrow input.
|
|
4308
|
+
style: dropdownMinWidth != null ? { minWidth: dropdownMinWidth } : undefined, className: buildClassName('w-[var(--input-width)] rounded-lg border border-[var(--ui-border)] bg-white dark:bg-gray-800 p-1 shadow-lg max-h-80 overflow-y-auto z-50 [--anchor-gap:var(--spacing-1)]', '[--anchor-max-height:20rem]', 'origin-top border transition duration-200 ease-[cubic-bezier(0.4, 0.0, 0.2, 1)] data-[closed]:translate-y-[-15%] data-[closed]:opacity-0', dropdownContainerClass), transition: true, "data-testid": "combobox-options", children: [multiple && showSelectAll && allOptions.length > 0 && (jsxRuntime.jsxs("div", { className: "pb-1 mb-1 border-b border-[var(--ui-border-muted)] px-3 pt-1 flex items-center justify-between", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleToggleSelectAll, className: "text-xs font-semibold text-blue-600 dark:text-blue-400 hover:underline cursor-pointer", children: isAllSelected ? 'Deselect All' : 'Select All' }), jsxRuntime.jsxs("span", { className: "text-[11px] font-medium text-gray-400", children: [Array.isArray(normalizedSelected) ? normalizedSelected.length : 0, " of", ' ', allOptions.length] })] })), optionsLoading ? (jsxRuntime.jsx(BodyText, { "data-testid": "options-loading", className: "flex items-center justify-center py-2 px-3 select-none", children: jsxRuntime.jsx(Loader, { size: "sm" }) })) : shouldShowTypeToSearch ? (jsxRuntime.jsx(BodyText, { className: "flex items-center justify-between py-2 px-3 select-none", children: "Type to search..." })) : (jsxRuntime.jsxs("div", { children: [allowFreeText &&
|
|
4104
4309
|
query.trim() &&
|
|
4105
4310
|
!allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }) && (jsxRuntime.jsx(react.ComboboxOption, { value: createFreeTextOption(query.trim()), className: "group flex items-center gap-2 rounded-lg py-1.5 px-3 select-none text-sm data-[focus]:bg-blue-50 dark:data-[focus]:bg-blue-950/40 text-blue-600 dark:text-blue-400 font-medium cursor-pointer w-full", "data-testid": "freetext-option", children: jsxRuntime.jsxs("span", { children: ["Use \"", query.trim(), "\""] }) }, `freetext-${query.trim()}`)), filteredGroups
|
|
4106
4311
|
? filteredGroups.map((grp, gIdx) => (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-[11px] font-bold uppercase tracking-wider text-gray-400 dark:text-gray-500 px-3 py-1 select-none", children: grp.group }), grp.options.map(renderOptionItem)] }, gIdx)))
|
|
@@ -4300,14 +4505,14 @@ StatusPill.displayName = 'StatusPill';
|
|
|
4300
4505
|
|
|
4301
4506
|
const ChevronUpIcon = ({ className }) => (jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: jsxRuntime.jsx("polyline", { points: "18 15 12 9 6 15" }) }));
|
|
4302
4507
|
const StickyActionBar = React.forwardRef((props, ref) => {
|
|
4303
|
-
const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', className, ...restProps } = props;
|
|
4508
|
+
const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', safeArea = true, bottomOffset, className, style, ...restProps } = props;
|
|
4304
4509
|
const [isDrawerOpen, setIsDrawerOpen] = React.useState(false);
|
|
4305
4510
|
const positionClass = position === 'fixed'
|
|
4306
4511
|
? 'fixed bottom-0 left-0 right-0 z-30'
|
|
4307
4512
|
: position === 'sticky'
|
|
4308
4513
|
? 'sticky bottom-0 z-30'
|
|
4309
4514
|
: 'relative z-10';
|
|
4310
|
-
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: ref, className: buildClassName(positionClass, 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all', className), "data-testid": "sticky-action-bar", ...restProps, children: jsxRuntime.jsxs("div", { className: buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsDrawerOpen(true), className: "flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1", "aria-label": "Expand summary details", "data-testid": "summary-trigger", children: [jsxRuntime.jsx("div", { children: summaryContent }), jsxRuntime.jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsxRuntime.jsx("div", { children: summaryContent })) })), actionsContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (jsxRuntime.jsx(Drawer, { isOpen: isDrawerOpen, onClose: () => setIsDrawerOpen(false), align: "bottom", size: "full", title: drawerTitle, showBackButton: true, contentClass: "px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto", children: drawerContent }))] }));
|
|
4515
|
+
return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { ref: ref, className: buildClassName(positionClass, 'border-t border-[var(--ui-border)] bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:bg-gray-900/90 transition-all', safeArea && 'pb-[calc(0.875rem+env(safe-area-inset-bottom,0px))]', className), style: bottomOffset && position !== 'relative' ? { bottom: bottomOffset, ...style } : style, "data-testid": "sticky-action-bar", ...restProps, children: jsxRuntime.jsxs("div", { className: buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (jsxRuntime.jsxs("button", { type: "button", onClick: () => setIsDrawerOpen(true), className: "flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1", "aria-label": "Expand summary details", "data-testid": "summary-trigger", children: [jsxRuntime.jsx("div", { children: summaryContent }), jsxRuntime.jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsxRuntime.jsx("div", { children: summaryContent })) })), actionsContent && (jsxRuntime.jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (jsxRuntime.jsx(Drawer, { isOpen: isDrawerOpen, onClose: () => setIsDrawerOpen(false), align: "bottom", size: "full", title: drawerTitle, showBackButton: true, contentClass: "px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto", children: drawerContent }))] }));
|
|
4311
4516
|
});
|
|
4312
4517
|
StickyActionBar.displayName = 'StickyActionBar';
|
|
4313
4518
|
|
|
@@ -4570,7 +4775,7 @@ const Toast = ({ id, title, message, type, variant = 'accent', action, icon, sho
|
|
|
4570
4775
|
const renderedIcon = icon !== null && icon !== void 0 ? icon : (variant === 'filled' ? filledIconMap[type] : iconMap[type]);
|
|
4571
4776
|
return (jsxRuntime.jsx(react.Transition, { appear: true, show: true, enter: "transform transition duration-300 ease-out", enterFrom: enterFrom, enterTo: "opacity-100 translate-y-0 scale-100", leave: "transition duration-200 ease-in", leaveFrom: "opacity-100 translate-y-0 scale-100", leaveTo: leaveTo, children: jsxRuntime.jsxs("div", { role: "status", id: id.toString(), className: buildClassName('relative overflow-hidden flex items-start gap-3 rounded-xl px-4 py-3.5 min-w-[280px] max-w-md transition-all', variantStyleMap[variant][type], className), children: [jsxRuntime.jsx("div", { className: "shrink-0 mt-0.5", children: variant === 'filled' ? (renderedIcon) : (jsxRuntime.jsx(TextContent, { className: buildClassName(textColorMap[type]), children: renderedIcon })) }), jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [title && (jsxRuntime.jsx("div", { className: buildClassName('font-semibold text-sm leading-snug', variant === 'filled' ? 'text-white' : 'text-gray-900 dark:text-gray-100'), children: title })), jsxRuntime.jsx("div", { className: buildClassName('text-sm font-medium leading-relaxed', variant === 'filled' ? 'text-white/95' : 'text-gray-700 dark:text-gray-300'), children: message }), action && (jsxRuntime.jsx("div", { className: "mt-1.5", children: jsxRuntime.jsx("button", { type: "button", onClick: action.onClick, className: buildClassName('text-xs font-semibold underline underline-offset-2 transition-opacity focus:outline-none', variant === 'filled'
|
|
4572
4777
|
? 'text-white hover:opacity-85'
|
|
4573
|
-
: 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), jsxRuntime.jsx(Button, { onClick: onClose, "aria-label": "Close toast", className: buildClassName('shrink-0 mt-0.5 opacity-60 hover:opacity-100 transition', variant === 'filled' && 'text-white hover:text-white'), iconOnly: true, size: "xs", variant: "plain", theme: variant === 'filled' ? 'primary' : 'secondary',
|
|
4778
|
+
: 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), jsxRuntime.jsx(Button, { onClick: onClose, "aria-label": "Close toast", className: buildClassName('shrink-0 mt-0.5 opacity-60 hover:opacity-100 focus-visible:opacity-100 transition', variant === 'filled' && 'text-white hover:text-white focus-visible:outline-current'), iconOnly: true, size: "xs", variant: "plain", theme: variant === 'filled' ? 'primary' : 'secondary', children: jsxRuntime.jsx(SvgX, { className: "size-4" }) }), showProgressBar && duration > 0 && (jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 h-1 bg-black/10 dark:bg-white/10 overflow-hidden", children: jsxRuntime.jsx("div", { className: buildClassName('h-full transition-all duration-75 ease-linear', variant === 'filled' ? 'bg-white/70' : progressBarColorMap[type]), style: { width: `${Math.max(0, progress)}%` } }) }))] }) }));
|
|
4574
4779
|
};
|
|
4575
4780
|
Toast.displayName = 'Toast';
|
|
4576
4781
|
|
|
@@ -4903,6 +5108,7 @@ const useIsTabletOrMobile = () => {
|
|
|
4903
5108
|
exports.Accordion = Accordion;
|
|
4904
5109
|
exports.ActionSheet = ActionSheet;
|
|
4905
5110
|
exports.Alert = Alert;
|
|
5111
|
+
exports.BREAKPOINT_QUERIES = BREAKPOINT_QUERIES;
|
|
4906
5112
|
exports.Badge = Badge;
|
|
4907
5113
|
exports.Banner = Banner;
|
|
4908
5114
|
exports.BodyText = BodyText;
|
|
@@ -4983,12 +5189,15 @@ exports.isArray = isArray;
|
|
|
4983
5189
|
exports.isEmpty = isEmpty;
|
|
4984
5190
|
exports.nextSortDirection = nextSortDirection;
|
|
4985
5191
|
exports.updateSortQuery = updateSortQuery;
|
|
5192
|
+
exports.useBreakpoint = useBreakpoint;
|
|
4986
5193
|
exports.useDebounce = useDebounce;
|
|
4987
5194
|
exports.useDebounceFn = useDebounceFn;
|
|
4988
5195
|
exports.useIsMobile = useIsMobile;
|
|
4989
5196
|
exports.useIsTablet = useIsTablet;
|
|
4990
5197
|
exports.useIsTabletOrMobile = useIsTabletOrMobile;
|
|
5198
|
+
exports.useMediaQuery = useMediaQuery;
|
|
4991
5199
|
exports.usePagination = usePagination;
|
|
5200
|
+
exports.useResponsiveValue = useResponsiveValue;
|
|
4992
5201
|
exports.useRowSelection = useRowSelection;
|
|
4993
5202
|
exports.useToast = useToast;
|
|
4994
5203
|
//# sourceMappingURL=react-tailwind.cjs.js.map
|