@ttt-productions/ui-core 0.11.18 → 0.11.19

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.
Files changed (52) hide show
  1. package/package.json +2 -4
  2. package/src/index.ts +0 -3
  3. package/src/lib/format-numbers.ts +0 -17
  4. package/src/lib/utils.ts +0 -9
  5. package/src/react/components/accordion.tsx +0 -53
  6. package/src/react/components/alert-dialog.tsx +0 -131
  7. package/src/react/components/alert.tsx +0 -59
  8. package/src/react/components/avatar.tsx +0 -51
  9. package/src/react/components/badge.tsx +0 -31
  10. package/src/react/components/button.tsx +0 -102
  11. package/src/react/components/card.tsx +0 -54
  12. package/src/react/components/checkbox.tsx +0 -28
  13. package/src/react/components/chunk-error-recovery.tsx +0 -57
  14. package/src/react/components/collapsible.tsx +0 -11
  15. package/src/react/components/consequence-dialog.tsx +0 -282
  16. package/src/react/components/date-picker.tsx +0 -567
  17. package/src/react/components/dialog.tsx +0 -114
  18. package/src/react/components/dropdown-menu.tsx +0 -200
  19. package/src/react/components/end-of-list-indicator.tsx +0 -27
  20. package/src/react/components/form.tsx +0 -174
  21. package/src/react/components/input.tsx +0 -23
  22. package/src/react/components/label.tsx +0 -21
  23. package/src/react/components/layout/screen-adaptive-view.tsx +0 -106
  24. package/src/react/components/list-pagination.tsx +0 -101
  25. package/src/react/components/menubar.tsx +0 -220
  26. package/src/react/components/popover.tsx +0 -31
  27. package/src/react/components/progress.tsx +0 -27
  28. package/src/react/components/radio-group.tsx +0 -36
  29. package/src/react/components/relative-time.tsx +0 -56
  30. package/src/react/components/scroll-area.tsx +0 -56
  31. package/src/react/components/scroll-to-top-button.tsx +0 -85
  32. package/src/react/components/search-dropdown.tsx +0 -233
  33. package/src/react/components/select.tsx +0 -165
  34. package/src/react/components/separator.tsx +0 -26
  35. package/src/react/components/sheet.tsx +0 -140
  36. package/src/react/components/skeleton.tsx +0 -16
  37. package/src/react/components/slider.tsx +0 -25
  38. package/src/react/components/spinner.tsx +0 -49
  39. package/src/react/components/switch.tsx +0 -41
  40. package/src/react/components/table.tsx +0 -121
  41. package/src/react/components/tabs.tsx +0 -59
  42. package/src/react/components/textarea.tsx +0 -22
  43. package/src/react/components/toast.tsx +0 -193
  44. package/src/react/components/toaster.tsx +0 -43
  45. package/src/react/components/tooltip.tsx +0 -28
  46. package/src/react/hooks/use-async-action.ts +0 -63
  47. package/src/react/hooks/use-cursor-page.ts +0 -139
  48. package/src/react/hooks/use-media-query.tsx +0 -26
  49. package/src/react/hooks/use-paged-list.ts +0 -106
  50. package/src/react/hooks/use-return-scroll.ts +0 -149
  51. package/src/react/hooks/use-toast.tsx +0 -176
  52. package/src/react/index.ts +0 -85
