@gv-tech/ui-web 2.15.2 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/accordion.cjs +1 -1
- package/dist/accordion.mjs +3 -3
- package/dist/alert-dialog.cjs +2 -2
- package/dist/alert-dialog.mjs +16 -16
- package/dist/alert.cjs +1 -1
- package/dist/alert.mjs +1 -1
- package/dist/avatar.cjs +1 -1
- package/dist/avatar.mjs +34 -34
- package/dist/badge.cjs +1 -1
- package/dist/badge.mjs +1 -1
- package/dist/breadcrumb.cjs +1 -1
- package/dist/breadcrumb.mjs +1 -1
- package/dist/button.cjs +1 -1
- package/dist/button.mjs +1 -1
- package/dist/calendar.cjs +1 -1
- package/dist/calendar.mjs +1 -1
- package/dist/card.cjs +1 -1
- package/dist/card.mjs +1 -1
- package/dist/carousel.cjs +1 -1
- package/dist/carousel.mjs +1 -1
- package/dist/chart.cjs +1 -1
- package/dist/chart.mjs +1 -1
- package/dist/checkbox.cjs +1 -1
- package/dist/checkbox.mjs +2 -2
- package/dist/command.cjs +1 -1
- package/dist/command.mjs +114 -114
- package/dist/context-menu.cjs +1 -1
- package/dist/context-menu.mjs +68 -68
- package/dist/dialog.cjs +1 -1
- package/dist/dialog.mjs +21 -21
- package/dist/drawer.cjs +1 -1
- package/dist/drawer.mjs +16 -16
- package/dist/dropdown-menu.cjs +1 -1
- package/dist/dropdown-menu.mjs +43 -43
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +1 -1
- package/dist/hover-card.cjs +1 -1
- package/dist/hover-card.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +52 -0
- package/dist/index.mjs +101 -95
- package/dist/input.cjs +1 -1
- package/dist/input.mjs +1 -1
- package/dist/label.cjs +1 -1
- package/dist/label.mjs +2 -2
- package/dist/menubar.cjs +1 -1
- package/dist/menubar.mjs +77 -77
- package/dist/navigation-menu.cjs +1 -1
- package/dist/navigation-menu.mjs +57 -57
- package/dist/pagination.cjs +1 -1
- package/dist/pagination.mjs +1 -1
- package/dist/popover.cjs +1 -1
- package/dist/popover.mjs +62 -62
- package/dist/progress.cjs +2 -2
- package/dist/progress.mjs +2 -2
- package/dist/radio-group.cjs +1 -1
- package/dist/radio-group.mjs +36 -36
- package/dist/resizable.cjs +1 -1
- package/dist/resizable.mjs +1 -1
- package/dist/scroll-area.cjs +1 -1
- package/dist/scroll-area.mjs +88 -88
- package/dist/scroll-to-top.cjs +1 -0
- package/dist/scroll-to-top.d.ts +12 -0
- package/dist/scroll-to-top.mjs +86 -0
- package/dist/search.cjs +1 -1
- package/dist/search.mjs +1 -1
- package/dist/select.cjs +1 -1
- package/dist/select.mjs +74 -74
- package/dist/separator.cjs +1 -1
- package/dist/separator.mjs +9 -9
- package/dist/sheet.cjs +1 -1
- package/dist/sheet.mjs +12 -12
- package/dist/skeleton.cjs +1 -1
- package/dist/skeleton.mjs +1 -1
- package/dist/slider.cjs +1 -1
- package/dist/slider.mjs +2 -2
- package/dist/support-fab.cjs +1 -0
- package/dist/support-fab.d.ts +20 -0
- package/dist/support-fab.mjs +120 -0
- package/dist/switch.cjs +1 -1
- package/dist/switch.mjs +12 -12
- package/dist/table-of-contents.cjs +1 -0
- package/dist/table-of-contents.d.ts +35 -0
- package/dist/table-of-contents.mjs +160 -0
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/dist/tabs.cjs +1 -1
- package/dist/tabs.mjs +41 -41
- package/dist/text.cjs +1 -1
- package/dist/text.mjs +1 -1
- package/dist/textarea.cjs +1 -1
- package/dist/textarea.mjs +1 -1
- package/dist/{theme-toggle-B4VZTDpe.js → theme-toggle-DXQGNfCe.js} +1 -1
- package/dist/{theme-toggle-WtPW9UZi.mjs → theme-toggle-tHXIbr8W.mjs} +1 -1
- package/dist/theme-toggle.cjs +1 -1
- package/dist/theme-toggle.mjs +2 -2
- package/dist/toast.cjs +1 -1
- package/dist/toast.mjs +35 -35
- package/dist/toggle-group.cjs +1 -1
- package/dist/toggle-group.mjs +15 -15
- package/dist/toggle.cjs +1 -1
- package/dist/toggle.mjs +4 -4
- package/dist/tooltip.cjs +1 -1
- package/dist/tooltip.mjs +3 -3
- package/dist/utils-DY6fhrgS.mjs +12 -0
- package/dist/utils-cdbZV8DZ.js +1 -0
- package/package.json +1 -1
- package/src/accordion.tsx +2 -2
- package/src/alert-dialog.tsx +6 -6
- package/src/avatar.tsx +3 -3
- package/src/checkbox.tsx +1 -1
- package/src/command.tsx +7 -7
- package/src/context-menu.tsx +8 -8
- package/src/dialog.tsx +4 -4
- package/src/drawer.tsx +3 -3
- package/src/dropdown-menu.tsx +8 -8
- package/src/hover-card.tsx +1 -1
- package/src/index.ts +12 -1
- package/src/label.tsx +1 -1
- package/src/lib/utils.ts +10 -0
- package/src/menubar.tsx +10 -10
- package/src/navigation-menu.tsx +6 -6
- package/src/popover.tsx +1 -1
- package/src/progress.tsx +1 -1
- package/src/radio-group.tsx +2 -2
- package/src/scroll-area.tsx +2 -2
- package/src/scroll-to-top.tsx +134 -0
- package/src/select.tsx +7 -7
- package/src/separator.tsx +1 -1
- package/src/setupTests.ts +1 -1
- package/src/sheet.tsx +4 -4
- package/src/slider.tsx +1 -1
- package/src/support-fab.tsx +190 -0
- package/src/switch.tsx +1 -1
- package/src/table-of-contents.tsx +285 -0
- package/src/tabs.tsx +3 -3
- package/src/toast.tsx +6 -6
- package/src/toggle-group.tsx +2 -2
- package/src/toggle.tsx +1 -1
- package/src/tooltip.tsx +2 -2
- package/dist/utils-B6yFEsav.mjs +0 -8
- package/dist/utils-IjLH3w2e.js +0 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Coffee, ExternalLink } from 'lucide-react';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Button } from './button';
|
|
6
|
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './dialog';
|
|
7
|
+
import { Drawer, DrawerContent, DrawerDescription, DrawerHeader, DrawerTitle } from './drawer';
|
|
8
|
+
import { cn } from './lib/utils';
|
|
9
|
+
|
|
10
|
+
const MOBILE_QUERY = '(max-width: 767px)';
|
|
11
|
+
|
|
12
|
+
const normalizeBaseUrl = (url: string) => {
|
|
13
|
+
const trimmed = url.trim();
|
|
14
|
+
return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const sanitizeCreator = (creatorId: string) => creatorId.trim().replace(/^@+/, '');
|
|
18
|
+
|
|
19
|
+
const buildPageUrl = (supportUrl: string, creatorId: string) => {
|
|
20
|
+
const base = normalizeBaseUrl(supportUrl);
|
|
21
|
+
const creator = encodeURIComponent(sanitizeCreator(creatorId));
|
|
22
|
+
return `${base}/${creator}`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const buildEmbedUrl = (supportUrl: string, creatorId: string) => {
|
|
26
|
+
const base = normalizeBaseUrl(supportUrl);
|
|
27
|
+
const creator = encodeURIComponent(sanitizeCreator(creatorId));
|
|
28
|
+
return `${base}/widget/page/${creator}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const useIsMobile = () => {
|
|
32
|
+
const getInitial = () => {
|
|
33
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return window.matchMedia(MOBILE_QUERY).matches;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const [isMobile, setIsMobile] = React.useState(getInitial);
|
|
40
|
+
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const mediaQuery = window.matchMedia(MOBILE_QUERY);
|
|
47
|
+
const onChange = (event: MediaQueryListEvent) => setIsMobile(event.matches);
|
|
48
|
+
|
|
49
|
+
setIsMobile(mediaQuery.matches);
|
|
50
|
+
mediaQuery.addEventListener('change', onChange);
|
|
51
|
+
|
|
52
|
+
return () => {
|
|
53
|
+
mediaQuery.removeEventListener('change', onChange);
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
return isMobile;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export interface SupportFabProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> {
|
|
61
|
+
supportUrl?: string;
|
|
62
|
+
creatorId: string;
|
|
63
|
+
title?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
iframeTitle?: string;
|
|
66
|
+
open?: boolean;
|
|
67
|
+
defaultOpen?: boolean;
|
|
68
|
+
onOpenChange?: (open: boolean) => void;
|
|
69
|
+
positionClassName?: string;
|
|
70
|
+
buttonClassName?: string;
|
|
71
|
+
panelClassName?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function SupportFab({
|
|
75
|
+
supportUrl = 'https://www.buymeacoffee.com',
|
|
76
|
+
creatorId,
|
|
77
|
+
title = 'Buy me a coffee',
|
|
78
|
+
description = 'Support the project directly from this panel.',
|
|
79
|
+
iframeTitle = 'Buy Me a Coffee support form',
|
|
80
|
+
open,
|
|
81
|
+
defaultOpen = false,
|
|
82
|
+
onOpenChange,
|
|
83
|
+
positionClassName,
|
|
84
|
+
buttonClassName,
|
|
85
|
+
panelClassName,
|
|
86
|
+
className,
|
|
87
|
+
type,
|
|
88
|
+
onClick,
|
|
89
|
+
...buttonProps
|
|
90
|
+
}: SupportFabProps) {
|
|
91
|
+
const isMobile = useIsMobile();
|
|
92
|
+
const isControlled = open !== undefined;
|
|
93
|
+
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
94
|
+
const isOpen = isControlled ? open : internalOpen;
|
|
95
|
+
|
|
96
|
+
const setOpen = React.useCallback(
|
|
97
|
+
(nextOpen: boolean) => {
|
|
98
|
+
if (!isControlled) {
|
|
99
|
+
setInternalOpen(nextOpen);
|
|
100
|
+
}
|
|
101
|
+
onOpenChange?.(nextOpen);
|
|
102
|
+
},
|
|
103
|
+
[isControlled, onOpenChange],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const pageUrl = React.useMemo(() => buildPageUrl(supportUrl, creatorId), [creatorId, supportUrl]);
|
|
107
|
+
const embedUrl = React.useMemo(() => buildEmbedUrl(supportUrl, creatorId), [creatorId, supportUrl]);
|
|
108
|
+
|
|
109
|
+
const panelBody = (
|
|
110
|
+
<div className="flex max-h-[75vh] flex-col gap-3">
|
|
111
|
+
<div className="overflow-hidden rounded-md border">
|
|
112
|
+
<iframe
|
|
113
|
+
title={iframeTitle}
|
|
114
|
+
src={embedUrl}
|
|
115
|
+
className="h-[560px] w-full border-0"
|
|
116
|
+
loading="lazy"
|
|
117
|
+
referrerPolicy="strict-origin-when-cross-origin"
|
|
118
|
+
/>
|
|
119
|
+
</div>
|
|
120
|
+
<div className="text-muted-foreground flex items-center justify-between gap-2 text-xs">
|
|
121
|
+
<span>If the embedded checkout is unavailable, open the support page directly.</span>
|
|
122
|
+
<a
|
|
123
|
+
href={pageUrl}
|
|
124
|
+
target="_blank"
|
|
125
|
+
rel="noopener noreferrer"
|
|
126
|
+
className="text-foreground inline-flex items-center gap-1 font-medium underline underline-offset-2"
|
|
127
|
+
>
|
|
128
|
+
Open Buy Me a Coffee
|
|
129
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
130
|
+
</a>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const trigger = (
|
|
136
|
+
<div className={cn('fixed right-6 bottom-6 z-50', positionClassName)}>
|
|
137
|
+
<Button
|
|
138
|
+
aria-label="Support this project"
|
|
139
|
+
type={type ?? 'button'}
|
|
140
|
+
className={cn(
|
|
141
|
+
'inline-flex h-14 w-14 rounded-full border border-black/15 bg-[#ffdd00] text-black shadow-[0_16px_36px_-18px_rgba(0,0,0,0.8)] transition hover:-translate-y-0.5 hover:scale-105 hover:bg-[#ffe347] focus-visible:ring-2 focus-visible:ring-offset-2 active:translate-y-0 active:scale-95',
|
|
142
|
+
buttonClassName,
|
|
143
|
+
className,
|
|
144
|
+
)}
|
|
145
|
+
onClick={(event) => {
|
|
146
|
+
onClick?.(event);
|
|
147
|
+
if (!event.defaultPrevented) {
|
|
148
|
+
setOpen(true);
|
|
149
|
+
}
|
|
150
|
+
}}
|
|
151
|
+
{...buttonProps}
|
|
152
|
+
>
|
|
153
|
+
<Coffee className="h-6 w-6" />
|
|
154
|
+
<span className="sr-only">Support this project</span>
|
|
155
|
+
</Button>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (isMobile) {
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
{trigger}
|
|
163
|
+
<Drawer open={isOpen} onOpenChange={setOpen}>
|
|
164
|
+
<DrawerContent className={cn('mx-auto w-full max-w-xl', panelClassName)}>
|
|
165
|
+
<DrawerHeader>
|
|
166
|
+
<DrawerTitle>{title}</DrawerTitle>
|
|
167
|
+
<DrawerDescription>{description}</DrawerDescription>
|
|
168
|
+
</DrawerHeader>
|
|
169
|
+
<div className="px-4 pb-4">{panelBody}</div>
|
|
170
|
+
</DrawerContent>
|
|
171
|
+
</Drawer>
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<>
|
|
178
|
+
{trigger}
|
|
179
|
+
<Dialog open={isOpen} onOpenChange={setOpen}>
|
|
180
|
+
<DialogContent className={cn('max-w-xl p-5', panelClassName)}>
|
|
181
|
+
<DialogHeader>
|
|
182
|
+
<DialogTitle>{title}</DialogTitle>
|
|
183
|
+
<DialogDescription>{description}</DialogDescription>
|
|
184
|
+
</DialogHeader>
|
|
185
|
+
{panelBody}
|
|
186
|
+
</DialogContent>
|
|
187
|
+
</Dialog>
|
|
188
|
+
</>
|
|
189
|
+
);
|
|
190
|
+
}
|
package/src/switch.tsx
CHANGED
|
@@ -25,7 +25,7 @@ const Switch = React.forwardRef<
|
|
|
25
25
|
/>
|
|
26
26
|
</SwitchPrimitives.Root>
|
|
27
27
|
));
|
|
28
|
-
Switch.displayName = SwitchPrimitives.Root
|
|
28
|
+
Switch.displayName = SwitchPrimitives.Root?.displayName || 'Switch';
|
|
29
29
|
|
|
30
30
|
export { Switch };
|
|
31
31
|
export type { SwitchBaseProps as SwitchProps };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
HeadingItem,
|
|
5
|
+
TableOfContentsBaseProps,
|
|
6
|
+
TableOfContentsContentBaseProps,
|
|
7
|
+
TableOfContentsListBaseProps,
|
|
8
|
+
TableOfContentsRootBaseProps,
|
|
9
|
+
} from '@gv-tech/ui-core';
|
|
10
|
+
import * as React from 'react';
|
|
11
|
+
import { cn, slugify } from './lib/utils';
|
|
12
|
+
|
|
13
|
+
// Context for sharing heading data
|
|
14
|
+
interface TOCContextValue {
|
|
15
|
+
headings: HeadingItem[];
|
|
16
|
+
activeId: string | null;
|
|
17
|
+
activeHeadingText: string | null;
|
|
18
|
+
registerHeadings: (headings: HeadingItem[]) => void;
|
|
19
|
+
setActiveId: (id: string | null) => void;
|
|
20
|
+
config: TableOfContentsBaseProps;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const TOCContext = React.createContext<TOCContextValue | null>(null);
|
|
24
|
+
|
|
25
|
+
function useTOC() {
|
|
26
|
+
const context = React.useContext(TOCContext);
|
|
27
|
+
if (!context) {
|
|
28
|
+
throw new Error('TOC components must be used within a TableOfContents provider');
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type TableOfContentsProps = TableOfContentsRootBaseProps;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Root component that provides the Table of Contents context.
|
|
37
|
+
* Can be used as a wrapper OR standalone.
|
|
38
|
+
* If used as a wrapper: <TableOfContents><List /><Content>{children}</Content></TableOfContents>
|
|
39
|
+
* If used standalone: <TableOfContents /> (requires container ref or defaults to document)
|
|
40
|
+
*/
|
|
41
|
+
function TableOfContents({
|
|
42
|
+
children,
|
|
43
|
+
className,
|
|
44
|
+
activeId: activeIdOverride,
|
|
45
|
+
minLevel = 1,
|
|
46
|
+
maxLevel = 4,
|
|
47
|
+
selector = 'h1, h2, h3, h4, h5, h6',
|
|
48
|
+
}: TableOfContentsProps) {
|
|
49
|
+
const [headings, setHeadings] = React.useState<HeadingItem[]>([]);
|
|
50
|
+
const [activeId, setActiveId] = React.useState<string | null>(null);
|
|
51
|
+
|
|
52
|
+
const activeHeadingText = React.useMemo(() => {
|
|
53
|
+
return headings.find((h) => h.id === activeId)?.text || null;
|
|
54
|
+
}, [headings, activeId]);
|
|
55
|
+
|
|
56
|
+
const registerHeadings = React.useCallback((newHeadings: HeadingItem[]) => {
|
|
57
|
+
setHeadings((prev) => {
|
|
58
|
+
if (JSON.stringify(prev) === JSON.stringify(newHeadings)) {
|
|
59
|
+
return prev;
|
|
60
|
+
}
|
|
61
|
+
return newHeadings;
|
|
62
|
+
});
|
|
63
|
+
}, []);
|
|
64
|
+
|
|
65
|
+
const value = React.useMemo(
|
|
66
|
+
() => ({
|
|
67
|
+
headings,
|
|
68
|
+
activeId: activeIdOverride || activeId,
|
|
69
|
+
activeHeadingText,
|
|
70
|
+
registerHeadings,
|
|
71
|
+
setActiveId,
|
|
72
|
+
config: { minLevel, maxLevel, selector, className },
|
|
73
|
+
}),
|
|
74
|
+
[
|
|
75
|
+
headings,
|
|
76
|
+
activeId,
|
|
77
|
+
activeIdOverride,
|
|
78
|
+
activeHeadingText,
|
|
79
|
+
registerHeadings,
|
|
80
|
+
minLevel,
|
|
81
|
+
maxLevel,
|
|
82
|
+
selector,
|
|
83
|
+
className,
|
|
84
|
+
],
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<TOCContext.Provider value={value}>
|
|
89
|
+
<div className={cn('relative', className)}>
|
|
90
|
+
{children || (
|
|
91
|
+
<div className="flex flex-col gap-4">
|
|
92
|
+
<TableOfContentsList />
|
|
93
|
+
<TableOfContentsContent>{null}</TableOfContentsContent>
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
</TOCContext.Provider>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Renders the actual list of links.
|
|
103
|
+
*/
|
|
104
|
+
function TableOfContentsList({ className }: TableOfContentsListBaseProps) {
|
|
105
|
+
const { headings, activeId, activeHeadingText } = useTOC();
|
|
106
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
107
|
+
|
|
108
|
+
// Auto-collapse on scroll
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
if (!isOpen) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const handleScroll = () => {
|
|
115
|
+
setIsOpen(false);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
119
|
+
return () => window.removeEventListener('scroll', handleScroll);
|
|
120
|
+
}, [isOpen]);
|
|
121
|
+
|
|
122
|
+
if (headings.length === 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const currentMinLevel = Math.min(...headings.map((h) => h.level));
|
|
127
|
+
|
|
128
|
+
const listContent = (
|
|
129
|
+
<ul className="m-0 list-none text-sm">
|
|
130
|
+
{headings.map((heading) => {
|
|
131
|
+
const isActive = activeId === heading.id;
|
|
132
|
+
const paddingLeft = `${(heading.level - currentMinLevel) * 1}rem`;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<li key={heading.id} className="mt-0 pt-2">
|
|
136
|
+
<a
|
|
137
|
+
href={`#${heading.id}`}
|
|
138
|
+
onClick={() => setIsOpen(false)}
|
|
139
|
+
className={cn(
|
|
140
|
+
'hover:text-foreground inline-block no-underline transition-colors',
|
|
141
|
+
isActive ? 'text-primary font-medium' : 'text-muted-foreground',
|
|
142
|
+
)}
|
|
143
|
+
style={{ paddingLeft }}
|
|
144
|
+
>
|
|
145
|
+
{heading.text}
|
|
146
|
+
</a>
|
|
147
|
+
</li>
|
|
148
|
+
);
|
|
149
|
+
})}
|
|
150
|
+
</ul>
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<>
|
|
155
|
+
{/* Mobile Sticky Header */}
|
|
156
|
+
<div className={cn('bg-background/95 sticky top-0 z-40 border-b backdrop-blur xl:hidden', className)}>
|
|
157
|
+
<button
|
|
158
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
159
|
+
className="flex w-full items-center justify-between px-4 py-3 text-left"
|
|
160
|
+
>
|
|
161
|
+
<div className="flex items-center gap-2 overflow-hidden">
|
|
162
|
+
<span className="text-muted-foreground text-xs font-semibold tracking-wider uppercase">On this page:</span>
|
|
163
|
+
<span className="truncate text-sm font-medium">{activeHeadingText || 'Overview'}</span>
|
|
164
|
+
</div>
|
|
165
|
+
<svg
|
|
166
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
167
|
+
width="16"
|
|
168
|
+
height="16"
|
|
169
|
+
viewBox="0 0 24 24"
|
|
170
|
+
fill="none"
|
|
171
|
+
stroke="currentColor"
|
|
172
|
+
strokeWidth="2"
|
|
173
|
+
strokeLinecap="round"
|
|
174
|
+
strokeLinejoin="round"
|
|
175
|
+
className={cn('shrink-0 transition-transform duration-200', isOpen && 'rotate-180')}
|
|
176
|
+
>
|
|
177
|
+
<path d="m6 9 6 6 6-6" />
|
|
178
|
+
</svg>
|
|
179
|
+
</button>
|
|
180
|
+
|
|
181
|
+
{isOpen && (
|
|
182
|
+
<div className="bg-background border-t px-4 pt-2 pb-6">
|
|
183
|
+
<nav aria-label="Table of contents mobile">{listContent}</nav>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
{/* Desktop Hidden List */}
|
|
189
|
+
<nav className={cn('hidden xl:block', className)} aria-label="Table of contents">
|
|
190
|
+
{listContent}
|
|
191
|
+
</nav>
|
|
192
|
+
</>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Wraps the content area and automatically detects headings within it.
|
|
198
|
+
*/
|
|
199
|
+
function TableOfContentsContent({ children, className }: TableOfContentsContentBaseProps) {
|
|
200
|
+
const { registerHeadings, setActiveId, config } = useTOC();
|
|
201
|
+
const contentRef = React.useRef<HTMLDivElement>(null);
|
|
202
|
+
|
|
203
|
+
React.useEffect(() => {
|
|
204
|
+
const root = contentRef.current;
|
|
205
|
+
if (!root) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const queryHeadings = () => {
|
|
210
|
+
const elements = Array.from(root.querySelectorAll(config.selector || 'h1, h2, h3, h4, h5, h6')).filter(
|
|
211
|
+
(element) => {
|
|
212
|
+
const level = parseInt(element.tagName.charAt(1), 10);
|
|
213
|
+
return level >= (config.minLevel ?? 1) && level <= (config.maxLevel ?? 4);
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const headingItems: HeadingItem[] = elements.map((element) => {
|
|
218
|
+
if (!element.id) {
|
|
219
|
+
element.id = slugify(element.textContent || 'heading');
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
id: element.id,
|
|
223
|
+
text: element.textContent || '',
|
|
224
|
+
level: parseInt(element.tagName.charAt(1), 10),
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
registerHeadings(headingItems);
|
|
229
|
+
return elements;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Initial query
|
|
233
|
+
const elements = queryHeadings();
|
|
234
|
+
|
|
235
|
+
// Intersection Observer for active ID
|
|
236
|
+
const observer = new IntersectionObserver(
|
|
237
|
+
(entries) => {
|
|
238
|
+
entries.forEach((entry) => {
|
|
239
|
+
if (entry.isIntersecting) {
|
|
240
|
+
setActiveId(entry.target.id);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
rootMargin: '0px 0px -80% 0px',
|
|
246
|
+
threshold: 0.1,
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
elements.forEach((el) => observer.observe(el));
|
|
251
|
+
|
|
252
|
+
// Mutation Observer for dynamic content
|
|
253
|
+
const mutationObserver = new MutationObserver(() => {
|
|
254
|
+
const newElements = queryHeadings();
|
|
255
|
+
observer.disconnect();
|
|
256
|
+
newElements.forEach((el) => observer.observe(el));
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
mutationObserver.observe(root, {
|
|
260
|
+
childList: true,
|
|
261
|
+
subtree: true,
|
|
262
|
+
characterData: true,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return () => {
|
|
266
|
+
observer.disconnect();
|
|
267
|
+
mutationObserver.disconnect();
|
|
268
|
+
};
|
|
269
|
+
}, [config.selector, config.minLevel, config.maxLevel, registerHeadings, setActiveId]);
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<div ref={contentRef} className={className}>
|
|
273
|
+
{children}
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
TableOfContents.List = TableOfContentsList;
|
|
279
|
+
TableOfContents.Content = TableOfContentsContent;
|
|
280
|
+
|
|
281
|
+
export type {
|
|
282
|
+
TableOfContentsContentBaseProps as TableOfContentsContentProps,
|
|
283
|
+
TableOfContentsListBaseProps as TableOfContentsListProps,
|
|
284
|
+
} from '@gv-tech/ui-core';
|
|
285
|
+
export { TableOfContents, TableOfContentsContent, TableOfContentsList };
|
package/src/tabs.tsx
CHANGED
|
@@ -21,7 +21,7 @@ const TabsList = React.forwardRef<
|
|
|
21
21
|
{...props}
|
|
22
22
|
/>
|
|
23
23
|
));
|
|
24
|
-
TabsList.displayName = TabsPrimitive.List
|
|
24
|
+
TabsList.displayName = TabsPrimitive.List?.displayName || 'TabsList';
|
|
25
25
|
|
|
26
26
|
const TabsTrigger = React.forwardRef<
|
|
27
27
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
|
@@ -36,7 +36,7 @@ const TabsTrigger = React.forwardRef<
|
|
|
36
36
|
{...props}
|
|
37
37
|
/>
|
|
38
38
|
));
|
|
39
|
-
TabsTrigger.displayName = TabsPrimitive.Trigger
|
|
39
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger?.displayName || 'TabsTrigger';
|
|
40
40
|
|
|
41
41
|
const TabsContent = React.forwardRef<
|
|
42
42
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
|
@@ -51,7 +51,7 @@ const TabsContent = React.forwardRef<
|
|
|
51
51
|
{...props}
|
|
52
52
|
/>
|
|
53
53
|
));
|
|
54
|
-
TabsContent.displayName = TabsPrimitive.Content
|
|
54
|
+
TabsContent.displayName = TabsPrimitive.Content?.displayName || 'TabsContent';
|
|
55
55
|
|
|
56
56
|
export { Tabs, TabsContent, TabsList, TabsTrigger };
|
|
57
57
|
export type {
|
package/src/toast.tsx
CHANGED
|
@@ -21,7 +21,7 @@ const ToastViewport = React.forwardRef<
|
|
|
21
21
|
{...props}
|
|
22
22
|
/>
|
|
23
23
|
));
|
|
24
|
-
ToastViewport.displayName = ToastPrimitives.Viewport
|
|
24
|
+
ToastViewport.displayName = ToastPrimitives.Viewport?.displayName || 'ToastViewport';
|
|
25
25
|
|
|
26
26
|
const toastVariants = cva(
|
|
27
27
|
'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
|
|
@@ -44,7 +44,7 @@ const Toast = React.forwardRef<
|
|
|
44
44
|
>(({ className, variant, ...props }, ref) => {
|
|
45
45
|
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
|
|
46
46
|
});
|
|
47
|
-
Toast.displayName = ToastPrimitives.Root
|
|
47
|
+
Toast.displayName = ToastPrimitives.Root?.displayName || 'Toast';
|
|
48
48
|
|
|
49
49
|
const ToastAction = React.forwardRef<
|
|
50
50
|
React.ElementRef<typeof ToastPrimitives.Action>,
|
|
@@ -59,7 +59,7 @@ const ToastAction = React.forwardRef<
|
|
|
59
59
|
{...props}
|
|
60
60
|
/>
|
|
61
61
|
));
|
|
62
|
-
ToastAction.displayName = ToastPrimitives.Action
|
|
62
|
+
ToastAction.displayName = ToastPrimitives.Action?.displayName || 'ToastAction';
|
|
63
63
|
|
|
64
64
|
const ToastClose = React.forwardRef<
|
|
65
65
|
React.ElementRef<typeof ToastPrimitives.Close>,
|
|
@@ -77,7 +77,7 @@ const ToastClose = React.forwardRef<
|
|
|
77
77
|
<X className="h-4 w-4" />
|
|
78
78
|
</ToastPrimitives.Close>
|
|
79
79
|
));
|
|
80
|
-
ToastClose.displayName = ToastPrimitives.Close
|
|
80
|
+
ToastClose.displayName = ToastPrimitives.Close?.displayName || 'ToastClose';
|
|
81
81
|
|
|
82
82
|
const ToastTitle = React.forwardRef<
|
|
83
83
|
React.ElementRef<typeof ToastPrimitives.Title>,
|
|
@@ -85,7 +85,7 @@ const ToastTitle = React.forwardRef<
|
|
|
85
85
|
>(({ className, ...props }, ref) => (
|
|
86
86
|
<ToastPrimitives.Title ref={ref} className={cn('text-sm font-semibold [&+div]:text-xs', className)} {...props} />
|
|
87
87
|
));
|
|
88
|
-
ToastTitle.displayName = ToastPrimitives.Title
|
|
88
|
+
ToastTitle.displayName = ToastPrimitives.Title?.displayName || 'ToastTitle';
|
|
89
89
|
|
|
90
90
|
const ToastDescription = React.forwardRef<
|
|
91
91
|
React.ElementRef<typeof ToastPrimitives.Description>,
|
|
@@ -93,7 +93,7 @@ const ToastDescription = React.forwardRef<
|
|
|
93
93
|
>(({ className, ...props }, ref) => (
|
|
94
94
|
<ToastPrimitives.Description ref={ref} className={cn('text-sm opacity-90', className)} {...props} />
|
|
95
95
|
));
|
|
96
|
-
ToastDescription.displayName = ToastPrimitives.Description
|
|
96
|
+
ToastDescription.displayName = ToastPrimitives.Description?.displayName || 'ToastDescription';
|
|
97
97
|
|
|
98
98
|
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
|
99
99
|
|
package/src/toggle-group.tsx
CHANGED
|
@@ -23,7 +23,7 @@ const ToggleGroup = React.forwardRef<React.ElementRef<typeof ToggleGroupPrimitiv
|
|
|
23
23
|
),
|
|
24
24
|
);
|
|
25
25
|
|
|
26
|
-
ToggleGroup.displayName = ToggleGroupPrimitive.Root
|
|
26
|
+
ToggleGroup.displayName = ToggleGroupPrimitive.Root?.displayName || 'ToggleGroup';
|
|
27
27
|
|
|
28
28
|
export type ToggleGroupItemProps = React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
|
29
29
|
VariantProps<typeof toggleVariants>;
|
|
@@ -50,6 +50,6 @@ const ToggleGroupItem = React.forwardRef<React.ElementRef<typeof ToggleGroupPrim
|
|
|
50
50
|
},
|
|
51
51
|
);
|
|
52
52
|
|
|
53
|
-
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item
|
|
53
|
+
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item?.displayName || 'ToggleGroupItem';
|
|
54
54
|
|
|
55
55
|
export { ToggleGroup, ToggleGroupItem };
|
package/src/toggle.tsx
CHANGED
|
@@ -19,6 +19,6 @@ const Toggle = React.forwardRef<React.ElementRef<typeof TogglePrimitive.Root>, T
|
|
|
19
19
|
),
|
|
20
20
|
);
|
|
21
21
|
|
|
22
|
-
Toggle.displayName = TogglePrimitive.Root
|
|
22
|
+
Toggle.displayName = TogglePrimitive.Root?.displayName || 'Toggle';
|
|
23
23
|
|
|
24
24
|
export { Toggle, toggleVariants };
|
package/src/tooltip.tsx
CHANGED
|
@@ -28,7 +28,7 @@ export type TooltipTriggerProps = React.ComponentPropsWithoutRef<typeof TooltipP
|
|
|
28
28
|
const TooltipTrigger = React.forwardRef<React.ElementRef<typeof TooltipPrimitive.Trigger>, TooltipTriggerProps>(
|
|
29
29
|
({ className, ...props }, ref) => <TooltipPrimitive.Trigger ref={ref} className={className} {...props} />,
|
|
30
30
|
);
|
|
31
|
-
TooltipTrigger.displayName = TooltipPrimitive.Trigger
|
|
31
|
+
TooltipTrigger.displayName = TooltipPrimitive.Trigger?.displayName || 'TooltipTrigger';
|
|
32
32
|
|
|
33
33
|
export type TooltipContentProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> &
|
|
34
34
|
TooltipContentBaseProps;
|
|
@@ -46,6 +46,6 @@ const TooltipContent = React.forwardRef<React.ElementRef<typeof TooltipPrimitive
|
|
|
46
46
|
/>
|
|
47
47
|
),
|
|
48
48
|
);
|
|
49
|
-
TooltipContent.displayName = TooltipPrimitive.Content
|
|
49
|
+
TooltipContent.displayName = TooltipPrimitive.Content?.displayName || 'TooltipContent';
|
|
50
50
|
|
|
51
51
|
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
package/dist/utils-B6yFEsav.mjs
DELETED
package/dist/utils-IjLH3w2e.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const r=require("clsx"),c=require("tailwind-merge");function n(...e){return c.twMerge(r.clsx(e))}exports.cn=n;
|