@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { useId, useMemo, useState, forwardRef, Children, isValidElement, cloneElement, useRef, useImperativeHandle,
|
|
3
|
+
import React__default, { useId, useMemo, useState, forwardRef, useEffect, useCallback, useSyncExternalStore, Children, isValidElement, cloneElement, useRef, useImperativeHandle, Fragment as Fragment$1, createContext, useContext, useLayoutEffect } from 'react';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
5
|
import { Dialog, DialogBackdrop, DialogPanel, DialogTitle, Menu, MenuButton, MenuItems, MenuItem, Combobox, ComboboxInput, ComboboxButton, ComboboxOptions, ComboboxOption, TabGroup, TabList, Tab, TabPanels, TabPanel, Transition } from '@headlessui/react';
|
|
6
6
|
import { FixedSizeList } from 'react-window';
|
|
@@ -131,59 +131,35 @@ TextContent.displayName = 'TextContent';
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Tailwind CSS classes for theme + variant combinations.
|
|
134
|
+
*
|
|
135
|
+
* Only colours live here. Focus outline width/offset and disabled styling are
|
|
136
|
+
* applied uniformly in the component so they can be toggled in one place.
|
|
134
137
|
*/
|
|
135
138
|
const THEMED_BTN_CLASS = {
|
|
136
139
|
primary: {
|
|
137
|
-
default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:
|
|
138
|
-
outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus-visible:
|
|
139
|
-
plain: 'text-[var(--ui-primary-text)] hover:bg-
|
|
140
|
-
},
|
|
141
|
-
danger: {
|
|
142
|
-
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)]',
|
|
143
|
-
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)]',
|
|
144
|
-
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)]',
|
|
145
|
-
},
|
|
146
|
-
secondary: {
|
|
147
|
-
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',
|
|
148
|
-
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)]',
|
|
149
|
-
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',
|
|
150
|
-
},
|
|
151
|
-
success: {
|
|
152
|
-
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',
|
|
153
|
-
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',
|
|
154
|
-
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',
|
|
155
|
-
},
|
|
156
|
-
warning: {
|
|
157
|
-
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',
|
|
158
|
-
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',
|
|
159
|
-
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',
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
const LOADER_TEXT_CLASS = {
|
|
163
|
-
primary: {
|
|
164
|
-
default: 'text-white',
|
|
165
|
-
outlined: 'text-[var(--ui-primary-text)]',
|
|
166
|
-
plain: 'text-[var(--ui-primary-text)]',
|
|
140
|
+
default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:outline-[var(--ui-primary-ring)]',
|
|
141
|
+
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)]',
|
|
142
|
+
plain: 'text-[var(--ui-primary-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-primary-ring)]',
|
|
167
143
|
},
|
|
168
144
|
danger: {
|
|
169
|
-
default: 'text-white',
|
|
170
|
-
outlined: 'text-[var(--ui-danger-text)]',
|
|
171
|
-
plain: 'text-[var(--ui-danger-text)]',
|
|
145
|
+
default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:outline-[var(--ui-danger-ring)]',
|
|
146
|
+
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)]',
|
|
147
|
+
plain: 'text-[var(--ui-danger-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-danger-ring)]',
|
|
172
148
|
},
|
|
173
149
|
secondary: {
|
|
174
|
-
default: 'text-gray-700 dark:text-white',
|
|
175
|
-
outlined: 'text-gray-700 dark:text-gray-200',
|
|
176
|
-
plain: 'text-gray-700 dark:text-gray-200',
|
|
150
|
+
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)]',
|
|
151
|
+
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)]',
|
|
152
|
+
plain: 'text-gray-700 hover:bg-current/10 active:bg-current/15 dark:text-gray-200 focus-visible:outline-[var(--ui-focus-ring)]',
|
|
177
153
|
},
|
|
178
154
|
success: {
|
|
179
|
-
default: 'text-white',
|
|
180
|
-
outlined: 'text-emerald-600 dark:text-emerald-400',
|
|
181
|
-
plain: 'text-emerald-
|
|
155
|
+
default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:outline-emerald-500',
|
|
156
|
+
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',
|
|
157
|
+
plain: 'text-emerald-700 hover:bg-current/10 active:bg-current/15 dark:text-emerald-400 focus-visible:outline-emerald-500',
|
|
182
158
|
},
|
|
183
159
|
warning: {
|
|
184
|
-
default: 'text-white',
|
|
185
|
-
outlined: 'text-amber-
|
|
186
|
-
plain: 'text-amber-
|
|
160
|
+
default: 'text-white bg-amber-500 hover:bg-amber-600 focus-visible:outline-amber-500',
|
|
161
|
+
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',
|
|
162
|
+
plain: 'text-amber-700 hover:bg-current/10 active:bg-current/15 dark:text-amber-400 focus-visible:outline-amber-500',
|
|
187
163
|
},
|
|
188
164
|
};
|
|
189
165
|
const SIZE_CLASSES = {
|
|
@@ -203,22 +179,32 @@ const LOADER_SIZE = {
|
|
|
203
179
|
};
|
|
204
180
|
/**
|
|
205
181
|
* Base shared tailwind classes.
|
|
182
|
+
*
|
|
183
|
+
* Disabled styling is keyed off both the native `disabled` attribute and
|
|
184
|
+
* `aria-disabled`, so non-button elements rendered via `as` look disabled too.
|
|
206
185
|
*/
|
|
207
186
|
const BASE_BTN_CLASS = 'font-medium inline-flex items-center justify-center shrink-0 cursor-pointer select-none ' +
|
|
208
|
-
'
|
|
209
|
-
'active:scale-[0.98]
|
|
187
|
+
'transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-150 ease-out ' +
|
|
188
|
+
'active:scale-[0.98] focus:outline-none ' +
|
|
189
|
+
'disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none ' +
|
|
190
|
+
'aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:shadow-none';
|
|
210
191
|
const Button = forwardRef(({ as, type, ...props }, ref) => {
|
|
211
|
-
var _a, _b, _c, _d;
|
|
212
192
|
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;
|
|
213
193
|
const Component = as || 'button';
|
|
214
194
|
const isNativeButton = Component === 'button';
|
|
195
|
+
// Clean invalid props when not rendering a real button
|
|
196
|
+
const { type: _ignoreType, disabled: _ignoreDisabled, role, tabIndex, onKeyDown, onKeyUp, ...domProps } = restProps;
|
|
197
|
+
const ariaLabel = domProps['aria-label'];
|
|
198
|
+
const ariaLabelledBy = domProps['aria-labelledby'];
|
|
215
199
|
// Accessibility guard for icon-only buttons
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (iconOnly && !ariaLabel && !ariaLabelledBy) {
|
|
202
|
+
console.warn('[ui] <Button iconOnly> requires aria-label or aria-labelledby for accessibility.');
|
|
203
|
+
}
|
|
204
|
+
}, [iconOnly, ariaLabel, ariaLabelledBy]);
|
|
219
205
|
const resolvedDisabled = Boolean(disabled || loading);
|
|
220
|
-
//
|
|
221
|
-
const
|
|
206
|
+
// Links (`href`, or `to` for router Link components) keep their native semantics.
|
|
207
|
+
const isLink = !isNativeButton && (domProps.href != null || domProps.to != null);
|
|
222
208
|
const handleClick = (e) => {
|
|
223
209
|
if (resolvedDisabled) {
|
|
224
210
|
e.preventDefault();
|
|
@@ -227,31 +213,73 @@ const Button = forwardRef(({ as, type, ...props }, ref) => {
|
|
|
227
213
|
}
|
|
228
214
|
onClick === null || onClick === void 0 ? void 0 : onClick(e);
|
|
229
215
|
};
|
|
216
|
+
// Mirror native <button> keyboard activation for non-button elements:
|
|
217
|
+
// Enter activates on keydown, Space on keyup (and must not scroll the page).
|
|
218
|
+
const handleKeyDown = (e) => {
|
|
219
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
|
|
220
|
+
if (e.defaultPrevented || e.target !== e.currentTarget)
|
|
221
|
+
return;
|
|
222
|
+
if (e.key === 'Enter') {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
e.currentTarget.click();
|
|
225
|
+
}
|
|
226
|
+
else if (e.key === ' ') {
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const handleKeyUp = (e) => {
|
|
231
|
+
onKeyUp === null || onKeyUp === void 0 ? void 0 : onKeyUp(e);
|
|
232
|
+
if (e.defaultPrevented || e.target !== e.currentTarget)
|
|
233
|
+
return;
|
|
234
|
+
if (e.key === ' ') {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
e.currentTarget.click();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
230
239
|
const elementProps = isNativeButton
|
|
231
240
|
? {
|
|
232
241
|
type: type !== null && type !== void 0 ? type : 'button',
|
|
233
242
|
disabled: resolvedDisabled,
|
|
243
|
+
role,
|
|
244
|
+
tabIndex,
|
|
234
245
|
onClick: handleClick,
|
|
246
|
+
onKeyDown,
|
|
247
|
+
onKeyUp,
|
|
235
248
|
}
|
|
236
|
-
:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
249
|
+
: isLink
|
|
250
|
+
? {
|
|
251
|
+
role,
|
|
252
|
+
tabIndex: resolvedDisabled ? -1 : tabIndex,
|
|
253
|
+
'aria-disabled': resolvedDisabled ? true : undefined,
|
|
254
|
+
onClick: handleClick,
|
|
255
|
+
onKeyDown,
|
|
256
|
+
onKeyUp,
|
|
257
|
+
}
|
|
258
|
+
: {
|
|
259
|
+
role: role !== null && role !== void 0 ? role : 'button',
|
|
260
|
+
tabIndex: resolvedDisabled ? -1 : (tabIndex !== null && tabIndex !== void 0 ? tabIndex : 0),
|
|
261
|
+
'aria-disabled': resolvedDisabled ? true : undefined,
|
|
262
|
+
onClick: handleClick,
|
|
263
|
+
onKeyDown: handleKeyDown,
|
|
264
|
+
onKeyUp: handleKeyUp,
|
|
265
|
+
};
|
|
242
266
|
const sizeObj = SIZE_CLASSES[size] || SIZE_CLASSES.md;
|
|
243
267
|
const classes = useMemo(() => {
|
|
244
268
|
var _a;
|
|
245
|
-
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,
|
|
269
|
+
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,
|
|
270
|
+
// An outline (not a ring) so the offset gap stays transparent on any surface/mode.
|
|
271
|
+
!noOutlineOnFocus &&
|
|
272
|
+
'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);
|
|
246
273
|
}, [theme, variant, sizeObj, iconOnly, noOutlineOnFocus, rounded, fullWidth, active, className]);
|
|
247
|
-
|
|
274
|
+
// The spinner and loading text inherit the button's text colour.
|
|
275
|
+
const loaderIcon = (jsx(Loader, { "data-testid": "btn-loader-icon", size: LOADER_SIZE[size] || 'xs', className: "text-current" }));
|
|
248
276
|
/**
|
|
249
277
|
* SPECIAL CASE: iconOnly + loading → show spinner only
|
|
250
278
|
*/
|
|
251
279
|
if (loading && iconOnly) {
|
|
252
|
-
return (jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...
|
|
280
|
+
return (jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loaderIcon }));
|
|
253
281
|
}
|
|
254
|
-
return (jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...
|
|
282
|
+
return (jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loading ? (jsxs("span", { className: "inline-flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, loadingText ? (jsx(TextContent, { className: "text-current", children: loadingText })) : (children)] })) : (jsxs(Fragment, { children: [leftIcon && jsx("span", { className: "shrink-0 inline-flex items-center", children: leftIcon }), children, rightIcon && jsx("span", { className: "shrink-0 inline-flex items-center", children: rightIcon })] })) }));
|
|
255
283
|
});
|
|
256
284
|
Button.displayName = 'Button';
|
|
257
285
|
|
|
@@ -293,6 +321,45 @@ var SvgX = function SvgX(props) {
|
|
|
293
321
|
})));
|
|
294
322
|
};
|
|
295
323
|
|
|
324
|
+
const BREAKPOINT_QUERIES = {
|
|
325
|
+
medium: '(min-width: 768px)',
|
|
326
|
+
expanded: '(min-width: 1024px)',
|
|
327
|
+
};
|
|
328
|
+
// jsdom and some embedded webviews have no matchMedia; treat every query as
|
|
329
|
+
// unmatched there (the compact layout) rather than throwing.
|
|
330
|
+
const hasMatchMedia = () => typeof window !== 'undefined' && typeof window.matchMedia === 'function';
|
|
331
|
+
/** Live result of a CSS media query. `false` during server rendering or without `matchMedia`. */
|
|
332
|
+
const useMediaQuery = (query) => {
|
|
333
|
+
const subscribe = useCallback((onChange) => {
|
|
334
|
+
if (!hasMatchMedia())
|
|
335
|
+
return () => { };
|
|
336
|
+
const list = window.matchMedia(query);
|
|
337
|
+
list.addEventListener('change', onChange);
|
|
338
|
+
return () => list.removeEventListener('change', onChange);
|
|
339
|
+
}, [query]);
|
|
340
|
+
return useSyncExternalStore(subscribe, () => hasMatchMedia() && window.matchMedia(query).matches, () => false);
|
|
341
|
+
};
|
|
342
|
+
/** Current layout range; `compact` during server rendering. */
|
|
343
|
+
const useBreakpoint = () => {
|
|
344
|
+
const isMedium = useMediaQuery(BREAKPOINT_QUERIES.medium);
|
|
345
|
+
const isExpanded = useMediaQuery(BREAKPOINT_QUERIES.expanded);
|
|
346
|
+
if (isExpanded)
|
|
347
|
+
return 'expanded';
|
|
348
|
+
return isMedium ? 'medium' : 'compact';
|
|
349
|
+
};
|
|
350
|
+
/** Resolves a `ResponsiveValue` for the current viewport. */
|
|
351
|
+
const useResponsiveValue = (value) => {
|
|
352
|
+
var _a, _b, _c;
|
|
353
|
+
const breakpoint = useBreakpoint();
|
|
354
|
+
if (typeof value !== 'object' || value === null || !('base' in value))
|
|
355
|
+
return value;
|
|
356
|
+
if (breakpoint === 'expanded')
|
|
357
|
+
return (_b = (_a = value.lg) !== null && _a !== void 0 ? _a : value.md) !== null && _b !== void 0 ? _b : value.base;
|
|
358
|
+
if (breakpoint === 'medium')
|
|
359
|
+
return (_c = value.md) !== null && _c !== void 0 ? _c : value.base;
|
|
360
|
+
return value.base;
|
|
361
|
+
};
|
|
362
|
+
|
|
296
363
|
const alignmentMap = {
|
|
297
364
|
center: 'items-center justify-center h-full',
|
|
298
365
|
start: 'justify-start h-full',
|
|
@@ -305,7 +372,8 @@ const panelAlignmentMap = {
|
|
|
305
372
|
start: 'data-[closed]:-translate-x-1/2 rounded-r-2xl',
|
|
306
373
|
end: 'data-[closed]:translate-x-1/2 rounded-l-2xl',
|
|
307
374
|
top: 'data-[closed]:-translate-y-1/2 rounded-b-2xl',
|
|
308
|
-
|
|
375
|
+
// Bottom sheets clear the home indicator on notched phones (0 elsewhere).
|
|
376
|
+
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)]',
|
|
309
377
|
};
|
|
310
378
|
const panelSizeMap = {
|
|
311
379
|
xs: 'max-w-xs',
|
|
@@ -316,7 +384,9 @@ const panelSizeMap = {
|
|
|
316
384
|
'2xl': 'max-w-2xl',
|
|
317
385
|
full: 'w-full',
|
|
318
386
|
};
|
|
319
|
-
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, }) => {
|
|
387
|
+
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, }) => {
|
|
388
|
+
const align = useResponsiveValue(alignProp);
|
|
389
|
+
const size = useResponsiveValue(sizeProp);
|
|
320
390
|
const showTitleSection = Boolean(title || description || showBackButton || showCloseButton);
|
|
321
391
|
const shouldShowDragHandle = dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom';
|
|
322
392
|
const [viewportHeight, setViewportHeight] = React__default.useState(null);
|
|
@@ -575,7 +645,7 @@ const Banner = forwardRef((props, ref) => {
|
|
|
575
645
|
const { type = 'info', iconSize = 'sm', children, icon, removable = false, onRemove, className, ...restProps } = props;
|
|
576
646
|
const DefaultIcon = IconMap[type];
|
|
577
647
|
const iconNode = icon !== undefined ? (icon) : (jsx(DefaultIcon, { "data-testid": `banner-${type}-icon`, className: buildClassName('shrink-0', sizeMap[iconSize]) }));
|
|
578
|
-
return (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, jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (jsx(Button, { iconOnly: true, "aria-label": "Dismiss banner", size: "xs", variant: "plain",
|
|
648
|
+
return (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, jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (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: jsx(SvgX, { className: "size-4.5", "data-testid": "banner-remove-icon" }) }))] }));
|
|
579
649
|
});
|
|
580
650
|
Banner.displayName = 'Banner';
|
|
581
651
|
|
|
@@ -631,20 +701,23 @@ const BodyText = forwardRef((props, ref) => {
|
|
|
631
701
|
});
|
|
632
702
|
BodyText.displayName = 'BodyText';
|
|
633
703
|
|
|
634
|
-
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', }) {
|
|
704
|
+
function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels = 'always', theme = 'light', activeIndicatorStyle = 'line', containerClass = '', itemClass = '', linkAs = 'a', linkHrefProp = 'href', ariaLabel = 'Bottom Navigation', hidden = false, }) {
|
|
635
705
|
const handleTabClick = (item) => {
|
|
636
706
|
var _a;
|
|
637
707
|
if (item.disabled)
|
|
638
708
|
return;
|
|
639
709
|
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item);
|
|
640
|
-
onChange(item.id);
|
|
710
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(item.id);
|
|
641
711
|
};
|
|
712
|
+
// Removed from the tree, not just covered, so hidden tabs can't take focus.
|
|
713
|
+
if (hidden)
|
|
714
|
+
return null;
|
|
642
715
|
const themeStyles = {
|
|
643
716
|
light: 'bg-white dark:bg-gray-900 border-t border-[var(--ui-border)] text-gray-600 dark:text-gray-400',
|
|
644
717
|
dark: 'bg-gray-900 text-gray-400 border-t border-gray-800',
|
|
645
718
|
primary: 'bg-blue-600 text-blue-100 border-t border-blue-700',
|
|
646
719
|
}[theme];
|
|
647
|
-
return (jsx("nav", { "aria-label":
|
|
720
|
+
return (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) => {
|
|
648
721
|
const isActive = item.id === activeId;
|
|
649
722
|
const showLabelText = showLabels === 'always' || (showLabels === 'active' && isActive);
|
|
650
723
|
const activeItemColor = {
|
|
@@ -667,11 +740,17 @@ function BottomNavigation({ items, activeId, onChange, fixed = true, showLabels
|
|
|
667
740
|
info: 'bg-blue-500 text-white',
|
|
668
741
|
success: 'bg-emerald-500 text-white',
|
|
669
742
|
}[item.badgeVariant || 'danger'];
|
|
670
|
-
|
|
743
|
+
// A disabled item stays a button: a link can't be disabled.
|
|
744
|
+
const isLink = Boolean(item.href) && !item.disabled;
|
|
745
|
+
const ItemElement = isLink ? linkAs : 'button';
|
|
746
|
+
const elementProps = isLink
|
|
747
|
+
? { [linkHrefProp]: item.href }
|
|
748
|
+
: { type: 'button', disabled: item.disabled };
|
|
749
|
+
return (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 && (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') })), jsxs("div", { className: buildClassName('relative inline-flex items-center justify-center transition-all duration-200', activeIndicatorStyle === 'pill'
|
|
671
750
|
? buildClassName('px-4 py-1 rounded-full', isActive
|
|
672
751
|
? pillActiveStyles
|
|
673
752
|
: 'group-hover:bg-gray-100/50 dark:group-hover:bg-gray-800/40')
|
|
674
|
-
: 'px-1 py-0.5'), children: [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 && (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 && (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 && (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));
|
|
753
|
+
: 'px-1 py-0.5'), children: [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 && (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 && (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 && (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));
|
|
675
754
|
}) }));
|
|
676
755
|
}
|
|
677
756
|
BottomNavigation.displayName = 'BottomNavigation';
|
|
@@ -2315,77 +2394,165 @@ function PinInput({ length = 4, value: externalValue, onChange, onComplete, mask
|
|
|
2315
2394
|
}
|
|
2316
2395
|
PinInput.displayName = 'PinInput';
|
|
2317
2396
|
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2397
|
+
// Resistance applied to the finger's travel, so the pull feels elastic.
|
|
2398
|
+
const RESISTANCE = 0.45;
|
|
2399
|
+
// Finger travel (px) before deciding whether a gesture is a pull or a sideways swipe.
|
|
2400
|
+
const AXIS_LOCK_SLOP = 8;
|
|
2401
|
+
// Overlay badge size and the gap it rests at below the container's top edge.
|
|
2402
|
+
const BADGE_SIZE = 40;
|
|
2403
|
+
const BADGE_TOP_GAP = 10;
|
|
2404
|
+
function PullArrowIcon({ progress, className }) {
|
|
2405
|
+
return (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: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 14l-7 7m0 0l-7-7m7 7V3" }) }));
|
|
2321
2406
|
}
|
|
2322
|
-
function PullToRefresh({ onRefresh, pullThreshold = 70, maxPullDistance = 120, disabled = false, refreshing: externalRefreshing, pullingContent, refreshingContent, children, className = '', contentClass = '', }) {
|
|
2407
|
+
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 = '', }) {
|
|
2323
2408
|
const containerRef = useRef(null);
|
|
2324
|
-
const startYRef = useRef(0);
|
|
2325
|
-
const isPullingRef = useRef(false);
|
|
2326
2409
|
const [pullDistance, setPullDistance] = useState(0);
|
|
2410
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
2327
2411
|
const [internalRefreshing, setInternalRefreshing] = useState(false);
|
|
2412
|
+
const [announcement, setAnnouncement] = useState('');
|
|
2328
2413
|
const isRefreshing = externalRefreshing !== undefined ? externalRefreshing : internalRefreshing;
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2414
|
+
// The touch listeners are attached once, natively (see below), so they read
|
|
2415
|
+
// the latest props and state through this ref instead of stale closures.
|
|
2416
|
+
const latest = useRef({
|
|
2417
|
+
disabled,
|
|
2418
|
+
isRefreshing,
|
|
2419
|
+
pullThreshold,
|
|
2420
|
+
maxPullDistance,
|
|
2421
|
+
onRefresh,
|
|
2422
|
+
onError,
|
|
2423
|
+
refreshedLabel,
|
|
2424
|
+
});
|
|
2425
|
+
useLayoutEffect(() => {
|
|
2426
|
+
latest.current = {
|
|
2427
|
+
disabled,
|
|
2428
|
+
isRefreshing,
|
|
2429
|
+
pullThreshold,
|
|
2430
|
+
maxPullDistance,
|
|
2431
|
+
onRefresh,
|
|
2432
|
+
onError,
|
|
2433
|
+
refreshedLabel,
|
|
2434
|
+
};
|
|
2435
|
+
});
|
|
2436
|
+
useEffect(() => {
|
|
2341
2437
|
const container = containerRef.current;
|
|
2342
|
-
if (!container
|
|
2438
|
+
if (!container)
|
|
2343
2439
|
return;
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
}
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2440
|
+
let mounted = true;
|
|
2441
|
+
let pulling = false;
|
|
2442
|
+
let axis = 'pending';
|
|
2443
|
+
let startX = 0;
|
|
2444
|
+
let startY = 0;
|
|
2445
|
+
let distance = 0;
|
|
2446
|
+
const setDistance = (next) => {
|
|
2447
|
+
distance = next;
|
|
2448
|
+
setPullDistance(next);
|
|
2449
|
+
};
|
|
2450
|
+
// Ends a gesture without refreshing: a cancelled touch, a sideways swipe,
|
|
2451
|
+
// or content that scrolled away from the top.
|
|
2452
|
+
const reset = () => {
|
|
2453
|
+
pulling = false;
|
|
2454
|
+
setIsDragging(false);
|
|
2455
|
+
setDistance(0);
|
|
2456
|
+
};
|
|
2457
|
+
const refresh = async () => {
|
|
2458
|
+
const { pullThreshold: threshold, onRefresh: run, onError: report } = latest.current;
|
|
2459
|
+
setDistance(threshold);
|
|
2364
2460
|
setInternalRefreshing(true);
|
|
2365
2461
|
try {
|
|
2366
|
-
await
|
|
2462
|
+
await run();
|
|
2463
|
+
if (mounted)
|
|
2464
|
+
setAnnouncement(latest.current.refreshedLabel);
|
|
2465
|
+
}
|
|
2466
|
+
catch (error) {
|
|
2467
|
+
report === null || report === void 0 ? void 0 : report(error);
|
|
2367
2468
|
}
|
|
2368
2469
|
finally {
|
|
2369
|
-
|
|
2370
|
-
|
|
2470
|
+
if (mounted) {
|
|
2471
|
+
setInternalRefreshing(false);
|
|
2472
|
+
setDistance(0);
|
|
2473
|
+
}
|
|
2371
2474
|
}
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2475
|
+
};
|
|
2476
|
+
const handleTouchStart = (e) => {
|
|
2477
|
+
var _a, _b;
|
|
2478
|
+
const { disabled: isDisabled, isRefreshing: busy } = latest.current;
|
|
2479
|
+
if (isDisabled || busy || container.scrollTop > 0 || e.touches.length !== 1)
|
|
2480
|
+
return;
|
|
2481
|
+
startX = (_a = e.touches[0].clientX) !== null && _a !== void 0 ? _a : 0;
|
|
2482
|
+
startY = (_b = e.touches[0].clientY) !== null && _b !== void 0 ? _b : 0;
|
|
2483
|
+
axis = 'pending';
|
|
2484
|
+
pulling = true;
|
|
2485
|
+
setAnnouncement('');
|
|
2486
|
+
setIsDragging(true);
|
|
2487
|
+
setDistance(0);
|
|
2488
|
+
};
|
|
2489
|
+
const handleTouchMove = (e) => {
|
|
2490
|
+
var _a, _b, _c, _d;
|
|
2491
|
+
if (!pulling)
|
|
2492
|
+
return;
|
|
2493
|
+
if (latest.current.disabled || latest.current.isRefreshing || container.scrollTop > 0) {
|
|
2494
|
+
reset();
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
const dx = ((_b = (_a = e.touches[0]) === null || _a === void 0 ? void 0 : _a.clientX) !== null && _b !== void 0 ? _b : 0) - startX;
|
|
2498
|
+
const dy = ((_d = (_c = e.touches[0]) === null || _c === void 0 ? void 0 : _c.clientY) !== null && _d !== void 0 ? _d : 0) - startY;
|
|
2499
|
+
if (axis === 'pending') {
|
|
2500
|
+
if (Math.abs(dx) < AXIS_LOCK_SLOP && Math.abs(dy) < AXIS_LOCK_SLOP)
|
|
2501
|
+
return;
|
|
2502
|
+
axis = dy > 0 && Math.abs(dy) > Math.abs(dx) ? 'vertical' : 'horizontal';
|
|
2503
|
+
// A sideways swipe or an upward scroll belongs to the content, not to us.
|
|
2504
|
+
if (axis === 'horizontal') {
|
|
2505
|
+
reset();
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
const next = Math.min(latest.current.maxPullDistance, Math.max(0, dy) * RESISTANCE);
|
|
2510
|
+
setDistance(next);
|
|
2511
|
+
// Needs the non-passive listener: stops the browser's own pull-to-refresh/overscroll.
|
|
2512
|
+
if (next > 0 && e.cancelable)
|
|
2513
|
+
e.preventDefault();
|
|
2514
|
+
};
|
|
2515
|
+
const handleTouchEnd = () => {
|
|
2516
|
+
if (!pulling)
|
|
2517
|
+
return;
|
|
2518
|
+
pulling = false;
|
|
2519
|
+
setIsDragging(false);
|
|
2520
|
+
if (distance >= latest.current.pullThreshold)
|
|
2521
|
+
void refresh();
|
|
2522
|
+
else
|
|
2523
|
+
setDistance(0);
|
|
2524
|
+
};
|
|
2525
|
+
// React registers touch listeners as passive, where preventDefault() is
|
|
2526
|
+
// ignored, so these are attached natively with touchmove non-passive.
|
|
2527
|
+
container.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
2528
|
+
container.addEventListener('touchmove', handleTouchMove, { passive: false });
|
|
2529
|
+
container.addEventListener('touchend', handleTouchEnd);
|
|
2530
|
+
container.addEventListener('touchcancel', reset);
|
|
2531
|
+
return () => {
|
|
2532
|
+
mounted = false;
|
|
2533
|
+
container.removeEventListener('touchstart', handleTouchStart);
|
|
2534
|
+
container.removeEventListener('touchmove', handleTouchMove);
|
|
2535
|
+
container.removeEventListener('touchend', handleTouchEnd);
|
|
2536
|
+
container.removeEventListener('touchcancel', reset);
|
|
2537
|
+
};
|
|
2538
|
+
}, []);
|
|
2377
2539
|
const progress = Math.min(1, pullDistance / pullThreshold);
|
|
2378
2540
|
const isReady = pullDistance >= pullThreshold;
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2541
|
+
const offset = isRefreshing ? pullThreshold : pullDistance;
|
|
2542
|
+
// Follow the finger exactly while dragging; animate only the settle afterwards.
|
|
2543
|
+
const motionClass = isDragging
|
|
2544
|
+
? 'transition-none'
|
|
2545
|
+
: 'transition-transform duration-200 ease-out motion-reduce:transition-none';
|
|
2546
|
+
return (jsxs("div", { ref: containerRef, className: buildClassName('relative overflow-y-auto overscroll-none', className), "data-testid": "pull-to-refresh-container", children: [jsx("span", { role: "status", "aria-live": "polite", className: "sr-only", children: isRefreshing ? refreshingLabel : announcement }), variant === 'overlay' ? (jsx("div", { className: buildClassName('pointer-events-none absolute inset-x-0 top-0 z-20 flex justify-center', motionClass), style: {
|
|
2547
|
+
transform: `translateY(${offset - BADGE_SIZE + BADGE_TOP_GAP}px)`,
|
|
2548
|
+
opacity: offset > 0 ? 1 : 0,
|
|
2549
|
+
}, "aria-hidden": "true", "data-testid": "pull-to-refresh-indicator", children: jsx("div", { className: "grid size-10 place-items-center rounded-full bg-[var(--ui-primary)] text-white shadow-lg", children: isRefreshing
|
|
2550
|
+
? (refreshingContent !== null && refreshingContent !== void 0 ? refreshingContent : jsx(Loader, { size: "xs", className: "text-white" }))
|
|
2551
|
+
: (pullingContent !== null && pullingContent !== void 0 ? pullingContent : jsx(PullArrowIcon, { progress: progress, className: "size-4" })) }) })) : (
|
|
2552
|
+
// Absolutely positioned, so the content's own translate is the only shift.
|
|
2553
|
+
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: 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
|
|
2554
|
+
? (refreshingContent !== null && refreshingContent !== void 0 ? refreshingContent : (jsxs(Fragment, { children: [jsx(Loader, { size: "sm" }), jsx("span", { children: refreshingLabel })] })))
|
|
2555
|
+
: (pullingContent !== null && pullingContent !== void 0 ? pullingContent : (jsxs(Fragment, { children: [jsx(PullArrowIcon, { progress: progress, className: "size-4 text-[var(--ui-primary)] dark:text-blue-400" }), jsx("span", { children: isReady ? releaseLabel : pullLabel })] }))) }) })), jsx("div", { className: buildClassName(variant === 'push' && motionClass, contentClass), style: variant === 'push' ? { transform: `translateY(${offset}px)` } : undefined, "data-testid": "pull-to-refresh-content", children: children })] }));
|
|
2389
2556
|
}
|
|
2390
2557
|
PullToRefresh.displayName = 'PullToRefresh';
|
|
2391
2558
|
|
|
@@ -3298,12 +3465,23 @@ function ChevronLeftIcon$1() {
|
|
|
3298
3465
|
function SearchIcon$1() {
|
|
3299
3466
|
return (jsx("svg", { className: "size-4 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }));
|
|
3300
3467
|
}
|
|
3468
|
+
function MoreIcon() {
|
|
3469
|
+
return (jsxs("svg", { className: "size-5", fill: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: [jsx("circle", { cx: "12", cy: "5", r: "1.75" }), jsx("circle", { cx: "12", cy: "12", r: "1.75" }), jsx("circle", { cx: "12", cy: "19", r: "1.75" })] }));
|
|
3470
|
+
}
|
|
3301
3471
|
function ClearIcon() {
|
|
3302
3472
|
return (jsx("svg", { className: "size-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }));
|
|
3303
3473
|
}
|
|
3304
|
-
function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabel = 'Back', leading, actions, searchable = false, searchValue: externalSearchValue, onSearchChange, searchPlaceholder = 'Search...', sticky = true, transparent = false, bottomSlot, className = '', }) {
|
|
3474
|
+
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 = '', }) {
|
|
3305
3475
|
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
3306
3476
|
const [internalSearchValue, setInternalSearchValue] = useState('');
|
|
3477
|
+
const [isOverflowOpen, setIsOverflowOpen] = useState(false);
|
|
3478
|
+
// The overflow button takes one of the visible slots.
|
|
3479
|
+
const allActions = actions !== null && actions !== void 0 ? actions : [];
|
|
3480
|
+
const shouldOverflow = maxVisibleActions !== undefined && allActions.length > maxVisibleActions;
|
|
3481
|
+
const visibleActions = shouldOverflow
|
|
3482
|
+
? allActions.slice(0, Math.max(maxVisibleActions - 1, 0))
|
|
3483
|
+
: allActions;
|
|
3484
|
+
const overflowActions = shouldOverflow ? allActions.slice(visibleActions.length) : [];
|
|
3307
3485
|
const searchValue = externalSearchValue !== undefined ? externalSearchValue : internalSearchValue;
|
|
3308
3486
|
const handleSearchInput = (e) => {
|
|
3309
3487
|
const val = e.target.value;
|
|
@@ -3318,14 +3496,20 @@ function MobileHeader({ title, subtitle, titleAlign = 'center', onBack, backLabe
|
|
|
3318
3496
|
? 'bg-white/80 dark:bg-gray-900/80 backdrop-blur-md border-b border-[var(--ui-border-muted)]'
|
|
3319
3497
|
: '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: [jsx("div", { className: "h-14 px-3.5 flex items-center justify-between gap-3 relative", children: searchable && isSearchActive ? (jsxs("div", { className: "flex-1 flex items-center gap-2", "data-testid": "mobile-header-search-container", children: [jsxs("div", { className: "relative flex-1 flex items-center", children: [jsx("span", { className: "absolute left-3 pointer-events-none", children: jsx(SearchIcon$1, {}) }), 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 && (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: jsx(ClearIcon, {}) }))] }), 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" })] })) : (jsxs(Fragment, { children: [jsx("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 z-10", children: onBack ? (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: [jsx(ChevronLeftIcon$1, {}), jsx("span", { className: "text-xs font-semibold hidden sm:inline", children: backLabel })] })) : (leading) }), jsxs("div", { className: buildClassName('flex-1 min-w-0 px-2', titleAlign === 'center'
|
|
3320
3498
|
? 'absolute inset-x-12 text-center pointer-events-none flex flex-col items-center justify-center'
|
|
3321
|
-
: 'text-left'), children: [title && (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 && (jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (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: jsx(SearchIcon$1, {}) })),
|
|
3499
|
+
: 'text-left'), children: [title && (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 && (jsx("div", { className: "text-[11px] text-gray-500 dark:text-gray-400 truncate pointer-events-auto", "data-testid": "mobile-header-subtitle", children: subtitle }))] }), jsxs("div", { className: "flex items-center gap-1 min-w-[40px] shrink-0 justify-end z-10", children: [searchable && (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: jsx(SearchIcon$1, {}) })), visibleActions.map((action) => {
|
|
3322
3500
|
const badgeColor = {
|
|
3323
3501
|
danger: 'bg-red-500 text-white',
|
|
3324
3502
|
info: 'bg-blue-500 text-white',
|
|
3325
3503
|
success: 'bg-emerald-500 text-white',
|
|
3326
3504
|
}[action.badgeVariant || 'danger'];
|
|
3327
3505
|
return (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: [jsx("span", { className: "text-base", children: action.icon }), action.badge !== undefined && action.badge !== null && (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));
|
|
3328
|
-
})
|
|
3506
|
+
}), overflowActions.length > 0 && (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: jsx(MoreIcon, {}) })), trailing] })] })) }), overflowActions.length > 0 && (jsx(ActionSheet, { isOpen: isOverflowOpen, onClose: () => setIsOverflowOpen(false), actions: overflowActions.map((action) => ({
|
|
3507
|
+
id: action.id,
|
|
3508
|
+
label: action.label,
|
|
3509
|
+
icon: action.icon,
|
|
3510
|
+
disabled: action.disabled,
|
|
3511
|
+
onClick: action.onClick,
|
|
3512
|
+
})) })), bottomSlot && (jsx("div", { className: "px-3 py-2 border-t border-[var(--ui-border-muted)]", "data-testid": "mobile-header-bottom-slot", children: bottomSlot }))] }));
|
|
3329
3513
|
}
|
|
3330
3514
|
MobileHeader.displayName = 'MobileHeader';
|
|
3331
3515
|
|
|
@@ -3828,9 +4012,18 @@ const sizeClasses$1 = {
|
|
|
3828
4012
|
},
|
|
3829
4013
|
};
|
|
3830
4014
|
const SelectBox = forwardRef((props, forwardedRef) => {
|
|
3831
|
-
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;
|
|
4015
|
+
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;
|
|
4016
|
+
// Uncontrolled when `selected` is not passed: keep the selection internally.
|
|
4017
|
+
const isControlled = selectedProp !== undefined;
|
|
4018
|
+
const [internalSelected, setInternalSelected] = useState(defaultSelected !== null && defaultSelected !== void 0 ? defaultSelected : null);
|
|
4019
|
+
const selected = isControlled ? selectedProp : internalSelected;
|
|
4020
|
+
const onChange = useCallback((value) => {
|
|
4021
|
+
if (!isControlled)
|
|
4022
|
+
setInternalSelected(value);
|
|
4023
|
+
onChangeProp(value);
|
|
4024
|
+
}, [isControlled, onChangeProp]);
|
|
3832
4025
|
if (props.asBottomSheet || props.mobileMode === 'sheet') {
|
|
3833
|
-
return jsx(MobilePicker, { ...props });
|
|
4026
|
+
return jsx(MobilePicker, { ...props, selected: selected, onChange: onChange });
|
|
3834
4027
|
}
|
|
3835
4028
|
const hasError = Boolean(error);
|
|
3836
4029
|
const [query, setQuery] = useState('');
|
|
@@ -4028,6 +4221,16 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
4028
4221
|
}
|
|
4029
4222
|
}, [isAllSelected, onChange, allOptions]);
|
|
4030
4223
|
const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
|
|
4224
|
+
// Reserve room for the chevron (and the clear button when shown) so long values
|
|
4225
|
+
// don't run underneath them.
|
|
4226
|
+
const showClearButton = allowClear && !multiple && !isEmpty(normalizedSelected);
|
|
4227
|
+
const rightPaddingClass = showClearButton
|
|
4228
|
+
? 'pe-16'
|
|
4229
|
+
: size === 'lg'
|
|
4230
|
+
? 'pe-11'
|
|
4231
|
+
: size === 'sm'
|
|
4232
|
+
? 'pe-9'
|
|
4233
|
+
: 'pe-10';
|
|
4031
4234
|
const hasFreeTextOption = Boolean(allowFreeText &&
|
|
4032
4235
|
query.trim() &&
|
|
4033
4236
|
!allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }));
|
|
@@ -4040,7 +4243,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
4040
4243
|
const isOptSelected = selectedValueSet.has(optVal);
|
|
4041
4244
|
return (jsx(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))) : (jsxs("div", { className: "flex items-center justify-between gap-2 text-gray-900 dark:text-white group-data-[selected]:text-blue-600", children: [jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] })) }, optVal));
|
|
4042
4245
|
};
|
|
4043
|
-
return (jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs(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 ? (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 && (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) &&
|
|
4246
|
+
return (jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs(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 ? (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 && (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) &&
|
|
4044
4247
|
(normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) > 0 &&
|
|
4045
4248
|
normalizedSelected.map((option, idx) => (jsx(Badge, { theme: "secondary", size: currentSize.badgeSize, removable: !disabled, onRemove: () => removeSelection(idx), children: getDisplayValue(option) }, `${getValue(option)}-${idx}`))), jsx(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) => {
|
|
4046
4249
|
const val = e.target.value;
|
|
@@ -4080,7 +4283,9 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
4080
4283
|
}
|
|
4081
4284
|
}, onFocus: () => {
|
|
4082
4285
|
setQuery('');
|
|
4083
|
-
}, 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" }),
|
|
4286
|
+
}, 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 && (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: jsx(SvgX, { className: "size-4 stroke-gray-500 dark:stroke-gray-300" }) })), jsx(ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5 flex items-center justify-center", "data-testid": "combobox-button", children: jsx(SvgChevronDown, { className: chevronIconClass }) })] })), jsxs(ComboboxOptions, { anchor: { to: 'bottom start', gap: 5, padding: 8 }, as: "div", modal: modalDropdown,
|
|
4287
|
+
// Matches the input width by default; min-width lets the panel grow wider than a narrow input.
|
|
4288
|
+
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 && (jsxs("div", { className: "pb-1 mb-1 border-b border-[var(--ui-border-muted)] px-3 pt-1 flex items-center justify-between", children: [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' }), jsxs("span", { className: "text-[11px] font-medium text-gray-400", children: [Array.isArray(normalizedSelected) ? normalizedSelected.length : 0, " of", ' ', allOptions.length] })] })), optionsLoading ? (jsx(BodyText, { "data-testid": "options-loading", className: "flex items-center justify-center py-2 px-3 select-none", children: jsx(Loader, { size: "sm" }) })) : shouldShowTypeToSearch ? (jsx(BodyText, { className: "flex items-center justify-between py-2 px-3 select-none", children: "Type to search..." })) : (jsxs("div", { children: [allowFreeText &&
|
|
4084
4289
|
query.trim() &&
|
|
4085
4290
|
!allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }) && (jsx(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: jsxs("span", { children: ["Use \"", query.trim(), "\""] }) }, `freetext-${query.trim()}`)), filteredGroups
|
|
4086
4291
|
? filteredGroups.map((grp, gIdx) => (jsxs("div", { children: [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)))
|
|
@@ -4280,14 +4485,14 @@ StatusPill.displayName = 'StatusPill';
|
|
|
4280
4485
|
|
|
4281
4486
|
const ChevronUpIcon = ({ className }) => (jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: jsx("polyline", { points: "18 15 12 9 6 15" }) }));
|
|
4282
4487
|
const StickyActionBar = forwardRef((props, ref) => {
|
|
4283
|
-
const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', className, ...restProps } = props;
|
|
4488
|
+
const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', safeArea = true, bottomOffset, className, style, ...restProps } = props;
|
|
4284
4489
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
4285
4490
|
const positionClass = position === 'fixed'
|
|
4286
4491
|
? 'fixed bottom-0 left-0 right-0 z-30'
|
|
4287
4492
|
: position === 'sticky'
|
|
4288
4493
|
? 'sticky bottom-0 z-30'
|
|
4289
4494
|
: 'relative z-10';
|
|
4290
|
-
return (jsxs(Fragment, { children: [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: jsxs("div", { className: buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (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: [jsx("div", { children: summaryContent }), jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsx("div", { children: summaryContent })) })), actionsContent && (jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (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 }))] }));
|
|
4495
|
+
return (jsxs(Fragment, { children: [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: jsxs("div", { className: buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (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: [jsx("div", { children: summaryContent }), jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsx("div", { children: summaryContent })) })), actionsContent && (jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (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 }))] }));
|
|
4291
4496
|
});
|
|
4292
4497
|
StickyActionBar.displayName = 'StickyActionBar';
|
|
4293
4498
|
|
|
@@ -4550,7 +4755,7 @@ const Toast = ({ id, title, message, type, variant = 'accent', action, icon, sho
|
|
|
4550
4755
|
const renderedIcon = icon !== null && icon !== void 0 ? icon : (variant === 'filled' ? filledIconMap[type] : iconMap[type]);
|
|
4551
4756
|
return (jsx(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: 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: [jsx("div", { className: "shrink-0 mt-0.5", children: variant === 'filled' ? (renderedIcon) : (jsx(TextContent, { className: buildClassName(textColorMap[type]), children: renderedIcon })) }), jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [title && (jsx("div", { className: buildClassName('font-semibold text-sm leading-snug', variant === 'filled' ? 'text-white' : 'text-gray-900 dark:text-gray-100'), children: title })), 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 && (jsx("div", { className: "mt-1.5", children: jsx("button", { type: "button", onClick: action.onClick, className: buildClassName('text-xs font-semibold underline underline-offset-2 transition-opacity focus:outline-none', variant === 'filled'
|
|
4552
4757
|
? 'text-white hover:opacity-85'
|
|
4553
|
-
: 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), 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',
|
|
4758
|
+
: 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), 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: jsx(SvgX, { className: "size-4" }) }), showProgressBar && duration > 0 && (jsx("div", { className: "absolute bottom-0 left-0 right-0 h-1 bg-black/10 dark:bg-white/10 overflow-hidden", children: 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)}%` } }) }))] }) }));
|
|
4554
4759
|
};
|
|
4555
4760
|
Toast.displayName = 'Toast';
|
|
4556
4761
|
|
|
@@ -4880,5 +5085,5 @@ const useIsTabletOrMobile = () => {
|
|
|
4880
5085
|
return isMobile || isTablet;
|
|
4881
5086
|
};
|
|
4882
5087
|
|
|
4883
|
-
export { Accordion, ActionSheet, Alert, Badge, Banner, BodyText, BottomNavigation, Breadcrumb, Button, ButtonGroup, Card, CardContent, CardDescription, CardFooter, CardHeader, CardMedia, CardTitle, Checkbox, CheckboxGroup, DOTS, DataList, DataTable, DetailedInformation, Dot, Drawer, Dropdown, EmptyState, EmptyTableRow, Flex, FlexItem, FloatingActionButton, Grid, GridCol, HeadingText, Input, Label, Loader, MobileHeader, MobilePicker, MobileStepper, PASSWORD_PLACEHOLDER, PASSWORD_TYPE, Pagination, PasswordInput, PinInput, PullToRefresh, QuantityStepper, Radio, RadioGroup, RadioSwitch, RangeInput, RangeSlider, SegmentedControl, SelectBox, Skeleton, SortIconMap, StatusPill, StickyActionBar, SwipeableRow, SwipeableTabs, Table, TableBody, TableCell, TableFoot, TableHead, TableHeaderCell, TableRow, Tabs, TextContent, Textarea, Toast, ToastProvider, VirtualizedDataTable, WheelPicker, buildClassName, buildInputClass, evaluatePasswordStrength, get, getSortIcon, isArray, isEmpty, nextSortDirection, updateSortQuery, useDebounce, useDebounceFn, useIsMobile, useIsTablet, useIsTabletOrMobile, usePagination, useRowSelection, useToast };
|
|
5088
|
+
export { Accordion, ActionSheet, Alert, BREAKPOINT_QUERIES, Badge, Banner, BodyText, BottomNavigation, Breadcrumb, Button, ButtonGroup, Card, CardContent, CardDescription, CardFooter, CardHeader, CardMedia, CardTitle, Checkbox, CheckboxGroup, DOTS, DataList, DataTable, DetailedInformation, Dot, Drawer, Dropdown, EmptyState, EmptyTableRow, Flex, FlexItem, FloatingActionButton, Grid, GridCol, HeadingText, Input, Label, Loader, MobileHeader, MobilePicker, MobileStepper, PASSWORD_PLACEHOLDER, PASSWORD_TYPE, Pagination, PasswordInput, PinInput, PullToRefresh, QuantityStepper, Radio, RadioGroup, RadioSwitch, RangeInput, RangeSlider, SegmentedControl, SelectBox, Skeleton, SortIconMap, StatusPill, StickyActionBar, SwipeableRow, SwipeableTabs, Table, TableBody, TableCell, TableFoot, TableHead, TableHeaderCell, TableRow, Tabs, TextContent, Textarea, Toast, ToastProvider, VirtualizedDataTable, WheelPicker, buildClassName, buildInputClass, evaluatePasswordStrength, get, getSortIcon, isArray, isEmpty, nextSortDirection, updateSortQuery, useBreakpoint, useDebounce, useDebounceFn, useIsMobile, useIsTablet, useIsTabletOrMobile, useMediaQuery, usePagination, useResponsiveValue, useRowSelection, useToast };
|
|
4884
5089
|
//# sourceMappingURL=react-tailwind.es.js.map
|