@djangocfg/ui-core 2.1.293 → 2.1.297
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/README.md +127 -26
- package/package.json +16 -4
- package/src/components/feedback/sonner/index.tsx +1 -1
- package/src/components/forms/button/index.tsx +21 -5
- package/src/components/forms/button-download/index.tsx +1 -1
- package/src/components/forms/input/index.tsx +1 -1
- package/src/components/forms/otp/index.tsx +1 -1
- package/src/components/forms/slider/index.tsx +1 -1
- package/src/components/forms/textarea/index.tsx +1 -1
- package/src/components/index.ts +2 -0
- package/src/components/layout/sticky/index.tsx +1 -1
- package/src/components/navigation/accordion/index.tsx +1 -1
- package/src/components/navigation/dropdown-menu/index.tsx +3 -2
- package/src/components/navigation/link/Link.tsx +124 -0
- package/src/components/navigation/link/LinkContext.tsx +52 -0
- package/src/components/navigation/link/index.ts +8 -0
- package/src/components/navigation/menubar/index.tsx +3 -2
- package/src/components/navigation/navigation-menu/index.tsx +2 -1
- package/src/components/navigation/tabs/index.tsx +1 -1
- package/src/components/overlay/responsive-sheet/index.tsx +1 -1
- package/src/components/select/combobox.tsx +1 -1
- package/src/components/select/multi-select.tsx +1 -1
- package/src/components/specialized/image-with-fallback/index.tsx +1 -1
- package/src/hooks/debug/index.ts +3 -0
- package/src/hooks/device/index.ts +7 -0
- package/src/hooks/dom/index.ts +12 -0
- package/src/hooks/{useBodyScrollLock.ts → dom/useBodyScrollLock.ts} +1 -1
- package/src/hooks/{useCopy.ts → dom/useCopy.ts} +1 -1
- package/src/hooks/dom/useScroll.ts +322 -0
- package/src/hooks/events/index.ts +3 -0
- package/src/hooks/feedback/index.ts +3 -0
- package/src/hooks/hotkey/index.ts +4 -0
- package/src/hooks/index.ts +82 -26
- package/src/hooks/media/index.ts +5 -0
- package/src/hooks/router/README.md +121 -0
- package/src/hooks/router/adapter.tsx +139 -0
- package/src/hooks/router/adapters/index.ts +5 -0
- package/src/hooks/router/adapters/nextjs.tsx +140 -0
- package/src/hooks/router/index.ts +90 -0
- package/src/hooks/router/parsers.ts +154 -0
- package/src/hooks/router/useBackOrFallback.ts +145 -0
- package/src/hooks/router/useIsActive.ts +60 -0
- package/src/hooks/router/useLocation.ts +163 -0
- package/src/hooks/router/useNavigate.ts +96 -0
- package/src/hooks/router/useQueryParams.ts +262 -0
- package/src/hooks/router/useQueryState.ts +106 -0
- package/src/hooks/router/useRouter.ts +81 -0
- package/src/hooks/router/useSmartLink.ts +157 -0
- package/src/hooks/router/useUrlBuilder.ts +118 -0
- package/src/hooks/state/index.ts +8 -0
- package/src/hooks/theme/index.ts +4 -0
- package/src/hooks/time/index.ts +4 -0
- package/src/lib/dialog-service/dialogs/AlertDialogUI.tsx +1 -1
- package/src/lib/dialog-service/dialogs/ConfirmDialogUI.tsx +1 -1
- package/src/lib/dialog-service/dialogs/PromptDialogUI.tsx +1 -1
- package/src/styles/palette/useThemePalette.ts +1 -1
- /package/src/hooks/{useDebugTools.ts → debug/useDebugTools.ts} +0 -0
- /package/src/hooks/{useBrowserDetect.ts → device/useBrowserDetect.ts} +0 -0
- /package/src/hooks/{useDeviceDetect.ts → device/useDeviceDetect.ts} +0 -0
- /package/src/hooks/{useShortcutModLabel.ts → device/useShortcutModLabel.ts} +0 -0
- /package/src/hooks/{useImageLoader.ts → dom/useImageLoader.ts} +0 -0
- /package/src/hooks/{useEventsBus.ts → events/useEventsBus.ts} +0 -0
- /package/src/hooks/{useToast.ts → feedback/useToast.ts} +0 -0
- /package/src/hooks/{useHotkey.ts → hotkey/useHotkey.ts} +0 -0
- /package/src/hooks/{useMediaQuery.ts → media/useMediaQuery.ts} +0 -0
- /package/src/hooks/{useMobile.tsx → media/useMobile.tsx} +0 -0
- /package/src/hooks/{useDebounce.ts → state/useDebounce.ts} +0 -0
- /package/src/hooks/{useDebouncedCallback.ts → state/useDebouncedCallback.ts} +0 -0
- /package/src/hooks/{useLocalStorage.ts → state/useLocalStorage.ts} +0 -0
- /package/src/hooks/{useSessionStorage.ts → state/useSessionStorage.ts} +0 -0
- /package/src/hooks/{useStoredValue.ts → state/useStoredValue.ts} +0 -0
- /package/src/hooks/{useResolvedTheme.ts → theme/useResolvedTheme.ts} +0 -0
- /package/src/hooks/{useCountdown.ts → time/useCountdown.ts} +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useRouter — composite facade over the atomic router hooks.
|
|
5
|
+
*
|
|
6
|
+
* WHY:
|
|
7
|
+
* For consumers who want a single import that "feels like" Next's
|
|
8
|
+
* `useRouter`. Composes useLocation + useNavigate + useQueryParams +
|
|
9
|
+
* useBackOrFallback + useUrlBuilder.
|
|
10
|
+
*
|
|
11
|
+
* NOTE on perf and tree-shaking:
|
|
12
|
+
* This hook subscribes to EVERY URL change (location, search, depth)
|
|
13
|
+
* because it composes hooks that subscribe to those things. If your
|
|
14
|
+
* component only needs `navigate` (no location read), prefer the
|
|
15
|
+
* atomic `useNavigate()` to avoid extra re-renders. Same for
|
|
16
|
+
* `useQueryParams`, `useUrlBuilder`, etc. The atomic hooks also
|
|
17
|
+
* tree-shake better — pulling in just `useNavigate` doesn't pay the
|
|
18
|
+
* cost of the snapshot machinery.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const router = useRouter();
|
|
22
|
+
* router.navigate('/users');
|
|
23
|
+
* router.query.set({ page: 2 });
|
|
24
|
+
* router.backOrFallback('/dashboard');
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { useMemo } from 'react';
|
|
28
|
+
import { useLocation, type LocationSnapshot } from './useLocation';
|
|
29
|
+
import { useNavigate, type UseNavigateReturn } from './useNavigate';
|
|
30
|
+
import { useQueryParams, type UseQueryParamsReturn } from './useQueryParams';
|
|
31
|
+
import { useBackOrFallback } from './useBackOrFallback';
|
|
32
|
+
import { useUrlBuilder, type UseUrlBuilderReturn } from './useUrlBuilder';
|
|
33
|
+
|
|
34
|
+
export interface UseRouterReturn extends LocationSnapshot, UseNavigateReturn {
|
|
35
|
+
/** Same as `useQueryParams()`. */
|
|
36
|
+
query: UseQueryParamsReturn;
|
|
37
|
+
/** Same as `useUrlBuilder().build`. */
|
|
38
|
+
build: UseUrlBuilderReturn['build'];
|
|
39
|
+
/** Same as `useUrlBuilder().withCurrentParams`. */
|
|
40
|
+
withCurrentParams: UseUrlBuilderReturn['withCurrentParams'];
|
|
41
|
+
/** Same as `useBackOrFallback().back`. */
|
|
42
|
+
backOrFallback: (fallback?: string) => void;
|
|
43
|
+
/** Same as `useBackOrFallback().canGoBack`. */
|
|
44
|
+
canGoBack: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convenience hook that exposes the full router surface.
|
|
49
|
+
* For minimal re-renders / smaller bundles, prefer the atomic hooks.
|
|
50
|
+
*/
|
|
51
|
+
export function useRouter(): UseRouterReturn {
|
|
52
|
+
const location = useLocation();
|
|
53
|
+
const nav = useNavigate();
|
|
54
|
+
const query = useQueryParams();
|
|
55
|
+
const builder = useUrlBuilder();
|
|
56
|
+
const { back: backOrFallback, canGoBack } = useBackOrFallback();
|
|
57
|
+
|
|
58
|
+
return useMemo<UseRouterReturn>(
|
|
59
|
+
() => ({
|
|
60
|
+
// Location snapshot
|
|
61
|
+
pathname: location.pathname,
|
|
62
|
+
search: location.search,
|
|
63
|
+
hash: location.hash,
|
|
64
|
+
href: location.href,
|
|
65
|
+
// Navigation pass-through
|
|
66
|
+
navigate: nav.navigate,
|
|
67
|
+
navigateExternal: nav.navigateExternal,
|
|
68
|
+
push: nav.push,
|
|
69
|
+
replace: nav.replace,
|
|
70
|
+
back: nav.back,
|
|
71
|
+
forward: nav.forward,
|
|
72
|
+
// Sub-APIs
|
|
73
|
+
query,
|
|
74
|
+
build: builder.build,
|
|
75
|
+
withCurrentParams: builder.withCurrentParams,
|
|
76
|
+
backOrFallback,
|
|
77
|
+
canGoBack,
|
|
78
|
+
}),
|
|
79
|
+
[location, nav, query, builder, backOrFallback, canGoBack]
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useSmartLink — turn a non-`<a>` element (card, table row, list item)
|
|
5
|
+
* into a proper link.
|
|
6
|
+
*
|
|
7
|
+
* WHY:
|
|
8
|
+
* "Clickable cards" used to either nest an `<a>` (which then can't
|
|
9
|
+
* contain other interactive children) or attach `onClick={navigate}`
|
|
10
|
+
* (which loses cmd-click, middle-click, keyboard, accessibility).
|
|
11
|
+
* This hook returns a small bag of handlers that gives a non-anchor
|
|
12
|
+
* element the right behavior in all those cases.
|
|
13
|
+
*
|
|
14
|
+
* Key behaviors:
|
|
15
|
+
* - Cmd/Ctrl+click and middle-click open in a new tab.
|
|
16
|
+
* - Plain click does SPA nav.
|
|
17
|
+
* - Enter / Space activate from keyboard.
|
|
18
|
+
* - Clicks inside nested `<a>` / `<button>` are ignored (so the
|
|
19
|
+
* inner element handles its own action).
|
|
20
|
+
* - `role="link"` and `tabIndex={0}` for screen readers / keyboard.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const link = useSmartLink('/products/42');
|
|
24
|
+
* <div {...link}>Product card</div>
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { useCallback, useMemo, type KeyboardEvent, type MouseEvent } from 'react';
|
|
28
|
+
import { useNavigate, type NavigateOptions } from './useNavigate';
|
|
29
|
+
|
|
30
|
+
export interface UseSmartLinkOptions extends NavigateOptions {
|
|
31
|
+
/** Disable all navigation (e.g. while a row is being edited). */
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* If true, modifier-clicks (cmd/ctrl) and middle-clicks DON'T open
|
|
35
|
+
* a new tab — they're treated as plain clicks. Default: false.
|
|
36
|
+
*/
|
|
37
|
+
ignoreModifiers?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SmartLinkHandlers {
|
|
41
|
+
onClick: (event: MouseEvent<HTMLElement>) => void;
|
|
42
|
+
onAuxClick: (event: MouseEvent<HTMLElement>) => void;
|
|
43
|
+
onKeyDown: (event: KeyboardEvent<HTMLElement>) => void;
|
|
44
|
+
role: 'link';
|
|
45
|
+
/** -1 when disabled so the element is removed from the tab order. */
|
|
46
|
+
tabIndex: 0 | -1;
|
|
47
|
+
/** Forwarded to the consumer element so AT announces disabled state. */
|
|
48
|
+
'aria-disabled'?: true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const INTERACTIVE_SELECTOR =
|
|
52
|
+
'a,button,input,textarea,select,label,[role="button"],[role="link"],[role="checkbox"],[role="switch"],[role="tab"],[role="menuitem"]';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* True if the click target is inside another interactive element
|
|
56
|
+
* nested within `currentTarget` — in which case we shouldn't intercept.
|
|
57
|
+
* Uses Element.closest for a single C-side traversal that also handles
|
|
58
|
+
* SVG/MathML, custom elements, and `:where()` semantics correctly.
|
|
59
|
+
*/
|
|
60
|
+
function isInsideNestedInteractive(event: MouseEvent<HTMLElement>): boolean {
|
|
61
|
+
const target = event.target as Element | null;
|
|
62
|
+
const current = event.currentTarget;
|
|
63
|
+
if (!target || target === current) return false;
|
|
64
|
+
const interactive = target.closest(INTERACTIVE_SELECTOR);
|
|
65
|
+
// Found an interactive ancestor strictly between target and currentTarget.
|
|
66
|
+
return !!interactive && interactive !== current && current.contains(interactive);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** True if the user has selected text — don't navigate, they're reading. */
|
|
70
|
+
function hasTextSelection(): boolean {
|
|
71
|
+
if (typeof window === 'undefined') return false;
|
|
72
|
+
const selection = window.getSelection();
|
|
73
|
+
return !!selection && selection.toString().length > 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Hook variant — gives an element link semantics with keyboard + new-tab support.
|
|
78
|
+
*/
|
|
79
|
+
export function useSmartLink(
|
|
80
|
+
href: string,
|
|
81
|
+
options?: UseSmartLinkOptions
|
|
82
|
+
): SmartLinkHandlers {
|
|
83
|
+
const { navigate } = useNavigate();
|
|
84
|
+
const disabled = options?.disabled ?? false;
|
|
85
|
+
const ignoreModifiers = options?.ignoreModifiers ?? false;
|
|
86
|
+
const replace = options?.replace;
|
|
87
|
+
const scroll = options?.scroll;
|
|
88
|
+
|
|
89
|
+
const openInNewTab = useCallback((url: string) => {
|
|
90
|
+
if (typeof window === 'undefined') return;
|
|
91
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
92
|
+
}, []);
|
|
93
|
+
|
|
94
|
+
const onClick = useCallback(
|
|
95
|
+
(event: MouseEvent<HTMLElement>) => {
|
|
96
|
+
if (disabled) return;
|
|
97
|
+
if (event.defaultPrevented) return;
|
|
98
|
+
if (isInsideNestedInteractive(event)) return;
|
|
99
|
+
if (hasTextSelection()) return;
|
|
100
|
+
|
|
101
|
+
// Modifier click → open in new tab. Don't preventDefault on a
|
|
102
|
+
// real <a>, but for div/span our default IS to navigate so we
|
|
103
|
+
// can branch freely.
|
|
104
|
+
if (
|
|
105
|
+
!ignoreModifiers &&
|
|
106
|
+
(event.metaKey || event.ctrlKey || event.shiftKey)
|
|
107
|
+
) {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
openInNewTab(href);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
navigate(href, { replace, scroll });
|
|
115
|
+
},
|
|
116
|
+
[disabled, ignoreModifiers, navigate, href, replace, scroll, openInNewTab]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const onAuxClick = useCallback(
|
|
120
|
+
(event: MouseEvent<HTMLElement>) => {
|
|
121
|
+
if (disabled) return;
|
|
122
|
+
if (event.defaultPrevented) return;
|
|
123
|
+
if (isInsideNestedInteractive(event)) return;
|
|
124
|
+
// Middle-click only.
|
|
125
|
+
if (event.button !== 1) return;
|
|
126
|
+
if (ignoreModifiers) return;
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
openInNewTab(href);
|
|
129
|
+
},
|
|
130
|
+
[disabled, ignoreModifiers, href, openInNewTab]
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const onKeyDown = useCallback(
|
|
134
|
+
(event: KeyboardEvent<HTMLElement>) => {
|
|
135
|
+
if (disabled) return;
|
|
136
|
+
// Only handle when the focused element IS the link container,
|
|
137
|
+
// otherwise we steal Enter from inputs etc.
|
|
138
|
+
if (event.target !== event.currentTarget) return;
|
|
139
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
navigate(href, { replace, scroll });
|
|
142
|
+
},
|
|
143
|
+
[disabled, navigate, href, replace, scroll]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return useMemo<SmartLinkHandlers>(
|
|
147
|
+
() => ({
|
|
148
|
+
onClick,
|
|
149
|
+
onAuxClick,
|
|
150
|
+
onKeyDown,
|
|
151
|
+
role: 'link',
|
|
152
|
+
tabIndex: disabled ? -1 : 0,
|
|
153
|
+
...(disabled ? { 'aria-disabled': true as const } : {}),
|
|
154
|
+
}),
|
|
155
|
+
[onClick, onAuxClick, onKeyDown, disabled]
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useUrlBuilder — pure URL/querystring assembly.
|
|
5
|
+
*
|
|
6
|
+
* WHY:
|
|
7
|
+
* Building URLs by hand (template literals + URLSearchParams) is fiddly:
|
|
8
|
+
* you have to remember to skip empty values, encode keys, handle arrays.
|
|
9
|
+
* This hook centralizes those rules so callers stay declarative.
|
|
10
|
+
* Zero side effects — only React-bound thing is `useCallback` for ref
|
|
11
|
+
* stability inside JSX.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const { build, withCurrentParams } = useUrlBuilder();
|
|
15
|
+
* build('/products', { page: 2, tag: ['a', 'b'], q: '' });
|
|
16
|
+
* // '/products?page=2&tag=a&tag=b'
|
|
17
|
+
* withCurrentParams('/products', { page: 1 });
|
|
18
|
+
* // keeps everything in current ?…, overrides `page`
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { useCallback, useMemo } from 'react';
|
|
22
|
+
import { useLocation } from './useLocation';
|
|
23
|
+
|
|
24
|
+
/** Value types accepted by `build`. Empty/null/undefined are stripped. */
|
|
25
|
+
export type QueryValue = string | number | boolean | null | undefined;
|
|
26
|
+
/** Param map. Arrays become repeated keys (`?tag=a&tag=b`), not csv. */
|
|
27
|
+
export type QueryParamsInput = Record<string, QueryValue | QueryValue[]>;
|
|
28
|
+
|
|
29
|
+
function appendValue(
|
|
30
|
+
params: URLSearchParams,
|
|
31
|
+
key: string,
|
|
32
|
+
value: QueryValue
|
|
33
|
+
): void {
|
|
34
|
+
if (value === null || value === undefined) return;
|
|
35
|
+
if (typeof value === 'string' && value === '') return;
|
|
36
|
+
// Booleans serialize as 'true'/'false' — common-sense default.
|
|
37
|
+
params.append(key, String(value));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a query-string fragment (no leading `?`) from a param map.
|
|
42
|
+
* Skips empty / null / undefined values; arrays become repeated keys.
|
|
43
|
+
* Exported standalone so utilities can share the same rules without a hook.
|
|
44
|
+
*/
|
|
45
|
+
export function buildQueryString(params?: QueryParamsInput): string {
|
|
46
|
+
if (!params) return '';
|
|
47
|
+
const search = new URLSearchParams();
|
|
48
|
+
for (const key of Object.keys(params)) {
|
|
49
|
+
const value = params[key];
|
|
50
|
+
if (Array.isArray(value)) {
|
|
51
|
+
for (const item of value) appendValue(search, key, item);
|
|
52
|
+
} else {
|
|
53
|
+
appendValue(search, key, value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return search.toString();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build a full path: `path + '?' + qs` (or just `path` if qs is empty).
|
|
61
|
+
* Pure — useful outside React (link generators, server code).
|
|
62
|
+
*/
|
|
63
|
+
export function buildUrl(path: string, params?: QueryParamsInput): string {
|
|
64
|
+
const qs = buildQueryString(params);
|
|
65
|
+
return qs ? `${path}?${qs}` : path;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface UseUrlBuilderReturn {
|
|
69
|
+
/** Assemble `path` + serialized params. */
|
|
70
|
+
build: (path: string, params?: QueryParamsInput) => string;
|
|
71
|
+
/**
|
|
72
|
+
* Assemble `path` keeping the current page's querystring,
|
|
73
|
+
* with `overrides` merged on top. Pass `null`/`undefined`/`''` in
|
|
74
|
+
* `overrides` to drop a key.
|
|
75
|
+
*/
|
|
76
|
+
withCurrentParams: (
|
|
77
|
+
path: string,
|
|
78
|
+
overrides?: QueryParamsInput
|
|
79
|
+
) => string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Stable URL builder helpers. `withCurrentParams` re-renders when the
|
|
84
|
+
* current querystring changes (it reads `useLocation` internally).
|
|
85
|
+
*/
|
|
86
|
+
export function useUrlBuilder(): UseUrlBuilderReturn {
|
|
87
|
+
const { search } = useLocation();
|
|
88
|
+
|
|
89
|
+
const build = useCallback((path: string, params?: QueryParamsInput): string => {
|
|
90
|
+
return buildUrl(path, params);
|
|
91
|
+
}, []);
|
|
92
|
+
|
|
93
|
+
const withCurrentParams = useCallback(
|
|
94
|
+
(path: string, overrides?: QueryParamsInput): string => {
|
|
95
|
+
const next = new URLSearchParams(search);
|
|
96
|
+
if (overrides) {
|
|
97
|
+
for (const key of Object.keys(overrides)) {
|
|
98
|
+
const value = overrides[key];
|
|
99
|
+
// Overrides semantics: drop on empty, replace otherwise.
|
|
100
|
+
next.delete(key);
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
for (const item of value) appendValue(next, key, item);
|
|
103
|
+
} else {
|
|
104
|
+
appendValue(next, key, value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const qs = next.toString();
|
|
109
|
+
return qs ? `${path}?${qs}` : path;
|
|
110
|
+
},
|
|
111
|
+
[search]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
return useMemo<UseUrlBuilderReturn>(
|
|
115
|
+
() => ({ build, withCurrentParams }),
|
|
116
|
+
[build, withCurrentParams]
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
export { useDebounce } from './useDebounce';
|
|
4
|
+
export { useDebouncedCallback } from './useDebouncedCallback';
|
|
5
|
+
export { useLocalStorage } from './useLocalStorage';
|
|
6
|
+
export { useSessionStorage } from './useSessionStorage';
|
|
7
|
+
export { useStoredValue } from './useStoredValue';
|
|
8
|
+
export type { UseStoredValueOptions, StorageType } from './useStoredValue';
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
AlertDialogHeader,
|
|
12
12
|
AlertDialogTitle,
|
|
13
13
|
} from '../../../components/overlay/alert-dialog';
|
|
14
|
-
import { useHotkey } from '../../../hooks
|
|
14
|
+
import { useHotkey } from '../../../hooks';
|
|
15
15
|
import { I18N_KEYS } from '../constants';
|
|
16
16
|
import type { DialogOptions } from '../types';
|
|
17
17
|
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
AlertDialogTitle,
|
|
14
14
|
} from '../../../components/overlay/alert-dialog';
|
|
15
15
|
import { buttonVariants } from '../../../components/forms/button';
|
|
16
|
-
import { useHotkey } from '../../../hooks
|
|
16
|
+
import { useHotkey } from '../../../hooks';
|
|
17
17
|
import { cn } from '../../utils';
|
|
18
18
|
import { I18N_KEYS } from '../constants';
|
|
19
19
|
import type { DialogOptions } from '../types';
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '../../../components/overlay/dialog';
|
|
13
13
|
import { Button } from '../../../components/forms/button';
|
|
14
14
|
import { Input } from '../../../components/forms/input';
|
|
15
|
-
import { useHotkey } from '../../../hooks
|
|
15
|
+
import { useHotkey } from '../../../hooks';
|
|
16
16
|
import { I18N_KEYS } from '../constants';
|
|
17
17
|
import type { DialogOptions } from '../types';
|
|
18
18
|
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { useMemo } from 'react';
|
|
18
|
-
import { useResolvedTheme } from '../../hooks
|
|
18
|
+
import { useResolvedTheme } from '../../hooks';
|
|
19
19
|
import { hslToHex } from './utils';
|
|
20
20
|
import type { ThemePalette, StylePresets, BoxColors } from './types';
|
|
21
21
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|