@terrafirmagreg-team/ui 0.1.0 → 1.2.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.
Files changed (157) hide show
  1. package/package.json +14 -14
  2. package/src/components/blocks/button/icon-button.tsx +1 -0
  3. package/src/components/blocks/canvas/chevron-edge.tsx +126 -18
  4. package/src/components/blocks/canvas/flow-canvas.tsx +4 -0
  5. package/src/components/blocks/canvas/flow-toolbar.tsx +1 -0
  6. package/src/components/blocks/canvas/flow-viewport.tsx +1 -0
  7. package/src/components/blocks/fit-box.tsx +26 -0
  8. package/src/components/blocks/git/github-info.tsx +1 -0
  9. package/src/components/blocks/lang/content.tsx +40 -10
  10. package/src/components/blocks/lang/flag.tsx +1 -0
  11. package/src/components/blocks/lang/link.tsx +1 -0
  12. package/src/components/blocks/lang/redirect.tsx +2 -1
  13. package/src/components/blocks/lang/switcher.tsx +1 -0
  14. package/src/components/blocks/lang/untranslated-callout.tsx +4 -3
  15. package/src/components/blocks/lang/untranslated-chip.tsx +53 -0
  16. package/src/components/blocks/link/link-preview.tsx +2 -1
  17. package/src/components/blocks/list/row.tsx +1 -0
  18. package/src/components/blocks/media/glb/model.tsx +1 -0
  19. package/src/components/blocks/media/glb/scene.tsx +13 -12
  20. package/src/components/blocks/media/glb/viewer.tsx +1 -0
  21. package/src/components/blocks/media/image.tsx +3 -2
  22. package/src/components/blocks/media/slideshow.tsx +4 -3
  23. package/src/components/blocks/nav/breadcrumb.tsx +1 -0
  24. package/src/components/blocks/nav/copy-menu.tsx +37 -0
  25. package/src/components/blocks/nav/link.tsx +1 -0
  26. package/src/components/blocks/nav/site-menu.tsx +1 -0
  27. package/src/components/blocks/nav/site-title.tsx +1 -0
  28. package/src/components/blocks/nav/switcher.tsx +1 -0
  29. package/src/components/blocks/page/empty-state.tsx +1 -0
  30. package/src/components/blocks/page/heading.tsx +3 -2
  31. package/src/components/blocks/page/pager.tsx +110 -2
  32. package/src/components/blocks/readability/fields.tsx +250 -0
  33. package/src/components/blocks/readability/menu.tsx +74 -258
  34. package/src/components/blocks/readability/track.tsx +2 -1
  35. package/src/components/blocks/search/command.tsx +1 -0
  36. package/src/components/blocks/search/dialog.tsx +21 -9
  37. package/src/components/blocks/search/provider.tsx +2 -1
  38. package/src/components/blocks/sidebar.tsx +5 -5
  39. package/src/components/blocks/sprite.tsx +231 -0
  40. package/src/components/blocks/text/format-text.tsx +110 -0
  41. package/src/components/blocks/theme/toggle.tsx +4 -0
  42. package/src/components/blocks/tooltip/hint.tsx +1 -0
  43. package/src/components/blocks/tooltip/lazy.tsx +41 -0
  44. package/src/components/blocks/viewer/controls.tsx +11 -6
  45. package/src/components/blocks/viewer/float-bar.tsx +1 -0
  46. package/src/components/layouts/layout.tsx +2 -1
  47. package/src/components/layouts/left-sidebar/content.tsx +1 -0
  48. package/src/components/layouts/left-sidebar/context.tsx +1 -0
  49. package/src/components/layouts/left-sidebar/expand.tsx +2 -1
  50. package/src/components/layouts/left-sidebar/footer.tsx +10 -4
  51. package/src/components/layouts/left-sidebar/index.ts +1 -0
  52. package/src/components/layouts/left-sidebar/items.tsx +2 -1
  53. package/src/components/layouts/left-sidebar/search.tsx +4 -3
  54. package/src/components/layouts/left-sidebar/sidebar.tsx +3 -2
  55. package/src/components/layouts/left-sidebar/tree.tsx +1 -0
  56. package/src/components/layouts/right-sidebar.tsx +1 -0
  57. package/src/components/layouts/tree-context.tsx +1 -0
  58. package/src/components/mdx/accordion.tsx +3 -2
  59. package/src/components/mdx/banner.tsx +15 -12
  60. package/src/components/mdx/callout.tsx +1 -0
  61. package/src/components/mdx/card.tsx +12 -5
  62. package/src/components/mdx/codeblock.tsx +5 -4
  63. package/src/components/mdx/collapsible.tsx +1 -0
  64. package/src/components/mdx/files.tsx +1 -0
  65. package/src/components/mdx/glow-header.tsx +17 -3
  66. package/src/components/mdx/gradient-text.tsx +1 -0
  67. package/src/components/mdx/icon.tsx +35 -0
  68. package/src/components/mdx/image-zoom.tsx +5 -4
  69. package/src/components/mdx/math.tsx +30 -0
  70. package/src/components/mdx/mermaid.tsx +8 -1
  71. package/src/components/mdx/multiblock.tsx +1 -0
  72. package/src/components/mdx/preview.tsx +2 -1
  73. package/src/components/mdx/steps.tsx +1 -0
  74. package/src/components/mdx/table.tsx +1 -0
  75. package/src/components/mdx/tabs.tsx +2 -1
  76. package/src/components/mdx/type-table.tsx +7 -6
  77. package/src/{provider.tsx → components/provider.tsx} +1 -3
  78. package/src/hooks/canvas/use-flow-hover.ts +1 -0
  79. package/src/hooks/canvas/use-flow-viewport-persist.ts +1 -0
  80. package/src/hooks/page/use-fit-pages.ts +1 -0
  81. package/src/hooks/page/use-location-search.ts +8 -2
  82. package/src/hooks/page/use-page-preview.ts +2 -1
  83. package/src/hooks/page/use-paged-list.ts +1 -0
  84. package/src/hooks/readability/{provider.ts → use-readability.ts} +1 -0
  85. package/src/hooks/search/{dialog.ts → use-dialog.ts} +1 -0
  86. package/src/hooks/search/use-highlight-query.ts +8 -1
  87. package/src/hooks/search/{provider.ts → use-provider.ts} +1 -0
  88. package/src/hooks/use-async-value.ts +6 -3
  89. package/src/hooks/use-close-on-scroll.ts +1 -0
  90. package/src/hooks/use-copy-button.ts +1 -0
  91. package/src/hooks/use-cycle-index.ts +1 -0
  92. package/src/hooks/use-document-title.ts +1 -0
  93. package/src/hooks/use-element-size.ts +1 -0
  94. package/src/hooks/use-external-meta.ts +2 -1
  95. package/src/hooks/use-hot-key.ts +1 -0
  96. package/src/hooks/use-hover-follow.ts +1 -0
  97. package/src/hooks/use-in-view.ts +1 -0
  98. package/src/hooks/{lang.ts → use-lang.ts} +1 -0
  99. package/src/hooks/use-last-value.ts +1 -0
  100. package/src/hooks/{layout.ts → use-layout.ts} +1 -0
  101. package/src/hooks/use-marquee.ts +1 -0
  102. package/src/hooks/use-media-query.ts +1 -0
  103. package/src/hooks/use-mobile.ts +1 -0
  104. package/src/hooks/use-modifier-keys.ts +1 -0
  105. package/src/hooks/use-on-change.ts +1 -0
  106. package/src/hooks/use-pane-keys.ts +1 -0
  107. package/src/hooks/{sidebar.ts → use-sidebar.ts} +1 -0
  108. package/src/hooks/use-swipe.ts +1 -0
  109. package/src/hooks/use-tree.ts +1 -3
  110. package/src/hooks/viewer/use-in-float-bar.ts +1 -0
  111. package/src/hooks/viewer/use-viewer-prefs.ts +1 -0
  112. package/src/lib/canvas/edge.ts +78 -49
  113. package/src/lib/canvas/geometry.ts +1 -0
  114. package/src/lib/canvas/state.ts +1 -0
  115. package/src/lib/canvas/viewport.ts +1 -0
  116. package/src/lib/canvas/xyflow.ts +1 -0
  117. package/src/lib/config.ts +25 -0
  118. package/src/lib/mdx/callout.ts +1 -0
  119. package/src/lib/mdx/gradient.ts +1 -0
  120. package/src/lib/mdx/mermaid.ts +1 -0
  121. package/src/lib/media/crop-opaque.ts +1 -0
  122. package/src/lib/media/nearby-indexes.ts +1 -0
  123. package/src/lib/media/trim-transparent.ts +1 -0
  124. package/src/lib/readability/config.ts +1 -0
  125. package/src/lib/readability/spotlight.ts +1 -0
  126. package/src/lib/readability/store.ts +1 -0
  127. package/src/lib/tree.ts +1 -3
  128. package/src/lib/utils/cn.ts +1 -0
  129. package/src/lib/utils/format.ts +3 -3
  130. package/src/lib/utils/highlight-style.ts +10 -0
  131. package/src/lib/utils/history.ts +1 -0
  132. package/src/lib/utils/merge-refs.ts +1 -0
  133. package/src/lib/utils/nav-active.ts +1 -0
  134. package/src/lib/utils/stored-value.ts +1 -0
  135. package/src/lib/utils/urls.ts +2 -1
  136. package/src/locales/en_us.json +108 -117
  137. package/src/locales/pl_pl.json +33 -35
  138. package/src/locales/ru_ru.json +108 -117
  139. package/src/locales/uk_ua.json +33 -35
  140. package/src/locales/zh_cn.json +15 -17
  141. package/src/locales/zh_tw.json +15 -17
  142. package/src/locales.ts +1 -0
  143. package/src/types/canvas.ts +6 -1
  144. package/src/types/glb.ts +1 -0
  145. package/src/types/mdx-directives.ts +106 -0
  146. package/src/types/nav.ts +1 -0
  147. package/src/types/preview.ts +7 -1
  148. package/src/types/readability.ts +1 -0
  149. package/src/types/search.ts +1 -0
  150. package/src/types/tree.ts +1 -0
  151. package/src/types/viewer.ts +1 -0
  152. package/styles/content/base.css +198 -194
  153. package/styles/content/shiki.css +107 -108
  154. package/styles/content/typography.mjs +3637 -3941
  155. package/styles/content.css +5 -7
  156. package/styles.css +430 -413
  157. package/src/lib/utils/async-cache.ts +0 -32
