@terrafirmagreg-team/ui 0.0.0-stage → 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 (225) hide show
  1. package/package.json +93 -3
  2. package/src/components/blocks/button/icon-button.tsx +49 -0
  3. package/src/components/blocks/canvas/chevron-edge.tsx +242 -0
  4. package/src/components/blocks/canvas/flow-canvas.tsx +181 -0
  5. package/src/components/blocks/canvas/flow-toolbar.tsx +33 -0
  6. package/src/components/blocks/canvas/flow-viewport.tsx +244 -0
  7. package/src/components/blocks/fit-box.tsx +26 -0
  8. package/src/components/blocks/git/github-info.tsx +78 -0
  9. package/src/components/blocks/lang/content.tsx +52 -0
  10. package/src/components/blocks/lang/flag.tsx +25 -0
  11. package/src/components/blocks/lang/link.tsx +23 -0
  12. package/src/components/blocks/lang/redirect.tsx +48 -0
  13. package/src/components/blocks/lang/switcher.tsx +54 -0
  14. package/src/components/blocks/lang/untranslated-callout.tsx +29 -0
  15. package/src/components/blocks/lang/untranslated-chip.tsx +53 -0
  16. package/src/components/blocks/link/link-preview.tsx +257 -0
  17. package/src/components/blocks/list/row.tsx +92 -0
  18. package/src/components/blocks/media/glb/model.tsx +244 -0
  19. package/src/components/blocks/media/glb/scene.tsx +373 -0
  20. package/src/components/blocks/media/glb/viewer.tsx +52 -0
  21. package/src/components/blocks/media/image.tsx +136 -0
  22. package/src/components/blocks/media/slideshow.tsx +178 -0
  23. package/src/components/blocks/nav/breadcrumb.tsx +51 -0
  24. package/src/components/blocks/nav/copy-menu.tsx +37 -0
  25. package/src/components/blocks/nav/link.tsx +86 -0
  26. package/src/components/blocks/nav/site-menu.tsx +78 -0
  27. package/src/components/blocks/nav/site-title.tsx +61 -0
  28. package/src/components/blocks/nav/switcher.tsx +206 -0
  29. package/src/components/blocks/page/empty-state.tsx +32 -0
  30. package/src/components/blocks/page/heading.tsx +54 -0
  31. package/src/components/blocks/page/pager.tsx +220 -0
  32. package/src/components/blocks/readability/fields.tsx +250 -0
  33. package/src/components/blocks/readability/menu.tsx +247 -0
  34. package/src/components/blocks/readability/track.tsx +165 -0
  35. package/src/components/blocks/search/command.tsx +80 -0
  36. package/src/components/blocks/search/dialog.tsx +724 -0
  37. package/src/components/blocks/search/provider.tsx +118 -0
  38. package/src/components/blocks/sidebar.tsx +406 -0
  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 +75 -0
  42. package/src/components/blocks/tooltip/hint.tsx +28 -0
  43. package/src/components/blocks/tooltip/lazy.tsx +41 -0
  44. package/src/components/blocks/viewer/controls.tsx +284 -0
  45. package/src/components/blocks/viewer/float-bar.tsx +51 -0
  46. package/src/components/layouts/layout.tsx +94 -0
  47. package/src/components/layouts/left-sidebar/content.tsx +63 -0
  48. package/src/components/layouts/left-sidebar/context.tsx +55 -0
  49. package/src/components/layouts/left-sidebar/expand.tsx +23 -0
  50. package/src/components/layouts/left-sidebar/footer.tsx +111 -0
  51. package/src/components/layouts/left-sidebar/index.ts +29 -0
  52. package/src/components/layouts/left-sidebar/items.tsx +103 -0
  53. package/src/components/layouts/left-sidebar/search.tsx +75 -0
  54. package/src/components/layouts/left-sidebar/sidebar.tsx +189 -0
  55. package/src/components/layouts/left-sidebar/tree.tsx +157 -0
  56. package/src/components/layouts/right-sidebar.tsx +96 -0
  57. package/src/components/layouts/tree-context.tsx +42 -0
  58. package/src/components/mdx/accordion.tsx +185 -0
  59. package/src/components/mdx/banner.tsx +157 -0
  60. package/src/components/mdx/callout.tsx +97 -0
  61. package/src/components/mdx/card.tsx +56 -0
  62. package/src/components/mdx/codeblock.tsx +250 -0
  63. package/src/components/mdx/collapsible.tsx +33 -0
  64. package/src/components/mdx/files.tsx +63 -0
  65. package/src/components/mdx/glow-header.tsx +54 -0
  66. package/src/components/mdx/gradient-text.tsx +28 -0
  67. package/src/components/mdx/icon.tsx +35 -0
  68. package/src/components/mdx/image-zoom.tsx +315 -0
  69. package/src/components/mdx/math.tsx +30 -0
  70. package/src/components/mdx/mermaid.tsx +23 -0
  71. package/src/components/mdx/multiblock.tsx +19 -0
  72. package/src/components/mdx/preview.tsx +101 -0
  73. package/src/components/mdx/steps.tsx +10 -0
  74. package/src/components/mdx/table.tsx +14 -0
  75. package/src/components/mdx/tabs.tsx +354 -0
  76. package/src/components/mdx/type-table.tsx +171 -0
  77. package/src/components/provider.tsx +139 -0
  78. package/src/components/ui/accordion.tsx +71 -0
  79. package/src/components/ui/alert-dialog.tsx +187 -0
  80. package/src/components/ui/alert.tsx +75 -0
  81. package/src/components/ui/aspect-ratio.tsx +22 -0
  82. package/src/components/ui/attachment.tsx +207 -0
  83. package/src/components/ui/avatar.tsx +108 -0
  84. package/src/components/ui/badge.tsx +51 -0
  85. package/src/components/ui/breadcrumb.tsx +124 -0
  86. package/src/components/ui/bubble.tsx +127 -0
  87. package/src/components/ui/button-group.tsx +87 -0
  88. package/src/components/ui/button.tsx +57 -0
  89. package/src/components/ui/calendar.tsx +221 -0
  90. package/src/components/ui/card.tsx +102 -0
  91. package/src/components/ui/carousel.tsx +242 -0
  92. package/src/components/ui/chart.tsx +371 -0
  93. package/src/components/ui/checkbox.tsx +28 -0
  94. package/src/components/ui/collapsible.tsx +21 -0
  95. package/src/components/ui/combobox.tsx +297 -0
  96. package/src/components/ui/command.tsx +198 -0
  97. package/src/components/ui/context-menu.tsx +270 -0
  98. package/src/components/ui/dialog.tsx +160 -0
  99. package/src/components/ui/direction.tsx +6 -0
  100. package/src/components/ui/drawer.tsx +227 -0
  101. package/src/components/ui/dropdown-menu.tsx +269 -0
  102. package/src/components/ui/empty.tsx +103 -0
  103. package/src/components/ui/field.tsx +238 -0
  104. package/src/components/ui/hover-card.tsx +50 -0
  105. package/src/components/ui/input-group.tsx +158 -0
  106. package/src/components/ui/input-otp.tsx +86 -0
  107. package/src/components/ui/input.tsx +19 -0
  108. package/src/components/ui/item.tsx +201 -0
  109. package/src/components/ui/kbd.tsx +26 -0
  110. package/src/components/ui/label.tsx +19 -0
  111. package/src/components/ui/marker.tsx +70 -0
  112. package/src/components/ui/menubar.tsx +280 -0
  113. package/src/components/ui/message-scroller.tsx +131 -0
  114. package/src/components/ui/message.tsx +91 -0
  115. package/src/components/ui/native-select.tsx +60 -0
  116. package/src/components/ui/navigation-menu.tsx +167 -0
  117. package/src/components/ui/pagination.tsx +130 -0
  118. package/src/components/ui/popover.tsx +91 -0
  119. package/src/components/ui/progress.tsx +82 -0
  120. package/src/components/ui/questionnaire.tsx +324 -0
  121. package/src/components/ui/radio-group.tsx +37 -0
  122. package/src/components/ui/resizable.tsx +49 -0
  123. package/src/components/ui/scroll-area.tsx +54 -0
  124. package/src/components/ui/select.tsx +200 -0
  125. package/src/components/ui/separator.tsx +24 -0
  126. package/src/components/ui/sheet.tsx +138 -0
  127. package/src/components/ui/sidebar.tsx +730 -0
  128. package/src/components/ui/skeleton.tsx +13 -0
  129. package/src/components/ui/slider.tsx +51 -0
  130. package/src/components/ui/spinner.tsx +10 -0
  131. package/src/components/ui/switch.tsx +31 -0
  132. package/src/components/ui/table.tsx +115 -0
  133. package/src/components/ui/tabs.tsx +81 -0
  134. package/src/components/ui/textarea.tsx +17 -0
  135. package/src/components/ui/toast.tsx +234 -0
  136. package/src/components/ui/toggle-group.tsx +89 -0
  137. package/src/components/ui/toggle.tsx +44 -0
  138. package/src/components/ui/tooltip.tsx +65 -0
  139. package/src/hooks/canvas/use-flow-hover.ts +19 -0
  140. package/src/hooks/canvas/use-flow-viewport-persist.ts +34 -0
  141. package/src/hooks/page/use-fit-pages.ts +92 -0
  142. package/src/hooks/page/use-location-search.ts +31 -0
  143. package/src/hooks/page/use-page-preview.ts +41 -0
  144. package/src/hooks/page/use-paged-list.ts +21 -0
  145. package/src/hooks/readability/use-readability.ts +87 -0
  146. package/src/hooks/search/use-dialog.ts +42 -0
  147. package/src/hooks/search/use-highlight-query.ts +58 -0
  148. package/src/hooks/search/use-provider.ts +26 -0
  149. package/src/hooks/use-async-value.ts +60 -0
  150. package/src/hooks/use-close-on-scroll.ts +24 -0
  151. package/src/hooks/use-copy-button.ts +47 -0
  152. package/src/hooks/use-cycle-index.ts +76 -0
  153. package/src/hooks/use-document-title.ts +10 -0
  154. package/src/hooks/use-element-size.ts +36 -0
  155. package/src/hooks/use-external-meta.ts +133 -0
  156. package/src/hooks/use-hot-key.ts +34 -0
  157. package/src/hooks/use-hover-follow.ts +55 -0
  158. package/src/hooks/use-in-view.ts +31 -0
  159. package/src/hooks/use-lang.ts +9 -0
  160. package/src/hooks/use-last-value.ts +16 -0
  161. package/src/hooks/use-layout.ts +19 -0
  162. package/src/hooks/use-marquee.ts +52 -0
  163. package/src/hooks/use-media-query.ts +15 -0
  164. package/src/hooks/use-mobile.ts +20 -0
  165. package/src/hooks/use-modifier-keys.ts +32 -0
  166. package/src/hooks/use-on-change.ts +36 -0
  167. package/src/hooks/use-pane-keys.ts +29 -0
  168. package/src/hooks/use-sidebar.ts +64 -0
  169. package/src/hooks/use-swipe.ts +27 -0
  170. package/src/hooks/use-tree.ts +100 -0
  171. package/src/hooks/viewer/use-in-float-bar.ts +7 -0
  172. package/src/hooks/viewer/use-viewer-prefs.ts +78 -0
  173. package/src/lib/canvas/edge.ts +202 -0
  174. package/src/lib/canvas/geometry.ts +54 -0
  175. package/src/lib/canvas/state.ts +48 -0
  176. package/src/lib/canvas/viewport.ts +38 -0
  177. package/src/lib/canvas/xyflow.ts +2 -0
  178. package/src/lib/config.ts +106 -0
  179. package/src/lib/mdx/callout.ts +21 -0
  180. package/src/lib/mdx/gradient.ts +16 -0
  181. package/src/lib/mdx/mermaid.ts +15 -0
  182. package/src/lib/media/crop-opaque.ts +77 -0
  183. package/src/lib/media/nearby-indexes.ts +7 -0
  184. package/src/lib/media/trim-transparent.ts +118 -0
  185. package/src/lib/readability/config.ts +11 -0
  186. package/src/lib/readability/spotlight.ts +187 -0
  187. package/src/lib/readability/store.ts +147 -0
  188. package/src/lib/tree.ts +90 -0
  189. package/src/lib/utils/cn.ts +2 -0
  190. package/src/lib/utils/format.ts +22 -0
  191. package/src/lib/utils/highlight-style.ts +10 -0
  192. package/src/lib/utils/history.ts +10 -0
  193. package/src/lib/utils/merge-refs.ts +14 -0
  194. package/src/lib/utils/nav-active.ts +59 -0
  195. package/src/lib/utils/stored-value.ts +137 -0
  196. package/src/lib/utils/urls.ts +18 -0
  197. package/src/locales/en_us.json +110 -0
  198. package/src/locales/pl_pl.json +33 -0
  199. package/src/locales/ru_ru.json +110 -0
  200. package/src/locales/uk_ua.json +33 -0
  201. package/src/locales/zh_cn.json +15 -0
  202. package/src/locales/zh_tw.json +15 -0
  203. package/src/locales.ts +6 -0
  204. package/src/types/canvas.ts +54 -0
  205. package/src/types/glb.ts +26 -0
  206. package/src/types/mdx-directives.ts +106 -0
  207. package/src/types/nav.ts +96 -0
  208. package/src/types/preview.ts +15 -0
  209. package/src/types/readability.ts +29 -0
  210. package/src/types/search.ts +57 -0
  211. package/src/types/tree.ts +19 -0
  212. package/src/types/viewer.ts +54 -0
  213. package/styles/content/base.css +198 -0
  214. package/styles/content/default-colors.css +51 -0
  215. package/styles/content/shiki.css +107 -0
  216. package/styles/content/typography.mjs +3637 -0
  217. package/styles/content.css +5 -0
  218. package/styles/image-zoom.css +83 -0
  219. package/styles/layout.css +28 -0
  220. package/styles/left-sidebar.css +140 -0
  221. package/styles/readability.css +50 -0
  222. package/styles/right-sidebar.css +6 -0
  223. package/styles/workspace.css +26 -0
  224. package/styles.css +430 -0
  225. package/README.md +0 -3