@@ -1,139 +0,0 @@
1
- "use client";
2
-
3
- import { useCallback, useState } from "react";
4
- import type { ListPaginationState } from "./use-paged-list.js";
5
-
6
- /**
7
- * Render-time page state for a cursor feed, shaped as {@link ListPaginationState}
8
- * so it drops straight into the ONE `ListPagination` control. Produced by
9
- * {@link CursorPager.paginationFor} once the query for `currentPage` has
10
- * answered with its `hasMore`.
11
- */
12
- export interface CursorPage extends ListPaginationState {
13
- /** Never known for cursor pagination — the counter reads "Page N". */
14
- totalPages?: undefined;
15
- }
16
-
17
- export interface UseCursorPageOptions {
18
- /** Called after the page actually changed. Same contract as `usePagedList`. */
19
- onPageChange?: () => void;
20
- /**
21
- * A snapshot of the query inputs the page number belongs to — a tag, a filter,
22
- * a sort. When it changes, the page is 1 again **in the same render**, so the
23
- * query never fires for "page 4" of inputs whose page 4 may not exist.
24
- *
25
- * Use this when the input change arrives as a prop (there is no event handler
26
- * of yours to hang the reset on); use {@link CursorPager.reset} when your own
27
- * handler already owns the change. Compose several inputs into one string
28
- * (`` `${type}|${projectId ?? ""}` ``) — the type is deliberately narrowed to
29
- * primitives so a fresh object literal cannot pin the list to page 1 forever.
30
- */
31
- resetKey?: string | number | boolean | null;
32
- }
33
-
34
- /**
35
- * What {@link useCursorPage} owns: the page number, the guarded step controls,
36
- * and the return-to-page-1 control.
37
- */
38
- export interface CursorPager {
39
- /** 1-based page to feed into the paged query. */
40
- currentPage: number;
41
- /**
42
- * Return to page 1 without walking back. Call it from the handler that changes
43
- * the query's inputs (a new filter, a new tag, a new sort) — page 4 of the old
44
- * result set is not page 4 of the new one. A no-op on page 1, so it never
45
- * fires `onPageChange` for nothing.
46
- */
47
- reset: () => void;
48
- /**
49
- * Bind the query's answer to the controls: pass the `hasMore` that came back
50
- * for {@link currentPage} and render the result as
51
- * `<ListPagination pagination={…} />`. `hasMore` binds HERE, after the query,
52
- * because that is the only moment it exists — a cursor query needs
53
- * `currentPage` as its input, so `hasMore` cannot be an argument to the hook
54
- * that produces `currentPage`.
55
- */
56
- paginationFor: (hasMore: boolean) => CursorPage;
57
- }
58
-
59
- /**
60
- * Page state for a server-paged (cursor / `hasMore`) list: the 1-based page
61
- * number, the guarded step controls, and the reset — the pieces each
62
- * server-paged surface was otherwise re-declaring as a local `useState(1)` plus
63
- * its own clamped increment/decrement and its own "reset when the filter
64
- * changed" adjustment (ENG-002, ENG-003).
65
- *
66
- * The data stays the caller's, and the two halves bind at different moments:
67
- *
68
- * ```tsx
69
- * const pager = useCursorPage({ resetKey: tag });
70
- * const { data, isFetching } = useThingsByTag(tag, pager.currentPage);
71
- * …
72
- * <ListPagination pagination={pager.paginationFor(data?.hasMore ?? false)} busy={isFetching} />
73
- * ```
74
- *
75
- * The guards live in here, not at the call site: a step that cannot move the
76
- * page changes nothing and does not fire `onPageChange`.
77
- *
78
- * A surface whose data hook ALREADY owns its page state (it returns
79
- * `page`/`hasNextPage`/`nextPage`/`prevPage` itself) does not need this hook —
80
- * it maps those fields onto {@link ListPaginationState} directly. The hook
81
- * exists for the surfaces that would otherwise hand-roll the state.
82
- */
83
- export function useCursorPage(options: UseCursorPageOptions = {}): CursorPager {
84
- const { onPageChange, resetKey } = options;
85
-
86
- // The stored page is only meaningful for the inputs it was reached under, so
87
- // the key it belongs to is stored WITH it.
88
- const [pageState, setPageState] = useState<{
89
- page: number;
90
- key: UseCursorPageOptions["resetKey"];
91
- }>({ page: 1, key: resetKey });
92
-
93
- // `Object.is`, the same comparison React uses for state and deps: a `NaN` key
94
- // must compare equal to itself, or the adjustment below would re-fire forever.
95
- const keyMatches = Object.is(pageState.key, resetKey);
96
-
97
- // React's "adjusting state when a prop changes": a new key retires the stored
98
- // page, because page 4 of the old result set is not page 4 of the new one.
99
- // Retiring it (rather than only ignoring it) is what makes going BACK to
100
- // earlier inputs start at page 1 too, instead of reviving a dead page.
101
- if (!keyMatches) {
102
- setPageState({ page: 1, key: resetKey });
103
- }
104
- // Derived as well as adjusted, so even the render that triggers the adjustment
105
- // already reports page 1 — the caller's query is never asked for a page of the
106
- // new inputs that the user never navigated to.
107
- const currentPage = keyMatches ? pageState.page : 1;
108
-
109
- const goToPreviousPage = useCallback(() => {
110
- if (currentPage <= 1) return;
111
- setPageState({ page: currentPage - 1, key: resetKey });
112
- onPageChange?.();
113
- }, [currentPage, resetKey, onPageChange]);
114
-
115
- const reset = useCallback(() => {
116
- if (currentPage === 1) return;
117
- setPageState({ page: 1, key: resetKey });
118
- onPageChange?.();
119
- }, [currentPage, resetKey, onPageChange]);
120
-
121
- // Memoized on the page state itself, so a consumer may hold it in a dep array
122
- // and it changes only when the page (or the callback/inputs) actually change.
123
- const paginationFor = useCallback(
124
- (hasMore: boolean): CursorPage => ({
125
- currentPage,
126
- canPreviousPage: currentPage > 1,
127
- canNextPage: hasMore,
128
- goToPreviousPage,
129
- goToNextPage: () => {
130
- if (!hasMore) return;
131
- setPageState({ page: currentPage + 1, key: resetKey });
132
- onPageChange?.();
133
- },
134
- }),
135
- [currentPage, resetKey, onPageChange, goToPreviousPage],
136
- );
137
-
138
- return { currentPage, reset, paginationFor };
139
- }
@@ -1,26 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
-
5
- export function useMediaQuery(query: string) {
6
- const [matches, setMatches] = React.useState(false);
7
-
8
- React.useEffect(() => {
9
- if (typeof window === "undefined" || !window.matchMedia) return;
10
-
11
- const mql = window.matchMedia(query);
12
- const onChange = (e: MediaQueryListEvent) => setMatches(e.matches);
13
-
14
- setMatches(mql.matches);
15
-
16
- if (mql.addEventListener) mql.addEventListener("change", onChange);
17
- else mql.addListener(onChange);
18
-
19
- return () => {
20
- if (mql.removeEventListener) mql.removeEventListener("change", onChange);
21
- else mql.removeListener(onChange);
22
- };
23
- }, [query]);
24
-
25
- return matches;
26
- }
@@ -1,106 +0,0 @@
1
- "use client";
2
-
3
- import { useCallback, useMemo, useState } from "react";
4
-
5
- /**
6
- * The control state a paginated list exposes to its paginator UI, rendered by
7
- * the ONE `ListPagination` component. Nothing else builds
8
- * Previous / counter / Next controls.
9
- *
10
- * `totalPages` is the flavor discriminant, and the ONLY difference between the
11
- * two flavors:
12
- *
13
- * - **Known total** (client-side slice pagination — {@link usePagedList}):
14
- * `totalPages` is a number and the counter reads `"2 of 5"`.
15
- * - **Unknown total** (server/cursor pagination — `useCursorPage().paginationFor(hasMore)`,
16
- * or a caller's own query hook): `totalPages` is omitted and the counter reads
17
- * `"Page 2"`.
18
- *
19
- * Both flavors share one visibility rule, one disabled-edge rule, one live
20
- * region, and one set of classes because they share one component (ENG-002,
21
- * FRONTEND-001).
22
- */
23
- export interface ListPaginationState {
24
- /** 1-based page currently on screen. */
25
- currentPage: number;
26
- /**
27
- * Total page count when it is knowable — at least 1, even for an empty list
28
- * (an empty list is one empty page). Omitted for cursor/`hasMore` pagination,
29
- * where the total is unknown until the last page is reached.
30
- */
31
- totalPages?: number;
32
- canPreviousPage: boolean;
33
- canNextPage: boolean;
34
- goToPreviousPage: () => void;
35
- goToNextPage: () => void;
36
- }
37
-
38
- export interface PagedList<T> extends ListPaginationState {
39
- /** Always known for a slice-paginated list. */
40
- totalPages: number;
41
- /** The slice of `items` belonging to `currentPage`. */
42
- pageItems: T[];
43
- }
44
-
45
- export interface UsePagedListOptions {
46
- /**
47
- * Called after the page actually changed. Surfaces that keep a per-page
48
- * selection (an open detail row) clear it here — the old selection is not on
49
- * the new page.
50
- */
51
- onPageChange?: () => void;
52
- }
53
-
54
- /**
55
- * The single owner of client-side slice pagination: local page state, the page
56
- * math, and the current slice over an ALREADY-FETCHED list.
57
- *
58
- * Page size is the caller's — every surface passes its own page-size constant
59
- * (ENG-005), never a literal. Ordering and filtering stay with the caller: pass
60
- * the list already sorted and filtered.
61
- *
62
- * Not for server-paged lists — a cursor/`hasMore` feed pages through the
63
- * caller's data layer, not through a slice; use `useCursorPage` (or the query
64
- * hook's own page state) with the same `ListPagination` control.
65
- */
66
- export function usePagedList<T>(
67
- items: readonly T[],
68
- pageSize: number,
69
- options: UsePagedListOptions = {},
70
- ): PagedList<T> {
71
- const { onPageChange } = options;
72
- const [requestedPage, setRequestedPage] = useState(1);
73
-
74
- const totalPages = Math.max(1, Math.ceil(items.length / pageSize));
75
- // The list can shrink under a page that is already open (an item is deleted,
76
- // a filter narrows). Clamping keeps the paginator on a real page instead of
77
- // rendering an empty slice under an out-of-range "4 of 2".
78
- const currentPage = Math.min(Math.max(1, requestedPage), totalPages);
79
-
80
- const pageItems = useMemo(() => {
81
- const startIndex = (currentPage - 1) * pageSize;
82
- return items.slice(startIndex, startIndex + pageSize);
83
- }, [items, currentPage, pageSize]);
84
-
85
- const goToPreviousPage = useCallback(() => {
86
- if (currentPage <= 1) return;
87
- setRequestedPage(currentPage - 1);
88
- onPageChange?.();
89
- }, [currentPage, onPageChange]);
90
-
91
- const goToNextPage = useCallback(() => {
92
- if (currentPage >= totalPages) return;
93
- setRequestedPage(currentPage + 1);
94
- onPageChange?.();
95
- }, [currentPage, totalPages, onPageChange]);
96
-
97
- return {
98
- currentPage,
99
- totalPages,
100
- pageItems,
101
- canPreviousPage: currentPage > 1,
102
- canNextPage: currentPage < totalPages,
103
- goToPreviousPage,
104
- goToNextPage,
105
- };
106
- }
@@ -1,149 +0,0 @@
1
- "use client";
2
-
3
- // Return-scroll: bring a list back to where the user left it after a REAL route change.
4
- //
5
- // A same-page view swap keeps its component mounted and can restore from a ref; a list
6
- // whose detail pages have their own URLs unmounts, so the offset has to survive in
7
- // per-tab storage and the restore has to wait until the list is displayed at its real
8
- // height again. This hook is the ONE owner of that mechanism. It knows nothing about
9
- // what the list is — the caller supplies a canonical key, decides eligibility once at
10
- // mount, and reports readiness.
11
- //
12
- // Contract (every rule below is tested):
13
- // - `save()` writes { y, savedAt } for `key` — call it immediately before navigating
14
- // away (inside any guarded-navigation callback, so a cancelled leave writes nothing).
15
- // - `eligible` is latched per key the first time the hook sees that key. Not eligible
16
- // ⇒ the entry is deleted immediately and permanently for that key; a later flip
17
- // of `eligible` or `ready` never re-arms the restore.
18
- // - `ready` ⇒ the restore fires ONCE, in a requestAnimationFrame, `behavior: "instant"`,
19
- // then consumes the entry. The scheduled frame is cancelled on unmount or key change.
20
- // React Strict Mode's mount/unmount/mount cancels and reschedules, so exactly one
21
- // scroll happens and the entry is consumed exactly once.
22
- // - Blocked, unavailable, or malformed storage is a silent no-op.
23
-
24
- import * as React from "react";
25
-
26
- const STORAGE_PREFIX = "return-scroll:";
27
-
28
- export interface ReturnScrollEntry {
29
- /** The saved `window.scrollY`. */
30
- y: number;
31
- /** Epoch ms at save time (diagnostic; the caller decides eligibility, not the hook). */
32
- savedAt: number;
33
- }
34
-
35
- export interface UseReturnScrollOptions {
36
- /** The caller's CANONICAL list URL — equivalent list states must give the same key. */
37
- key: string;
38
- /** Decided by the caller ONCE at mount (e.g. "the list's data was already cached on first render"). */
39
- eligible: boolean;
40
- /** The list is displayed at its real height: data present, no error surface in its place. */
41
- ready: boolean;
42
- }
43
-
44
- export interface UseReturnScrollResult {
45
- /** Record the current window offset for `key`. Call right before navigating away. */
46
- save: () => void;
47
- }
48
-
49
- function storageKey(key: string): string {
50
- return `${STORAGE_PREFIX}${key}`;
51
- }
52
-
53
- function sessionStorageOrNull(): Storage | null {
54
- try {
55
- if (typeof window === "undefined") return null;
56
- return window.sessionStorage ?? null;
57
- } catch {
58
- return null;
59
- }
60
- }
61
-
62
- /** The stored entry for `key`, or null when absent, unreadable, or malformed. */
63
- export function readReturnScroll(key: string): ReturnScrollEntry | null {
64
- const storage = sessionStorageOrNull();
65
- if (!storage) return null;
66
- try {
67
- const raw = storage.getItem(storageKey(key));
68
- if (!raw) return null;
69
- const parsed: unknown = JSON.parse(raw);
70
- if (typeof parsed !== "object" || parsed === null) return null;
71
- const { y, savedAt } = parsed as { y?: unknown; savedAt?: unknown };
72
- if (typeof y !== "number" || !Number.isFinite(y) || y < 0) return null;
73
- return { y, savedAt: typeof savedAt === "number" ? savedAt : 0 };
74
- } catch {
75
- return null;
76
- }
77
- }
78
-
79
- /** Record `y` (default: the current `window.scrollY`) for `key`. No-op without storage. */
80
- export function saveReturnScroll(key: string, y?: number): void {
81
- const storage = sessionStorageOrNull();
82
- if (!storage) return;
83
- try {
84
- const offset = typeof y === "number" ? y : window.scrollY;
85
- const entry: ReturnScrollEntry = { y: offset, savedAt: Date.now() };
86
- storage.setItem(storageKey(key), JSON.stringify(entry));
87
- } catch {
88
- /* storage blocked or full — nothing to restore later, which is the honest outcome */
89
- }
90
- }
91
-
92
- /** Drop the entry for `key` (e.g. the list changed underneath the saved offset). */
93
- export function clearReturnScroll(key: string): void {
94
- const storage = sessionStorageOrNull();
95
- if (!storage) return;
96
- try {
97
- storage.removeItem(storageKey(key));
98
- } catch {
99
- /* nothing to clear */
100
- }
101
- }
102
-
103
- interface Decision {
104
- key: string;
105
- /** The offset to restore, or null when this key's return is not restorable. */
106
- y: number | null;
107
- done: boolean;
108
- }
109
-
110
- export function useReturnScroll({ key, eligible, ready }: UseReturnScrollOptions): UseReturnScrollResult {
111
- const decisionRef = React.useRef<Decision | null>(null);
112
-
113
- // Decide ONCE per key, the first time the hook sees it. Effects may read and write refs;
114
- // the decision is latched, so a later `eligible` flip finds it already made and returns.
115
- React.useEffect(() => {
116
- if (decisionRef.current?.key === key) return;
117
- const entry = readReturnScroll(key);
118
- if (!entry) {
119
- decisionRef.current = { key, y: null, done: true };
120
- return;
121
- }
122
- if (!eligible) {
123
- // A cold return: nothing cached to restore against. Discard permanently for this visit.
124
- clearReturnScroll(key);
125
- decisionRef.current = { key, y: null, done: true };
126
- return;
127
- }
128
- decisionRef.current = { key, y: entry.y, done: false };
129
- }, [key, eligible]);
130
-
131
- // Restore once the list is displayed at its real height. The frame is cancelled on unmount
132
- // or key change; Strict Mode's re-run reschedules it, so exactly one scroll happens.
133
- React.useEffect(() => {
134
- const decision = decisionRef.current;
135
- if (!decision || decision.key !== key || decision.y === null || decision.done || !ready) return;
136
- if (typeof window === "undefined") return;
137
- const y = decision.y;
138
- const frame = window.requestAnimationFrame(() => {
139
- window.scrollTo({ top: y, left: 0, behavior: "instant" as ScrollBehavior });
140
- clearReturnScroll(key);
141
- decision.done = true;
142
- });
143
- return () => window.cancelAnimationFrame(frame);
144
- }, [key, ready]);
145
-
146
- const save = React.useCallback(() => saveReturnScroll(key), [key]);
147
-
148
- return { save };
149
- }
@@ -1,176 +0,0 @@
1
- import * as React from "react";
2
-
3
- const TOAST_LIMIT = 3;
4
- // Time between Radix flipping `open=false` and the toast actually unmounting.
5
- // Just long enough for the close animation to finish. Larger values produce a
6
- // visible dead-air gap after the countdown bar reaches empty.
7
- const TOAST_REMOVE_DELAY = 150;
8
-
9
- export type ToastVariant = "default" | "destructive" | "success" | "warning" | "error";
10
-
11
- export type ToasterToast = {
12
- id: string;
13
- title?: React.ReactNode;
14
- description?: React.ReactNode;
15
- action?: React.ReactNode;
16
- variant?: ToastVariant;
17
-
18
- /** Radix controlled open state */
19
- open?: boolean;
20
- onOpenChange?: (open: boolean) => void;
21
-
22
- /** ms */
23
- duration?: number;
24
-
25
- /** show X button */
26
- dismissible?: boolean;
27
-
28
- /**
29
- * When `true`, the toast does not auto-dismiss and renders no countdown bar.
30
- * `duration` is ignored. The user must click the X (or programmatic `dismiss`)
31
- * to remove it.
32
- */
33
- persistent?: boolean;
34
- };
35
-
36
- type ToastProps = ToasterToast;
37
-
38
- type Action =
39
- | { type: "ADD_TOAST"; toast: ToastProps }
40
- | { type: "UPDATE_TOAST"; toast: Partial<ToastProps> & { id: string } }
41
- | { type: "DISMISS_TOAST"; toastId?: string }
42
- | { type: "REMOVE_TOAST"; toastId?: string };
43
-
44
- interface State {
45
- toasts: ToastProps[];
46
- }
47
-
48
- const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
49
- const listeners: Array<(state: State) => void> = [];
50
-
51
- let memoryState: State = { toasts: [] };
52
-
53
- const reducer = (state: State, action: Action): State => {
54
- switch (action.type) {
55
- case "ADD_TOAST":
56
- return { ...state, toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT) };
57
-
58
- case "UPDATE_TOAST":
59
- return {
60
- ...state,
61
- toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
62
- };
63
-
64
- case "DISMISS_TOAST": {
65
- const { toastId } = action;
66
-
67
- if (toastId) {
68
- return {
69
- ...state,
70
- toasts: state.toasts.map((t) => (t.id === toastId ? { ...t, open: false } : t)),
71
- };
72
- }
73
-
74
- return { ...state, toasts: state.toasts.map((t) => ({ ...t, open: false })) };
75
- }
76
-
77
- case "REMOVE_TOAST": {
78
- const { toastId } = action;
79
- if (!toastId) return { ...state, toasts: [] };
80
- return { ...state, toasts: state.toasts.filter((t) => t.id !== toastId) };
81
- }
82
-
83
- default:
84
- return state;
85
- }
86
- };
87
-
88
- function dispatch(action: Action) {
89
- memoryState = reducer(memoryState, action);
90
- listeners.forEach((listener) => listener(memoryState));
91
- }
92
-
93
- function genId() {
94
- return Math.random().toString(36).slice(2, 10);
95
- }
96
-
97
- function addToRemoveQueue(toastId: string) {
98
- if (toastTimeouts.has(toastId)) return;
99
-
100
- const timeout = setTimeout(() => {
101
- toastTimeouts.delete(toastId);
102
- dispatch({ type: "REMOVE_TOAST", toastId });
103
- }, TOAST_REMOVE_DELAY);
104
-
105
- toastTimeouts.set(toastId, timeout);
106
- }
107
-
108
- function defaultDurationForVariant(variant: ToastVariant | undefined): number {
109
- switch (variant) {
110
- case "success":
111
- return 3000;
112
- case "destructive":
113
- case "error":
114
- return 6000;
115
- case "warning":
116
- return 5000;
117
- case "default":
118
- default:
119
- return 4000;
120
- }
121
- }
122
-
123
- export function toast(input: Omit<ToastProps, "id">) {
124
- const id = genId();
125
-
126
- const dismiss = () => {
127
- dispatch({ type: "DISMISS_TOAST", toastId: id });
128
- addToRemoveQueue(id);
129
- };
130
-
131
- const update = (props: Partial<ToastProps>) => dispatch({ type: "UPDATE_TOAST", toast: { ...props, id } });
132
-
133
- const variant = input.variant ?? "default";
134
- // Persistent toasts disable auto-dismiss entirely; Radix accepts `Infinity`.
135
- const duration = input.persistent
136
- ? Infinity
137
- : input.duration ?? defaultDurationForVariant(variant);
138
-
139
- dispatch({
140
- type: "ADD_TOAST",
141
- toast: {
142
- id,
143
- open: true,
144
- onOpenChange: (open) => {
145
- if (!open) dismiss();
146
- },
147
- dismissible: true,
148
- ...input,
149
- variant,
150
- duration,
151
- },
152
- });
153
-
154
- return { id, dismiss, update };
155
- }
156
-
157
- export function useToast() {
158
- const [state, setState] = React.useState<State>(memoryState);
159
-
160
- React.useEffect(() => {
161
- listeners.push(setState);
162
- return () => {
163
- const index = listeners.indexOf(setState);
164
- if (index > -1) listeners.splice(index, 1);
165
- };
166
- }, []);
167
-
168
- return {
169
- ...state,
170
- toast,
171
- dismiss: (toastId?: string) => {
172
- dispatch({ type: "DISMISS_TOAST", toastId });
173
- if (toastId) addToRemoveQueue(toastId);
174
- },
175
- };
176
- }
@@ -1,85 +0,0 @@
1
- "use client";
2
-
3
- export type { ButtonProps } from "./components/button.js";
4
- export { Button, buttonVariants } from "./components/button.js";
5
- export type { SpinnerProps, SpinnerSize } from "./components/spinner.js";
6
- export { Spinner } from "./components/spinner.js";
7
- export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } from "./components/card.js";
8
- export { Label } from "./components/label.js";
9
- export { Textarea } from "./components/textarea.js";
10
- export { Skeleton } from "./components/skeleton.js";
11
- export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from "./components/dialog.js";
12
- export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } from "./components/popover.js";
13
- export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup } from "./components/dropdown-menu.js";
14
- export { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem, MenubarSeparator, MenubarLabel, MenubarCheckboxItem, MenubarRadioGroup, MenubarRadioItem, MenubarPortal, MenubarSubContent, MenubarSubTrigger, MenubarGroup, MenubarSub, MenubarShortcut } from "./components/menubar.js";
15
- export { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel } from "./components/alert-dialog.js";
16
- export { Alert, AlertTitle, AlertDescription } from "./components/alert.js";
17
- export type { ToastProps, ToastActionElement } from "./components/toast.js";
18
- export { ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction } from "./components/toast.js";
19
- export { Toaster } from "./components/toaster.js";
20
- export type { ToastVariant, ToasterToast } from "./hooks/use-toast.js";
21
- export { toast, useToast } from "./hooks/use-toast.js";
22
- export { Input } from "./components/input.js";
23
- export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton } from "./components/select.js";
24
- export { Tabs, TabsList, TabsTrigger, TabsContent } from "./components/tabs.js";
25
- export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from "./components/tooltip.js";
26
- export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField } from "./components/form.js";
27
- export { Checkbox } from "./components/checkbox.js";
28
- export { Switch } from "./components/switch.js";
29
- export type { BadgeProps } from "./components/badge.js";
30
- export { Badge, badgeVariants } from "./components/badge.js";
31
- export { Avatar, AvatarImage, AvatarFallback } from "./components/avatar.js";
32
- export { Separator } from "./components/separator.js";
33
- export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "./components/accordion.js";
34
- export { RadioGroup, RadioGroupItem } from "./components/radio-group.js";
35
- export { Progress } from "./components/progress.js";
36
- export { Slider } from "./components/slider.js";
37
- export type { DatePickerProps } from "./components/date-picker.js";
38
- export { DatePicker } from "./components/date-picker.js";
39
- export { ScrollArea, ScrollBar } from "./components/scroll-area.js";
40
- export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption } from "./components/table.js";
41
- export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } from "./components/sheet.js";
42
- export { Collapsible, CollapsibleTrigger, CollapsibleContent } from "./components/collapsible.js";
43
- export type { SearchDropdownProps } from "./components/search-dropdown.js";
44
- export { SearchDropdown } from "./components/search-dropdown.js";
45
-
46
- export type { ConsequenceDialogProps, ConsequenceDialogReason, ConsequenceDialogTypedConfirmation } from "./components/consequence-dialog.js";
47
- export { ConsequenceDialog } from "./components/consequence-dialog.js";
48
-
49
- export type { MaxWidthOption, ScreenAdaptiveViewProps } from "./components/layout/screen-adaptive-view.js";
50
- export { ScreenAdaptiveView } from "./components/layout/screen-adaptive-view.js";
51
-
52
- export { useMediaQuery } from "./hooks/use-media-query.js";
53
-
54
- export { useAsyncAction } from "./hooks/use-async-action.js";
55
- export type { AsyncAction, UseAsyncActionOptions } from "./hooks/use-async-action.js";
56
-
57
- // Shared building blocks
58
- export { RelativeTime } from "./components/relative-time.js";
59
- export type { RelativeTimeProps } from "./components/relative-time.js";
60
-
61
- export { EndOfListIndicator } from "./components/end-of-list-indicator.js";
62
- export type { EndOfListIndicatorProps } from "./components/end-of-list-indicator.js";
63
-
64
- export { ScrollToTopButton } from "./components/scroll-to-top-button.js";
65
- export type { ScrollToTopButtonProps } from "./components/scroll-to-top-button.js";
66
-
67
- export { ChunkErrorRecovery } from "./components/chunk-error-recovery.js";
68
- export type { ChunkErrorRecoveryProps } from "./components/chunk-error-recovery.js";
69
-
70
- // List pagination — the one Previous / counter / Next control plus the page-state
71
- // hooks for its two flavors (known total, and cursor/`hasMore`).
72
- export { ListPagination } from "./components/list-pagination.js";
73
- export type { ListPaginationProps } from "./components/list-pagination.js";
74
-
75
- export { usePagedList } from "./hooks/use-paged-list.js";
76
- export type { ListPaginationState, PagedList, UsePagedListOptions } from "./hooks/use-paged-list.js";
77
-
78
- export { useCursorPage } from "./hooks/use-cursor-page.js";
79
- export type { CursorPage, CursorPager, UseCursorPageOptions } from "./hooks/use-cursor-page.js";
80
-
81
- // Return-scroll: restore a list's window offset when the user comes back to it after a
82
- // route change (save on leave, restore once on an eligible + ready return, keyed by the
83
- // caller's canonical list URL).
84
- export { useReturnScroll, saveReturnScroll, clearReturnScroll, readReturnScroll } from "./hooks/use-return-scroll.js";
85
- export type { ReturnScrollEntry, UseReturnScrollOptions, UseReturnScrollResult } from "./hooks/use-return-scroll.js";