@@ -8,6 +8,17 @@ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-transl
8
8
 
9
9
  type BannerVariant = 'rainbow' | 'normal';
10
10
 
11
+ export type BannerProps = HTMLAttributes<HTMLDivElement> & {
12
+
13
+ height?: string;
14
+
15
+ variant?: BannerVariant;
16
+
17
+ rainbowColors?: string[];
18
+
19
+ changeLayout?: boolean;
20
+ };
21
+
11
22
  export function Banner({
12
23
  id,
13
24
  variant = 'normal',
@@ -20,16 +31,7 @@ export function Banner({
20
31
  'rgba(131,255,166,0.66)',
21
32
  ],
22
33
  ...props
23
- }: HTMLAttributes<HTMLDivElement> & {
24
-
25
- height?: string;
26
-
27
- variant?: BannerVariant;
28
-
29
- rainbowColors?: string[];
30
-
31
- changeLayout?: boolean;
32
- }) {
34
+ }: BannerProps) {
33
35
  const t = useTranslations();
34
36
  const [open, setOpen] = useState(true);
35
37
  const globalKey = id ? `banner-${encodeBase32(id)}` : null;
@@ -50,7 +52,7 @@ export function Banner({
50
52
  id={id}
51
53
  {...props}
52
54
  className={cn(
53
- 'sticky top-0 z-40 flex flex-row items-center justify-center px-4 text-center text-sm font-medium',
55
+ 'sticky top-0 z-40 flex flex-row items-center justify-center rounded-xl px-4 text-center text-sm font-medium',
54
56
  variant === 'normal' && 'bg-secondary',
55
57
  variant === 'rainbow' && 'bg-background',
56
58
  !open && 'hidden',
@@ -85,7 +87,7 @@ export function Banner({
85
87
  {id ? (
86
88
  <button
87
89
  type="button"
88
- aria-label={t('ui.banner_close', { note: 'aria-label' })}
90
+ aria-label={t('Close Banner', { note: 'ui.banner_close' })}
89
91
  onClick={onClose}
90
92
  className={cn(
91
93
  buttonVariants({
@@ -152,3 +154,4 @@ function encodeBase32(str: string) {
152
154
 
153
155
  return encoded;
154
156
  }
157
+
@@ -94,3 +94,4 @@ export function CalloutDescription({ children, className, ...props }: ComponentP
94
94
  </div>
95
95
  );
96
96
  }
97
+
@@ -2,6 +2,7 @@
2
2
  import { useUiComponents } from '@terrafirmagreg-team/ui/provider';
3
3
  import type { ComponentProps, ComponentType, HTMLAttributes, ReactNode } from 'react';
4
4
  import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
5
+ import { Icon } from '@terrafirmagreg-team/ui/components/mdx/icon';
5
6
 
6
7
  export function Cards(props: HTMLAttributes<HTMLDivElement>) {
7
8
  return (
@@ -12,7 +13,7 @@ export function Cards(props: HTMLAttributes<HTMLDivElement>) {
12
13
  }
13
14
 
14
15
  export type CardProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
15
- icon?: ReactNode;
16
+ icon?: ReactNode | string;
16
17
  title: ReactNode;
17
18
  description?: ReactNode;
18
19
 
@@ -22,11 +23,16 @@ export type CardProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
22
23
 
23
24
  export function Card({ icon, title, description, ...props }: CardProps) {
24
25
  const { Link } = useUiComponents();
25
- const E = (props.href ? Link : 'div') as ComponentType<ComponentProps<'a'>>;
26
+ const { external, href, ...rest } = props;
27
+ const E = (href ? Link : 'div') as ComponentType<ComponentProps<'a'>>;
26
28
 
27
29
  return (
28
30
  <E
29
- {...props}
31
+ {...rest}
32
+ href={href}
33
+ {...(external
34
+ ? { target: '_blank', rel: 'noopener noreferrer' }
35
+ : {})}
30
36
  data-card
31
37
  className={cn(
32
38
  'block rounded-xl border bg-card p-4 text-card-foreground transition-colors @max-lg:col-span-full',
@@ -35,8 +41,8 @@ export function Card({ icon, title, description, ...props }: CardProps) {
35
41
  )}
36
42
  >
37
43
  {icon ? (
38
- <div className="not-prose mb-2 w-fit shadow-md rounded-lg border bg-muted p-1.5 text-muted-foreground [&_svg]:size-4">
39
- {icon}
44
+ <div className="not-prose mb-2 inline-flex h-7 min-w-8 items-center justify-center rounded-lg border bg-muted px-2 text-muted-foreground shadow-md [&_svg]:size-4">
45
+ {typeof icon === 'string' ? <Icon name={icon} /> : icon}
40
46
  </div>
41
47
  ) : null}
42
48
  <h3 className="not-prose mb-1 text-sm font-medium">{title}</h3>
@@ -47,3 +53,4 @@ export function Card({ icon, title, description, ...props }: CardProps) {
47
53
  </E>
48
54
  );
49
55
  }
56
+
@@ -142,7 +142,7 @@ function CopyButton({
142
142
  if (!pre) return;
143
143
 
144
144
  const clone = pre.cloneNode(true) as HTMLElement;
145
- clone.querySelectorAll('.copy-ignore').forEach((node) => {
145
+ clone.querySelectorAll('.copy-ignore, .nd-copy-ignore').forEach((node) => {
146
146
  node.replaceWith('\n');
147
147
  });
148
148
 
@@ -168,8 +168,8 @@ function CopyButton({
168
168
  {checked ? <Check /> : <Clipboard />}
169
169
  <span className="sr-only">
170
170
  {checked
171
- ? t('ui.copied_text', { note: 'aria-label' })
172
- : t('ui.copy_text', { note: 'aria-label' })}
171
+ ? t('Copied Text', { note: 'ui.copied_text' })
172
+ : t('Copy Text', { note: 'ui.copy_text' })}
173
173
  </span>
174
174
  </button>
175
175
  );
@@ -232,7 +232,7 @@ export function CodeBlockTabsTrigger({
232
232
  {...props}
233
233
  className={(s) =>
234
234
  cn(
235
- 'relative group inline-flex text-sm font-medium text-nowrap items-center transition-colors gap-2 px-2 py-1.5 [&_svg]:size-3.5',
235
+ 'relative group inline-flex text-sm font-medium text-nowrap items-center transition-colors gap-2 px-2 py-1.5 [&_svg]:size-3.5 border-b-0',
236
236
  s.active ? 'text-primary' : 'hover:text-accent-foreground',
237
237
  typeof className === 'function' ? className(s) : className,
238
238
  )
@@ -247,3 +247,4 @@ export function CodeBlockTabsTrigger({
247
247
  export function CodeBlockTab(props: ComponentProps<typeof TabsContent>) {
248
248
  return <TabsContent {...props} />;
249
249
  }
250
+
@@ -30,3 +30,4 @@ export function CollapsibleContent({
30
30
  export type CollapsibleProps = Primitive.Root.Props;
31
31
  export type CollapsibleContentProps = Primitive.Panel.Props;
32
32
  export type CollapsibleTriggerProps = Primitive.Trigger.Props;
33
+
@@ -60,3 +60,4 @@ export function Folder({ name, defaultOpen = false, ...props }: FolderProps): Re
60
60
  </Collapsible>
61
61
  );
62
62
  }
63
+
@@ -11,6 +11,7 @@ export function GlowHeader({
11
11
  image,
12
12
  style,
13
13
  className,
14
+ children,
14
15
  ...props
15
16
  }: GradientProps & { fade?: boolean; gradient?: boolean } & ComponentProps<'span'>) {
16
17
  const boxFrom = from ?? '#fa18cf';
@@ -20,7 +21,7 @@ export function GlowHeader({
20
21
  return (
21
22
  <span
22
23
  className={cn(
23
- 'mb-6 inline-block rounded-[20px] border border-transparent px-4 py-2.5 text-center',
24
+ 'relative mb-6 inline-block rounded-[20px] px-4 py-2.5 text-center',
24
25
  className,
25
26
  )}
26
27
  style={{
@@ -31,10 +32,23 @@ export function GlowHeader({
31
32
  WebkitTextFillColor: 'transparent',
32
33
  }),
33
34
  boxShadow: `0 10px 20px color-mix(in srgb, ${boxFrom} 25%, transparent)`,
34
- ...(fade && { borderImage: `${gradientBg} 1` }),
35
35
  ...style,
36
36
  }}
37
37
  {...props}
38
- />
38
+ >
39
+ {children}
40
+ {fade && (
41
+ <span
42
+ aria-hidden
43
+ className="pointer-events-none absolute inset-x-4 -bottom-0.5 h-0.5 rounded-full"
44
+ style={{
45
+ backgroundImage: gradientBg,
46
+ maskImage:
47
+ 'linear-gradient(to right, transparent, black 25%, black 75%, transparent)',
48
+ }}
49
+ />
50
+ )}
51
+ </span>
39
52
  );
40
53
  }
54
+
@@ -25,3 +25,4 @@ export function GradientText({
25
25
  />
26
26
  );
27
27
  }
28
+
@@ -0,0 +1,35 @@
1
+ import { icons, type LucideProps } from 'lucide-react';
2
+ import * as SI from '@icons-pack/react-simple-icons';
3
+ import type { ComponentType } from 'react';
4
+ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
5
+
6
+ const registry = icons as Record<string, ComponentType<LucideProps>>;
7
+ const brands = SI as unknown as Record<string, ComponentType<LucideProps>>;
8
+
9
+ function toPascal(name: string): string {
10
+ return name
11
+ .split(/[-_\s]+/)
12
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
13
+ .join('');
14
+ }
15
+
16
+ export function Icon({
17
+ name,
18
+ className,
19
+ ...props
20
+ }: { name: string } & LucideProps) {
21
+ const Component =
22
+ registry[name] ?? registry[toPascal(name)] ?? brands[`Si${toPascal(name)}`];
23
+ if (!Component) return null;
24
+ return (
25
+ <Component
26
+ className={cn(
27
+ 'inline-block align-[-0.125em]',
28
+ props.size === undefined && 'size-[1em]',
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ );
34
+ }
35
+
@@ -163,7 +163,7 @@ export function Lightbox({ children }: { children: ReactNode }) {
163
163
  <dialog
164
164
  ref={initDialog}
165
165
  tabIndex={-1}
166
- aria-label={t('ui.image_viewer', { note: 'aria-label' })}
166
+ aria-label={t('Image Viewer', { note: 'ui.image_viewer' })}
167
167
  onCancel={(e) => {
168
168
  e.preventDefault();
169
169
  close();
@@ -179,7 +179,7 @@ export function Lightbox({ children }: { children: ReactNode }) {
179
179
  >
180
180
  <button
181
181
  type="button"
182
- aria-label={t('ui.close', { note: 'aria-label' })}
182
+ aria-label={t('Close', { note: 'ui.close' })}
183
183
  onClick={close}
184
184
  className={cn(
185
185
  buttonVariants({
@@ -226,7 +226,7 @@ export function Lightbox({ children }: { children: ReactNode }) {
226
226
  <>
227
227
  <button
228
228
  type="button"
229
- aria-label={t('ui.prev_image', { note: 'aria-label' })}
229
+ aria-label={t('Previous Image', { note: 'ui.prev_image' })}
230
230
  onClick={() => navigate(-1)}
231
231
  className={cn(
232
232
  buttonVariants({
@@ -240,7 +240,7 @@ export function Lightbox({ children }: { children: ReactNode }) {
240
240
  </button>
241
241
  <button
242
242
  type="button"
243
- aria-label={t('ui.next_image', { note: 'aria-label' })}
243
+ aria-label={t('Next Image', { note: 'ui.next_image' })}
244
244
  onClick={() => navigate(1)}
245
245
  className={cn(
246
246
  buttonVariants({
@@ -312,3 +312,4 @@ function transition(from: HTMLElement, update: () => HTMLElement) {
312
312
  if (to) to.style.viewTransitionName = '';
313
313
  });
314
314
  }
315
+
@@ -0,0 +1,30 @@
1
+ import katex from 'katex';
2
+ import { useMemo } from 'react';
3
+ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
4
+
5
+ export function Math({
6
+ children,
7
+ block = false,
8
+ className,
9
+ }: {
10
+ children?: string;
11
+ block?: boolean;
12
+ className?: string;
13
+ }) {
14
+ const html = useMemo(
15
+ () =>
16
+ katex.renderToString(children ?? '', {
17
+ displayMode: block,
18
+ throwOnError: false,
19
+ }),
20
+ [children, block],
21
+ );
22
+ const Tag = block ? 'div' : 'span';
23
+ return (
24
+ <Tag
25
+ className={cn(block && 'my-4 overflow-x-auto text-center', className)}
26
+ dangerouslySetInnerHTML={{ __html: html }}
27
+ />
28
+ );
29
+ }
30
+
@@ -7,10 +7,17 @@ export function Mermaid({ chart }: { chart: string }) {
7
7
  if (svg === null) {
8
8
  return (
9
9
  <CodeBlock title="Mermaid">
10
- <Pre>{chart}</Pre>
10
+ <Pre>
11
+ {chart.split('\n').map((line, i) => (
12
+ <span key={i} className="line">
13
+ {line}
14
+ </span>
15
+ ))}
16
+ </Pre>
11
17
  </CodeBlock>
12
18
  );
13
19
  }
14
20
 
15
21
  return <div dangerouslySetInnerHTML={{ __html: svg }} />;
16
22
  }
23
+
@@ -16,3 +16,4 @@ export function Multiblock({
16
16
  </div>
17
17
  );
18
18
  }
19
+
@@ -17,7 +17,7 @@ function Pane({
17
17
  }) {
18
18
  const t = useTranslations();
19
19
  const text =
20
- label ?? (kind === 'input' ? t('ui.input') : t('ui.output'));
20
+ label ?? (kind === 'input' ? t('Input', { note: 'ui.input' }) : t('Output', { note: 'ui.output' }));
21
21
 
22
22
  return (
23
23
  <div className={cn('min-w-0', className)}>
@@ -98,3 +98,4 @@ function Output({
98
98
  }
99
99
 
100
100
  export { Preview, Input, Output };
101
+
@@ -7,3 +7,4 @@ export function Steps({ children }: { children: ReactNode }) {
7
7
  export function Step({ children }: { children: ReactNode }) {
8
8
  return <div className="fd-step">{children}</div>;
9
9
  }
10
+
@@ -11,3 +11,4 @@ export function Table(props: ComponentProps<'table'>) {
11
11
  </div>
12
12
  );
13
13
  }
14
+
@@ -224,7 +224,7 @@ export function TabsList({ className, ...props }: React.ComponentProps<typeof Ba
224
224
  {...props}
225
225
  className={(s) =>
226
226
  cn(
227
- 'flex gap-3.5 text-secondary-foreground overflow-x-auto px-4 not-prose',
227
+ 'flex gap-3.5 text-secondary-foreground overflow-x-auto px-4 not-prose border-b border-border',
228
228
  typeof className === 'function' ? className(s) : className,
229
229
  )
230
230
  }
@@ -351,3 +351,4 @@ function useCollectionIndex() {
351
351
  function escapeValue(v: string): string {
352
352
  return v.toLowerCase().replace(/\s/, '-');
353
353
  }
354
+
@@ -53,8 +53,8 @@ export function TypeTable({
53
53
  {...props}
54
54
  >
55
55
  <div className="flex font-medium items-center px-3 py-1 not-prose text-muted-foreground">
56
- <p className="w-1/4">{t('ui.prop')}</p>
57
- <p className="@max-xl:hidden">{t('ui.type')}</p>
56
+ <p className="w-1/4">{t('Prop', { note: 'ui.prop' })}</p>
57
+ <p className="@max-xl:hidden">{t('Type', { note: 'ui.type' })}</p>
58
58
  </div>
59
59
  {Object.entries(type).map(([key, value]) => (
60
60
  <Item key={key} parentId={id} name={key} item={value} />
@@ -134,19 +134,19 @@ function Item({
134
134
  </div>
135
135
  {typeDescription && (
136
136
  <>
137
- <p className={cn(fieldVariants())}>{t('ui.type')}</p>
137
+ <p className={cn(fieldVariants())}>{t('Type', { note: 'ui.type' })}</p>
138
138
  <p className="my-auto not-prose">{typeDescription}</p>
139
139
  </>
140
140
  )}
141
141
  {defaultValue && (
142
142
  <>
143
- <p className={cn(fieldVariants())}>{t('ui.default')}</p>
143
+ <p className={cn(fieldVariants())}>{t('Default', { note: 'ui.default' })}</p>
144
144
  <p className="my-auto not-prose">{defaultValue}</p>
145
145
  </>
146
146
  )}
147
147
  {parameters.length > 0 && (
148
148
  <>
149
- <p className={cn(fieldVariants())}>{t('ui.parameters')}</p>
149
+ <p className={cn(fieldVariants())}>{t('Parameters', { note: 'ui.parameters' })}</p>
150
150
  <div className="flex flex-col gap-2">
151
151
  {parameters.map((param) => (
152
152
  <div key={param.name} className="inline-flex items-center flex-wrap gap-1">
@@ -159,7 +159,7 @@ function Item({
159
159
  )}
160
160
  {returns && (
161
161
  <>
162
- <p className={cn(fieldVariants())}>{t('ui.returns')}</p>
162
+ <p className={cn(fieldVariants())}>{t('Returns', { note: 'ui.returns' })}</p>
163
163
  <div className="my-auto prose prose-sm prose-no-margin">{returns}</div>
164
164
  </>
165
165
  )}
@@ -168,3 +168,4 @@ function Item({
168
168
  </Collapsible>
169
169
  );
170
170
  }
171
+
@@ -68,11 +68,9 @@ export interface ThemeOptions extends ThemeProviderProps {
68
68
 
69
69
  enabled?: boolean;
70
70
 
71
-
72
71
  hotKey?: string | ((e: KeyboardEvent) => boolean) | false;
73
72
  }
74
73
 
75
-
76
74
  function ThemeHotKey({
77
75
  hotKey,
78
76
  }: {
@@ -106,7 +104,6 @@ function ThemeHotKey({
106
104
  return null;
107
105
  }
108
106
 
109
-
110
107
  export function UiRootProvider({
111
108
  children,
112
109
  dir = 'ltr',
@@ -139,3 +136,4 @@ export function UiRootProvider({
139
136
 
140
137
  return <DirectionProvider direction={dir}>{body}</DirectionProvider>;
141
138
  }
139
+
@@ -16,3 +16,4 @@ export function useFlowHover(): FlowHoverValue {
16
16
  export function useFlowHoverState(): FlowHoverValue {
17
17
  return useContext(FlowHoverContext);
18
18
  }
19
+
@@ -31,3 +31,4 @@ export function useFlowViewportPersist(storageKey: string, resetKey: string) {
31
31
 
32
32
  return { markReady, persist };
33
33
  }
34
+
@@ -89,3 +89,4 @@ export function useFitPages<T>(
89
89
  // eslint-disable-next-line react-hooks/exhaustive-deps -- dim is item-keyed
90
90
  }, [items, w, h, urlPage, gap, focus, vertical]);
91
91
  }
92
+
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
 
3
- import { useSyncExternalStore } from 'react';
3
+ import { useMemo, useSyncExternalStore } from 'react';
4
4
 
5
- let search = '';
5
+ let search = typeof window === 'undefined' ? '' : window.location.search.slice(1);
6
6
  const listeners = new Set<() => void>();
7
7
 
8
8
  export function publishLocationSearch(value: string) {
@@ -23,3 +23,9 @@ export function useLocationSearch(): string {
23
23
  () => '',
24
24
  );
25
25
  }
26
+
27
+ export function useLocationSearchParams(): URLSearchParams {
28
+ const search = useLocationSearch();
29
+ return useMemo(() => new URLSearchParams(search), [search]);
30
+ }
31
+
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { useEffect, useState } from 'react';
3
3
  import { uiConfig } from '@terrafirmagreg-team/ui/lib/config';
4
- import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
4
+ import { createAsyncCache } from '@terrafirmagreg-team/data/lib/async-cache';
5
5
  import type { PagePreviewData } from '@terrafirmagreg-team/ui/types/preview';
6
6
 
7
7
  const previewCache = createAsyncCache(
@@ -38,3 +38,4 @@ export function usePagePreview(
38
38
  }, [href, active, lang]);
39
39
  return preview;
40
40
  }
41
+
@@ -18,3 +18,4 @@ export function usePagedList<T>(
18
18
  return { page, pages, shown, from, to, total: items.length };
19
19
  }, [items, pageIndex, pageSize]);
20
20
  }
21
+
@@ -84,3 +84,4 @@ export function ReadabilityProvider({ children }: { children: ReactNode }) {
84
84
 
85
85
  return createElement(ReadabilityContext.Provider, { value }, children);
86
86
  }
87
+
@@ -39,3 +39,4 @@ export function useSearchTagsList() {
39
39
  if (!ctx) throw new Error('Missing <SearchTagsList />');
40
40
  return ctx;
41
41
  }
42
+
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
  import { useCallback } from 'react';
3
+ import { injectHighlightStyle } from '@terrafirmagreg-team/ui/lib/utils/highlight-style';
3
4
 
4
5
  function buildRegexFromQuery(query: string): RegExp | null {
5
6
  const source = query
@@ -16,8 +17,13 @@ export function useHighlightQuery(query: string) {
16
17
  if (!element || !regex || typeof Highlight === 'undefined')
17
18
  return () => {};
18
19
  let highlight = CSS.highlights.get('search-hit');
19
- if (!highlight)
20
+ if (!highlight) {
20
21
  CSS.highlights.set('search-hit', (highlight = new Highlight()));
22
+ injectHighlightStyle(
23
+ 'search-hit',
24
+ 'color:var(--color-primary);text-decoration-line:underline',
25
+ );
26
+ }
21
27
  const walker = document.createTreeWalker(
22
28
  element,
23
29
  NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
@@ -49,3 +55,4 @@ export function useHighlightQuery(query: string) {
49
55
  [query],
50
56
  );
51
57
  }
58
+
@@ -23,3 +23,4 @@ export const SearchContext = createContext<SearchContextType>({
23
23
  export function useSearchContext(): SearchContextType {
24
24
  return use(SearchContext);
25
25
  }
26
+
@@ -32,18 +32,19 @@ export function useAsyncValue<T>(
32
32
  export function useAsyncResult<T>(
33
33
  factory: (() => Promise<T>) | false | null | undefined,
34
34
  deps: DependencyList,
35
- ): { value: T | undefined; pending: boolean } {
35
+ ): { value: T | undefined; pending: boolean; error: unknown } {
36
36
  const [entry, setEntry] = useState<{
37
37
  deps: DependencyList;
38
38
  value: T | undefined;
39
+ error?: unknown;
39
40
  }>();
40
41
  useAsyncEffect(async (isCancelled) => {
41
42
  if (!factory) return;
42
43
  try {
43
44
  const next = await factory();
44
45
  if (!isCancelled()) setEntry({ deps, value: next });
45
- } catch {
46
- if (!isCancelled()) setEntry({ deps, value: undefined });
46
+ } catch (error) {
47
+ if (!isCancelled()) setEntry({ deps, value: undefined, error });
47
48
  }
48
49
  }, deps);
49
50
  const resolved =
@@ -53,5 +54,7 @@ export function useAsyncResult<T>(
53
54
  return {
54
55
  value: factory && resolved ? entry?.value : undefined,
55
56
  pending: Boolean(factory) && !resolved,
57
+ error: factory && resolved ? entry?.error : undefined,
56
58
  };
57
59
  }
60
+
@@ -21,3 +21,4 @@ export function useCloseOnScroll(open: boolean, close: () => void): void {
21
21
  return () => window.removeEventListener('scroll', onScroll, true);
22
22
  }, [open]);
23
23
  }
24
+
@@ -44,3 +44,4 @@ export function useCopyButton(onCopy: () => unknown): {
44
44
 
45
45
  return { checked, isLoading, error, onClick };
46
46
  }
47
+
@@ -73,3 +73,4 @@ export function useCycleIndex(
73
73
 
74
74
  return [index, setIndex] as const;
75
75
  }
76
+
@@ -7,3 +7,4 @@ export function useDocumentTitle(title: string): void {
7
7
  document.title = title;
8
8
  }, [title]);
9
9
  }
10
+
@@ -33,3 +33,4 @@ export function useElementSize(ref: RefObject<HTMLElement | null>) {
33
33
 
34
34
  return size;
35
35
  }
36
+
@@ -5,7 +5,7 @@ import {
5
5
  uiConfig,
6
6
  type ExternalMetaProvider,
7
7
  } from '@terrafirmagreg-team/ui/lib/config';
8
- import { createAsyncCache } from '@terrafirmagreg-team/ui/lib/utils/async-cache';
8
+ import { createAsyncCache } from '@terrafirmagreg-team/data/lib/async-cache';
9
9
 
10
10
  export interface ExternalMeta {
11
11
  title: string;
@@ -130,3 +130,4 @@ export function useExternalMeta(
130
130
  }, [href, active]);
131
131
  return meta;
132
132
  }
133
+
@@ -31,3 +31,4 @@ function isTypingTarget(target: EventTarget | null): boolean {
31
31
 
32
32
  return target.closest('[role="dialog"]') !== null;
33
33
  }
34
+
@@ -52,3 +52,4 @@ export function useHoverFollow(): RefCallback<HTMLElement> {
52
52
  return () => controller.abort();
53
53
  }, []);
54
54
  }
55
+
@@ -28,3 +28,4 @@ export function useInView<T extends Element>(
28
28
  }, [ref, rootMargin, inView]);
29
29
  return inView;
30
30
  }
31
+