@@ -0,0 +1,185 @@
1
+ 'use client';
2
+
3
+ import { Check, LinkIcon } from 'lucide-react';
4
+ import { type ComponentProps, type ReactNode, useEffect, useRef, useState } from 'react';
5
+ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
6
+ import { Accordion as Primitive } from '@base-ui/react/accordion';
7
+ import { ChevronRight } from 'lucide-react';
8
+ import { useCopyButton } from '@terrafirmagreg-team/ui/hooks/use-copy-button';
9
+ import { buttonVariants } from '@terrafirmagreg-team/ui/components/ui/button';
10
+ import { mergeRefs } from '@terrafirmagreg-team/ui/lib/utils/merge-refs';
11
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
12
+
13
+ export function AccordionRoot({ className, ...props }: ComponentProps<typeof Primitive.Root>) {
14
+ return (
15
+ <Primitive.Root
16
+ className={(s) =>
17
+ cn(
18
+ 'divide-y divide-border overflow-hidden rounded-lg border bg-card',
19
+ typeof className === 'function' ? className(s) : className,
20
+ )
21
+ }
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+
27
+ export function AccordionItem({ children, ...props }: ComponentProps<typeof Primitive.Item>) {
28
+ return <Primitive.Item {...props}>{children}</Primitive.Item>;
29
+ }
30
+
31
+ export function AccordionHeader({
32
+ className,
33
+ children,
34
+ ...props
35
+ }: ComponentProps<typeof Primitive.Header>) {
36
+ return (
37
+ <Primitive.Header
38
+ className={(s) =>
39
+ cn(
40
+ 'scroll-m-24 not-prose flex flex-row items-center text-card-foreground font-medium has-focus-visible:bg-accent',
41
+ typeof className === 'function' ? className(s) : className,
42
+ )
43
+ }
44
+ {...props}
45
+ >
46
+ {children}
47
+ </Primitive.Header>
48
+ );
49
+ }
50
+
51
+ export function AccordionTrigger({
52
+ className,
53
+ children,
54
+ ...props
55
+ }: ComponentProps<typeof Primitive.Trigger>) {
56
+ return (
57
+ <Primitive.Trigger
58
+ className={(s) =>
59
+ cn(
60
+ 'group flex flex-1 items-center gap-2 px-3 py-2.5 text-start focus-visible:outline-none',
61
+ typeof className === 'function' ? className(s) : className,
62
+ )
63
+ }
64
+ {...props}
65
+ >
66
+ <ChevronRight className="size-4 shrink-0 text-muted-foreground transition-transform duration-200 group-data-panel-open:rotate-90" />
67
+ {children}
68
+ </Primitive.Trigger>
69
+ );
70
+ }
71
+
72
+ export function AccordionContent({
73
+ className,
74
+ children,
75
+ ...props
76
+ }: ComponentProps<typeof Primitive.Panel>) {
77
+ return (
78
+ <Primitive.Panel
79
+ className={(s) =>
80
+ cn(
81
+ 'h-(--accordion-panel-height) overflow-hidden transition-[height] ease-out data-ending-style:h-0 data-starting-style:h-0',
82
+ typeof className === 'function' ? className(s) : className,
83
+ )
84
+ }
85
+ {...props}
86
+ >
87
+ {children}
88
+ </Primitive.Panel>
89
+ );
90
+ }
91
+
92
+ export function Accordions({
93
+ ref,
94
+ className,
95
+ defaultValue,
96
+ ...props
97
+ }: ComponentProps<typeof AccordionRoot>) {
98
+ const rootRef = useRef<HTMLDivElement>(null);
99
+ const composedRef = mergeRefs(ref, rootRef);
100
+ const [value, setValue] = useState<unknown[]>(defaultValue ?? []);
101
+
102
+ useEffect(() => {
103
+ const id = window.location.hash.substring(1);
104
+ const element = rootRef.current;
105
+ if (!element || id.length === 0) return;
106
+
107
+ const selected = document.getElementById(id);
108
+ if (!selected || !element.contains(selected)) return;
109
+ const value = selected.getAttribute('data-accordion-value');
110
+
111
+ if (value) setValue((prev) => [value, ...prev]);
112
+ }, []);
113
+
114
+ return (
115
+ <AccordionRoot
116
+ ref={composedRef}
117
+ value={value}
118
+ onValueChange={setValue}
119
+ className={(s) =>
120
+ cn(
121
+ 'divide-y divide-border overflow-hidden rounded-lg border bg-card',
122
+ typeof className === 'function' ? className(s) : className,
123
+ )
124
+ }
125
+ {...props}
126
+ />
127
+ );
128
+ }
129
+
130
+ export function Accordion({
131
+ title,
132
+ id,
133
+ value = String(title),
134
+ children,
135
+ ...props
136
+ }: Omit<ComponentProps<typeof AccordionItem>, 'value' | 'title'> & {
137
+ title: string | ReactNode;
138
+ value?: string;
139
+ }) {
140
+ return (
141
+ <AccordionItem value={value} {...props}>
142
+ <AccordionHeader id={id} data-accordion-value={value}>
143
+ <AccordionTrigger>{title}</AccordionTrigger>
144
+ {id ? <CopyButton id={id} /> : null}
145
+ </AccordionHeader>
146
+ <AccordionContent hiddenUntilFound>
147
+ <div className="px-4 pb-2 text-[0.9375rem] prose-no-margin [&[hidden]:not([hidden='until-found'])]:hidden">
148
+ {children}
149
+ </div>
150
+ </AccordionContent>
151
+ </AccordionItem>
152
+ );
153
+ }
154
+
155
+ function CopyButton({ id }: { id: string }) {
156
+ const t = useTranslations();
157
+ const { checked, onClick } = useCopyButton(() => {
158
+ const url = new URL(window.location.href);
159
+ url.hash = id;
160
+
161
+ return navigator.clipboard.writeText(url.toString());
162
+ });
163
+
164
+ return (
165
+ <button
166
+ type="button"
167
+ aria-live="polite"
168
+ className={cn(
169
+ buttonVariants({
170
+ variant: 'ghost',
171
+ className: 'text-muted-foreground me-2',
172
+ }),
173
+ )}
174
+ onClick={onClick}
175
+ >
176
+ {checked ? <Check className="size-3.5" /> : <LinkIcon className="size-3.5" />}
177
+ <span className="sr-only">
178
+ {checked
179
+ ? t('Copied Link', { note: 'ui.copied_link' })
180
+ : t('Copy Link', { note: 'ui.copy_link' })}
181
+ </span>
182
+ </button>
183
+ );
184
+ }
185
+
@@ -0,0 +1,157 @@
1
+ 'use client';
2
+
3
+ import { type HTMLAttributes, useEffect, useState } from 'react';
4
+ import { X } from 'lucide-react';
5
+ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
6
+ import { buttonVariants } from '@terrafirmagreg-team/ui/components/ui/button';
7
+ import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
8
+
9
+ type BannerVariant = 'rainbow' | 'normal';
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
+
22
+ export function Banner({
23
+ id,
24
+ variant = 'normal',
25
+ changeLayout = true,
26
+ height = '3rem',
27
+ rainbowColors = [
28
+ 'rgba(0,149,255,0.56)',
29
+ 'rgba(231,77,255,0.77)',
30
+ 'rgba(255,0,0,0.73)',
31
+ 'rgba(131,255,166,0.66)',
32
+ ],
33
+ ...props
34
+ }: BannerProps) {
35
+ const t = useTranslations();
36
+ const [open, setOpen] = useState(true);
37
+ const globalKey = id ? `banner-${encodeBase32(id)}` : null;
38
+
39
+ useEffect(() => {
40
+ if (globalKey && localStorage.getItem(globalKey) === 'true') setOpen(false);
41
+ }, [globalKey]);
42
+
43
+ function onClose() {
44
+ setOpen(false);
45
+ if (globalKey) localStorage.setItem(globalKey, 'true');
46
+ }
47
+
48
+ if (!open) return null;
49
+
50
+ return (
51
+ <div
52
+ id={id}
53
+ {...props}
54
+ className={cn(
55
+ 'sticky top-0 z-40 flex flex-row items-center justify-center rounded-xl px-4 text-center text-sm font-medium',
56
+ variant === 'normal' && 'bg-secondary',
57
+ variant === 'rainbow' && 'bg-background',
58
+ !open && 'hidden',
59
+ props.className,
60
+ )}
61
+ style={{
62
+ height,
63
+ }}
64
+ >
65
+ {changeLayout && open ? (
66
+ <style>
67
+ {globalKey
68
+ ? `:root:not(.${globalKey}) { --banner-height: ${height}; }`
69
+ : `:root { --banner-height: ${height}; }`}
70
+ </style>
71
+ ) : null}
72
+ {globalKey ? <style>{`.${globalKey} #${id} { display: none; }`}</style> : null}
73
+ {globalKey ? (
74
+ <script
75
+ dangerouslySetInnerHTML={{
76
+ __html: `if (localStorage.getItem('${globalKey}') === 'true') document.documentElement.classList.add('${globalKey}');`,
77
+ }}
78
+ />
79
+ ) : null}
80
+
81
+ {variant === 'rainbow'
82
+ ? flow({
83
+ colors: rainbowColors,
84
+ })
85
+ : null}
86
+ {props.children}
87
+ {id ? (
88
+ <button
89
+ type="button"
90
+ aria-label={t('Close Banner', { note: 'ui.banner_close' })}
91
+ onClick={onClose}
92
+ className={cn(
93
+ buttonVariants({
94
+ variant: 'ghost',
95
+ className: 'absolute inset-e-2 top-1/2 -translate-y-1/2 text-muted-foreground/50',
96
+ size: 'icon-sm',
97
+ }),
98
+ )}
99
+ >
100
+ <X />
101
+ </button>
102
+ ) : null}
103
+ </div>
104
+ );
105
+ }
106
+
107
+ const maskImage =
108
+ 'linear-gradient(to bottom,white,transparent), radial-gradient(circle at top center, white, transparent)';
109
+
110
+ function flow({ colors }: { colors: string[] }) {
111
+ return (
112
+ <>
113
+ <div
114
+ className="absolute inset-0 -z-1"
115
+ style={{
116
+ maskImage,
117
+ maskComposite: 'intersect',
118
+ animation: 'moving-banner 20s linear infinite',
119
+ backgroundImage: `repeating-linear-gradient(70deg, ${[...colors, colors[0]].map((color, i) => `${color} ${(i * 50) / colors.length}%`).join(', ')})`,
120
+ backgroundSize: '200% 100%',
121
+ filter: 'saturate(2)',
122
+ }}
123
+ />
124
+ <style>
125
+ {`@keyframes moving-banner {
126
+ from { background-position: 0% 0; }
127
+ to { background-position: 100% 0; }
128
+ }`}
129
+ </style>
130
+ </>
131
+ );
132
+ }
133
+
134
+ function encodeBase32(str: string) {
135
+ const alphabet = 'abcdefghijklmnopqrstuvwxyz234567';
136
+ let encoded = '';
137
+
138
+ let buffer = 0;
139
+ let bitsLeft = 0;
140
+
141
+ for (let i = 0; i < str.length; i++) {
142
+ buffer = (buffer << 8) | str.charCodeAt(i);
143
+ bitsLeft += 8;
144
+
145
+ while (bitsLeft >= 5) {
146
+ bitsLeft -= 5;
147
+ encoded += alphabet[(buffer >> bitsLeft) & 31];
148
+ }
149
+ }
150
+
151
+ if (bitsLeft > 0) {
152
+ encoded += alphabet[(buffer << (5 - bitsLeft)) & 31];
153
+ }
154
+
155
+ return encoded;
156
+ }
157
+
@@ -0,0 +1,97 @@
1
+ import { CircleCheck, CircleX, Info, Lightbulb, StickyNote, TriangleAlert } from 'lucide-react';
2
+ import type { ComponentProps, CSSProperties, ReactNode } from 'react';
3
+ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
4
+ import { calloutStyle } from '@terrafirmagreg-team/ui/lib/mdx/callout';
5
+
6
+ export type CalloutType =
7
+ | 'info'
8
+ | 'note'
9
+ | 'tip'
10
+ | 'warn'
11
+ | 'warning'
12
+ | 'error'
13
+ | 'danger'
14
+ | 'success'
15
+ | 'idea';
16
+
17
+ const iconClass = 'size-5 -me-0.5 fill-(--callout-color) text-card';
18
+
19
+ const ICONS: Record<string, ReactNode> = {
20
+ info: <Info className={iconClass} />,
21
+ note: <StickyNote className={iconClass} />,
22
+ tip: (
23
+ <Lightbulb className="size-5 -me-0.5 fill-(--callout-color) text-(--callout-color)" />
24
+ ),
25
+ warn: <TriangleAlert className={iconClass} />,
26
+ warning: <TriangleAlert className={iconClass} />,
27
+ error: <CircleX className={iconClass} />,
28
+ danger: <CircleX className={iconClass} />,
29
+ success: <CircleCheck className={iconClass} />,
30
+ idea: (
31
+ <Lightbulb className="size-5 -me-0.5 fill-(--callout-color) text-(--callout-color)" />
32
+ ),
33
+ };
34
+
35
+ export function Callout({
36
+ children,
37
+ title,
38
+ ...props
39
+ }: { title?: ReactNode } & Omit<CalloutContainerProps, 'title'>) {
40
+ return (
41
+ <CalloutContainer {...props}>
42
+ {title && <CalloutTitle>{title}</CalloutTitle>}
43
+ <CalloutDescription>{children}</CalloutDescription>
44
+ </CalloutContainer>
45
+ );
46
+ }
47
+
48
+ export interface CalloutContainerProps extends ComponentProps<'div'> {
49
+
50
+ type?: CalloutType;
51
+
52
+ icon?: ReactNode;
53
+ }
54
+
55
+ export function CalloutContainer({
56
+ type = 'info',
57
+ icon,
58
+ children,
59
+ className,
60
+ style,
61
+ ...props
62
+ }: CalloutContainerProps) {
63
+ return (
64
+ <div
65
+ className={cn(
66
+ 'flex gap-2 my-4 rounded-xl border bg-card p-3 ps-1 text-sm text-card-foreground',
67
+ className,
68
+ )}
69
+ style={calloutStyle(type, style)}
70
+ {...props}
71
+ >
72
+ <div role="none" className="w-0.5 bg-(--callout-color)/50 rounded-sm" />
73
+ {icon ?? ICONS[type]}
74
+ <div className="flex flex-col gap-2 min-w-0 flex-1">{children}</div>
75
+ </div>
76
+ );
77
+ }
78
+
79
+ export function CalloutTitle({ children, className, ...props }: ComponentProps<'p'>) {
80
+ return (
81
+ <p className={cn('font-medium my-0!', className)} {...props}>
82
+ {children}
83
+ </p>
84
+ );
85
+ }
86
+
87
+ export function CalloutDescription({ children, className, ...props }: ComponentProps<'p'>) {
88
+ return (
89
+ <div
90
+ className={cn('text-muted-foreground prose-no-margin empty:hidden', className)}
91
+ {...props}
92
+ >
93
+ {children}
94
+ </div>
95
+ );
96
+ }
97
+
@@ -0,0 +1,56 @@
1
+ 'use client';
2
+ import { useUiComponents } from '@terrafirmagreg-team/ui/provider';
3
+ import type { ComponentProps, ComponentType, HTMLAttributes, ReactNode } from 'react';
4
+ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
5
+ import { Icon } from '@terrafirmagreg-team/ui/components/mdx/icon';
6
+
7
+ export function Cards(props: HTMLAttributes<HTMLDivElement>) {
8
+ return (
9
+ <div {...props} className={cn('grid grid-cols-2 gap-3 @container', props.className)}>
10
+ {props.children}
11
+ </div>
12
+ );
13
+ }
14
+
15
+ export type CardProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
16
+ icon?: ReactNode | string;
17
+ title: ReactNode;
18
+ description?: ReactNode;
19
+
20
+ href?: string;
21
+ external?: boolean;
22
+ };
23
+
24
+ export function Card({ icon, title, description, ...props }: CardProps) {
25
+ const { Link } = useUiComponents();
26
+ const { external, href, ...rest } = props;
27
+ const E = (href ? Link : 'div') as ComponentType<ComponentProps<'a'>>;
28
+
29
+ return (
30
+ <E
31
+ {...rest}
32
+ href={href}
33
+ {...(external
34
+ ? { target: '_blank', rel: 'noopener noreferrer' }
35
+ : {})}
36
+ data-card
37
+ className={cn(
38
+ 'block rounded-xl border bg-card p-4 text-card-foreground transition-colors @max-lg:col-span-full',
39
+ props.href && 'hover:bg-accent/80',
40
+ props.className,
41
+ )}
42
+ >
43
+ {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}
46
+ </div>
47
+ ) : null}
48
+ <h3 className="not-prose mb-1 text-sm font-medium">{title}</h3>
49
+ {description ? <p className="my-0! text-sm text-muted-foreground">{description}</p> : null}
50
+ <div className="text-sm text-muted-foreground prose-no-margin empty:hidden">
51
+ {props.children}
52
+ </div>
53
+ </E>
54
+ );
55
+ }
56
+