@gerege-systems/ui 0.11.2
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/LICENSE +21 -0
- package/README.md +234 -0
- package/dist-lib/__tests__/helpers/color.d.ts +9 -0
- package/dist-lib/__tests__/helpers/theme.d.ts +18 -0
- package/dist-lib/components/ui/Accordion.d.ts +21 -0
- package/dist-lib/components/ui/Accordion.js +71 -0
- package/dist-lib/components/ui/Accordion.js.map +1 -0
- package/dist-lib/components/ui/Alert.d.ts +51 -0
- package/dist-lib/components/ui/Alert.js +79 -0
- package/dist-lib/components/ui/Alert.js.map +1 -0
- package/dist-lib/components/ui/Avatar.d.ts +52 -0
- package/dist-lib/components/ui/Avatar.js +94 -0
- package/dist-lib/components/ui/Avatar.js.map +1 -0
- package/dist-lib/components/ui/Badge.d.ts +26 -0
- package/dist-lib/components/ui/Badge.js +76 -0
- package/dist-lib/components/ui/Badge.js.map +1 -0
- package/dist-lib/components/ui/Breadcrumbs.d.ts +33 -0
- package/dist-lib/components/ui/Breadcrumbs.js +52 -0
- package/dist-lib/components/ui/Breadcrumbs.js.map +1 -0
- package/dist-lib/components/ui/Button.d.ts +52 -0
- package/dist-lib/components/ui/Button.js +131 -0
- package/dist-lib/components/ui/Button.js.map +1 -0
- package/dist-lib/components/ui/Calendar.d.ts +15 -0
- package/dist-lib/components/ui/Calendar.js +139 -0
- package/dist-lib/components/ui/Calendar.js.map +1 -0
- package/dist-lib/components/ui/Card.d.ts +39 -0
- package/dist-lib/components/ui/Card.js +97 -0
- package/dist-lib/components/ui/Card.js.map +1 -0
- package/dist-lib/components/ui/Carousel.d.ts +27 -0
- package/dist-lib/components/ui/Carousel.js +174 -0
- package/dist-lib/components/ui/Carousel.js.map +1 -0
- package/dist-lib/components/ui/Chart.d.ts +87 -0
- package/dist-lib/components/ui/Chart.js +487 -0
- package/dist-lib/components/ui/Chart.js.map +1 -0
- package/dist-lib/components/ui/Checkbox.d.ts +28 -0
- package/dist-lib/components/ui/Checkbox.js +70 -0
- package/dist-lib/components/ui/Checkbox.js.map +1 -0
- package/dist-lib/components/ui/Combobox.d.ts +67 -0
- package/dist-lib/components/ui/Combobox.js +195 -0
- package/dist-lib/components/ui/Combobox.js.map +1 -0
- package/dist-lib/components/ui/CommandPalette.d.ts +112 -0
- package/dist-lib/components/ui/CommandPalette.js +179 -0
- package/dist-lib/components/ui/CommandPalette.js.map +1 -0
- package/dist-lib/components/ui/ContextMenu.d.ts +50 -0
- package/dist-lib/components/ui/ContextMenu.js +165 -0
- package/dist-lib/components/ui/ContextMenu.js.map +1 -0
- package/dist-lib/components/ui/DataGrid.d.ts +79 -0
- package/dist-lib/components/ui/DataGrid.js +99 -0
- package/dist-lib/components/ui/DataGrid.js.map +1 -0
- package/dist-lib/components/ui/DatePicker.d.ts +45 -0
- package/dist-lib/components/ui/DatePicker.js +193 -0
- package/dist-lib/components/ui/DatePicker.js.map +1 -0
- package/dist-lib/components/ui/DesignSystemProvider.d.ts +147 -0
- package/dist-lib/components/ui/DesignSystemProvider.js +135 -0
- package/dist-lib/components/ui/DesignSystemProvider.js.map +1 -0
- package/dist-lib/components/ui/Dialog.d.ts +77 -0
- package/dist-lib/components/ui/Dialog.js +165 -0
- package/dist-lib/components/ui/Dialog.js.map +1 -0
- package/dist-lib/components/ui/Drawer.d.ts +44 -0
- package/dist-lib/components/ui/Drawer.js +132 -0
- package/dist-lib/components/ui/Drawer.js.map +1 -0
- package/dist-lib/components/ui/DropdownMenu.d.ts +47 -0
- package/dist-lib/components/ui/DropdownMenu.js +169 -0
- package/dist-lib/components/ui/DropdownMenu.js.map +1 -0
- package/dist-lib/components/ui/EmptyState.d.ts +48 -0
- package/dist-lib/components/ui/EmptyState.js +46 -0
- package/dist-lib/components/ui/EmptyState.js.map +1 -0
- package/dist-lib/components/ui/ErrorState.d.ts +51 -0
- package/dist-lib/components/ui/ErrorState.js +69 -0
- package/dist-lib/components/ui/ErrorState.js.map +1 -0
- package/dist-lib/components/ui/FileUpload.d.ts +26 -0
- package/dist-lib/components/ui/FileUpload.js +111 -0
- package/dist-lib/components/ui/FileUpload.js.map +1 -0
- package/dist-lib/components/ui/Form.d.ts +36 -0
- package/dist-lib/components/ui/Form.js +131 -0
- package/dist-lib/components/ui/Form.js.map +1 -0
- package/dist-lib/components/ui/Icon.d.ts +34 -0
- package/dist-lib/components/ui/Icon.js +32 -0
- package/dist-lib/components/ui/Icon.js.map +1 -0
- package/dist-lib/components/ui/IconButton.d.ts +34 -0
- package/dist-lib/components/ui/IconButton.js +64 -0
- package/dist-lib/components/ui/IconButton.js.map +1 -0
- package/dist-lib/components/ui/Input.d.ts +54 -0
- package/dist-lib/components/ui/Input.js +158 -0
- package/dist-lib/components/ui/Input.js.map +1 -0
- package/dist-lib/components/ui/Kbd.d.ts +16 -0
- package/dist-lib/components/ui/Kbd.js +24 -0
- package/dist-lib/components/ui/Kbd.js.map +1 -0
- package/dist-lib/components/ui/MultiSelect.d.ts +64 -0
- package/dist-lib/components/ui/MultiSelect.js +210 -0
- package/dist-lib/components/ui/MultiSelect.js.map +1 -0
- package/dist-lib/components/ui/Pagination.d.ts +55 -0
- package/dist-lib/components/ui/Pagination.js +153 -0
- package/dist-lib/components/ui/Pagination.js.map +1 -0
- package/dist-lib/components/ui/Popover.d.ts +16 -0
- package/dist-lib/components/ui/Popover.js +35 -0
- package/dist-lib/components/ui/Popover.js.map +1 -0
- package/dist-lib/components/ui/Progress.d.ts +40 -0
- package/dist-lib/components/ui/Progress.js +100 -0
- package/dist-lib/components/ui/Progress.js.map +1 -0
- package/dist-lib/components/ui/RadioGroup.d.ts +27 -0
- package/dist-lib/components/ui/RadioGroup.js +81 -0
- package/dist-lib/components/ui/RadioGroup.js.map +1 -0
- package/dist-lib/components/ui/RelativeTime.d.ts +19 -0
- package/dist-lib/components/ui/RelativeTime.js +31 -0
- package/dist-lib/components/ui/RelativeTime.js.map +1 -0
- package/dist-lib/components/ui/ScrollArea.d.ts +21 -0
- package/dist-lib/components/ui/ScrollArea.js +60 -0
- package/dist-lib/components/ui/ScrollArea.js.map +1 -0
- package/dist-lib/components/ui/Select.d.ts +61 -0
- package/dist-lib/components/ui/Select.js +139 -0
- package/dist-lib/components/ui/Select.js.map +1 -0
- package/dist-lib/components/ui/Separator.d.ts +13 -0
- package/dist-lib/components/ui/Separator.js +26 -0
- package/dist-lib/components/ui/Separator.js.map +1 -0
- package/dist-lib/components/ui/Sheet.d.ts +55 -0
- package/dist-lib/components/ui/Sheet.js +136 -0
- package/dist-lib/components/ui/Sheet.js.map +1 -0
- package/dist-lib/components/ui/Sidebar.d.ts +91 -0
- package/dist-lib/components/ui/Sidebar.js +212 -0
- package/dist-lib/components/ui/Sidebar.js.map +1 -0
- package/dist-lib/components/ui/Skeleton.d.ts +37 -0
- package/dist-lib/components/ui/Skeleton.js +29 -0
- package/dist-lib/components/ui/Skeleton.js.map +1 -0
- package/dist-lib/components/ui/Slider.d.ts +33 -0
- package/dist-lib/components/ui/Slider.js +64 -0
- package/dist-lib/components/ui/Slider.js.map +1 -0
- package/dist-lib/components/ui/Snackbar.d.ts +21 -0
- package/dist-lib/components/ui/Snackbar.js +57 -0
- package/dist-lib/components/ui/Snackbar.js.map +1 -0
- package/dist-lib/components/ui/Spinner.d.ts +23 -0
- package/dist-lib/components/ui/Spinner.js +54 -0
- package/dist-lib/components/ui/Spinner.js.map +1 -0
- package/dist-lib/components/ui/Stepper.d.ts +40 -0
- package/dist-lib/components/ui/Stepper.js +111 -0
- package/dist-lib/components/ui/Stepper.js.map +1 -0
- package/dist-lib/components/ui/Switch.d.ts +33 -0
- package/dist-lib/components/ui/Switch.js +80 -0
- package/dist-lib/components/ui/Switch.js.map +1 -0
- package/dist-lib/components/ui/Table.d.ts +71 -0
- package/dist-lib/components/ui/Table.js +179 -0
- package/dist-lib/components/ui/Table.js.map +1 -0
- package/dist-lib/components/ui/Tabs.d.ts +32 -0
- package/dist-lib/components/ui/Tabs.js +91 -0
- package/dist-lib/components/ui/Tabs.js.map +1 -0
- package/dist-lib/components/ui/TagInput.d.ts +39 -0
- package/dist-lib/components/ui/TagInput.js +111 -0
- package/dist-lib/components/ui/TagInput.js.map +1 -0
- package/dist-lib/components/ui/Textarea.d.ts +34 -0
- package/dist-lib/components/ui/Textarea.js +88 -0
- package/dist-lib/components/ui/Textarea.js.map +1 -0
- package/dist-lib/components/ui/Timeline.d.ts +33 -0
- package/dist-lib/components/ui/Timeline.js +56 -0
- package/dist-lib/components/ui/Timeline.js.map +1 -0
- package/dist-lib/components/ui/Toast.d.ts +53 -0
- package/dist-lib/components/ui/Toast.js +158 -0
- package/dist-lib/components/ui/Toast.js.map +1 -0
- package/dist-lib/components/ui/Tooltip.d.ts +38 -0
- package/dist-lib/components/ui/Tooltip.js +60 -0
- package/dist-lib/components/ui/Tooltip.js.map +1 -0
- package/dist-lib/components/ui/TopNav.d.ts +37 -0
- package/dist-lib/components/ui/TopNav.js +69 -0
- package/dist-lib/components/ui/TopNav.js.map +1 -0
- package/dist-lib/components/ui/Tree.d.ts +51 -0
- package/dist-lib/components/ui/Tree.js +170 -0
- package/dist-lib/components/ui/Tree.js.map +1 -0
- package/dist-lib/hooks/use-debounce.d.ts +19 -0
- package/dist-lib/hooks/use-debounce.js +36 -0
- package/dist-lib/hooks/use-debounce.js.map +1 -0
- package/dist-lib/hooks/use-delayed-loading.d.ts +21 -0
- package/dist-lib/hooks/use-delayed-loading.js +20 -0
- package/dist-lib/hooks/use-delayed-loading.js.map +1 -0
- package/dist-lib/hooks/use-field-ids.d.ts +30 -0
- package/dist-lib/hooks/use-field-ids.js +18 -0
- package/dist-lib/hooks/use-field-ids.js.map +1 -0
- package/dist-lib/hooks/use-media-query.d.ts +11 -0
- package/dist-lib/hooks/use-media-query.js +22 -0
- package/dist-lib/hooks/use-media-query.js.map +1 -0
- package/dist-lib/hooks/use-modifier-key.d.ts +13 -0
- package/dist-lib/hooks/use-modifier-key.js +19 -0
- package/dist-lib/hooks/use-modifier-key.js.map +1 -0
- package/dist-lib/hooks/use-strings.d.ts +5 -0
- package/dist-lib/hooks/use-strings.js +12 -0
- package/dist-lib/hooks/use-strings.js.map +1 -0
- package/dist-lib/hooks/use-toast.d.ts +48 -0
- package/dist-lib/hooks/use-toast.js +67 -0
- package/dist-lib/hooks/use-toast.js.map +1 -0
- package/dist-lib/icon.d.ts +8 -0
- package/dist-lib/icon.js +7 -0
- package/dist-lib/icon.js.map +1 -0
- package/dist-lib/icons/index.d.ts +80 -0
- package/dist-lib/icons/index.js +163 -0
- package/dist-lib/icons/index.js.map +1 -0
- package/dist-lib/illustrations/index.d.ts +10 -0
- package/dist-lib/illustrations/index.js +114 -0
- package/dist-lib/illustrations/index.js.map +1 -0
- package/dist-lib/index.d.ts +68 -0
- package/dist-lib/index.js +291 -0
- package/dist-lib/index.js.map +1 -0
- package/dist-lib/lib/cva.d.ts +6 -0
- package/dist-lib/lib/format.d.ts +65 -0
- package/dist-lib/lib/format.js +95 -0
- package/dist-lib/lib/format.js.map +1 -0
- package/dist-lib/lib/return-focus.d.ts +11 -0
- package/dist-lib/lib/return-focus.js +12 -0
- package/dist-lib/lib/return-focus.js.map +1 -0
- package/dist-lib/lib/strings.d.ts +190 -0
- package/dist-lib/lib/strings.js +141 -0
- package/dist-lib/lib/strings.js.map +1 -0
- package/dist-lib/lib/strings.mn.d.ts +3 -0
- package/dist-lib/lib/strings.mn.js +115 -0
- package/dist-lib/lib/strings.mn.js.map +1 -0
- package/dist-lib/lib/utils.d.ts +11 -0
- package/dist-lib/lib/utils.js +10 -0
- package/dist-lib/lib/utils.js.map +1 -0
- package/dist-lib/styles.css +1 -0
- package/dist-lib/theme.css +840 -0
- package/package.json +158 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
3
|
+
/**
|
|
4
|
+
* Wrap the app in a single `<TooltipProvider>` near the root. All Tooltips
|
|
5
|
+
* share the same `delayDuration` and `skipDelayDuration`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TooltipProvider: {
|
|
8
|
+
({ delayDuration, skipDelayDuration, ...props }: ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
displayName: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const TooltipRoot: import('react').FC<TooltipPrimitive.TooltipProps>;
|
|
12
|
+
export declare const TooltipTrigger: import('react').ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
13
|
+
export declare const TooltipContent: import('react').ForwardRefExoticComponent<Omit<TooltipPrimitive.TooltipContentProps & import('react').RefAttributes<HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
14
|
+
export interface TooltipProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
label: ReactNode;
|
|
17
|
+
side?: 'top' | 'right' | 'bottom' | 'left';
|
|
18
|
+
align?: 'start' | 'center' | 'end';
|
|
19
|
+
/** Disable showing the tooltip (e.g. when text is not truncated). */
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
/** Override the global delay. */
|
|
22
|
+
delayDuration?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Shorthand for the most common Tooltip case.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <Tooltip label="Copy link"><IconButton aria-label="Copy" icon={<Copy/>} /></Tooltip>
|
|
29
|
+
*
|
|
30
|
+
* @do Keep tooltip text short — one line, no full sentences. Use a Popover
|
|
31
|
+
* for any content that needs structure.
|
|
32
|
+
* @dont Wrap disabled buttons in Tooltip without `asChild + tabIndex={0}`.
|
|
33
|
+
* Disabled elements don't receive focus, so the tooltip never opens.
|
|
34
|
+
*/
|
|
35
|
+
export declare function Tooltip({ children, label, side, align, disabled, delayDuration, }: TooltipProps): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
export declare namespace Tooltip {
|
|
37
|
+
var displayName: string;
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as o, Fragment as p, jsxs as l } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as m } from "react";
|
|
4
|
+
import * as a from "@radix-ui/react-tooltip";
|
|
5
|
+
import { cn as c } from "../../lib/utils.js";
|
|
6
|
+
const f = ({
|
|
7
|
+
delayDuration: t = 500,
|
|
8
|
+
skipDelayDuration: e = 200,
|
|
9
|
+
...i
|
|
10
|
+
}) => /* @__PURE__ */ o(
|
|
11
|
+
a.Provider,
|
|
12
|
+
{
|
|
13
|
+
delayDuration: t,
|
|
14
|
+
skipDelayDuration: e,
|
|
15
|
+
...i
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
f.displayName = "TooltipProvider";
|
|
19
|
+
const u = a.Root, T = a.Trigger, d = m(function({ className: e, sideOffset: i = 4, ...r }, n) {
|
|
20
|
+
return /* @__PURE__ */ o(a.Portal, { children: /* @__PURE__ */ o(
|
|
21
|
+
a.Content,
|
|
22
|
+
{
|
|
23
|
+
ref: n,
|
|
24
|
+
sideOffset: i,
|
|
25
|
+
className: c(
|
|
26
|
+
"z-[var(--z-tooltip)] overflow-hidden rounded-md px-2 py-1 text-xs font-medium",
|
|
27
|
+
"bg-tooltip text-tooltip-foreground",
|
|
28
|
+
"max-w-xs shadow-sm",
|
|
29
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out",
|
|
30
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
31
|
+
"data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
32
|
+
e
|
|
33
|
+
),
|
|
34
|
+
...r
|
|
35
|
+
}
|
|
36
|
+
) });
|
|
37
|
+
});
|
|
38
|
+
d.displayName = "TooltipContent";
|
|
39
|
+
function x({
|
|
40
|
+
children: t,
|
|
41
|
+
label: e,
|
|
42
|
+
side: i = "top",
|
|
43
|
+
align: r = "center",
|
|
44
|
+
disabled: n,
|
|
45
|
+
delayDuration: s
|
|
46
|
+
}) {
|
|
47
|
+
return n ? /* @__PURE__ */ o(p, { children: t }) : /* @__PURE__ */ l(u, { delayDuration: s, children: [
|
|
48
|
+
/* @__PURE__ */ o(T, { asChild: !0, children: t }),
|
|
49
|
+
/* @__PURE__ */ o(d, { side: i, align: r, children: e })
|
|
50
|
+
] });
|
|
51
|
+
}
|
|
52
|
+
x.displayName = "Tooltip";
|
|
53
|
+
export {
|
|
54
|
+
x as Tooltip,
|
|
55
|
+
d as TooltipContent,
|
|
56
|
+
f as TooltipProvider,
|
|
57
|
+
u as TooltipRoot,
|
|
58
|
+
T as TooltipTrigger
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=Tooltip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tooltip.js","sources":["../../../src/components/ui/Tooltip.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type ComponentPropsWithoutRef, type ElementRef, type ReactNode } from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\nimport { cn } from '@/lib/utils';\n\n/**\n * Wrap the app in a single `<TooltipProvider>` near the root. All Tooltips\n * share the same `delayDuration` and `skipDelayDuration`.\n */\nexport const TooltipProvider = ({\n delayDuration = 500,\n skipDelayDuration = 200,\n ...props\n}: ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>) => (\n <TooltipPrimitive.Provider\n delayDuration={delayDuration}\n skipDelayDuration={skipDelayDuration}\n {...props}\n />\n);\nTooltipProvider.displayName = 'TooltipProvider';\n\nexport const TooltipRoot = TooltipPrimitive.Root;\nexport const TooltipTrigger = TooltipPrimitive.Trigger;\n\nexport const TooltipContent = forwardRef<\n ElementRef<typeof TooltipPrimitive.Content>,\n ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>\n>(function TooltipContent({ className, sideOffset = 4, ...props }, ref) {\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n ref={ref}\n sideOffset={sideOffset}\n className={cn(\n 'z-[var(--z-tooltip)] overflow-hidden rounded-md px-2 py-1 text-xs font-medium',\n 'bg-tooltip text-tooltip-foreground',\n 'max-w-xs shadow-sm',\n 'data-[state=open]:animate-in data-[state=closed]:animate-out',\n 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',\n 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',\n className,\n )}\n {...props}\n />\n </TooltipPrimitive.Portal>\n );\n});\nTooltipContent.displayName = 'TooltipContent';\n\nexport interface TooltipProps {\n children: ReactNode;\n label: ReactNode;\n side?: 'top' | 'right' | 'bottom' | 'left';\n align?: 'start' | 'center' | 'end';\n /** Disable showing the tooltip (e.g. when text is not truncated). */\n disabled?: boolean;\n /** Override the global delay. */\n delayDuration?: number;\n}\n\n/**\n * Shorthand for the most common Tooltip case.\n *\n * @example\n * <Tooltip label=\"Copy link\"><IconButton aria-label=\"Copy\" icon={<Copy/>} /></Tooltip>\n *\n * @do Keep tooltip text short — one line, no full sentences. Use a Popover\n * for any content that needs structure.\n * @dont Wrap disabled buttons in Tooltip without `asChild + tabIndex={0}`.\n * Disabled elements don't receive focus, so the tooltip never opens.\n */\nexport function Tooltip({\n children,\n label,\n side = 'top',\n align = 'center',\n disabled,\n delayDuration,\n}: TooltipProps) {\n if (disabled) return <>{children}</>;\n return (\n <TooltipRoot delayDuration={delayDuration}>\n <TooltipTrigger asChild>{children}</TooltipTrigger>\n <TooltipContent side={side} align={align}>\n {label}\n </TooltipContent>\n </TooltipRoot>\n );\n}\nTooltip.displayName = 'Tooltip';\n"],"names":["TooltipProvider","delayDuration","skipDelayDuration","props","jsx","TooltipPrimitive","TooltipRoot","TooltipTrigger","TooltipContent","forwardRef","className","sideOffset","ref","cn","Tooltip","children","label","side","align","disabled","Fragment","jsxs"],"mappings":";;;;;AAUO,MAAMA,IAAkB,CAAC;AAAA,EAC9B,eAAAC,IAAgB;AAAA,EAChB,mBAAAC,IAAoB;AAAA,EACpB,GAAGC;AACL,MACE,gBAAAC;AAAA,EAACC,EAAiB;AAAA,EAAjB;AAAA,IACC,eAAAJ;AAAA,IACA,mBAAAC;AAAA,IACC,GAAGC;AAAA,EAAA;AACN;AAEFH,EAAgB,cAAc;AAEvB,MAAMM,IAAcD,EAAiB,MAC/BE,IAAiBF,EAAiB,SAElCG,IAAiBC,EAG5B,SAAwB,EAAE,WAAAC,GAAW,YAAAC,IAAa,GAAG,GAAGR,EAAA,GAASS,GAAK;AACtE,SACE,gBAAAR,EAACC,EAAiB,QAAjB,EACC,UAAA,gBAAAD;AAAA,IAACC,EAAiB;AAAA,IAAjB;AAAA,MACC,KAAAO;AAAA,MACA,YAAAD;AAAA,MACA,WAAWE;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAH;AAAA,MAAA;AAAA,MAED,GAAGP;AAAA,IAAA;AAAA,EAAA,GAER;AAEJ,CAAC;AACDK,EAAe,cAAc;AAwBtB,SAASM,EAAQ;AAAA,EACtB,UAAAC;AAAA,EACA,OAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,OAAAC,IAAQ;AAAA,EACR,UAAAC;AAAA,EACA,eAAAlB;AACF,GAAiB;AACf,SAAIkB,IAAiB,gBAAAf,EAAAgB,GAAA,EAAG,UAAAL,EAAA,CAAS,IAE/B,gBAAAM,EAACf,KAAY,eAAAL,GACX,UAAA;AAAA,IAAA,gBAAAG,EAACG,GAAA,EAAe,SAAO,IAAE,UAAAQ,EAAA,CAAS;AAAA,IAClC,gBAAAX,EAACI,GAAA,EAAe,MAAAS,GAAY,OAAAC,GACzB,UAAAF,EAAA,CACH;AAAA,EAAA,GACF;AAEJ;AACAF,EAAQ,cAAc;"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AnchorHTMLAttributes, HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface TopNavProps extends HTMLAttributes<HTMLElement> {
|
|
3
|
+
/** Logo / wordmark slot. Shown on the far left. */
|
|
4
|
+
logo: ReactNode;
|
|
5
|
+
/** Primary navigation links — typically a list of `<TopNavLink>`. */
|
|
6
|
+
nav?: ReactNode;
|
|
7
|
+
/** Search / command bar slot — fills the centre on wide screens. */
|
|
8
|
+
search?: ReactNode;
|
|
9
|
+
/** Right-aligned cluster — notifications, theme toggle, user menu. */
|
|
10
|
+
actions?: ReactNode;
|
|
11
|
+
/** Accessible name of the inner `<nav>` landmark. Default "Primary". */
|
|
12
|
+
navLabel?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* App-level top bar. Layout: `logo · nav · search · actions`. Search and nav
|
|
16
|
+
* are optional.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <TopNav
|
|
20
|
+
* logo={<Logo />}
|
|
21
|
+
* nav={<><TopNavLink href="/" active>Home</TopNavLink>…</>}
|
|
22
|
+
* search={<Input type="search" placeholder="Search…" />}
|
|
23
|
+
* actions={<><Bell /><Avatar /></>}
|
|
24
|
+
* />
|
|
25
|
+
*
|
|
26
|
+
* @do Pick one primary nav style — links here or in the Sidebar, not both.
|
|
27
|
+
* @dont Stack two rows of navigation in the TopNav. If you need tabs as well,
|
|
28
|
+
* put them below the bar inside the page content.
|
|
29
|
+
*/
|
|
30
|
+
export declare const TopNav: import('react').ForwardRefExoticComponent<TopNavProps & import('react').RefAttributes<HTMLElement>>;
|
|
31
|
+
export interface TopNavLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
32
|
+
href?: string;
|
|
33
|
+
active?: boolean;
|
|
34
|
+
/** Render the child (e.g. router `<Link>`) instead of an `<a>`. */
|
|
35
|
+
asChild?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export declare const TopNavLink: import('react').ForwardRefExoticComponent<TopNavLinkProps & import('react').RefAttributes<HTMLAnchorElement>>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as l, jsx as o } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as d } from "react";
|
|
4
|
+
import { Slot as p } from "@radix-ui/react-slot";
|
|
5
|
+
import { useStrings as g } from "../../hooks/use-strings.js";
|
|
6
|
+
import { cn as f } from "../../lib/utils.js";
|
|
7
|
+
const x = d(function({ logo: n, nav: e, search: r, actions: i, navLabel: t, className: a, ...s }, m) {
|
|
8
|
+
const u = g();
|
|
9
|
+
return /* @__PURE__ */ l(
|
|
10
|
+
"header",
|
|
11
|
+
{
|
|
12
|
+
ref: m,
|
|
13
|
+
className: f(
|
|
14
|
+
"sticky top-0 z-[var(--z-sticky)] flex h-14 w-full items-center gap-4",
|
|
15
|
+
// Fixed three-column track from lg up: the centre slot keeps its
|
|
16
|
+
// position however wide the logo slot grows, so navigating between
|
|
17
|
+
// pages (breadcrumbs of different lengths) never shifts the search.
|
|
18
|
+
// Below lg the bar stays flex — there is not enough room for three
|
|
19
|
+
// tracks, and logo slots that vary in width (breadcrumbs) are hidden
|
|
20
|
+
// at those widths anyway.
|
|
21
|
+
r && "lg:grid lg:grid-cols-[minmax(0,1fr)_minmax(0,28rem)_minmax(0,1fr)]",
|
|
22
|
+
"border-border bg-background border-b px-4",
|
|
23
|
+
a
|
|
24
|
+
),
|
|
25
|
+
...s,
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ l("div", { className: "flex min-w-0 items-center gap-6", children: [
|
|
28
|
+
n,
|
|
29
|
+
e && /* @__PURE__ */ o(
|
|
30
|
+
"nav",
|
|
31
|
+
{
|
|
32
|
+
"aria-label": t ?? u.topNav.label,
|
|
33
|
+
className: "hidden items-center gap-1 md:flex",
|
|
34
|
+
children: e
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
] }),
|
|
38
|
+
r && /* @__PURE__ */ o("div", { className: "mx-auto w-full max-w-md flex-1 lg:flex-none", children: r }),
|
|
39
|
+
i && /* @__PURE__ */ o("div", { className: "ml-auto flex shrink-0 items-center gap-2", children: i })
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
x.displayName = "TopNav";
|
|
45
|
+
const v = d(function({ href: n, active: e, asChild: r, className: i, children: t, ...a }, s) {
|
|
46
|
+
return /* @__PURE__ */ o(
|
|
47
|
+
r ? p : "a",
|
|
48
|
+
{
|
|
49
|
+
ref: s,
|
|
50
|
+
href: n,
|
|
51
|
+
"aria-current": e ? "page" : void 0,
|
|
52
|
+
className: f(
|
|
53
|
+
"inline-flex h-9 items-center rounded-md px-3 text-sm font-medium outline-none",
|
|
54
|
+
"transition-colors duration-[var(--duration-fast)]",
|
|
55
|
+
e ? "text-foreground" : "text-foreground-muted hover:text-foreground",
|
|
56
|
+
"focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
57
|
+
i
|
|
58
|
+
),
|
|
59
|
+
...a,
|
|
60
|
+
children: t
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
v.displayName = "TopNavLink";
|
|
65
|
+
export {
|
|
66
|
+
x as TopNav,
|
|
67
|
+
v as TopNavLink
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=TopNav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TopNav.js","sources":["../../../src/components/ui/TopNav.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type AnchorHTMLAttributes, type HTMLAttributes, type ReactNode } from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { useStrings } from '@/hooks/use-strings';\nimport { cn } from '@/lib/utils';\n\nexport interface TopNavProps extends HTMLAttributes<HTMLElement> {\n /** Logo / wordmark slot. Shown on the far left. */\n logo: ReactNode;\n /** Primary navigation links — typically a list of `<TopNavLink>`. */\n nav?: ReactNode;\n /** Search / command bar slot — fills the centre on wide screens. */\n search?: ReactNode;\n /** Right-aligned cluster — notifications, theme toggle, user menu. */\n actions?: ReactNode;\n /** Accessible name of the inner `<nav>` landmark. Default \"Primary\". */\n navLabel?: string;\n}\n\n/**\n * App-level top bar. Layout: `logo · nav · search · actions`. Search and nav\n * are optional.\n *\n * @example\n * <TopNav\n * logo={<Logo />}\n * nav={<><TopNavLink href=\"/\" active>Home</TopNavLink>…</>}\n * search={<Input type=\"search\" placeholder=\"Search…\" />}\n * actions={<><Bell /><Avatar /></>}\n * />\n *\n * @do Pick one primary nav style — links here or in the Sidebar, not both.\n * @dont Stack two rows of navigation in the TopNav. If you need tabs as well,\n * put them below the bar inside the page content.\n */\nexport const TopNav = forwardRef<HTMLElement, TopNavProps>(function TopNav(\n { logo, nav, search, actions, navLabel, className, ...props },\n ref,\n) {\n const strings = useStrings();\n return (\n <header\n ref={ref}\n className={cn(\n 'sticky top-0 z-[var(--z-sticky)] flex h-14 w-full items-center gap-4',\n // Fixed three-column track from lg up: the centre slot keeps its\n // position however wide the logo slot grows, so navigating between\n // pages (breadcrumbs of different lengths) never shifts the search.\n // Below lg the bar stays flex — there is not enough room for three\n // tracks, and logo slots that vary in width (breadcrumbs) are hidden\n // at those widths anyway.\n search && 'lg:grid lg:grid-cols-[minmax(0,1fr)_minmax(0,28rem)_minmax(0,1fr)]',\n 'border-border bg-background border-b px-4',\n className,\n )}\n {...props}\n >\n <div className=\"flex min-w-0 items-center gap-6\">\n {logo}\n {nav && (\n <nav\n aria-label={navLabel ?? strings.topNav.label}\n className=\"hidden items-center gap-1 md:flex\"\n >\n {nav}\n </nav>\n )}\n </div>\n {search && <div className=\"mx-auto w-full max-w-md flex-1 lg:flex-none\">{search}</div>}\n {actions && <div className=\"ml-auto flex shrink-0 items-center gap-2\">{actions}</div>}\n </header>\n );\n});\nTopNav.displayName = 'TopNav';\n\nexport interface TopNavLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n href?: string;\n active?: boolean;\n /** Render the child (e.g. router `<Link>`) instead of an `<a>`. */\n asChild?: boolean;\n}\n\nexport const TopNavLink = forwardRef<HTMLAnchorElement, TopNavLinkProps>(function TopNavLink(\n { href, active, asChild, className, children, ...props },\n ref,\n) {\n const Comp = asChild ? Slot : 'a';\n return (\n <Comp\n ref={ref}\n href={href}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'inline-flex h-9 items-center rounded-md px-3 text-sm font-medium outline-none',\n 'transition-colors duration-[var(--duration-fast)]',\n active ? 'text-foreground' : 'text-foreground-muted hover:text-foreground',\n 'focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2',\n className,\n )}\n {...props}\n >\n {children}\n </Comp>\n );\n});\nTopNavLink.displayName = 'TopNavLink';\n"],"names":["TopNav","forwardRef","logo","nav","search","actions","navLabel","className","props","ref","strings","useStrings","jsxs","cn","jsx","TopNavLink","href","active","asChild","children","Slot"],"mappings":";;;;;;AAoCO,MAAMA,IAASC,EAAqC,SACzD,EAAE,MAAAC,GAAM,KAAAC,GAAK,QAAAC,GAAQ,SAAAC,GAAS,UAAAC,GAAU,WAAAC,GAAW,GAAGC,EAAA,GACtDC,GACA;AACA,QAAMC,IAAUC,EAAA;AAChB,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAH;AAAA,MACA,WAAWI;AAAA,QACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOAT,KAAU;AAAA,QACV;AAAA,QACAG;AAAA,MAAA;AAAA,MAED,GAAGC;AAAA,MAEJ,UAAA;AAAA,QAAA,gBAAAI,EAAC,OAAA,EAAI,WAAU,mCACZ,UAAA;AAAA,UAAAV;AAAA,UACAC,KACC,gBAAAW;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,cAAYR,KAAYI,EAAQ,OAAO;AAAA,cACvC,WAAU;AAAA,cAET,UAAAP;AAAA,YAAA;AAAA,UAAA;AAAA,QACH,GAEJ;AAAA,QACCC,KAAU,gBAAAU,EAAC,OAAA,EAAI,WAAU,+CAA+C,UAAAV,GAAO;AAAA,QAC/EC,KAAW,gBAAAS,EAAC,OAAA,EAAI,WAAU,4CAA4C,UAAAT,EAAA,CAAQ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGrF,CAAC;AACDL,EAAO,cAAc;AASd,MAAMe,IAAad,EAA+C,SACvE,EAAE,MAAAe,GAAM,QAAAC,GAAQ,SAAAC,GAAS,WAAAX,GAAW,UAAAY,GAAU,GAAGX,EAAA,GACjDC,GACA;AAEA,SACE,gBAAAK;AAAA,IAFWI,IAAUE,IAAO;AAAA,IAE3B;AAAA,MACC,KAAAX;AAAA,MACA,MAAAO;AAAA,MACA,gBAAcC,IAAS,SAAS;AAAA,MAChC,WAAWJ;AAAA,QACT;AAAA,QACA;AAAA,QACAI,IAAS,oBAAoB;AAAA,QAC7B;AAAA,QACAV;AAAA,MAAA;AAAA,MAED,GAAGC;AAAA,MAEH,UAAAW;AAAA,IAAA;AAAA,EAAA;AAGP,CAAC;AACDJ,EAAW,cAAc;"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
export interface TreeNode {
|
|
3
|
+
id: string;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
/** If undefined the node is a leaf; if empty array it is an empty folder. */
|
|
6
|
+
children?: TreeNode[];
|
|
7
|
+
/** Optional custom icon (overrides default file/folder icons). */
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
export interface TreeProps extends Omit<HTMLAttributes<HTMLUListElement>, 'onSelect'> {
|
|
11
|
+
/** The root nodes. */
|
|
12
|
+
data: TreeNode[];
|
|
13
|
+
/** Ids to expand by default (uncontrolled). */
|
|
14
|
+
defaultExpanded?: string[];
|
|
15
|
+
/** Controlled set of expanded ids. */
|
|
16
|
+
expanded?: string[];
|
|
17
|
+
/** Called with the full next set of expanded ids. */
|
|
18
|
+
onExpandedChange?: (next: string[]) => void;
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated Use `selected` + `onSelectedChange`. Kept for backward
|
|
21
|
+
* compatibility; ignored when `selected` is present.
|
|
22
|
+
*/
|
|
23
|
+
selectedId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Controlled selected id. The component is controlled whenever this key is
|
|
26
|
+
* present in props (even as `undefined`), mirroring React inputs.
|
|
27
|
+
*/
|
|
28
|
+
selected?: string;
|
|
29
|
+
/** Default selected id (uncontrolled). */
|
|
30
|
+
defaultSelected?: string;
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated Use `onSelectedChange`. Still called on every activation.
|
|
33
|
+
*/
|
|
34
|
+
onSelect?: (id: string) => void;
|
|
35
|
+
/** Called when a node is activated (click / Enter / Space). */
|
|
36
|
+
onSelectedChange?: (id: string) => void;
|
|
37
|
+
/** Accessible name for the tree. */
|
|
38
|
+
'aria-label'?: string;
|
|
39
|
+
'aria-labelledby'?: string;
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Expandable file-tree style list following the WAI-ARIA tree pattern.
|
|
44
|
+
* Single selection, roving tabindex (one tab stop), and full arrow-key
|
|
45
|
+
* navigation: Up/Down move, Right expands or moves into a folder, Left
|
|
46
|
+
* collapses or moves to the parent, Home/End jump, Enter/Space select.
|
|
47
|
+
*
|
|
48
|
+
* Expansion and selection are uncontrolled by default; pass
|
|
49
|
+
* `expanded`/`onExpandedChange` and `selected`/`onSelectedChange` to control.
|
|
50
|
+
*/
|
|
51
|
+
export declare const Tree: import('react').ForwardRefExoticComponent<TreeProps & import('react').RefAttributes<HTMLUListElement>>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as f, jsxs as j } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as _, useState as F, useMemo as K, useRef as ee, useCallback as R } from "react";
|
|
4
|
+
import { ChevronRight as te, FolderOpen as re, Folder as ie, File as se } from "../../icons/index.js";
|
|
5
|
+
import { cn as T } from "../../lib/utils.js";
|
|
6
|
+
import { useStrings as ae } from "../../hooks/use-strings.js";
|
|
7
|
+
const ne = _(function(I, M) {
|
|
8
|
+
var P;
|
|
9
|
+
const {
|
|
10
|
+
data: m,
|
|
11
|
+
defaultExpanded: H = [],
|
|
12
|
+
expanded: u,
|
|
13
|
+
onExpandedChange: h,
|
|
14
|
+
selectedId: U,
|
|
15
|
+
selected: $,
|
|
16
|
+
defaultSelected: q,
|
|
17
|
+
onSelect: b,
|
|
18
|
+
onSelectedChange: g,
|
|
19
|
+
className: B,
|
|
20
|
+
"aria-label": G,
|
|
21
|
+
"aria-labelledby": z,
|
|
22
|
+
...J
|
|
23
|
+
} = I, D = "selected" in I || "selectedId" in I, Q = ae(), [A, V] = F(
|
|
24
|
+
() => new Set(H)
|
|
25
|
+
), c = K(
|
|
26
|
+
() => u ? new Set(u) : A,
|
|
27
|
+
[u, A]
|
|
28
|
+
), [W, X] = F(q), x = D ? $ ?? U : W, [N, y] = F(void 0), S = ee(/* @__PURE__ */ new Map()), C = R(
|
|
29
|
+
(e) => {
|
|
30
|
+
u === void 0 && V(e), h == null || h([...e]);
|
|
31
|
+
},
|
|
32
|
+
[u, h]
|
|
33
|
+
), v = R(
|
|
34
|
+
(e, t) => {
|
|
35
|
+
if (c.has(e) === t) return;
|
|
36
|
+
const r = new Set(c);
|
|
37
|
+
t ? r.add(e) : r.delete(e), C(r);
|
|
38
|
+
},
|
|
39
|
+
[c, C]
|
|
40
|
+
), L = R(
|
|
41
|
+
(e) => {
|
|
42
|
+
D || X(e), b == null || b(e), g == null || g(e);
|
|
43
|
+
},
|
|
44
|
+
[b, g, D]
|
|
45
|
+
), n = K(() => {
|
|
46
|
+
const e = [], t = (r, l, d) => {
|
|
47
|
+
r.forEach((s, a) => {
|
|
48
|
+
e.push({ node: s, level: l, setSize: r.length, posInSet: a + 1, parentId: d }), s.children && c.has(s.id) && t(s.children, l + 1, s.id);
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
return t(m, 1, null), e;
|
|
52
|
+
}, [m, c]), Y = N && n.some((e) => e.node.id === N) && N || x && n.some((e) => e.node.id === x) && x || ((P = n[0]) == null ? void 0 : P.node.id), o = (e) => {
|
|
53
|
+
var t;
|
|
54
|
+
e && (y(e), (t = S.current.get(e)) == null || t.focus());
|
|
55
|
+
}, Z = (e, t) => {
|
|
56
|
+
var a, p, k, w, i;
|
|
57
|
+
const { node: r } = t, l = !!r.children, d = c.has(r.id), s = n.findIndex((E) => E.node.id === r.id);
|
|
58
|
+
switch (e.key) {
|
|
59
|
+
case "ArrowDown":
|
|
60
|
+
e.preventDefault(), o((a = n[s + 1]) == null ? void 0 : a.node.id);
|
|
61
|
+
break;
|
|
62
|
+
case "ArrowUp":
|
|
63
|
+
e.preventDefault(), o((p = n[s - 1]) == null ? void 0 : p.node.id);
|
|
64
|
+
break;
|
|
65
|
+
case "ArrowRight":
|
|
66
|
+
e.preventDefault(), l && !d ? v(r.id, !0) : l && d && o((k = r.children[0]) == null ? void 0 : k.id);
|
|
67
|
+
break;
|
|
68
|
+
case "ArrowLeft":
|
|
69
|
+
e.preventDefault(), l && d ? v(r.id, !1) : t.parentId && o(t.parentId);
|
|
70
|
+
break;
|
|
71
|
+
case "Home":
|
|
72
|
+
e.preventDefault(), o((w = n[0]) == null ? void 0 : w.node.id);
|
|
73
|
+
break;
|
|
74
|
+
case "End":
|
|
75
|
+
e.preventDefault(), o((i = n[n.length - 1]) == null ? void 0 : i.node.id);
|
|
76
|
+
break;
|
|
77
|
+
case "Enter":
|
|
78
|
+
case " ":
|
|
79
|
+
e.preventDefault(), L(r.id), l && v(r.id, !d);
|
|
80
|
+
break;
|
|
81
|
+
default:
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
e.stopPropagation();
|
|
85
|
+
};
|
|
86
|
+
function O(e, t, r, l, d) {
|
|
87
|
+
const s = !!e.children, a = c.has(e.id), p = x === e.id, k = s ? a ? re : ie : se, w = { node: e, parentId: d };
|
|
88
|
+
return /* @__PURE__ */ j(
|
|
89
|
+
"li",
|
|
90
|
+
{
|
|
91
|
+
ref: (i) => {
|
|
92
|
+
i ? S.current.set(e.id, i) : S.current.delete(e.id);
|
|
93
|
+
},
|
|
94
|
+
role: "treeitem",
|
|
95
|
+
"aria-expanded": s ? a : void 0,
|
|
96
|
+
"aria-selected": p,
|
|
97
|
+
"aria-level": t,
|
|
98
|
+
"aria-setsize": r,
|
|
99
|
+
"aria-posinset": l,
|
|
100
|
+
tabIndex: Y === e.id ? 0 : -1,
|
|
101
|
+
onFocus: (i) => {
|
|
102
|
+
i.target === i.currentTarget && y(e.id);
|
|
103
|
+
},
|
|
104
|
+
onKeyDown: (i) => {
|
|
105
|
+
i.target === i.currentTarget && Z(i, w);
|
|
106
|
+
},
|
|
107
|
+
onClick: (i) => {
|
|
108
|
+
i.stopPropagation(), y(e.id), L(e.id), s && v(e.id, !a);
|
|
109
|
+
},
|
|
110
|
+
className: "outline-none",
|
|
111
|
+
children: [
|
|
112
|
+
/* @__PURE__ */ j(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
style: { paddingLeft: `${t - 1 + 0.5}rem` },
|
|
116
|
+
className: T(
|
|
117
|
+
"flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-2 text-left text-sm",
|
|
118
|
+
"hover:bg-background-muted",
|
|
119
|
+
"[li:focus-visible>&]:ring-ring [li:focus-visible>&]:ring-offset-background [li:focus-visible>&]:ring-2 [li:focus-visible>&]:ring-offset-1",
|
|
120
|
+
p && "bg-accent-soft text-foreground"
|
|
121
|
+
),
|
|
122
|
+
children: [
|
|
123
|
+
s ? /* @__PURE__ */ f(
|
|
124
|
+
te,
|
|
125
|
+
{
|
|
126
|
+
className: T(
|
|
127
|
+
"text-foreground-subtle size-3.5 shrink-0 transition-transform",
|
|
128
|
+
a && "rotate-90"
|
|
129
|
+
),
|
|
130
|
+
"aria-hidden": !0
|
|
131
|
+
}
|
|
132
|
+
) : /* @__PURE__ */ f("span", { className: "w-3.5 shrink-0", "aria-hidden": !0 }),
|
|
133
|
+
e.icon ?? /* @__PURE__ */ f(k, { className: "text-foreground-muted size-4 shrink-0", "aria-hidden": !0 }),
|
|
134
|
+
/* @__PURE__ */ f(
|
|
135
|
+
"span",
|
|
136
|
+
{
|
|
137
|
+
className: "truncate",
|
|
138
|
+
title: typeof e.label == "string" ? e.label : void 0,
|
|
139
|
+
children: e.label
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
s && a && /* @__PURE__ */ f("ul", { role: "group", className: "mt-0.5", children: e.children.map(
|
|
146
|
+
(i, E) => O(i, t + 1, e.children.length, E + 1, e.id)
|
|
147
|
+
) })
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
e.id
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return /* @__PURE__ */ f(
|
|
154
|
+
"ul",
|
|
155
|
+
{
|
|
156
|
+
ref: M,
|
|
157
|
+
role: "tree",
|
|
158
|
+
"aria-label": G ?? (z ? void 0 : Q.tree.label),
|
|
159
|
+
"aria-labelledby": z,
|
|
160
|
+
className: T("flex flex-col gap-0.5", B),
|
|
161
|
+
...J,
|
|
162
|
+
children: m.map((e, t) => O(e, 1, m.length, t + 1, null))
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
ne.displayName = "Tree";
|
|
167
|
+
export {
|
|
168
|
+
ne as Tree
|
|
169
|
+
};
|
|
170
|
+
//# sourceMappingURL=Tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tree.js","sources":["../../../src/components/ui/Tree.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useCallback,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from 'react';\nimport { ChevronRight, File as FileIcon, Folder, FolderOpen } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\n\nexport interface TreeNode {\n id: string;\n label: ReactNode;\n /** If undefined the node is a leaf; if empty array it is an empty folder. */\n children?: TreeNode[];\n /** Optional custom icon (overrides default file/folder icons). */\n icon?: ReactNode;\n}\n\nexport interface TreeProps extends Omit<HTMLAttributes<HTMLUListElement>, 'onSelect'> {\n /** The root nodes. */\n data: TreeNode[];\n /** Ids to expand by default (uncontrolled). */\n defaultExpanded?: string[];\n /** Controlled set of expanded ids. */\n expanded?: string[];\n /** Called with the full next set of expanded ids. */\n onExpandedChange?: (next: string[]) => void;\n /**\n * @deprecated Use `selected` + `onSelectedChange`. Kept for backward\n * compatibility; ignored when `selected` is present.\n */\n selectedId?: string;\n /**\n * Controlled selected id. The component is controlled whenever this key is\n * present in props (even as `undefined`), mirroring React inputs.\n */\n selected?: string;\n /** Default selected id (uncontrolled). */\n defaultSelected?: string;\n /**\n * @deprecated Use `onSelectedChange`. Still called on every activation.\n */\n onSelect?: (id: string) => void;\n /** Called when a node is activated (click / Enter / Space). */\n onSelectedChange?: (id: string) => void;\n /** Accessible name for the tree. */\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n}\n\ninterface FlatNode {\n node: TreeNode;\n level: number;\n setSize: number;\n posInSet: number;\n parentId: string | null;\n}\n\n/**\n * Expandable file-tree style list following the WAI-ARIA tree pattern.\n * Single selection, roving tabindex (one tab stop), and full arrow-key\n * navigation: Up/Down move, Right expands or moves into a folder, Left\n * collapses or moves to the parent, Home/End jump, Enter/Space select.\n *\n * Expansion and selection are uncontrolled by default; pass\n * `expanded`/`onExpandedChange` and `selected`/`onSelectedChange` to control.\n */\nexport const Tree = forwardRef<HTMLUListElement, TreeProps>(function Tree(props, ref) {\n const {\n data,\n defaultExpanded = [],\n expanded: expandedProp,\n onExpandedChange,\n selectedId,\n selected: selectedProp,\n defaultSelected,\n onSelect,\n onSelectedChange,\n className,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...rest\n } = props;\n const isControlled = 'selected' in props || 'selectedId' in props;\n const strings = useStrings();\n const [internalExpanded, setInternalExpanded] = useState<Set<string>>(\n () => new Set(defaultExpanded),\n );\n const expanded = useMemo(\n () => (expandedProp ? new Set(expandedProp) : internalExpanded),\n [expandedProp, internalExpanded],\n );\n\n const [internalSelected, setInternalSelected] = useState<string | undefined>(defaultSelected);\n const selected = isControlled ? (selectedProp ?? selectedId) : internalSelected;\n\n const [focusedId, setFocusedId] = useState<string | undefined>(undefined);\n const itemRefs = useRef(new Map<string, HTMLLIElement>());\n\n const setExpanded = useCallback(\n (next: Set<string>) => {\n if (expandedProp === undefined) setInternalExpanded(next);\n onExpandedChange?.([...next]);\n },\n [expandedProp, onExpandedChange],\n );\n\n const setOpen = useCallback(\n (id: string, open: boolean) => {\n if (expanded.has(id) === open) return;\n const next = new Set(expanded);\n if (open) next.add(id);\n else next.delete(id);\n setExpanded(next);\n },\n [expanded, setExpanded],\n );\n\n const select = useCallback(\n (id: string) => {\n if (!isControlled) setInternalSelected(id);\n onSelect?.(id);\n onSelectedChange?.(id);\n },\n [onSelect, onSelectedChange, isControlled],\n );\n\n // Flatten the *visible* nodes in DOM order — this is what arrow keys walk.\n const visible = useMemo(() => {\n const out: FlatNode[] = [];\n const walk = (nodes: TreeNode[], level: number, parentId: string | null) => {\n nodes.forEach((node, i) => {\n out.push({ node, level, setSize: nodes.length, posInSet: i + 1, parentId });\n if (node.children && expanded.has(node.id)) walk(node.children, level + 1, node.id);\n });\n };\n walk(data, 1, null);\n return out;\n }, [data, expanded]);\n\n // The single tab stop: focused item if still visible, else selected, else first.\n const tabStopId =\n (focusedId && visible.some((v) => v.node.id === focusedId) && focusedId) ||\n (selected && visible.some((v) => v.node.id === selected) && selected) ||\n visible[0]?.node.id;\n\n const focusItem = (id: string | undefined) => {\n if (!id) return;\n setFocusedId(id);\n itemRefs.current.get(id)?.focus();\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLLIElement>, flat: FlatNode) => {\n const { node } = flat;\n const hasChildren = !!node.children;\n const isOpen = expanded.has(node.id);\n const index = visible.findIndex((v) => v.node.id === node.id);\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n focusItem(visible[index + 1]?.node.id);\n break;\n case 'ArrowUp':\n e.preventDefault();\n focusItem(visible[index - 1]?.node.id);\n break;\n case 'ArrowRight':\n e.preventDefault();\n if (hasChildren && !isOpen) setOpen(node.id, true);\n else if (hasChildren && isOpen) focusItem(node.children![0]?.id);\n break;\n case 'ArrowLeft':\n e.preventDefault();\n if (hasChildren && isOpen) setOpen(node.id, false);\n else if (flat.parentId) focusItem(flat.parentId);\n break;\n case 'Home':\n e.preventDefault();\n focusItem(visible[0]?.node.id);\n break;\n case 'End':\n e.preventDefault();\n focusItem(visible[visible.length - 1]?.node.id);\n break;\n case 'Enter':\n case ' ':\n e.preventDefault();\n select(node.id);\n if (hasChildren) setOpen(node.id, !isOpen);\n break;\n default:\n return;\n }\n e.stopPropagation();\n };\n\n function renderNode(\n node: TreeNode,\n level: number,\n setSize: number,\n posInSet: number,\n parentId: string | null,\n ): ReactNode {\n const hasChildren = !!node.children;\n const isOpen = expanded.has(node.id);\n const isSelected = selected === node.id;\n const DefaultIcon = hasChildren ? (isOpen ? FolderOpen : Folder) : FileIcon;\n const flat: FlatNode = { node, level, setSize, posInSet, parentId };\n\n return (\n <li\n key={node.id}\n ref={(el) => {\n if (el) itemRefs.current.set(node.id, el);\n else itemRefs.current.delete(node.id);\n }}\n role=\"treeitem\"\n aria-expanded={hasChildren ? isOpen : undefined}\n aria-selected={isSelected}\n aria-level={level}\n aria-setsize={setSize}\n aria-posinset={posInSet}\n tabIndex={tabStopId === node.id ? 0 : -1}\n onFocus={(e) => {\n if (e.target === e.currentTarget) setFocusedId(node.id);\n }}\n onKeyDown={(e) => {\n if (e.target === e.currentTarget) handleKeyDown(e, flat);\n }}\n onClick={(e) => {\n e.stopPropagation();\n setFocusedId(node.id);\n select(node.id);\n if (hasChildren) setOpen(node.id, !isOpen);\n }}\n className=\"outline-none\"\n >\n <div\n style={{ paddingLeft: `${level - 1 + 0.5}rem` }}\n className={cn(\n 'flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-2 text-left text-sm',\n 'hover:bg-background-muted',\n '[li:focus-visible>&]:ring-ring [li:focus-visible>&]:ring-offset-background [li:focus-visible>&]:ring-2 [li:focus-visible>&]:ring-offset-1',\n isSelected && 'bg-accent-soft text-foreground',\n )}\n >\n {hasChildren ? (\n <ChevronRight\n className={cn(\n 'text-foreground-subtle size-3.5 shrink-0 transition-transform',\n isOpen && 'rotate-90',\n )}\n aria-hidden\n />\n ) : (\n <span className=\"w-3.5 shrink-0\" aria-hidden />\n )}\n {node.icon ?? (\n <DefaultIcon className=\"text-foreground-muted size-4 shrink-0\" aria-hidden />\n )}\n <span\n className=\"truncate\"\n title={typeof node.label === 'string' ? node.label : undefined}\n >\n {node.label}\n </span>\n </div>\n {hasChildren && isOpen && (\n <ul role=\"group\" className=\"mt-0.5\">\n {node.children!.map((c, i) =>\n renderNode(c, level + 1, node.children!.length, i + 1, node.id),\n )}\n </ul>\n )}\n </li>\n );\n }\n\n return (\n <ul\n ref={ref}\n role=\"tree\"\n aria-label={ariaLabel ?? (ariaLabelledby ? undefined : strings.tree.label)}\n aria-labelledby={ariaLabelledby}\n className={cn('flex flex-col gap-0.5', className)}\n {...rest}\n >\n {data.map((n, i) => renderNode(n, 1, data.length, i + 1, null))}\n </ul>\n );\n});\nTree.displayName = 'Tree';\n"],"names":["Tree","forwardRef","props","ref","data","defaultExpanded","expandedProp","onExpandedChange","selectedId","selectedProp","defaultSelected","onSelect","onSelectedChange","className","ariaLabel","ariaLabelledby","rest","isControlled","strings","useStrings","internalExpanded","setInternalExpanded","useState","expanded","useMemo","internalSelected","setInternalSelected","selected","focusedId","setFocusedId","itemRefs","useRef","setExpanded","useCallback","next","setOpen","id","open","select","visible","out","walk","nodes","level","parentId","node","i","tabStopId","v","_a","focusItem","handleKeyDown","flat","hasChildren","isOpen","index","_b","_c","_d","_e","renderNode","setSize","posInSet","isSelected","DefaultIcon","FolderOpen","Folder","FileIcon","jsxs","el","e","cn","jsx","ChevronRight","c","n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns `value` after it has stopped changing for `delay` ms. Use for
|
|
3
|
+
* search inputs that drive a query (URL or fetch) without firing per keystroke.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const [q, setQ] = useState('');
|
|
7
|
+
* const debouncedQ = useDebounce(q); // 300ms
|
|
8
|
+
* useEffect(() => { fetchRows(debouncedQ); }, [debouncedQ]);
|
|
9
|
+
*/
|
|
10
|
+
export declare function useDebounce<T>(value: T, delay?: number): T;
|
|
11
|
+
/**
|
|
12
|
+
* Stable callback that runs `fn` once `delay` ms have passed since the last
|
|
13
|
+
* call. Always invokes the latest `fn`; pending calls are dropped on unmount.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const save = useDebouncedCallback((draft: string) => api.save(draft), 500);
|
|
17
|
+
* <Textarea onChange={(e) => save(e.target.value)} />
|
|
18
|
+
*/
|
|
19
|
+
export declare function useDebouncedCallback<A extends unknown[]>(fn: (...args: A) => void, delay?: number): (...args: A) => void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState as i, useEffect as c, useRef as o, useCallback as s } from "react";
|
|
3
|
+
function w(t, n = 300) {
|
|
4
|
+
const [r, e] = i(t);
|
|
5
|
+
return c(() => {
|
|
6
|
+
if (n <= 0) {
|
|
7
|
+
e(t);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const u = window.setTimeout(() => e(t), n);
|
|
11
|
+
return () => window.clearTimeout(u);
|
|
12
|
+
}, [t, n]), r;
|
|
13
|
+
}
|
|
14
|
+
function f(t, n = 300) {
|
|
15
|
+
const r = o(t), e = o(null);
|
|
16
|
+
return c(() => {
|
|
17
|
+
r.current = t;
|
|
18
|
+
}, [t]), c(
|
|
19
|
+
() => () => {
|
|
20
|
+
e.current !== null && window.clearTimeout(e.current);
|
|
21
|
+
},
|
|
22
|
+
[]
|
|
23
|
+
), s(
|
|
24
|
+
(...u) => {
|
|
25
|
+
e.current !== null && window.clearTimeout(e.current), e.current = window.setTimeout(() => {
|
|
26
|
+
e.current = null, r.current(...u);
|
|
27
|
+
}, n);
|
|
28
|
+
},
|
|
29
|
+
[n]
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
w as useDebounce,
|
|
34
|
+
f as useDebouncedCallback
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=use-debounce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-debounce.js","sources":["../../src/hooks/use-debounce.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Returns `value` after it has stopped changing for `delay` ms. Use for\n * search inputs that drive a query (URL or fetch) without firing per keystroke.\n *\n * @example\n * const [q, setQ] = useState('');\n * const debouncedQ = useDebounce(q); // 300ms\n * useEffect(() => { fetchRows(debouncedQ); }, [debouncedQ]);\n */\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debounced, setDebounced] = useState(value);\n useEffect(() => {\n if (delay <= 0) {\n setDebounced(value);\n return;\n }\n const t = window.setTimeout(() => setDebounced(value), delay);\n return () => window.clearTimeout(t);\n }, [value, delay]);\n return debounced;\n}\n\n/**\n * Stable callback that runs `fn` once `delay` ms have passed since the last\n * call. Always invokes the latest `fn`; pending calls are dropped on unmount.\n *\n * @example\n * const save = useDebouncedCallback((draft: string) => api.save(draft), 500);\n * <Textarea onChange={(e) => save(e.target.value)} />\n */\nexport function useDebouncedCallback<A extends unknown[]>(\n fn: (...args: A) => void,\n delay = 300,\n): (...args: A) => void {\n const fnRef = useRef(fn);\n const timer = useRef<number | null>(null);\n useEffect(() => {\n fnRef.current = fn;\n }, [fn]);\n useEffect(\n () => () => {\n if (timer.current !== null) window.clearTimeout(timer.current);\n },\n [],\n );\n return useCallback(\n (...args: A) => {\n if (timer.current !== null) window.clearTimeout(timer.current);\n timer.current = window.setTimeout(() => {\n timer.current = null;\n fnRef.current(...args);\n }, delay);\n },\n [delay],\n );\n}\n"],"names":["useDebounce","value","delay","debounced","setDebounced","useState","useEffect","t","useDebouncedCallback","fn","fnRef","useRef","timer","useCallback","args"],"mappings":";;AAaO,SAASA,EAAeC,GAAUC,IAAQ,KAAQ;AACvD,QAAM,CAACC,GAAWC,CAAY,IAAIC,EAASJ,CAAK;AAChD,SAAAK,EAAU,MAAM;AACd,QAAIJ,KAAS,GAAG;AACd,MAAAE,EAAaH,CAAK;AAClB;AAAA,IACF;AACA,UAAMM,IAAI,OAAO,WAAW,MAAMH,EAAaH,CAAK,GAAGC,CAAK;AAC5D,WAAO,MAAM,OAAO,aAAaK,CAAC;AAAA,EACpC,GAAG,CAACN,GAAOC,CAAK,CAAC,GACVC;AACT;AAUO,SAASK,EACdC,GACAP,IAAQ,KACc;AACtB,QAAMQ,IAAQC,EAAOF,CAAE,GACjBG,IAAQD,EAAsB,IAAI;AACxC,SAAAL,EAAU,MAAM;AACd,IAAAI,EAAM,UAAUD;AAAA,EAClB,GAAG,CAACA,CAAE,CAAC,GACPH;AAAA,IACE,MAAM,MAAM;AACV,MAAIM,EAAM,YAAY,QAAM,OAAO,aAAaA,EAAM,OAAO;AAAA,IAC/D;AAAA,IACA,CAAA;AAAA,EAAC,GAEIC;AAAA,IACL,IAAIC,MAAY;AACd,MAAIF,EAAM,YAAY,QAAM,OAAO,aAAaA,EAAM,OAAO,GAC7DA,EAAM,UAAU,OAAO,WAAW,MAAM;AACtC,QAAAA,EAAM,UAAU,MAChBF,EAAM,QAAQ,GAAGI,CAAI;AAAA,MACvB,GAAGZ,CAAK;AAAA,IACV;AAAA,IACA,CAACA,CAAK;AAAA,EAAA;AAEV;"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface DelayedLoadingOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Once `true`, the value holds for at least this many ms even if `ms`
|
|
4
|
+
* changes, so a placeholder that did appear never flickers away. Default 500.
|
|
5
|
+
*/
|
|
6
|
+
minVisible?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Returns `false` until `ms` has elapsed, then `true`. Gate skeletons and
|
|
10
|
+
* spinners behind it so sub-300ms loads never flash a placeholder.
|
|
11
|
+
* `ms <= 0` returns `true` immediately (and synchronously on first render).
|
|
12
|
+
*
|
|
13
|
+
* Once shown, the value stays `true` for `minVisible` ms (default 500). Keep
|
|
14
|
+
* the gated component mounted for that window on the consumer side too —
|
|
15
|
+
* unmounting it early defeats the hold.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const showSkeleton = useDelayedLoading(300);
|
|
19
|
+
* if (isLoading) return showSkeleton ? <Skeleton /> : null;
|
|
20
|
+
*/
|
|
21
|
+
export declare function useDelayedLoading(ms: number, { minVisible }?: DelayedLoadingOptions): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState as f, useRef as i, useEffect as l } from "react";
|
|
3
|
+
function d(t, { minVisible: u = 500 } = {}) {
|
|
4
|
+
const [a, n] = f(t <= 0), e = i(t <= 0 ? Date.now() : null);
|
|
5
|
+
return l(() => {
|
|
6
|
+
const c = () => {
|
|
7
|
+
e.current = Date.now(), n(!0);
|
|
8
|
+
};
|
|
9
|
+
if (t <= 0) {
|
|
10
|
+
e.current ?? (e.current = Date.now()), n(!0);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const r = e.current === null ? 0 : Math.max(0, u - (Date.now() - e.current)), o = [];
|
|
14
|
+
return r > 0 ? o.push(window.setTimeout(() => n(!1), r)) : n(!1), o.push(window.setTimeout(c, r + t)), () => o.forEach((s) => window.clearTimeout(s));
|
|
15
|
+
}, [t, u]), a;
|
|
16
|
+
}
|
|
17
|
+
export {
|
|
18
|
+
d as useDelayedLoading
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=use-delayed-loading.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-delayed-loading.js","sources":["../../src/hooks/use-delayed-loading.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nexport interface DelayedLoadingOptions {\n /**\n * Once `true`, the value holds for at least this many ms even if `ms`\n * changes, so a placeholder that did appear never flickers away. Default 500.\n */\n minVisible?: number;\n}\n\n/**\n * Returns `false` until `ms` has elapsed, then `true`. Gate skeletons and\n * spinners behind it so sub-300ms loads never flash a placeholder.\n * `ms <= 0` returns `true` immediately (and synchronously on first render).\n *\n * Once shown, the value stays `true` for `minVisible` ms (default 500). Keep\n * the gated component mounted for that window on the consumer side too —\n * unmounting it early defeats the hold.\n *\n * @example\n * const showSkeleton = useDelayedLoading(300);\n * if (isLoading) return showSkeleton ? <Skeleton /> : null;\n */\nexport function useDelayedLoading(\n ms: number,\n { minVisible = 500 }: DelayedLoadingOptions = {},\n): boolean {\n const [ready, setReady] = useState(ms <= 0);\n const shownAt = useRef<number | null>(ms <= 0 ? Date.now() : null);\n\n useEffect(() => {\n const show = () => {\n shownAt.current = Date.now();\n setReady(true);\n };\n if (ms <= 0) {\n shownAt.current ??= Date.now();\n setReady(true);\n return;\n }\n // Respect the minimum-visible window before hiding again.\n const hold =\n shownAt.current === null ? 0 : Math.max(0, minVisible - (Date.now() - shownAt.current));\n const timers: number[] = [];\n if (hold > 0) {\n timers.push(window.setTimeout(() => setReady(false), hold));\n } else {\n setReady(false);\n }\n timers.push(window.setTimeout(show, hold + ms));\n return () => timers.forEach((t) => window.clearTimeout(t));\n }, [ms, minVisible]);\n\n return ready;\n}\n"],"names":["useDelayedLoading","ms","minVisible","ready","setReady","useState","shownAt","useRef","useEffect","show","hold","timers","t"],"mappings":";;AAyBO,SAASA,EACdC,GACA,EAAE,YAAAC,IAAa,IAAA,IAA+B,CAAA,GACrC;AACT,QAAM,CAACC,GAAOC,CAAQ,IAAIC,EAASJ,KAAM,CAAC,GACpCK,IAAUC,EAAsBN,KAAM,IAAI,KAAK,IAAA,IAAQ,IAAI;AAEjE,SAAAO,EAAU,MAAM;AACd,UAAMC,IAAO,MAAM;AACjB,MAAAH,EAAQ,UAAU,KAAK,IAAA,GACvBF,EAAS,EAAI;AAAA,IACf;AACA,QAAIH,KAAM,GAAG;AACX,MAAAK,EAAQ,YAARA,EAAQ,UAAY,KAAK,IAAA,IACzBF,EAAS,EAAI;AACb;AAAA,IACF;AAEA,UAAMM,IACJJ,EAAQ,YAAY,OAAO,IAAI,KAAK,IAAI,GAAGJ,KAAc,KAAK,IAAA,IAAQI,EAAQ,QAAQ,GAClFK,IAAmB,CAAA;AACzB,WAAID,IAAO,IACTC,EAAO,KAAK,OAAO,WAAW,MAAMP,EAAS,EAAK,GAAGM,CAAI,CAAC,IAE1DN,EAAS,EAAK,GAEhBO,EAAO,KAAK,OAAO,WAAWF,GAAMC,IAAOT,CAAE,CAAC,GACvC,MAAMU,EAAO,QAAQ,CAACC,MAAM,OAAO,aAAaA,CAAC,CAAC;AAAA,EAC3D,GAAG,CAACX,GAAIC,CAAU,CAAC,GAEZC;AACT;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface FieldIds {
|
|
2
|
+
/** Id for the control itself (`<input id>` / `<label htmlFor>`). */
|
|
3
|
+
fieldId: string;
|
|
4
|
+
/** Id for the description / helper text element (`<fieldId>-desc`). */
|
|
5
|
+
helperId: string;
|
|
6
|
+
/** Id for the validation message element. */
|
|
7
|
+
errorId: string;
|
|
8
|
+
/**
|
|
9
|
+
* `aria-describedby` value computed from what actually renders — the error
|
|
10
|
+
* wins over the helper; `undefined` when neither is present.
|
|
11
|
+
*/
|
|
12
|
+
describedBy: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Shared id wiring for form fields: one stable id for the control plus
|
|
16
|
+
* derived helper / error ids, and an `aria-describedby` that only references
|
|
17
|
+
* elements that exist. Pass a consumer `id` to keep `<label htmlFor>` in sync.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { fieldId, helperId, errorId, describedBy } = useFieldIds(id, {
|
|
21
|
+
* hasHelper: Boolean(helperText),
|
|
22
|
+
* hasError: Boolean(error),
|
|
23
|
+
* });
|
|
24
|
+
*/
|
|
25
|
+
export declare function useFieldIds(id?: string, present?: {
|
|
26
|
+
hasHelper?: boolean;
|
|
27
|
+
hasError?: boolean;
|
|
28
|
+
/** Consumer-supplied `aria-describedby` ids, appended after the helper / error id. */
|
|
29
|
+
extra?: string;
|
|
30
|
+
}): FieldIds;
|