@sovereignfs/ui 0.21.3 → 0.33.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/Button.module.css +75 -8
- package/dist/Calendar.module.css +126 -0
- package/dist/Card.module.css +4 -2
- package/dist/Checkbox.module.css +109 -0
- package/dist/CodeTextarea.module.css +53 -0
- package/dist/ConfirmDialog.module.css +109 -0
- package/dist/DatePicker.module.css +57 -0
- package/dist/Dialog.module.css +73 -46
- package/dist/DragHandleRow.module.css +57 -0
- package/dist/Drawer.module.css +61 -0
- package/dist/FormField.module.css +5 -0
- package/dist/Icon.module.css +5 -0
- package/dist/Input.module.css +10 -0
- package/dist/Menu.module.css +82 -0
- package/dist/NavTabs.module.css +4 -2
- package/dist/OverlayHeader.module.css +98 -0
- package/dist/Popover.module.css +31 -1
- package/dist/SegmentedControl.module.css +4 -2
- package/dist/Select.module.css +9 -0
- package/dist/Sheet.module.css +55 -0
- package/dist/SplitPane.module.css +85 -0
- package/dist/StatusBadge.module.css +59 -0
- package/dist/SystemBanner.module.css +4 -2
- package/dist/Tabs.module.css +4 -2
- package/dist/TagInput.module.css +141 -0
- package/dist/Textarea.module.css +39 -0
- package/dist/Toast.module.css +7 -5
- package/dist/Toggle.module.css +1 -1
- package/dist/Tooltip.module.css +6 -1
- package/dist/index.d.ts +549 -20
- package/dist/index.js +1496 -193
- package/dist/tokens/primitives.css +19 -0
- package/dist/tokens/semantic.css +19 -0
- package/package.json +1 -1
- package/src/__tests__/motion.test.tsx +105 -0
- package/src/components/Button/Button.module.css +75 -8
- package/src/components/Button/Button.stories.tsx +6 -0
- package/src/components/Button/Button.tsx +17 -1
- package/src/components/Button/__tests__/Button.test.tsx +19 -0
- package/src/components/Calendar/Calendar.module.css +126 -0
- package/src/components/Calendar/Calendar.tsx +213 -0
- package/src/components/Calendar/__tests__/Calendar.test.tsx +98 -0
- package/src/components/Calendar/dateUtils.ts +97 -0
- package/src/components/Card/Card.module.css +4 -2
- package/src/components/Checkbox/Checkbox.module.css +109 -0
- package/src/components/Checkbox/Checkbox.tsx +78 -0
- package/src/components/CodeTextarea/CodeTextarea.module.css +53 -0
- package/src/components/CodeTextarea/CodeTextarea.stories.tsx +77 -0
- package/src/components/CodeTextarea/CodeTextarea.tsx +36 -0
- package/src/components/ConfirmDialog/ConfirmDialog.module.css +109 -0
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +124 -0
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +146 -0
- package/src/components/DatePicker/DatePicker.module.css +57 -0
- package/src/components/DatePicker/DatePicker.tsx +103 -0
- package/src/components/DatePicker/__tests__/DatePicker.test.tsx +89 -0
- package/src/components/Dialog/Dialog.module.css +73 -46
- package/src/components/Dialog/Dialog.stories.tsx +51 -1
- package/src/components/Dialog/Dialog.tsx +86 -22
- package/src/components/Dialog/__tests__/Dialog.test.tsx +67 -3
- package/src/components/DragHandleRow/DragHandleRow.module.css +57 -0
- package/src/components/DragHandleRow/DragHandleRow.tsx +61 -0
- package/src/components/Drawer/Drawer.module.css +61 -0
- package/src/components/Drawer/Drawer.tsx +117 -13
- package/src/components/Drawer/__tests__/Drawer.test.tsx +123 -2
- package/src/components/FormField/FormField.module.css +5 -0
- package/src/components/FormField/FormField.tsx +36 -11
- package/src/components/FormField/__tests__/FormField.test.tsx +83 -0
- package/src/components/Icon/Icon.module.css +5 -0
- package/src/components/Icon/Icon.stories.tsx +4 -3
- package/src/components/Icon/Icon.tsx +1 -1
- package/src/components/Icon/icons/calendar.tsx +23 -0
- package/src/components/Icon/icons/ellipsis-vertical.tsx +22 -0
- package/src/components/Icon/icons/index.ts +8 -4
- package/src/components/Icon/icons/sliders-horizontal.tsx +9 -9
- package/src/components/Input/Input.module.css +10 -0
- package/src/components/Menu/Menu.module.css +82 -0
- package/src/components/Menu/Menu.tsx +137 -0
- package/src/components/Menu/__tests__/Menu.test.tsx +197 -0
- package/src/components/NavTabs/NavTabs.module.css +4 -2
- package/src/components/OverlayHeader/OverlayHeader.module.css +98 -0
- package/src/components/OverlayHeader/OverlayHeader.tsx +62 -0
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +51 -0
- package/src/components/Popover/Popover.module.css +31 -1
- package/src/components/Popover/Popover.stories.tsx +126 -1
- package/src/components/Popover/Popover.tsx +140 -7
- package/src/components/SegmentedControl/SegmentedControl.module.css +4 -2
- package/src/components/Select/Select.module.css +9 -0
- package/src/components/Select/Select.tsx +10 -2
- package/src/components/Sheet/Sheet.module.css +55 -0
- package/src/components/Sheet/Sheet.tsx +151 -0
- package/src/components/Sheet/__tests__/Sheet.test.tsx +99 -0
- package/src/components/SplitPane/SplitPane.module.css +85 -0
- package/src/components/SplitPane/SplitPane.stories.tsx +160 -0
- package/src/components/SplitPane/SplitPane.tsx +149 -0
- package/src/components/SplitPane/__tests__/SplitPane.test.tsx +51 -0
- package/src/components/StatusBadge/StatusBadge.module.css +59 -0
- package/src/components/StatusBadge/StatusBadge.stories.tsx +74 -0
- package/src/components/StatusBadge/StatusBadge.tsx +65 -0
- package/src/components/SystemBanner/SystemBanner.module.css +4 -2
- package/src/components/Tabs/Tabs.module.css +4 -2
- package/src/components/TagInput/TagInput.module.css +141 -0
- package/src/components/TagInput/TagInput.stories.tsx +109 -0
- package/src/components/TagInput/TagInput.tsx +174 -0
- package/src/components/TagInput/__tests__/TagInput.test.tsx +69 -0
- package/src/components/Textarea/Textarea.module.css +39 -0
- package/src/components/Textarea/Textarea.tsx +15 -0
- package/src/components/Textarea/__tests__/Textarea.test.tsx +29 -0
- package/src/components/Toast/Toast.module.css +7 -5
- package/src/components/Toggle/Toggle.module.css +1 -1
- package/src/components/Tooltip/Tooltip.module.css +6 -1
- package/src/hooks/__tests__/useDoubleTap.test.tsx +117 -0
- package/src/hooks/__tests__/useIsMobile.test.tsx +80 -0
- package/src/hooks/__tests__/useLongPress.test.tsx +158 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useDoubleTap.ts +91 -0
- package/src/hooks/useIsMobile.ts +33 -0
- package/src/hooks/useLongPress.ts +185 -0
- package/src/index.ts +37 -2
- package/src/motion.ts +78 -0
- package/src/stories/Checkbox.stories.tsx +45 -0
- package/src/stories/DesignSystemOverview.stories.tsx +354 -8
- package/src/stories/DragHandleRow.stories.tsx +41 -0
- package/src/stories/FormField.stories.tsx +8 -8
- package/src/stories/InteractionHooks.stories.tsx +388 -0
- package/src/stories/MobilePatterns.stories.tsx +4 -4
- package/src/stories/Textarea.stories.tsx +51 -0
- package/src/stories/TokenGallery.stories.tsx +87 -1
- package/src/tokens/primitives.css +19 -0
- package/src/tokens/semantic.css +19 -0
- package/src/components/Icon/icons/terminal.tsx +0 -19
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode, ButtonHTMLAttributes, SelectHTMLAttributes, ReactElement, InputHTMLAttributes, HTMLAttributes } from 'react';
|
|
2
|
+
import { ReactNode, ButtonHTMLAttributes, SelectHTMLAttributes, ReactElement, CSSProperties, InputHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, PointerEvent, MouseEvent } from 'react';
|
|
3
3
|
|
|
4
4
|
type BadgeVariant = 'role' | 'status' | 'mono';
|
|
5
5
|
type BadgeStatus = 'active' | 'enabled' | 'deactivated' | 'failed' | 'invited' | 'pending' | 'neutral';
|
|
@@ -18,7 +18,7 @@ interface BadgeProps {
|
|
|
18
18
|
*/
|
|
19
19
|
declare function Badge({ variant, status, children }: BadgeProps): react.JSX.Element;
|
|
20
20
|
|
|
21
|
-
type IconName = 'house' | 'settings' | 'log-out' | 'chevron-right' | 'chevron-left' | 'chevron-down' | 'chevron-up' | 'x' | 'check' | 'plus' | 'trash-2' | 'pencil' | 'rotate-ccw' | 'search' | 'user' | 'shield' | 'lock' | 'eye' | 'eye-off' | 'mail' | 'bell' | 'activity' | 'package' | 'grid-2x2' | 'info' | 'alert-triangle' | 'sliders-horizontal' | '
|
|
21
|
+
type IconName = 'house' | 'settings' | 'log-out' | 'chevron-right' | 'chevron-left' | 'chevron-down' | 'chevron-up' | 'x' | 'check' | 'plus' | 'trash-2' | 'pencil' | 'rotate-ccw' | 'search' | 'user' | 'shield' | 'lock' | 'eye' | 'eye-off' | 'mail' | 'bell' | 'activity' | 'package' | 'grid-2x2' | 'info' | 'alert-triangle' | 'calendar' | 'sliders-horizontal' | 'ellipsis-vertical';
|
|
22
22
|
|
|
23
23
|
/** Decorative icon — visually meaningful but described by surrounding text. */
|
|
24
24
|
type DecorativeProps = {
|
|
@@ -34,7 +34,7 @@ interface IconProps {
|
|
|
34
34
|
/** Name of the icon from the curated Sovereign set. */
|
|
35
35
|
name: IconName;
|
|
36
36
|
/** Visual size. Defaults to `"md"` (20 px). */
|
|
37
|
-
size?: 'sm' | 'md' | 'lg';
|
|
37
|
+
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
38
38
|
/** Additional CSS class applied to the SVG element. */
|
|
39
39
|
className?: string;
|
|
40
40
|
}
|
|
@@ -121,6 +121,12 @@ type SelectProps = Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size'> & {
|
|
|
121
121
|
* Uses the native picker on mobile — no custom dropdown, maximum a11y coverage.
|
|
122
122
|
*
|
|
123
123
|
* RSC-safe: presentational, no hooks, forwards all native select props.
|
|
124
|
+
*
|
|
125
|
+
* `className` sizes the outer box (the wrapper), not the `<select>` itself —
|
|
126
|
+
* the chevron is absolutely positioned against the wrapper, so a className
|
|
127
|
+
* that constrains only the `<select>` (e.g. `max-width`) shrinks the select
|
|
128
|
+
* while the wrapper (and thus the chevron) stays full width, leaving the
|
|
129
|
+
* chevron stranded past the visible box.
|
|
124
130
|
*/
|
|
125
131
|
declare function Select({ className, size, children, ...rest }: SelectProps): react.JSX.Element;
|
|
126
132
|
|
|
@@ -147,25 +153,53 @@ interface PopoverProps {
|
|
|
147
153
|
onClose: () => void;
|
|
148
154
|
/** Which edge of the trigger the panel aligns to. Default: 'right'. */
|
|
149
155
|
align?: 'left' | 'right';
|
|
150
|
-
/** Panel width in px
|
|
151
|
-
|
|
156
|
+
/** Panel width in px, or `'trigger'` to match the trigger element's own
|
|
157
|
+
* rendered width exactly (e.g. a dropdown under a full-width form field,
|
|
158
|
+
* where a fixed pixel width would either overflow or leave a gap
|
|
159
|
+
* depending on viewport size). Default: 288. */
|
|
160
|
+
width?: number | 'trigger';
|
|
161
|
+
/**
|
|
162
|
+
* Inline style overrides for the panel, merged after `width`. Escape hatch
|
|
163
|
+
* for callers that need to override the panel's own chrome (e.g. no
|
|
164
|
+
* rounded corners for a compact swatch picker) — inline styles always win
|
|
165
|
+
* regardless of CSS module load order, unlike passing an extra className.
|
|
166
|
+
*/
|
|
167
|
+
panelStyle?: CSSProperties;
|
|
152
168
|
'aria-label': string;
|
|
153
169
|
children: ReactNode;
|
|
154
170
|
}
|
|
155
171
|
/**
|
|
156
|
-
* Popover — floating panel anchored
|
|
172
|
+
* Popover — floating panel anchored to a trigger element, below it by
|
|
173
|
+
* default.
|
|
157
174
|
*
|
|
158
175
|
* Positioning is `position: absolute` inside a `position: relative` wrapper —
|
|
159
|
-
* no floating-ui dependency. Supports left/right alignment against the trigger
|
|
176
|
+
* no floating-ui dependency. Supports left/right alignment against the trigger
|
|
177
|
+
* edge, and automatically opens on whichever side (above or below the
|
|
178
|
+
* trigger) has more room within the viewport.
|
|
179
|
+
*
|
|
180
|
+
* If the panel doesn't fit on *either* side as the trigger currently sits —
|
|
181
|
+
* common for a tall panel (e.g. a full calendar month) nested partway down a
|
|
182
|
+
* long scrollable mobile sheet — the trigger's scrollable ancestor is
|
|
183
|
+
* scrolled so the trigger sits at the top of it first, maximizing the room
|
|
184
|
+
* available below, then the panel opens downward against that. This mirrors
|
|
185
|
+
* how most mobile date pickers behave (scroll the page, not the picker) and
|
|
186
|
+
* avoids the panel needing its own internal scroll in the vast majority of
|
|
187
|
+
* cases. If it's still too tall even after that (a genuinely tiny viewport,
|
|
188
|
+
* or an unusually tall panel), it's capped to whatever room actually exists
|
|
189
|
+
* and scrolls internally as a last resort, rather than rendering past the
|
|
190
|
+
* edge of the viewport with no way to reach the rest of it.
|
|
191
|
+
*
|
|
160
192
|
* Closes on outside click or Escape. Does not trap focus (it is non-modal).
|
|
161
193
|
*/
|
|
162
|
-
declare function Popover({ trigger, open, onClose, align, width, 'aria-label': ariaLabel, children, }: PopoverProps): react.JSX.Element;
|
|
194
|
+
declare function Popover({ trigger, open, onClose, align, width, panelStyle, 'aria-label': ariaLabel, children, }: PopoverProps): react.JSX.Element;
|
|
163
195
|
|
|
164
196
|
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'destructive';
|
|
165
197
|
type ButtonSize = 'sm' | 'md';
|
|
166
198
|
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
167
199
|
variant?: ButtonVariant;
|
|
168
200
|
size?: ButtonSize;
|
|
201
|
+
/** Shows a spinner and disables the button while an async action is pending. */
|
|
202
|
+
loading?: boolean;
|
|
169
203
|
children: ReactNode;
|
|
170
204
|
}
|
|
171
205
|
/**
|
|
@@ -174,7 +208,7 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
174
208
|
* renders in both Server and Client Components. All styling references `--sv-*`
|
|
175
209
|
* tokens via CSS Modules; there are no hardcoded values.
|
|
176
210
|
*/
|
|
177
|
-
declare function Button({ variant, size, type, className, children, ...rest }: ButtonProps): react.JSX.Element;
|
|
211
|
+
declare function Button({ variant, size, type, loading, disabled, className, children, ...rest }: ButtonProps): react.JSX.Element;
|
|
178
212
|
|
|
179
213
|
type InputProps = InputHTMLAttributes<HTMLInputElement>;
|
|
180
214
|
/**
|
|
@@ -184,6 +218,105 @@ type InputProps = InputHTMLAttributes<HTMLInputElement>;
|
|
|
184
218
|
*/
|
|
185
219
|
declare function Input({ type, className, ...rest }: InputProps): react.JSX.Element;
|
|
186
220
|
|
|
221
|
+
type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
222
|
+
/**
|
|
223
|
+
* Textarea — the primitive multi-line text field. Presentational and
|
|
224
|
+
* RSC-safe: it forwards all native textarea props to the underlying
|
|
225
|
+
* `<textarea>`. Styling references `--sv-*` tokens via CSS Modules; there
|
|
226
|
+
* are no hardcoded values.
|
|
227
|
+
*/
|
|
228
|
+
declare function Textarea({ className, rows, ...rest }: TextareaProps): react.JSX.Element;
|
|
229
|
+
|
|
230
|
+
interface CodeTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
231
|
+
invalid?: boolean;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* CodeTextarea — monospace textarea for Markdown, YAML, JSON, and other
|
|
235
|
+
* whitespace-sensitive content. Pair with FormField for labels, hints, and
|
|
236
|
+
* validation text.
|
|
237
|
+
*/
|
|
238
|
+
declare function CodeTextarea({ className, rows, spellCheck, wrap, invalid, 'aria-invalid': ariaInvalid, ...rest }: CodeTextareaProps): react.JSX.Element;
|
|
239
|
+
|
|
240
|
+
type StatusBadgeStatus = 'unmodified' | 'draft' | 'committed' | 'conflict' | 'pending-delete' | 'synced' | 'warning' | 'error';
|
|
241
|
+
interface StatusBadgeProps {
|
|
242
|
+
status: StatusBadgeStatus;
|
|
243
|
+
children?: ReactNode;
|
|
244
|
+
/** Accessible label when the visible text is abbreviated or contextual. */
|
|
245
|
+
'aria-label'?: string;
|
|
246
|
+
className?: string;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* StatusBadge — compact inline status indicator for editor lists, draft
|
|
250
|
+
* workflows, and sync states.
|
|
251
|
+
*/
|
|
252
|
+
declare function StatusBadge({ status, children, className, 'aria-label': ariaLabel, }: StatusBadgeProps): react.JSX.Element;
|
|
253
|
+
|
|
254
|
+
interface SplitPaneProps {
|
|
255
|
+
primary: ReactNode;
|
|
256
|
+
secondary: ReactNode;
|
|
257
|
+
defaultPrimarySize?: number;
|
|
258
|
+
minPrimarySize?: number;
|
|
259
|
+
maxPrimarySize?: number;
|
|
260
|
+
resizable?: boolean;
|
|
261
|
+
primaryLabel?: string;
|
|
262
|
+
secondaryLabel?: string;
|
|
263
|
+
resizeLabel?: string;
|
|
264
|
+
className?: string;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* SplitPane — responsive two-pane layout for editor/preview and list/detail
|
|
268
|
+
* workflows. Desktop panes can be resized; narrow viewports fall back to a
|
|
269
|
+
* stable single-column stack.
|
|
270
|
+
*/
|
|
271
|
+
declare function SplitPane({ primary, secondary, defaultPrimarySize, minPrimarySize, maxPrimarySize, resizable, primaryLabel, secondaryLabel, resizeLabel, className, }: SplitPaneProps): react.JSX.Element;
|
|
272
|
+
|
|
273
|
+
interface TagInputProps {
|
|
274
|
+
value: string[];
|
|
275
|
+
onChange: (value: string[]) => void;
|
|
276
|
+
id?: string;
|
|
277
|
+
name?: string;
|
|
278
|
+
placeholder?: string;
|
|
279
|
+
disabled?: boolean;
|
|
280
|
+
required?: boolean;
|
|
281
|
+
error?: string;
|
|
282
|
+
hint?: string;
|
|
283
|
+
validateTag?: (tag: string, currentTags: string[]) => string | undefined;
|
|
284
|
+
separators?: RegExp;
|
|
285
|
+
className?: string;
|
|
286
|
+
'aria-describedby'?: string;
|
|
287
|
+
'aria-invalid'?: boolean;
|
|
288
|
+
'aria-label'?: string;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* TagInput — controlled multi-value input for frontmatter tags, labels, and
|
|
292
|
+
* lightweight taxonomies. Use inside FormField just like a native input.
|
|
293
|
+
*/
|
|
294
|
+
declare function TagInput({ value, onChange, id, name, placeholder, disabled, required, error, hint, validateTag, separators, className, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, }: TagInputProps): react.JSX.Element;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Lets content deep inside a `Dialog` (e.g. a plugin's own route layout,
|
|
298
|
+
* rendered several levels below wherever the `Dialog` itself is
|
|
299
|
+
* instantiated) supply the second-row content of the Dialog's mobile
|
|
300
|
+
* `OverlayHeader` — typically a tab strip. Solves the "double header"
|
|
301
|
+
* problem: without this, a plugin's own tab strip has no way to reach the
|
|
302
|
+
* Dialog's header and ends up rendered a second time, as its own sticky bar,
|
|
303
|
+
* inside the scrolling content area.
|
|
304
|
+
*
|
|
305
|
+
* A no-op when there is no enclosing `Dialog` (e.g. the same plugin layout
|
|
306
|
+
* also rendered on a plain, non-overlay route) — safe to call unconditionally.
|
|
307
|
+
* Returns whether an enclosing `Dialog` actually received the content, so a
|
|
308
|
+
* caller that also renders its own inline header/tab-strip copy for the
|
|
309
|
+
* no-Dialog case can hide that copy on mobile specifically when this
|
|
310
|
+
* returned `true` (the Dialog's own header is showing it instead there).
|
|
311
|
+
*
|
|
312
|
+
* ```tsx
|
|
313
|
+
* function AccountLayout({ children }) {
|
|
314
|
+
* const insideOverlay = useOverlaySecondRow(<nav>...tab strip...</nav>);
|
|
315
|
+
* return <div>{children}</div>;
|
|
316
|
+
* }
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
declare function useOverlaySecondRow(node: ReactNode | null): boolean;
|
|
187
320
|
type DialogSize = 'sm' | 'md' | 'lg' | 'full';
|
|
188
321
|
interface DialogProps {
|
|
189
322
|
/** Whether the dialog is shown. When false, nothing renders. */
|
|
@@ -208,29 +341,235 @@ interface DialogProps {
|
|
|
208
341
|
* is restored to the previously-focused element on close; Tab is trapped within
|
|
209
342
|
* the panel. Styling references `--sv-*` tokens only; on mobile the panel becomes
|
|
210
343
|
* a full-screen sheet regardless of `size`.
|
|
344
|
+
*
|
|
345
|
+
* Animated open/close: fade + scale on desktop, slide-up on mobile (matching
|
|
346
|
+
* the "feels like a page push" framing of the mobile sheet). The `open`/
|
|
347
|
+
* `onClose` API is unchanged — closing still stays mounted internally for the
|
|
348
|
+
* exit transition before actually unmounting; `prefers-reduced-motion: reduce`
|
|
349
|
+
* collapses both to near-instant.
|
|
211
350
|
*/
|
|
212
351
|
declare function Dialog({ open, onClose, size, 'aria-label': ariaLabel, title, children, }: DialogProps): react.JSX.Element | null;
|
|
213
352
|
|
|
214
353
|
interface DrawerProps {
|
|
215
354
|
/** Whether the drawer is shown. When false, nothing renders. */
|
|
216
355
|
open: boolean;
|
|
217
|
-
/** Called on Esc or
|
|
356
|
+
/** Called on Esc, scrim click, or a swipe-down past the dismiss threshold. */
|
|
218
357
|
onClose: () => void;
|
|
219
358
|
/** Accessible name for the drawer panel (sets `aria-label`). */
|
|
220
359
|
'aria-label'?: string;
|
|
360
|
+
/** `'content'` (default) sizes to content, capped at 80dvh. `'half'` fixes
|
|
361
|
+
* the panel to 50dvh regardless of content — per the design-system's
|
|
362
|
+
* "half screen or less" convention for Drawer, this is the only other
|
|
363
|
+
* supported size; taller content should use `Sheet` instead. */
|
|
364
|
+
snapHeight?: 'content' | 'half';
|
|
221
365
|
children: ReactNode;
|
|
222
366
|
}
|
|
223
367
|
/**
|
|
224
368
|
* Drawer — a dismissable bottom-sheet panel. Used by the mobile shell for
|
|
225
369
|
* plugin navigation; also available to plugins for any bottom-up surface.
|
|
226
370
|
*
|
|
227
|
-
* Behaviour: Esc
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
371
|
+
* Behaviour: Esc, scrim click, or dragging the grab handle down past
|
|
372
|
+
* `SWIPE_DISMISS_THRESHOLD_PX` all dismiss; focus moves to the first
|
|
373
|
+
* focusable element on open and is restored on close; Tab is trapped within
|
|
374
|
+
* the panel. The panel respects `env(safe-area-inset-bottom)` so it clears
|
|
375
|
+
* the home indicator in standalone/fullscreen mode. Shares dismissal
|
|
376
|
+
* conventions and tokens (`--sv-color-scrim`, `--sv-shadow-overlay`) with
|
|
377
|
+
* `Dialog`.
|
|
378
|
+
*
|
|
379
|
+
* Animated open/close: slides up from the bottom, scrim fades. The `open`/
|
|
380
|
+
* `onClose` API is unchanged — closing stays mounted internally for the exit
|
|
381
|
+
* transition before actually unmounting; `prefers-reduced-motion: reduce`
|
|
382
|
+
* collapses it to near-instant.
|
|
383
|
+
*
|
|
384
|
+
* The grab handle is the *only* drag-initiation region (not the whole panel)
|
|
385
|
+
* — dragging from body content would fight that content's own internal
|
|
386
|
+
* scroll, the same reasoning behind the tasks plugin's edge-zone-only
|
|
387
|
+
* swipe-to-reveal technique.
|
|
388
|
+
*/
|
|
389
|
+
declare function Drawer({ open, onClose, 'aria-label': ariaLabel, snapHeight, children, }: DrawerProps): react.JSX.Element | null;
|
|
390
|
+
|
|
391
|
+
interface OverlayHeaderProps {
|
|
392
|
+
/** Overlay title, shown between the optional back button and the trailing
|
|
393
|
+
* slots. Optional — some consumers (e.g. `Dialog`'s mobile bar) need the
|
|
394
|
+
* row's close button present even when no title applies. */
|
|
395
|
+
title?: string;
|
|
396
|
+
/** Always renders a close button; called on click. */
|
|
397
|
+
onClose: () => void;
|
|
398
|
+
/** When provided, renders a back-chevron button before the title (for
|
|
399
|
+
* hierarchical navigation within a single overlay — going back without
|
|
400
|
+
* fully closing it). Omit for a flat overlay that only ever closes. */
|
|
401
|
+
onBack?: () => void;
|
|
402
|
+
/** Trailing slot next to the close button — e.g. a "Save" action. */
|
|
403
|
+
action?: ReactNode;
|
|
404
|
+
/** A second row below the title row — e.g. a tab strip. */
|
|
405
|
+
secondRow?: ReactNode;
|
|
406
|
+
className?: string;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* OverlayHeader — the shared fixed secondary header for `Dialog`'s mobile
|
|
410
|
+
* mode, `Sheet`, and `Drawer`: title + close (+ optional back button /
|
|
411
|
+
* trailing action / tab-strip row). Not itself `position: sticky` or
|
|
412
|
+
* `position: fixed` — it stays visually pinned because the consuming overlay
|
|
413
|
+
* renders it as a non-scrolling flex sibling before its own scrollable
|
|
414
|
+
* content region, the same technique `Dialog`'s `.mobileBar` already used
|
|
415
|
+
* before this component existed to generalize it.
|
|
416
|
+
*
|
|
417
|
+
* Content that needs a fully custom header (e.g. an editable title, or
|
|
418
|
+
* controls that aren't a simple title/back/action shape) should not use this
|
|
419
|
+
* component — render its own header row instead; `Sheet`'s `title` prop is
|
|
420
|
+
* optional specifically to leave that escape hatch open.
|
|
421
|
+
*/
|
|
422
|
+
declare function OverlayHeader({ title, onClose, onBack, action, secondRow, className, }: OverlayHeaderProps): react.JSX.Element;
|
|
423
|
+
|
|
424
|
+
interface SheetProps {
|
|
425
|
+
/** Whether the sheet is shown. When false, nothing renders. */
|
|
426
|
+
open: boolean;
|
|
427
|
+
/** Called on Esc, or the built-in `OverlayHeader`'s close button. There is
|
|
428
|
+
* no scrim to tap — a Sheet replaces the content area it fills, not a
|
|
429
|
+
* layer floating above it (see the component doc comment). */
|
|
430
|
+
onClose: () => void;
|
|
431
|
+
/** Accessible name for the sheet. Defaults to `title` when omitted. */
|
|
432
|
+
'aria-label'?: string;
|
|
433
|
+
/** Renders a built-in `OverlayHeader` (title + close, optional back button
|
|
434
|
+
* and trailing action). Omit when the content supplies its own header —
|
|
435
|
+
* e.g. a form whose title doubles as an editable field. */
|
|
436
|
+
title?: string;
|
|
437
|
+
/** Forwarded to `OverlayHeader` — see its own `onBack` doc. Has no effect
|
|
438
|
+
* without `title`. */
|
|
439
|
+
onBack?: () => void;
|
|
440
|
+
/** Forwarded to `OverlayHeader`'s trailing-action slot. Has no effect
|
|
441
|
+
* without `title`. */
|
|
442
|
+
headerAction?: ReactNode;
|
|
443
|
+
/**
|
|
444
|
+
* Which edge the panel slides in from. Defaults to `'bottom'` (detail
|
|
445
|
+
* views, forms — content the user is reaching down into). `'top'` suits a
|
|
446
|
+
* short options/actions menu opened from a header button near the top of
|
|
447
|
+
* the screen, where sliding up from the bottom would travel past empty
|
|
448
|
+
* space before reaching content that visually belongs near the trigger.
|
|
449
|
+
*/
|
|
450
|
+
slideFrom?: 'bottom' | 'top';
|
|
451
|
+
children: ReactNode;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Sheet — a full-page overlay that replaces a plugin's content area between
|
|
455
|
+
* the shell's fixed header and footer (RFC 0013's mobile chrome stays visible
|
|
456
|
+
* and interactive around it), sliding in from an edge instead of centering
|
|
457
|
+
* like `Dialog`. Promoted from the tasks plugin's `MobileFullPageOverlay`
|
|
458
|
+
* (now the reference pattern for this component). Desktop has no equivalent
|
|
459
|
+
* presentation — a desktop layout shows the same content inline (columns,
|
|
460
|
+
* panes) instead of mounting a `Sheet` at all; this component is for the
|
|
461
|
+
* mobile case specifically.
|
|
462
|
+
*
|
|
463
|
+
* Unlike `Dialog`/`Drawer` there is no scrim: a Sheet visually *replaces* the
|
|
464
|
+
* region it covers rather than floating a layer above it, so there is no
|
|
465
|
+
* "outside" to tap-dismiss. Behaviour: Esc dismisses; focus moves into the
|
|
466
|
+
* panel on open and is restored on close; Tab is trapped within the panel;
|
|
467
|
+
* animated open/close via the same two-phase mount as `Dialog`/`Drawer`,
|
|
468
|
+
* respecting `prefers-reduced-motion`.
|
|
469
|
+
*/
|
|
470
|
+
declare function Sheet({ open, onClose, 'aria-label': ariaLabel, title, onBack, headerAction, slideFrom, children, }: SheetProps): react.JSX.Element | null;
|
|
471
|
+
|
|
472
|
+
interface ConfirmDialogProps {
|
|
473
|
+
/** Whether the dialog is shown. */
|
|
474
|
+
open: boolean;
|
|
475
|
+
/** Called on Esc, backdrop click, or the Cancel button. */
|
|
476
|
+
onClose: () => void;
|
|
477
|
+
title: string;
|
|
478
|
+
/** Accepts rich content (e.g. `Remove <strong>{name}</strong>?`), not just
|
|
479
|
+
* plain text. */
|
|
480
|
+
message: ReactNode;
|
|
481
|
+
/** Called when the confirm action is clicked. Does not close the dialog
|
|
482
|
+
* itself — pass `pending`/close it via `onClose` from the caller once the
|
|
483
|
+
* action settles, so an async action that fails can keep the dialog open
|
|
484
|
+
* to show `error`. */
|
|
485
|
+
onConfirm: () => void;
|
|
486
|
+
confirmLabel?: string;
|
|
487
|
+
cancelLabel?: string;
|
|
488
|
+
/** Styles the confirm action as a destructive (red) action. */
|
|
489
|
+
destructive?: boolean;
|
|
490
|
+
/** Disables both actions — pair with a `confirmLabel` reflecting the
|
|
491
|
+
* in-flight state (e.g. "Removing…") for an `onConfirm` that triggers an
|
|
492
|
+
* async action. */
|
|
493
|
+
pending?: boolean;
|
|
494
|
+
/** Optional inline error, shown between the message and the actions — e.g.
|
|
495
|
+
* a failed async `onConfirm` that keeps the dialog open to report it. */
|
|
496
|
+
error?: ReactNode;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* ConfirmDialog — a small, content-sized confirm/cancel prompt. Deliberately
|
|
500
|
+
* NOT built on `Dialog`, which is a fixed-size box by design (so switching
|
|
501
|
+
* views inside it never resizes the panel) — a confirm prompt is the opposite
|
|
502
|
+
* case, sized to its message. Built on the native `<dialog>` element instead,
|
|
503
|
+
* same as the pattern this component replaces (duplicated across the account
|
|
504
|
+
* and console plugins): top-layer rendering and `::backdrop` come from the
|
|
505
|
+
* browser for free, which is what makes a confirm opened *from inside*
|
|
506
|
+
* another overlay (Dialog, Sheet) reliably stack above it without a manual
|
|
507
|
+
* z-index scheme.
|
|
508
|
+
*
|
|
509
|
+
* Same presentation on desktop and mobile — a small centered card, not a
|
|
510
|
+
* full-screen sheet (unlike Dialog/Sheet, which do fork by viewport).
|
|
232
511
|
*/
|
|
233
|
-
declare function
|
|
512
|
+
declare function ConfirmDialog({ open, onClose, title, message, onConfirm, confirmLabel, cancelLabel, destructive, pending, error, }: ConfirmDialogProps): react.JSX.Element;
|
|
513
|
+
|
|
514
|
+
interface MenuItem {
|
|
515
|
+
type?: 'item';
|
|
516
|
+
label: string;
|
|
517
|
+
onSelect: () => void;
|
|
518
|
+
icon?: IconName;
|
|
519
|
+
/** Styles the item as a destructive action (e.g. "Delete list"). */
|
|
520
|
+
destructive?: boolean;
|
|
521
|
+
disabled?: boolean;
|
|
522
|
+
/**
|
|
523
|
+
* Marks the item as one of a mutually-exclusive set (e.g. the active sort
|
|
524
|
+
* order) — renders a leading checkmark and `role="menuitemradio"` instead
|
|
525
|
+
* of `role="menuitem"`. Pass this on every item in the group, including
|
|
526
|
+
* the unchecked ones, so their labels stay aligned with the checked one's
|
|
527
|
+
* reserved checkmark gutter. Omit entirely for plain action items (e.g.
|
|
528
|
+
* "Delete list") — they render with no reserved leading space at all.
|
|
529
|
+
*/
|
|
530
|
+
checked?: boolean;
|
|
531
|
+
}
|
|
532
|
+
/** A non-interactive section heading above a run of items (e.g. "Sort by"). */
|
|
533
|
+
interface MenuLabel {
|
|
534
|
+
type: 'label';
|
|
535
|
+
label: string;
|
|
536
|
+
}
|
|
537
|
+
/** A visual divider between sections. */
|
|
538
|
+
interface MenuSeparator {
|
|
539
|
+
type: 'separator';
|
|
540
|
+
}
|
|
541
|
+
type MenuEntry = MenuItem | MenuLabel | MenuSeparator;
|
|
542
|
+
interface MenuProps {
|
|
543
|
+
/** The element that opens the menu — rendered as-is; the caller wires its
|
|
544
|
+
* onClick to flip `open` (same controlled pattern as `Popover`). */
|
|
545
|
+
trigger: ReactElement;
|
|
546
|
+
open: boolean;
|
|
547
|
+
onClose: () => void;
|
|
548
|
+
items: MenuEntry[];
|
|
549
|
+
'aria-label': string;
|
|
550
|
+
/** Forwarded to `Popover` on desktop. Has no effect on mobile, where the
|
|
551
|
+
* menu is always a full-width `Drawer`. */
|
|
552
|
+
align?: 'left' | 'right';
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Menu — an adaptive action menu: `Popover` on desktop, a bottom-sheet
|
|
556
|
+
* `Drawer` on mobile. Replaces the desktop-Popover/mobile-Drawer fork that
|
|
557
|
+
* `⋯` action menus (list options, row actions) otherwise re-derive per
|
|
558
|
+
* plugin. Same list of `items` renders in both presentations — only the
|
|
559
|
+
* surrounding chrome (floating panel vs. bottom sheet) differs, matching the
|
|
560
|
+
* platform's `useIsMobile` breakpoint.
|
|
561
|
+
*
|
|
562
|
+
* `items` accepts three entry shapes: a plain action (`MenuItem`, `type`
|
|
563
|
+
* omitted or `'item'`), a non-interactive section heading (`MenuLabel`,
|
|
564
|
+
* `type: 'label'`), and a `MenuSeparator` (`type: 'separator'`) — enough to
|
|
565
|
+
* express grouped sections like "Filter" / "Sort by" above a run of
|
|
566
|
+
* destructive actions, matching how native OS menus lay out mixed
|
|
567
|
+
* action/selection groups.
|
|
568
|
+
*
|
|
569
|
+
* Selecting an item both closes the menu and calls its `onSelect` — a
|
|
570
|
+
* consumer's `onSelect` never needs to call `onClose` itself.
|
|
571
|
+
*/
|
|
572
|
+
declare function Menu({ trigger, open, onClose, items, 'aria-label': ariaLabel, align }: MenuProps): react.JSX.Element;
|
|
234
573
|
|
|
235
574
|
interface ToastItem {
|
|
236
575
|
id: string;
|
|
@@ -262,16 +601,32 @@ interface CardProps extends HTMLAttributes<HTMLElement> {
|
|
|
262
601
|
}
|
|
263
602
|
declare function Card({ as: Tag, interactive, padding, className, children, ...rest }: CardProps): react.JSX.Element;
|
|
264
603
|
|
|
604
|
+
/** Props to spread onto the field's actual form control. */
|
|
605
|
+
interface FormFieldRenderProps {
|
|
606
|
+
id: string;
|
|
607
|
+
'aria-describedby'?: string;
|
|
608
|
+
'aria-invalid'?: boolean;
|
|
609
|
+
required?: boolean;
|
|
610
|
+
}
|
|
265
611
|
interface FormFieldProps {
|
|
266
612
|
label: string;
|
|
267
613
|
hint?: string;
|
|
268
614
|
error?: string;
|
|
269
|
-
|
|
615
|
+
/** Explicit control id. Auto-generated via `useId()` when omitted. */
|
|
616
|
+
id?: string;
|
|
270
617
|
required?: boolean;
|
|
271
|
-
children: ReactNode;
|
|
272
618
|
className?: string;
|
|
619
|
+
/** Render prop — receives the props that must be spread onto the control
|
|
620
|
+
* so the label, hint, and error stay associated with it. */
|
|
621
|
+
children: (field: FormFieldRenderProps) => ReactNode;
|
|
273
622
|
}
|
|
274
|
-
|
|
623
|
+
/**
|
|
624
|
+
* FormField — accessible label + hint/error wrapper for a single form control.
|
|
625
|
+
* Wires `htmlFor`/`id` and `aria-describedby`/`aria-invalid` onto the control
|
|
626
|
+
* itself (via the render-prop `field` object), not a surrounding element, so
|
|
627
|
+
* screen readers reliably announce the hint or error as part of the control.
|
|
628
|
+
*/
|
|
629
|
+
declare function FormField({ label, hint, error, id, required, className, children, }: FormFieldProps): react.JSX.Element;
|
|
275
630
|
|
|
276
631
|
interface PageHeaderProps {
|
|
277
632
|
title: string;
|
|
@@ -324,4 +679,178 @@ interface TooltipProps {
|
|
|
324
679
|
}
|
|
325
680
|
declare function Tooltip({ content, children, side }: TooltipProps): react.JSX.Element;
|
|
326
681
|
|
|
327
|
-
|
|
682
|
+
interface CheckboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'type' | 'onChange'> {
|
|
683
|
+
checked: boolean;
|
|
684
|
+
onChange: (checked: boolean) => void;
|
|
685
|
+
label: string;
|
|
686
|
+
/** Renders the label with a strike-through when checked. */
|
|
687
|
+
strikeThrough?: boolean;
|
|
688
|
+
disabled?: boolean;
|
|
689
|
+
}
|
|
690
|
+
declare function Checkbox({ checked, onChange, label, strikeThrough, disabled, className, id, ...rest }: CheckboxProps): react.JSX.Element;
|
|
691
|
+
|
|
692
|
+
interface DragHandleRowProps extends HTMLAttributes<HTMLDivElement> {
|
|
693
|
+
children: ReactNode;
|
|
694
|
+
/** Pass drag-listener and drag-control props from dnd-kit here. */
|
|
695
|
+
handleProps?: HTMLAttributes<HTMLButtonElement>;
|
|
696
|
+
isDragging?: boolean;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* A row wrapper that surfaces a drag handle on the left.
|
|
700
|
+
* Consumers attach dnd-kit's useSortable listeners to `handleProps`.
|
|
701
|
+
*/
|
|
702
|
+
declare function DragHandleRow({ children, handleProps, isDragging, className, ...rest }: DragHandleRowProps): react.JSX.Element;
|
|
703
|
+
|
|
704
|
+
interface CalendarProps {
|
|
705
|
+
/** Selected date, or `null` for no selection. */
|
|
706
|
+
value: Date | null;
|
|
707
|
+
onChange: (date: Date) => void;
|
|
708
|
+
/** Dates before this are disabled and unselectable. */
|
|
709
|
+
minDate?: Date;
|
|
710
|
+
/** Dates after this are disabled and unselectable. */
|
|
711
|
+
maxDate?: Date;
|
|
712
|
+
'aria-label'?: string;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Calendar — a keyboard-navigable month grid. Date-only (no time or range
|
|
716
|
+
* selection — decision D6 in the mobile design-system plan); a `DatePicker`
|
|
717
|
+
* field wraps this in a `Popover` (desktop) or `Drawer` (mobile).
|
|
718
|
+
*
|
|
719
|
+
* Keyboard (WAI-ARIA APG grid pattern, roving tabindex — exactly one day
|
|
720
|
+
* button is ever tab-stoppable): arrow keys move focus by day/week, Home/End
|
|
721
|
+
* jump to the start/end of the focused week, PageUp/PageDown change month,
|
|
722
|
+
* Enter/Space selects the focused date. Navigating into an adjacent month
|
|
723
|
+
* (arrow keys at a week's edge, or PageUp/PageDown) updates the displayed
|
|
724
|
+
* month to follow focus.
|
|
725
|
+
*/
|
|
726
|
+
declare function Calendar({ value, onChange, minDate, maxDate, 'aria-label': ariaLabel, }: CalendarProps): react.JSX.Element;
|
|
727
|
+
|
|
728
|
+
interface DatePickerProps {
|
|
729
|
+
value: Date | null;
|
|
730
|
+
onChange: (date: Date) => void;
|
|
731
|
+
minDate?: Date;
|
|
732
|
+
maxDate?: Date;
|
|
733
|
+
placeholder?: string;
|
|
734
|
+
'aria-label': string;
|
|
735
|
+
disabled?: boolean;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* DatePicker — a form field pairing a compact trigger with `Calendar`:
|
|
739
|
+
* `Popover` on desktop, a bottom-sheet `Drawer` on mobile (the platform's
|
|
740
|
+
* standard adaptive-surface pattern, matching `Menu`). Date-only (decision
|
|
741
|
+
* D6 in the mobile design-system plan) — time and range selection are future
|
|
742
|
+
* scope; recurrence UI stays plugin-side.
|
|
743
|
+
*
|
|
744
|
+
* Unlike `Menu`, the trigger is built in rather than supplied by the caller
|
|
745
|
+
* — a date picker is a form field (consistent with `Input`/`Select`), not an
|
|
746
|
+
* arbitrary action-menu trigger.
|
|
747
|
+
*/
|
|
748
|
+
declare function DatePicker({ value, onChange, minDate, maxDate, placeholder, 'aria-label': ariaLabel, disabled, }: DatePickerProps): react.JSX.Element;
|
|
749
|
+
|
|
750
|
+
interface UseLongPressOptions {
|
|
751
|
+
/** Called once the press has been held for `delay` without exceeding `moveTolerance`. */
|
|
752
|
+
onLongPress: () => void;
|
|
753
|
+
/** Hold duration in ms before `onLongPress` fires. Default 500. */
|
|
754
|
+
delay?: number;
|
|
755
|
+
/** Accumulated pointer movement (px) that cancels a pending press. Default 10 —
|
|
756
|
+
* a real finger jitters a couple of px on a still hold; this must be forgiving
|
|
757
|
+
* of that or the gesture never fires. */
|
|
758
|
+
moveTolerance?: number;
|
|
759
|
+
/** How long (ms) after firing to swallow the click that may or may not follow.
|
|
760
|
+
* Default 700. Time-boxed rather than "clear on next click" — iOS frequently
|
|
761
|
+
* sends no click at all after a long hold, so a flag that only clears on the
|
|
762
|
+
* next click stays armed forever and silently eats the user's next real tap. */
|
|
763
|
+
suppressClickMs?: number;
|
|
764
|
+
/** Fire `navigator.vibrate(10)` when the press triggers, where supported. Default true. */
|
|
765
|
+
vibrate?: boolean;
|
|
766
|
+
/** Skip entirely (hooks can't be called conditionally, so this is the escape hatch
|
|
767
|
+
* for a caller whose long-press action isn't available in the current state). */
|
|
768
|
+
disabled?: boolean;
|
|
769
|
+
}
|
|
770
|
+
interface LongPressHandlers {
|
|
771
|
+
onPointerDown: (e: PointerEvent) => void;
|
|
772
|
+
onPointerMove: (e: PointerEvent) => void;
|
|
773
|
+
onPointerUp: (e: PointerEvent) => void;
|
|
774
|
+
onPointerCancel: (e: PointerEvent) => void;
|
|
775
|
+
onPointerLeave: (e: PointerEvent) => void;
|
|
776
|
+
onContextMenu: (e: MouseEvent) => void;
|
|
777
|
+
onClick: (e: MouseEvent) => void;
|
|
778
|
+
/** Spread onto the target element. Empty on devices whose primary pointer
|
|
779
|
+
* isn't coarse (desktop mouse/trackpad) — see the coarse-pointer gate below. */
|
|
780
|
+
style: CSSProperties;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* useLongPress — touch-and-hold gesture with the full suppression recipe a
|
|
784
|
+
* long-press needs to behave, not just a bare timer.
|
|
785
|
+
*
|
|
786
|
+
* A naive `setTimeout` started on `pointerdown` and cleared on `pointermove`
|
|
787
|
+
* fails in three specific ways this hook exists to fix:
|
|
788
|
+
* 1. Any movement at all cancels it — but a real finger jitters a few px even
|
|
789
|
+
* on a "still" hold, so the gesture misfires constantly. `moveTolerance`
|
|
790
|
+
* requires deliberate movement before cancelling.
|
|
791
|
+
* 2. `pointercancel` (the browser converting the touch into a scroll) isn't
|
|
792
|
+
* handled — the timer survives and fires mid-scroll. Handled here.
|
|
793
|
+
* 3. Nothing suppresses the OS's own reaction to a long touch-hold: iOS shows
|
|
794
|
+
* its link-preview callout, Android starts text selection or opens a
|
|
795
|
+
* context menu, and either can fire *alongside* the gesture this hook
|
|
796
|
+
* triggers. `onContextMenu` + the returned `style` (touch-callout,
|
|
797
|
+
* user-select, touch-action) close all three, gated to coarse-pointer
|
|
798
|
+
* devices only — unconditionally disabling `user-select` would break
|
|
799
|
+
* mouse text selection on desktop, which never held this long-press to
|
|
800
|
+
* begin with (see `onPointerDown`'s own `pointerType` check).
|
|
801
|
+
*
|
|
802
|
+
* The click that may or may not follow a fired long-press (iOS often sends
|
|
803
|
+
* none at all) is swallowed for `suppressClickMs`, not indefinitely — an
|
|
804
|
+
* unbounded suppression flag left armed by a browser that never sends that
|
|
805
|
+
* click silently eats the user's *next* unrelated tap on the same element.
|
|
806
|
+
*/
|
|
807
|
+
declare function useLongPress({ onLongPress, delay, moveTolerance, suppressClickMs, vibrate, disabled, }: UseLongPressOptions): LongPressHandlers;
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* useDoubleTapHandler — fires `onDoubleTap` on a real double-click (desktop)
|
|
811
|
+
* or two taps within `DOUBLE_TAP_MS` (touch).
|
|
812
|
+
*
|
|
813
|
+
* Only safe when the *single* tap/click has no default action of its own to
|
|
814
|
+
* preempt — e.g. a colour swatch with nothing to cancel. If the single tap
|
|
815
|
+
* navigates, opens something, or otherwise does something a following double
|
|
816
|
+
* needs to be able to undo before it happens, use `useSingleOrDoubleTap`
|
|
817
|
+
* instead: this hook fires `onDoubleTap` only, leaving any single-tap handling
|
|
818
|
+
* to the caller's own `onClick`, which already ran immediately and can't be
|
|
819
|
+
* un-done here.
|
|
820
|
+
*
|
|
821
|
+
* The event is passed through to `onDoubleTap` rather than calling
|
|
822
|
+
* `preventDefault` internally — not every call site needs to cancel
|
|
823
|
+
* something, so the decision is left to the caller.
|
|
824
|
+
*/
|
|
825
|
+
declare function useDoubleTapHandler<E extends {
|
|
826
|
+
detail: number;
|
|
827
|
+
}>(onDoubleTap: (e: E) => void): (e: E) => void;
|
|
828
|
+
/**
|
|
829
|
+
* useSingleOrDoubleTap — for call sites where the single tap/click *does*
|
|
830
|
+
* have a default action (e.g. navigating) that a following double-tap must be
|
|
831
|
+
* able to preempt.
|
|
832
|
+
*
|
|
833
|
+
* A real double-click gets away with firing the single action immediately and
|
|
834
|
+
* only detecting the double afterward, because `e.detail === 2` is the
|
|
835
|
+
* browser's own resolved signal, arriving on the very click that matters. A
|
|
836
|
+
* touch double-tap has no equivalent "hold on, there might be a second one"
|
|
837
|
+
* signal — the only way to know is to wait out the window before committing
|
|
838
|
+
* to the single action, which is what this hook does: `onSingle` is deferred
|
|
839
|
+
* by `DOUBLE_TAP_MS` and only actually runs if no second tap arrives in that
|
|
840
|
+
* time. This means every single tap through this hook incurs that latency —
|
|
841
|
+
* only use it where a genuine double-tap gesture must be able to preempt the
|
|
842
|
+
* single action; `useDoubleTapHandler` above has no such delay.
|
|
843
|
+
*/
|
|
844
|
+
declare function useSingleOrDoubleTap<E extends {
|
|
845
|
+
detail: number;
|
|
846
|
+
}>(onSingle: (e: E) => void, onDouble: (e: E) => void): (e: E) => void;
|
|
847
|
+
|
|
848
|
+
declare const MOBILE_BREAKPOINT_PX = 768;
|
|
849
|
+
/**
|
|
850
|
+
* useIsMobile — whether the viewport is at or below the platform's mobile
|
|
851
|
+
* breakpoint. SSR-safe: defaults to `false` (desktop) until the client mounts
|
|
852
|
+
* and reads the real viewport, avoiding a hydration mismatch on first paint.
|
|
853
|
+
*/
|
|
854
|
+
declare function useIsMobile(breakpointPx?: number): boolean;
|
|
855
|
+
|
|
856
|
+
export { Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeStatus, type BadgeVariant, type BannerVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Calendar, type CalendarProps, Card, type CardProps, Checkbox, type CheckboxProps, CodeTextarea, type CodeTextareaProps, ConfirmDialog, type ConfirmDialogProps, DatePicker, type DatePickerProps, Dialog, type DialogProps, type DialogSize, DragHandleRow, type DragHandleRowProps, Drawer, type DrawerProps, EmptyState, type EmptyStateProps, FormField, type FormFieldProps, type FormFieldRenderProps, Icon, type IconName, type IconProps, Input, type InputProps, type LongPressHandlers, MOBILE_BREAKPOINT_PX, Menu, type MenuEntry, type MenuItem, type MenuLabel, type MenuProps, type MenuSeparator, type NavTabItem, NavTabs, type NavTabsProps, OverlayHeader, type OverlayHeaderProps, PageHeader, type PageHeaderProps, Popover, type PopoverProps, SegmentedControl, type SegmentedControlProps, type SegmentedOption, Select, type SelectProps, Sheet, type SheetProps, Spinner, type SpinnerProps, SplitPane, type SplitPaneProps, StatusBadge, type StatusBadgeProps, type StatusBadgeStatus, SystemBanner, type SystemBannerProps, type TabItem, Tabs, type TabsProps, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastContextValue, type ToastItem, ToastProvider, Toggle, type ToggleProps, Tooltip, type TooltipProps, type UseLongPressOptions, useDoubleTapHandler, useIsMobile, useLongPress, useOverlaySecondRow, useSingleOrDoubleTap, useToast };
|