@djangocfg/ui-core 2.1.539 → 2.1.541
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/package.json +4 -4
- package/src/components/forms/input/index.tsx +33 -6
- package/src/components/forms/input-affix/index.tsx +65 -0
- package/src/components/forms/input-group/index.tsx +47 -17
- package/src/components/forms/textarea/index.tsx +10 -4
- package/src/components/index.ts +1 -0
- package/src/components/navigation/pagination/pagination.tsx +9 -0
- package/src/components/navigation/pagination/ssr-pagination.tsx +3 -0
- package/src/hooks/router/useNavigate.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.541",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
129
129
|
},
|
|
130
130
|
"peerDependencies": {
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
131
|
+
"@djangocfg/i18n": "^2.1.541",
|
|
132
132
|
"consola": "^3.4.2",
|
|
133
133
|
"lucide-react": "^0.545.0",
|
|
134
134
|
"moment": "^2.30.1",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"@chenglou/pretext": "^0.0.8"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@djangocfg/i18n": "^2.1.
|
|
210
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
209
|
+
"@djangocfg/i18n": "^2.1.541",
|
|
210
|
+
"@djangocfg/typescript-config": "^2.1.541",
|
|
211
211
|
"@types/node": "^24.13.3",
|
|
212
212
|
"@types/react": "19.2.15",
|
|
213
213
|
"@types/react-dom": "19.2.3",
|
|
@@ -35,6 +35,18 @@ export interface InputProps extends React.ComponentProps<"input"> {
|
|
|
35
35
|
* - `sm` — 36px, smaller text (inline edits, dense rows, settings chips).
|
|
36
36
|
*/
|
|
37
37
|
inputSize?: 'default' | 'sm';
|
|
38
|
+
/**
|
|
39
|
+
* Drop the field chrome — border, surface, rounding, focus ring — for an
|
|
40
|
+
* input that sits inside a control that draws them itself (`InputGroup`).
|
|
41
|
+
*
|
|
42
|
+
* A wrapper cannot do this from the outside: `focus-visible:ring-0` and
|
|
43
|
+
* `focus-visible:ring-1` are the same specificity, so which one wins is
|
|
44
|
+
* decided by their order in the generated stylesheet, not by the order they
|
|
45
|
+
* are passed to `cn()`. Suppression like that works until a Tailwind version
|
|
46
|
+
* reorders its output. Omitting the rules is the only stable way to not have
|
|
47
|
+
* them.
|
|
48
|
+
*/
|
|
49
|
+
bare?: boolean;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
/**
|
|
@@ -49,9 +61,19 @@ export interface InputProps extends React.ComponentProps<"input"> {
|
|
|
49
61
|
* and a tight 1px solid ring of the same colour doubles it to a clean ~2px
|
|
50
62
|
* edge (NO blurry translucent halo). `:focus-visible` only, so mouse users on
|
|
51
63
|
* other controls don't get rings. This is the sharp Vercel/Linear look.
|
|
64
|
+
*
|
|
65
|
+
* Split in two so an input inside `InputGroup` can omit the chrome rather than
|
|
66
|
+
* override it — see `bare`.
|
|
52
67
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
/** Everything an input needs regardless of who draws its edge. */
|
|
69
|
+
const INPUT_CORE =
|
|
70
|
+
"flex w-full transition-[color,background-color,border-color,box-shadow] file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50";
|
|
71
|
+
|
|
72
|
+
/** The field's own edge: border, surface, rounding, and the focus treatment. */
|
|
73
|
+
const INPUT_CHROME =
|
|
74
|
+
"rounded-[var(--radius)] border border-border bg-input shadow-sm focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring";
|
|
75
|
+
|
|
76
|
+
const INPUT_BASE = cn(INPUT_CORE, INPUT_CHROME);
|
|
55
77
|
|
|
56
78
|
const INPUT_SIZE: Record<NonNullable<InputProps['inputSize']>, string> = {
|
|
57
79
|
default: "h-10 px-3 py-2 text-base md:text-sm",
|
|
@@ -59,8 +81,10 @@ const INPUT_SIZE: Record<NonNullable<InputProps['inputSize']>, string> = {
|
|
|
59
81
|
};
|
|
60
82
|
|
|
61
83
|
/** Resolve the full input class for a given size. */
|
|
62
|
-
export const inputClass = (
|
|
63
|
-
|
|
84
|
+
export const inputClass = (
|
|
85
|
+
size: NonNullable<InputProps['inputSize']> = 'default',
|
|
86
|
+
bare = false,
|
|
87
|
+
) => cn(bare ? INPUT_CORE : INPUT_BASE, INPUT_SIZE[size], bare && 'h-full px-0');
|
|
64
88
|
|
|
65
89
|
/** Default-size input styling — kept as a named export for reuse (e.g. Editable). */
|
|
66
90
|
export const INPUT_CLASS = inputClass('default');
|
|
@@ -71,9 +95,12 @@ export const INPUT_CLASS = inputClass('default');
|
|
|
71
95
|
*/
|
|
72
96
|
export const TEXTAREA_CLASS = cn(INPUT_BASE, 'min-h-[60px] px-3 py-2 text-sm');
|
|
73
97
|
|
|
98
|
+
/** `TEXTAREA_CLASS` without the field's own edge — for use inside `InputGroup`. */
|
|
99
|
+
export const TEXTAREA_BARE_CLASS = cn(INPUT_CORE, 'min-h-[60px] py-3 text-sm');
|
|
100
|
+
|
|
74
101
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
75
|
-
({ className, type, storageKey, storageType, storageTtl, inputSize = 'default', onChange, defaultValue, value, ...props }, ref) => {
|
|
76
|
-
const resolvedClass = cn(inputClass(inputSize), className);
|
|
102
|
+
({ className, type, storageKey, storageType, storageTtl, inputSize = 'default', bare = false, onChange, defaultValue, value, ...props }, ref) => {
|
|
103
|
+
const resolvedClass = cn(inputClass(inputSize, bare), className);
|
|
77
104
|
const storageOptions: UseStoredValueOptions | undefined =
|
|
78
105
|
storageKey ? { storage: storageType ?? 'local', ttl: storageTtl } : undefined;
|
|
79
106
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../../lib/utils';
|
|
6
|
+
import {
|
|
7
|
+
InputGroup,
|
|
8
|
+
InputGroupAddon,
|
|
9
|
+
InputGroupInput,
|
|
10
|
+
} from '../input-group';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An input with a fixed symbol before and/or after the value.
|
|
14
|
+
*
|
|
15
|
+
* `InputGroup` already does this, but it takes four components and the right
|
|
16
|
+
* `align` on each — enough ceremony that a currency field gets written as a
|
|
17
|
+
* bare `Input` with the unit in the label instead, which is how a form ends up
|
|
18
|
+
* not saying what unit it is in.
|
|
19
|
+
*
|
|
20
|
+
* This is the same thing in one component:
|
|
21
|
+
*
|
|
22
|
+
* <InputAffix prefix="$" value={price} onChange={onPrice} />
|
|
23
|
+
* <InputAffix suffix="%" value={rate} onChange={onRate} />
|
|
24
|
+
* <InputAffix prefix="$" suffix="/ yr" value={rent} onChange={onRent} />
|
|
25
|
+
*
|
|
26
|
+
* The affixes are **static marks, not content**: a currency symbol, a unit, a
|
|
27
|
+
* per-period suffix. They carry `aria-hidden` because the accessible name
|
|
28
|
+
* belongs on the field's label — a screen reader announcing "dollar sign" as
|
|
29
|
+
* it enters the field is noise, and a label reading "Purchase price in US
|
|
30
|
+
* dollars" is the thing that actually helps. Where an affix carries meaning
|
|
31
|
+
* the label does not, put it in the label or in a `FieldDescription`.
|
|
32
|
+
*
|
|
33
|
+
* Not for a button, a select, or anything interactive. Reach for `InputGroup`
|
|
34
|
+
* directly there — that is what `InputGroupButton` is for.
|
|
35
|
+
*/
|
|
36
|
+
export interface InputAffixProps
|
|
37
|
+
extends Omit<React.ComponentProps<typeof InputGroupInput>, 'prefix'> {
|
|
38
|
+
/** Shown before the value — a currency symbol, a unit. */
|
|
39
|
+
prefix?: React.ReactNode;
|
|
40
|
+
/** Shown after the value — a unit, a period. */
|
|
41
|
+
suffix?: React.ReactNode;
|
|
42
|
+
/** Applied to the group, so callers can size the whole control. */
|
|
43
|
+
groupClassName?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const InputAffix = React.forwardRef<HTMLInputElement, InputAffixProps>(
|
|
47
|
+
({ prefix, suffix, className, groupClassName, ...props }, ref) => (
|
|
48
|
+
<InputGroup className={groupClassName}>
|
|
49
|
+
{prefix ? (
|
|
50
|
+
<InputGroupAddon align="inline-start" aria-hidden="true">
|
|
51
|
+
{prefix}
|
|
52
|
+
</InputGroupAddon>
|
|
53
|
+
) : null}
|
|
54
|
+
<InputGroupInput ref={ref} className={cn(className)} {...props} />
|
|
55
|
+
{suffix ? (
|
|
56
|
+
<InputGroupAddon align="inline-end" aria-hidden="true">
|
|
57
|
+
{suffix}
|
|
58
|
+
</InputGroupAddon>
|
|
59
|
+
) : null}
|
|
60
|
+
</InputGroup>
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
InputAffix.displayName = 'InputAffix';
|
|
64
|
+
|
|
65
|
+
export { InputAffix };
|
|
@@ -14,8 +14,24 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
14
14
|
data-slot="input-group"
|
|
15
15
|
role="group"
|
|
16
16
|
className={cn(
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
// `border-border` and `bg-input`, the same pair `Input` resolves to. The
|
|
18
|
+
// group had `border-input`, which in the light theme is the *fill*
|
|
19
|
+
// token (97% L) rather than the edge one (86%) — so its border was
|
|
20
|
+
// nearly invisible next to a plain field's, and a focused money input
|
|
21
|
+
// showed a crisp inner rectangle inside a ghost of an outer one.
|
|
22
|
+
"group/input-group border-border bg-input shadow-xs relative flex w-full items-center rounded-[var(--radius)] border outline-none transition-[color,background-color,border-color,box-shadow]",
|
|
23
|
+
// Matches `Input`'s default size (h-10), not its `sm` one.
|
|
24
|
+
//
|
|
25
|
+
// This was `h-9`, which made a grouped input a row shorter than a bare
|
|
26
|
+
// `Input` or a `SelectTrigger` beside it — visible the moment a form
|
|
27
|
+
// puts a prefixed money field next to a plain select, and not
|
|
28
|
+
// attributable to either component from the outside. A wrapper that
|
|
29
|
+
// changes a control's height is a wrapper that cannot be dropped into
|
|
30
|
+
// an existing row.
|
|
31
|
+
//
|
|
32
|
+
// Use `className="h-9"` at the call site for the compact size, the
|
|
33
|
+
// same way `inputSize="sm"` is opted into rather than inherited.
|
|
34
|
+
"h-10 has-[>textarea]:h-auto",
|
|
19
35
|
|
|
20
36
|
// Variants based on alignment.
|
|
21
37
|
"has-[>[data-align=inline-start]]:[&>input]:pl-2",
|
|
@@ -23,8 +39,11 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
|
23
39
|
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
|
24
40
|
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
|
25
41
|
|
|
26
|
-
// Focus state.
|
|
27
|
-
|
|
42
|
+
// Focus state. Border AND ring, the same pair `Input` uses on its own
|
|
43
|
+
// — the group is standing in for a field's edge, so it has to draw the
|
|
44
|
+
// whole treatment. Ring alone left the group's border its resting grey
|
|
45
|
+
// while the control inside lit up, which reads as two nested fields.
|
|
46
|
+
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring has-[[data-slot=input-group-control]:focus-visible]:ring-1",
|
|
28
47
|
|
|
29
48
|
// Error state.
|
|
30
49
|
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
|
@@ -128,21 +147,34 @@ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
|
128
147
|
)
|
|
129
148
|
}
|
|
130
149
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Forwards its ref, unlike the other parts of this group.
|
|
152
|
+
*
|
|
153
|
+
* `Input` is a `forwardRef`, and a plain function wrapper around it swallows
|
|
154
|
+
* the ref silently — no warning, no type error, just a null ref at the call
|
|
155
|
+
* site. Anything wrapping this (see `InputAffix`) needs the node to focus or
|
|
156
|
+
* measure it.
|
|
157
|
+
*/
|
|
158
|
+
const InputGroupInput = React.forwardRef<
|
|
159
|
+
HTMLInputElement,
|
|
160
|
+
React.ComponentProps<"input">
|
|
161
|
+
>(({ className, ...props }, ref) => {
|
|
135
162
|
return (
|
|
136
163
|
<Input
|
|
164
|
+
ref={ref}
|
|
165
|
+
bare
|
|
137
166
|
data-slot="input-group-control"
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
167
|
+
// `bare` omits the field chrome instead of overriding it. Cancelling it
|
|
168
|
+
// from here needed `focus-visible:ring-0` to beat `focus-visible:ring-1`
|
|
169
|
+
// at equal specificity — decided by stylesheet order, not by `cn()` — so
|
|
170
|
+
// a focused money field drew a rectangle inside a rectangle whenever the
|
|
171
|
+
// order went the other way.
|
|
172
|
+
className={cn("flex-1", className)}
|
|
142
173
|
{...props}
|
|
143
174
|
/>
|
|
144
175
|
)
|
|
145
|
-
}
|
|
176
|
+
})
|
|
177
|
+
InputGroupInput.displayName = "InputGroupInput"
|
|
146
178
|
|
|
147
179
|
function InputGroupTextarea({
|
|
148
180
|
className,
|
|
@@ -150,11 +182,9 @@ function InputGroupTextarea({
|
|
|
150
182
|
}: React.ComponentProps<"textarea">) {
|
|
151
183
|
return (
|
|
152
184
|
<Textarea
|
|
185
|
+
bare
|
|
153
186
|
data-slot="input-group-control"
|
|
154
|
-
className={cn(
|
|
155
|
-
"flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
|
|
156
|
-
className
|
|
157
|
-
)}
|
|
187
|
+
className={cn("flex-1 resize-none", className)}
|
|
158
188
|
{...props}
|
|
159
189
|
/>
|
|
160
190
|
)
|
|
@@ -2,7 +2,7 @@ import * as React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { useStoredValue, type StorageType, type UseStoredValueOptions } from '../../../hooks';
|
|
4
4
|
import { cn } from '../../../lib/utils';
|
|
5
|
-
import { TEXTAREA_CLASS } from '../input';
|
|
5
|
+
import { TEXTAREA_BARE_CLASS, TEXTAREA_CLASS } from '../input';
|
|
6
6
|
|
|
7
7
|
export interface TextareaProps extends React.ComponentProps<"textarea"> {
|
|
8
8
|
/**
|
|
@@ -15,10 +15,16 @@ export interface TextareaProps extends React.ComponentProps<"textarea"> {
|
|
|
15
15
|
storageType?: StorageType;
|
|
16
16
|
/** TTL in ms */
|
|
17
17
|
storageTtl?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Drop the field chrome for a textarea inside a control that draws its own
|
|
20
|
+
* (`InputGroup`). See `Input`'s `bare` for why this is a variant rather than
|
|
21
|
+
* something a wrapper overrides.
|
|
22
|
+
*/
|
|
23
|
+
bare?: boolean;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
21
|
-
({ className, storageKey, storageType, storageTtl, onChange, defaultValue, value, ...props }, ref) => {
|
|
27
|
+
({ className, storageKey, storageType, storageTtl, bare = false, onChange, defaultValue, value, ...props }, ref) => {
|
|
22
28
|
const storageOptions: UseStoredValueOptions | undefined =
|
|
23
29
|
storageKey ? { storage: storageType ?? 'local', ttl: storageTtl } : undefined;
|
|
24
30
|
|
|
@@ -41,7 +47,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
41
47
|
if (value !== undefined) {
|
|
42
48
|
return (
|
|
43
49
|
<textarea
|
|
44
|
-
className={cn(TEXTAREA_CLASS, className)}
|
|
50
|
+
className={cn(bare ? TEXTAREA_BARE_CLASS : TEXTAREA_CLASS, className)}
|
|
45
51
|
ref={ref}
|
|
46
52
|
value={value}
|
|
47
53
|
onChange={storageKey ? handleChange : onChange}
|
|
@@ -52,7 +58,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
52
58
|
|
|
53
59
|
return (
|
|
54
60
|
<textarea
|
|
55
|
-
className={cn(TEXTAREA_CLASS, className)}
|
|
61
|
+
className={cn(bare ? TEXTAREA_BARE_CLASS : TEXTAREA_CLASS, className)}
|
|
56
62
|
ref={ref}
|
|
57
63
|
defaultValue={storageKey && storedValue ? storedValue : defaultValue}
|
|
58
64
|
onChange={storageKey ? handleChange : onChange}
|
package/src/components/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { Slider } from './forms/slider';
|
|
|
19
19
|
export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText, buttonGroupVariants } from './forms/button-group';
|
|
20
20
|
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField } from './forms/form';
|
|
21
21
|
export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea } from './forms/input-group';
|
|
22
|
+
export { InputAffix, type InputAffixProps } from './forms/input-affix';
|
|
22
23
|
export { InputOTP, InputOTPGroup, InputOTPSlot } from './forms/input-otp';
|
|
23
24
|
export { PhoneInput } from './forms/phone-input';
|
|
24
25
|
export type { PhoneInputProps } from './forms/phone-input';
|
|
@@ -38,6 +38,13 @@ PaginationItem.displayName = "PaginationItem"
|
|
|
38
38
|
type PaginationLinkProps = {
|
|
39
39
|
isActive?: boolean
|
|
40
40
|
href?: string
|
|
41
|
+
/**
|
|
42
|
+
* Scroll to the top after navigating. Forwarded to `<Link>`, which defaults
|
|
43
|
+
* it to `false` — so a paginator that omits it leaves the visitor at the
|
|
44
|
+
* scroll offset they had on the previous page, looking at row 40 of a list
|
|
45
|
+
* that now starts again at row 1.
|
|
46
|
+
*/
|
|
47
|
+
scroll?: boolean
|
|
41
48
|
} & Pick<ButtonProps, "size"> &
|
|
42
49
|
React.ComponentProps<"a">
|
|
43
50
|
|
|
@@ -46,6 +53,7 @@ const PaginationLink = ({
|
|
|
46
53
|
isActive,
|
|
47
54
|
size = "icon",
|
|
48
55
|
href,
|
|
56
|
+
scroll,
|
|
49
57
|
children,
|
|
50
58
|
...props
|
|
51
59
|
}: PaginationLinkProps) => {
|
|
@@ -63,6 +71,7 @@ const PaginationLink = ({
|
|
|
63
71
|
href={href}
|
|
64
72
|
aria-current={isActive ? "page" : undefined}
|
|
65
73
|
className={classes}
|
|
74
|
+
scroll={scroll}
|
|
66
75
|
>
|
|
67
76
|
{children}
|
|
68
77
|
</Link>
|
|
@@ -143,6 +143,7 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
|
|
|
143
143
|
<PaginationPrevious
|
|
144
144
|
href={actualHasPreviousPage ? getPageUrl(actualCurrentPage - 1) : undefined}
|
|
145
145
|
className={!actualHasPreviousPage ? "pointer-events-none opacity-50" : undefined}
|
|
146
|
+
scroll
|
|
146
147
|
/>
|
|
147
148
|
</PaginationItem>
|
|
148
149
|
|
|
@@ -154,6 +155,7 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
|
|
|
154
155
|
<PaginationLink
|
|
155
156
|
href={getPageUrl(page)}
|
|
156
157
|
isActive={page === actualCurrentPage}
|
|
158
|
+
scroll
|
|
157
159
|
>
|
|
158
160
|
{page}
|
|
159
161
|
</PaginationLink>
|
|
@@ -165,6 +167,7 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
|
|
|
165
167
|
<PaginationNext
|
|
166
168
|
href={hasNextPage ? getPageUrl(actualCurrentPage + 1) : undefined}
|
|
167
169
|
className={!hasNextPage ? "pointer-events-none opacity-50" : undefined}
|
|
170
|
+
scroll
|
|
168
171
|
/>
|
|
169
172
|
</PaginationItem>
|
|
170
173
|
</PaginationContent>
|
|
@@ -38,7 +38,8 @@ export interface NavigateOptions {
|
|
|
38
38
|
export interface UseNavigateReturn {
|
|
39
39
|
/**
|
|
40
40
|
* SPA navigation through the active adapter.
|
|
41
|
-
* Default: pushState
|
|
41
|
+
* Default: pushState, and NO scroll — pass `scroll: true` for a transition
|
|
42
|
+
* that should land the visitor at the top.
|
|
42
43
|
*/
|
|
43
44
|
navigate: (href: string, opts?: NavigateOptions) => void;
|
|
44
45
|
/**
|