@hanzo/ui 5.3.26 → 5.3.29

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 (100) hide show
  1. package/content/index.ts +26 -0
  2. package/dist/util/index.js +6 -0
  3. package/dist/util/index.mjs +6 -1
  4. package/docs/_registry/index.ts +426 -0
  5. package/docs/_registry/layout/docs-min.tsx +197 -0
  6. package/docs/_registry/layout/page-min.tsx +128 -0
  7. package/docs/components/accordion.tsx +118 -0
  8. package/docs/components/banner.tsx +144 -0
  9. package/docs/components/callout.tsx +112 -0
  10. package/docs/components/card.tsx +52 -0
  11. package/docs/components/codeblock.tsx +258 -0
  12. package/docs/components/dialog/search-algolia.tsx +132 -0
  13. package/docs/components/dialog/search-default.tsx +131 -0
  14. package/docs/components/dialog/search-orama.tsx +143 -0
  15. package/docs/components/dialog/search.tsx +529 -0
  16. package/docs/components/dynamic-codeblock.tsx +129 -0
  17. package/docs/components/files.tsx +81 -0
  18. package/docs/components/github-info.tsx +107 -0
  19. package/docs/components/heading.tsx +33 -0
  20. package/docs/components/image-zoom.css +77 -0
  21. package/docs/components/image-zoom.tsx +58 -0
  22. package/docs/components/index.ts +7 -0
  23. package/docs/components/inline-toc.tsx +48 -0
  24. package/docs/components/sidebar/base.tsx +451 -0
  25. package/docs/components/sidebar/link-item.tsx +65 -0
  26. package/docs/components/sidebar/page-tree.tsx +113 -0
  27. package/docs/components/sidebar/tabs/dropdown.tsx +109 -0
  28. package/docs/components/sidebar/tabs/index.tsx +89 -0
  29. package/docs/components/steps.tsx +9 -0
  30. package/docs/components/tabs.tsx +203 -0
  31. package/docs/components/toc/clerk.tsx +173 -0
  32. package/docs/components/toc/default.tsx +57 -0
  33. package/docs/components/toc/index.tsx +136 -0
  34. package/docs/components/type-table.tsx +174 -0
  35. package/docs/components/ui/accordion.tsx +88 -0
  36. package/docs/components/ui/button.tsx +28 -0
  37. package/docs/components/ui/collapsible.tsx +42 -0
  38. package/docs/components/ui/navigation-menu.tsx +83 -0
  39. package/docs/components/ui/popover.tsx +32 -0
  40. package/docs/components/ui/scroll-area.tsx +59 -0
  41. package/docs/components/ui/tabs.tsx +145 -0
  42. package/docs/contexts/i18n.tsx +56 -0
  43. package/docs/contexts/search.tsx +165 -0
  44. package/docs/contexts/tree.tsx +65 -0
  45. package/docs/css/black.css +39 -0
  46. package/docs/css/catppuccin.css +49 -0
  47. package/docs/css/colors/index.css +51 -0
  48. package/docs/css/dusk.css +47 -0
  49. package/docs/css/layouts/docs.css +1 -0
  50. package/docs/css/layouts/home.css +1 -0
  51. package/docs/css/layouts/notebook.css +1 -0
  52. package/docs/css/neutral.css +7 -0
  53. package/docs/css/ocean.css +48 -0
  54. package/docs/css/preset.css +305 -0
  55. package/docs/css/purple.css +39 -0
  56. package/docs/css/shadcn.css +36 -0
  57. package/docs/css/shiki.css +90 -0
  58. package/docs/css/solar.css +75 -0
  59. package/docs/css/style.css +9 -0
  60. package/docs/css/vitepress.css +77 -0
  61. package/docs/i18n.tsx +30 -0
  62. package/docs/icons.tsx +354 -0
  63. package/docs/layouts/docs/client.tsx +129 -0
  64. package/docs/layouts/docs/index.tsx +321 -0
  65. package/docs/layouts/docs/page/client.tsx +376 -0
  66. package/docs/layouts/docs/page/index.tsx +251 -0
  67. package/docs/layouts/docs/sidebar.tsx +265 -0
  68. package/docs/layouts/home/client.tsx +375 -0
  69. package/docs/layouts/home/index.tsx +51 -0
  70. package/docs/layouts/home/navbar.tsx +55 -0
  71. package/docs/layouts/notebook/client.tsx +281 -0
  72. package/docs/layouts/notebook/index.tsx +461 -0
  73. package/docs/layouts/notebook/page/client.tsx +375 -0
  74. package/docs/layouts/notebook/page/index.tsx +251 -0
  75. package/docs/layouts/notebook/sidebar.tsx +248 -0
  76. package/docs/layouts/shared/index.tsx +89 -0
  77. package/docs/layouts/shared/language-toggle.tsx +66 -0
  78. package/docs/layouts/shared/link-item.tsx +119 -0
  79. package/docs/layouts/shared/search-toggle.tsx +78 -0
  80. package/docs/layouts/shared/theme-toggle.tsx +86 -0
  81. package/docs/mdx.server.tsx +37 -0
  82. package/docs/mdx.tsx +97 -0
  83. package/docs/og.tsx +101 -0
  84. package/docs/page.tsx +85 -0
  85. package/docs/provider/base.tsx +173 -0
  86. package/docs/provider/next.tsx +23 -0
  87. package/docs/provider/react-router.tsx +23 -0
  88. package/docs/provider/tanstack.tsx +23 -0
  89. package/docs/provider/waku.tsx +23 -0
  90. package/docs/source.ts +3 -0
  91. package/docs/theme/typography/LICENSE +21 -0
  92. package/docs/theme/typography/index.ts +201 -0
  93. package/docs/theme/typography/styles.ts +449 -0
  94. package/docs/utils/cn.ts +1 -0
  95. package/docs/utils/is-active.ts +23 -0
  96. package/docs/utils/merge-refs.ts +15 -0
  97. package/docs/utils/use-copy-button.ts +39 -0
  98. package/docs/utils/use-footer-items.ts +27 -0
  99. package/docs/utils/use-is-scroll-top.ts +21 -0
  100. package/package.json +4 -2
@@ -0,0 +1,258 @@
1
+ 'use client';
2
+ import { Check, Clipboard } from '@icons';
3
+ import {
4
+ type ComponentProps,
5
+ createContext,
6
+ type HTMLAttributes,
7
+ type ReactNode,
8
+ type RefObject,
9
+ use,
10
+ useMemo,
11
+ useRef,
12
+ } from 'react';
13
+ import { cn } from '@/utils/cn';
14
+ import { useCopyButton } from '@/utils/use-copy-button';
15
+ import { buttonVariants } from '@/components/ui/button';
16
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
17
+ import { mergeRefs } from '@/utils/merge-refs';
18
+
19
+ export interface CodeBlockProps extends ComponentProps<'figure'> {
20
+ /**
21
+ * Icon of code block
22
+ *
23
+ * When passed as a string, it assumes the value is the HTML of icon
24
+ */
25
+ icon?: ReactNode;
26
+
27
+ /**
28
+ * Allow to copy code with copy button
29
+ *
30
+ * @defaultValue true
31
+ */
32
+ allowCopy?: boolean;
33
+
34
+ /**
35
+ * Keep original background color generated by Shiki or Rehype Code
36
+ *
37
+ * @defaultValue false
38
+ */
39
+ keepBackground?: boolean;
40
+
41
+ viewportProps?: HTMLAttributes<HTMLElement>;
42
+
43
+ /**
44
+ * show line numbers
45
+ */
46
+ 'data-line-numbers'?: boolean;
47
+
48
+ /**
49
+ * @defaultValue 1
50
+ */
51
+ 'data-line-numbers-start'?: number;
52
+
53
+ Actions?: (props: { className?: string; children?: ReactNode }) => ReactNode;
54
+ }
55
+
56
+ const TabsContext = createContext<{
57
+ containerRef: RefObject<HTMLDivElement | null>;
58
+ nested: boolean;
59
+ } | null>(null);
60
+
61
+ export function Pre(props: ComponentProps<'pre'>) {
62
+ return (
63
+ <pre
64
+ {...props}
65
+ className={cn('min-w-full w-max *:flex *:flex-col', props.className)}
66
+ >
67
+ {props.children}
68
+ </pre>
69
+ );
70
+ }
71
+
72
+ export function CodeBlock({
73
+ ref,
74
+ title,
75
+ allowCopy = true,
76
+ keepBackground = false,
77
+ icon,
78
+ viewportProps = {},
79
+ children,
80
+ Actions = (props) => (
81
+ <div {...props} className={cn('empty:hidden', props.className)} />
82
+ ),
83
+ ...props
84
+ }: CodeBlockProps) {
85
+ const inTab = use(TabsContext) !== null;
86
+ const areaRef = useRef<HTMLDivElement>(null);
87
+
88
+ return (
89
+ <figure
90
+ ref={ref}
91
+ dir="ltr"
92
+ {...props}
93
+ tabIndex={-1}
94
+ className={cn(
95
+ inTab
96
+ ? 'bg-fd-secondary -mx-px -mb-px last:rounded-b-xl'
97
+ : 'my-4 bg-fd-card rounded-xl',
98
+ keepBackground && 'bg-(--shiki-light-bg) dark:bg-(--shiki-dark-bg)',
99
+
100
+ 'shiki relative border shadow-sm not-prose overflow-hidden text-sm',
101
+ props.className,
102
+ )}
103
+ >
104
+ {title ? (
105
+ <div className="flex text-fd-muted-foreground items-center gap-2 h-9.5 border-b px-4">
106
+ {typeof icon === 'string' ? (
107
+ <div
108
+ className="[&_svg]:size-3.5"
109
+ dangerouslySetInnerHTML={{
110
+ __html: icon,
111
+ }}
112
+ />
113
+ ) : (
114
+ icon
115
+ )}
116
+ <figcaption className="flex-1 truncate">{title}</figcaption>
117
+ {Actions({
118
+ className: '-me-2',
119
+ children: allowCopy && <CopyButton containerRef={areaRef} />,
120
+ })}
121
+ </div>
122
+ ) : (
123
+ Actions({
124
+ className:
125
+ 'absolute top-2 right-2 z-2 backdrop-blur-lg rounded-lg text-fd-muted-foreground',
126
+ children: allowCopy && <CopyButton containerRef={areaRef} />,
127
+ })
128
+ )}
129
+ <div
130
+ ref={areaRef}
131
+ {...viewportProps}
132
+ role="region"
133
+ tabIndex={0}
134
+ className={cn(
135
+ 'text-[0.8125rem] py-3.5 overflow-auto max-h-[600px] fd-scroll-container focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-fd-ring',
136
+ viewportProps.className,
137
+ )}
138
+ style={
139
+ {
140
+ // space for toolbar
141
+ '--padding-right': !title ? 'calc(var(--spacing) * 8)' : undefined,
142
+ counterSet: props['data-line-numbers']
143
+ ? `line ${Number(props['data-line-numbers-start'] ?? 1) - 1}`
144
+ : undefined,
145
+ ...viewportProps.style,
146
+ } as object
147
+ }
148
+ >
149
+ {children}
150
+ </div>
151
+ </figure>
152
+ );
153
+ }
154
+
155
+ function CopyButton({
156
+ className,
157
+ containerRef,
158
+ ...props
159
+ }: ComponentProps<'button'> & {
160
+ containerRef: RefObject<HTMLElement | null>;
161
+ }) {
162
+ const [checked, onClick] = useCopyButton(() => {
163
+ const pre = containerRef.current?.getElementsByTagName('pre').item(0);
164
+ if (!pre) return;
165
+
166
+ const clone = pre.cloneNode(true) as HTMLElement;
167
+ clone.querySelectorAll('.nd-copy-ignore').forEach((node) => {
168
+ node.replaceWith('\n');
169
+ });
170
+
171
+ void navigator.clipboard.writeText(clone.textContent ?? '');
172
+ });
173
+
174
+ return (
175
+ <button
176
+ type="button"
177
+ data-checked={checked || undefined}
178
+ className={cn(
179
+ buttonVariants({
180
+ className:
181
+ 'hover:text-fd-accent-foreground data-checked:text-fd-accent-foreground',
182
+ size: 'icon-xs',
183
+ }),
184
+ className,
185
+ )}
186
+ aria-label={checked ? 'Copied Text' : 'Copy Text'}
187
+ onClick={onClick}
188
+ {...props}
189
+ >
190
+ {checked ? <Check /> : <Clipboard />}
191
+ </button>
192
+ );
193
+ }
194
+
195
+ export function CodeBlockTabs({ ref, ...props }: ComponentProps<typeof Tabs>) {
196
+ const containerRef = useRef<HTMLDivElement>(null);
197
+ const nested = use(TabsContext) !== null;
198
+
199
+ return (
200
+ <Tabs
201
+ ref={mergeRefs(containerRef, ref)}
202
+ {...props}
203
+ className={cn(
204
+ 'bg-fd-card rounded-xl border',
205
+ !nested && 'my-4',
206
+ props.className,
207
+ )}
208
+ >
209
+ <TabsContext
210
+ value={useMemo(
211
+ () => ({
212
+ containerRef,
213
+ nested,
214
+ }),
215
+ [nested],
216
+ )}
217
+ >
218
+ {props.children}
219
+ </TabsContext>
220
+ </Tabs>
221
+ );
222
+ }
223
+
224
+ export function CodeBlockTabsList(props: ComponentProps<typeof TabsList>) {
225
+ return (
226
+ <TabsList
227
+ {...props}
228
+ className={cn(
229
+ 'flex flex-row px-2 overflow-x-auto text-fd-muted-foreground',
230
+ props.className,
231
+ )}
232
+ >
233
+ {props.children}
234
+ </TabsList>
235
+ );
236
+ }
237
+
238
+ export function CodeBlockTabsTrigger({
239
+ children,
240
+ ...props
241
+ }: ComponentProps<typeof TabsTrigger>) {
242
+ return (
243
+ <TabsTrigger
244
+ {...props}
245
+ className={cn(
246
+ 'relative group inline-flex text-sm font-medium text-nowrap items-center transition-colors gap-2 px-2 py-1.5 hover:text-fd-accent-foreground data-[state=active]:text-fd-primary [&_svg]:size-3.5',
247
+ props.className,
248
+ )}
249
+ >
250
+ <div className="absolute inset-x-2 bottom-0 h-px group-data-[state=active]:bg-fd-primary" />
251
+ {children}
252
+ </TabsTrigger>
253
+ );
254
+ }
255
+
256
+ export function CodeBlockTab(props: ComponentProps<typeof TabsContent>) {
257
+ return <TabsContent {...props} />;
258
+ }
@@ -0,0 +1,132 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type AlgoliaOptions,
5
+ useDocsSearch,
6
+ } from '@hanzo/docs-core/search/client';
7
+ import { type ReactNode, useMemo, useState } from 'react';
8
+ import { useOnChange } from '@hanzo/docs-core/utils/use-on-change';
9
+ import {
10
+ SearchDialog,
11
+ SearchDialogClose,
12
+ SearchDialogContent,
13
+ SearchDialogFooter,
14
+ SearchDialogHeader,
15
+ SearchDialogIcon,
16
+ SearchDialogInput,
17
+ SearchDialogList,
18
+ SearchDialogOverlay,
19
+ type SharedProps,
20
+ TagsList,
21
+ TagsListItem,
22
+ } from './search';
23
+ import type { SortedResult } from '@hanzo/docs-core/search';
24
+ import type { SearchLink, TagItem } from '@/contexts/search';
25
+ import { useI18n } from '@/contexts/i18n';
26
+
27
+ export interface AlgoliaSearchDialogProps extends SharedProps {
28
+ searchOptions: AlgoliaOptions;
29
+ links?: SearchLink[];
30
+
31
+ footer?: ReactNode;
32
+
33
+ defaultTag?: string;
34
+ tags?: TagItem[];
35
+
36
+ /**
37
+ * Add the "Powered by Algolia" label, this is useful for free tier users
38
+ *
39
+ * @defaultValue false
40
+ */
41
+ showAlgolia?: boolean;
42
+
43
+ /**
44
+ * Allow to clear tag filters
45
+ *
46
+ * @defaultValue false
47
+ */
48
+ allowClear?: boolean;
49
+ }
50
+
51
+ export default function AlgoliaSearchDialog({
52
+ searchOptions,
53
+ tags = [],
54
+ defaultTag,
55
+ showAlgolia = false,
56
+ allowClear = false,
57
+ links = [],
58
+ footer,
59
+ ...props
60
+ }: AlgoliaSearchDialogProps) {
61
+ const [tag, setTag] = useState(defaultTag);
62
+ const { locale } = useI18n();
63
+ const { search, setSearch, query } = useDocsSearch({
64
+ type: 'algolia',
65
+ tag,
66
+ locale,
67
+ ...searchOptions,
68
+ });
69
+ const defaultItems = useMemo<SortedResult[] | null>(() => {
70
+ if (links.length === 0) return null;
71
+ return links.map(([name, link]) => ({
72
+ type: 'page',
73
+ id: name,
74
+ content: name,
75
+ url: link,
76
+ }));
77
+ }, [links]);
78
+
79
+ useOnChange(defaultTag, (v) => {
80
+ setTag(v);
81
+ });
82
+
83
+ const label = showAlgolia && <AlgoliaTitle />;
84
+
85
+ return (
86
+ <SearchDialog
87
+ search={search}
88
+ onSearchChange={setSearch}
89
+ isLoading={query.isLoading}
90
+ {...props}
91
+ >
92
+ <SearchDialogOverlay />
93
+ <SearchDialogContent>
94
+ <SearchDialogHeader>
95
+ <SearchDialogIcon />
96
+ <SearchDialogInput />
97
+ <SearchDialogClose />
98
+ </SearchDialogHeader>
99
+ <SearchDialogList
100
+ items={query.data !== 'empty' ? query.data : defaultItems}
101
+ />
102
+ </SearchDialogContent>
103
+ <SearchDialogFooter>
104
+ {tags.length > 0 ? (
105
+ <TagsList tag={tag} onTagChange={setTag} allowClear={allowClear}>
106
+ {tags.map((tag) => (
107
+ <TagsListItem key={tag.value} value={tag.value}>
108
+ {tag.name}
109
+ </TagsListItem>
110
+ ))}
111
+ {label}
112
+ </TagsList>
113
+ ) : (
114
+ label
115
+ )}
116
+ {footer}
117
+ </SearchDialogFooter>
118
+ </SearchDialog>
119
+ );
120
+ }
121
+
122
+ function AlgoliaTitle() {
123
+ return (
124
+ <a
125
+ href="https://algolia.com"
126
+ rel="noreferrer noopener"
127
+ className="ms-auto text-xs text-fd-muted-foreground"
128
+ >
129
+ Search powered by Algolia
130
+ </a>
131
+ );
132
+ }
@@ -0,0 +1,131 @@
1
+ 'use client';
2
+
3
+ import { useDocsSearch } from '@hanzo/docs-core/search/client';
4
+ import { type ReactNode, useMemo, useState } from 'react';
5
+ import { useOnChange } from '@hanzo/docs-core/utils/use-on-change';
6
+ import { useI18n } from '@/contexts/i18n';
7
+ import {
8
+ SearchDialog,
9
+ SearchDialogClose,
10
+ SearchDialogContent,
11
+ SearchDialogFooter,
12
+ SearchDialogHeader,
13
+ SearchDialogIcon,
14
+ SearchDialogInput,
15
+ SearchDialogList,
16
+ SearchDialogOverlay,
17
+ type SharedProps,
18
+ TagsList,
19
+ TagsListItem,
20
+ } from './search';
21
+ import type { SortedResult } from '@hanzo/docs-core/search';
22
+ import type { SearchLink, TagItem } from '@/contexts/search';
23
+
24
+ export interface DefaultSearchDialogProps extends SharedProps {
25
+ links?: SearchLink[];
26
+
27
+ /**
28
+ * @defaultValue 'fetch'
29
+ */
30
+ type?: 'fetch' | 'static';
31
+
32
+ defaultTag?: string;
33
+ tags?: TagItem[];
34
+
35
+ /**
36
+ * Search API URL
37
+ */
38
+ api?: string;
39
+
40
+ /**
41
+ * The debounced delay for performing a search.
42
+ */
43
+ delayMs?: number;
44
+
45
+ footer?: ReactNode;
46
+
47
+ /**
48
+ * Allow to clear tag filters
49
+ *
50
+ * @defaultValue false
51
+ */
52
+ allowClear?: boolean;
53
+ }
54
+
55
+ export default function DefaultSearchDialog({
56
+ defaultTag,
57
+ tags = [],
58
+ api,
59
+ delayMs,
60
+ type = 'fetch',
61
+ allowClear = false,
62
+ links = [],
63
+ footer,
64
+ ...props
65
+ }: DefaultSearchDialogProps) {
66
+ const { locale } = useI18n();
67
+ const [tag, setTag] = useState(defaultTag);
68
+ const { search, setSearch, query } = useDocsSearch(
69
+ type === 'fetch'
70
+ ? {
71
+ type: 'fetch',
72
+ api,
73
+ locale,
74
+ tag,
75
+ delayMs,
76
+ }
77
+ : {
78
+ type: 'static',
79
+ from: api,
80
+ locale,
81
+ tag,
82
+ delayMs,
83
+ },
84
+ );
85
+ const defaultItems = useMemo<SortedResult[] | null>(() => {
86
+ if (links.length === 0) return null;
87
+ return links.map(([name, link]) => ({
88
+ type: 'page',
89
+ id: name,
90
+ content: name,
91
+ url: link,
92
+ }));
93
+ }, [links]);
94
+
95
+ useOnChange(defaultTag, (v) => {
96
+ setTag(v);
97
+ });
98
+
99
+ return (
100
+ <SearchDialog
101
+ search={search}
102
+ onSearchChange={setSearch}
103
+ isLoading={query.isLoading}
104
+ {...props}
105
+ >
106
+ <SearchDialogOverlay />
107
+ <SearchDialogContent>
108
+ <SearchDialogHeader>
109
+ <SearchDialogIcon />
110
+ <SearchDialogInput />
111
+ <SearchDialogClose />
112
+ </SearchDialogHeader>
113
+ <SearchDialogList
114
+ items={query.data !== 'empty' ? query.data : defaultItems}
115
+ />
116
+ </SearchDialogContent>
117
+ <SearchDialogFooter>
118
+ {tags.length > 0 && (
119
+ <TagsList tag={tag} onTagChange={setTag} allowClear={allowClear}>
120
+ {tags.map((tag) => (
121
+ <TagsListItem key={tag.value} value={tag.value}>
122
+ {tag.name}
123
+ </TagsListItem>
124
+ ))}
125
+ </TagsList>
126
+ )}
127
+ {footer}
128
+ </SearchDialogFooter>
129
+ </SearchDialog>
130
+ );
131
+ }
@@ -0,0 +1,143 @@
1
+ 'use client';
2
+
3
+ import {
4
+ type OramaCloudOptions,
5
+ useDocsSearch,
6
+ } from '@hanzo/docs-core/search/client';
7
+ import { type ReactNode, useMemo, useState } from 'react';
8
+ import { useOnChange } from '@hanzo/docs-core/utils/use-on-change';
9
+ import {
10
+ SearchDialog,
11
+ SearchDialogClose,
12
+ SearchDialogContent,
13
+ SearchDialogFooter,
14
+ SearchDialogHeader,
15
+ SearchDialogIcon,
16
+ SearchDialogInput,
17
+ SearchDialogList,
18
+ SearchDialogOverlay,
19
+ type SharedProps,
20
+ TagsList,
21
+ TagsListItem,
22
+ } from './search';
23
+ import type { SortedResult } from '@hanzo/docs-core/search';
24
+ import type { SearchLink, TagItem } from '@/contexts/search';
25
+ import { useI18n } from '@/contexts/i18n';
26
+
27
+ export interface OramaSearchDialogProps extends SharedProps {
28
+ links?: SearchLink[];
29
+ client: OramaCloudOptions['client'];
30
+ searchOptions?: OramaCloudOptions['params'];
31
+ index?: OramaCloudOptions['index'];
32
+
33
+ footer?: ReactNode;
34
+
35
+ defaultTag?: string;
36
+ tags?: TagItem[];
37
+
38
+ /**
39
+ * Add the "Powered by Orama" label
40
+ *
41
+ * @defaultValue true
42
+ */
43
+ showOrama?: boolean;
44
+
45
+ /**
46
+ * Allow to clear tag filters
47
+ *
48
+ * @defaultValue false
49
+ */
50
+ allowClear?: boolean;
51
+ }
52
+
53
+ /**
54
+ * Orama Cloud integration
55
+ */
56
+ export default function OramaSearchDialog({
57
+ client,
58
+ searchOptions,
59
+ tags = [],
60
+ defaultTag,
61
+ showOrama = true,
62
+ allowClear = false,
63
+ index,
64
+ footer,
65
+ links = [],
66
+ ...props
67
+ }: OramaSearchDialogProps) {
68
+ const { locale } = useI18n();
69
+ const [tag, setTag] = useState(defaultTag);
70
+ const { search, setSearch, query } = useDocsSearch({
71
+ type: 'orama-cloud',
72
+ client,
73
+ index,
74
+ params: searchOptions,
75
+ locale,
76
+ tag,
77
+ });
78
+
79
+ const defaultItems = useMemo<SortedResult[] | null>(() => {
80
+ if (links.length === 0) return null;
81
+
82
+ return links.map(([name, link]) => ({
83
+ type: 'page',
84
+ id: name,
85
+ content: name,
86
+ url: link,
87
+ }));
88
+ }, [links]);
89
+
90
+ useOnChange(defaultTag, (v) => {
91
+ setTag(v);
92
+ });
93
+
94
+ const label = showOrama && <Label />;
95
+
96
+ return (
97
+ <SearchDialog
98
+ search={search}
99
+ onSearchChange={setSearch}
100
+ isLoading={query.isLoading}
101
+ {...props}
102
+ >
103
+ <SearchDialogOverlay />
104
+ <SearchDialogContent>
105
+ <SearchDialogHeader>
106
+ <SearchDialogIcon />
107
+ <SearchDialogInput />
108
+ <SearchDialogClose />
109
+ </SearchDialogHeader>
110
+ <SearchDialogList
111
+ items={query.data !== 'empty' ? query.data : defaultItems}
112
+ />
113
+ <SearchDialogFooter>
114
+ {tags.length > 0 ? (
115
+ <TagsList tag={tag} onTagChange={setTag} allowClear={allowClear}>
116
+ {tags.map((tag) => (
117
+ <TagsListItem key={tag.value} value={tag.value}>
118
+ {tag.name}
119
+ </TagsListItem>
120
+ ))}
121
+ {label}
122
+ </TagsList>
123
+ ) : (
124
+ label
125
+ )}
126
+ {footer}
127
+ </SearchDialogFooter>
128
+ </SearchDialogContent>
129
+ </SearchDialog>
130
+ );
131
+ }
132
+
133
+ function Label() {
134
+ return (
135
+ <a
136
+ href="https://orama.com"
137
+ rel="noreferrer noopener"
138
+ className="ms-auto text-xs text-fd-muted-foreground"
139
+ >
140
+ Search powered by Orama
141
+ </a>
142
+ );
143
+ }