@terrafirmagreg-team/ui 0.0.0-stage → 0.1.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 (215) hide show
  1. package/package.json +93 -3
  2. package/src/components/blocks/button/icon-button.tsx +48 -0
  3. package/src/components/blocks/canvas/chevron-edge.tsx +134 -0
  4. package/src/components/blocks/canvas/flow-canvas.tsx +177 -0
  5. package/src/components/blocks/canvas/flow-toolbar.tsx +32 -0
  6. package/src/components/blocks/canvas/flow-viewport.tsx +243 -0
  7. package/src/components/blocks/git/github-info.tsx +77 -0
  8. package/src/components/blocks/lang/content.tsx +22 -0
  9. package/src/components/blocks/lang/flag.tsx +24 -0
  10. package/src/components/blocks/lang/link.tsx +22 -0
  11. package/src/components/blocks/lang/redirect.tsx +47 -0
  12. package/src/components/blocks/lang/switcher.tsx +53 -0
  13. package/src/components/blocks/lang/untranslated-callout.tsx +28 -0
  14. package/src/components/blocks/link/link-preview.tsx +256 -0
  15. package/src/components/blocks/list/row.tsx +91 -0
  16. package/src/components/blocks/media/glb/model.tsx +243 -0
  17. package/src/components/blocks/media/glb/scene.tsx +372 -0
  18. package/src/components/blocks/media/glb/viewer.tsx +51 -0
  19. package/src/components/blocks/media/image.tsx +135 -0
  20. package/src/components/blocks/media/slideshow.tsx +177 -0
  21. package/src/components/blocks/nav/breadcrumb.tsx +50 -0
  22. package/src/components/blocks/nav/link.tsx +85 -0
  23. package/src/components/blocks/nav/site-menu.tsx +77 -0
  24. package/src/components/blocks/nav/site-title.tsx +60 -0
  25. package/src/components/blocks/nav/switcher.tsx +205 -0
  26. package/src/components/blocks/page/empty-state.tsx +31 -0
  27. package/src/components/blocks/page/heading.tsx +53 -0
  28. package/src/components/blocks/page/pager.tsx +112 -0
  29. package/src/components/blocks/readability/menu.tsx +431 -0
  30. package/src/components/blocks/readability/track.tsx +164 -0
  31. package/src/components/blocks/search/command.tsx +79 -0
  32. package/src/components/blocks/search/dialog.tsx +712 -0
  33. package/src/components/blocks/search/provider.tsx +117 -0
  34. package/src/components/blocks/sidebar.tsx +406 -0
  35. package/src/components/blocks/theme/toggle.tsx +71 -0
  36. package/src/components/blocks/tooltip/hint.tsx +27 -0
  37. package/src/components/blocks/viewer/controls.tsx +279 -0
  38. package/src/components/blocks/viewer/float-bar.tsx +50 -0
  39. package/src/components/layouts/layout.tsx +93 -0
  40. package/src/components/layouts/left-sidebar/content.tsx +62 -0
  41. package/src/components/layouts/left-sidebar/context.tsx +54 -0
  42. package/src/components/layouts/left-sidebar/expand.tsx +22 -0
  43. package/src/components/layouts/left-sidebar/footer.tsx +105 -0
  44. package/src/components/layouts/left-sidebar/index.ts +28 -0
  45. package/src/components/layouts/left-sidebar/items.tsx +102 -0
  46. package/src/components/layouts/left-sidebar/search.tsx +74 -0
  47. package/src/components/layouts/left-sidebar/sidebar.tsx +188 -0
  48. package/src/components/layouts/left-sidebar/tree.tsx +156 -0
  49. package/src/components/layouts/right-sidebar.tsx +95 -0
  50. package/src/components/layouts/tree-context.tsx +41 -0
  51. package/src/components/mdx/accordion.tsx +184 -0
  52. package/src/components/mdx/banner.tsx +154 -0
  53. package/src/components/mdx/callout.tsx +96 -0
  54. package/src/components/mdx/card.tsx +49 -0
  55. package/src/components/mdx/codeblock.tsx +249 -0
  56. package/src/components/mdx/collapsible.tsx +32 -0
  57. package/src/components/mdx/files.tsx +62 -0
  58. package/src/components/mdx/glow-header.tsx +40 -0
  59. package/src/components/mdx/gradient-text.tsx +27 -0
  60. package/src/components/mdx/image-zoom.tsx +314 -0
  61. package/src/components/mdx/mermaid.tsx +16 -0
  62. package/src/components/mdx/multiblock.tsx +18 -0
  63. package/src/components/mdx/preview.tsx +100 -0
  64. package/src/components/mdx/steps.tsx +9 -0
  65. package/src/components/mdx/table.tsx +13 -0
  66. package/src/components/mdx/tabs.tsx +353 -0
  67. package/src/components/mdx/type-table.tsx +170 -0
  68. package/src/components/ui/accordion.tsx +71 -0
  69. package/src/components/ui/alert-dialog.tsx +187 -0
  70. package/src/components/ui/alert.tsx +75 -0
  71. package/src/components/ui/aspect-ratio.tsx +22 -0
  72. package/src/components/ui/attachment.tsx +207 -0
  73. package/src/components/ui/avatar.tsx +108 -0
  74. package/src/components/ui/badge.tsx +51 -0
  75. package/src/components/ui/breadcrumb.tsx +124 -0
  76. package/src/components/ui/bubble.tsx +127 -0
  77. package/src/components/ui/button-group.tsx +87 -0
  78. package/src/components/ui/button.tsx +57 -0
  79. package/src/components/ui/calendar.tsx +221 -0
  80. package/src/components/ui/card.tsx +102 -0
  81. package/src/components/ui/carousel.tsx +242 -0
  82. package/src/components/ui/chart.tsx +371 -0
  83. package/src/components/ui/checkbox.tsx +28 -0
  84. package/src/components/ui/collapsible.tsx +21 -0
  85. package/src/components/ui/combobox.tsx +297 -0
  86. package/src/components/ui/command.tsx +198 -0
  87. package/src/components/ui/context-menu.tsx +270 -0
  88. package/src/components/ui/dialog.tsx +160 -0
  89. package/src/components/ui/direction.tsx +6 -0
  90. package/src/components/ui/drawer.tsx +227 -0
  91. package/src/components/ui/dropdown-menu.tsx +269 -0
  92. package/src/components/ui/empty.tsx +103 -0
  93. package/src/components/ui/field.tsx +238 -0
  94. package/src/components/ui/hover-card.tsx +50 -0
  95. package/src/components/ui/input-group.tsx +158 -0
  96. package/src/components/ui/input-otp.tsx +86 -0
  97. package/src/components/ui/input.tsx +19 -0
  98. package/src/components/ui/item.tsx +201 -0
  99. package/src/components/ui/kbd.tsx +26 -0
  100. package/src/components/ui/label.tsx +19 -0
  101. package/src/components/ui/marker.tsx +70 -0
  102. package/src/components/ui/menubar.tsx +280 -0
  103. package/src/components/ui/message-scroller.tsx +131 -0
  104. package/src/components/ui/message.tsx +91 -0
  105. package/src/components/ui/native-select.tsx +60 -0
  106. package/src/components/ui/navigation-menu.tsx +167 -0
  107. package/src/components/ui/pagination.tsx +130 -0
  108. package/src/components/ui/popover.tsx +91 -0
  109. package/src/components/ui/progress.tsx +82 -0
  110. package/src/components/ui/questionnaire.tsx +324 -0
  111. package/src/components/ui/radio-group.tsx +37 -0
  112. package/src/components/ui/resizable.tsx +49 -0
  113. package/src/components/ui/scroll-area.tsx +54 -0
  114. package/src/components/ui/select.tsx +200 -0
  115. package/src/components/ui/separator.tsx +24 -0
  116. package/src/components/ui/sheet.tsx +138 -0
  117. package/src/components/ui/sidebar.tsx +730 -0
  118. package/src/components/ui/skeleton.tsx +13 -0
  119. package/src/components/ui/slider.tsx +51 -0
  120. package/src/components/ui/spinner.tsx +10 -0
  121. package/src/components/ui/switch.tsx +31 -0
  122. package/src/components/ui/table.tsx +115 -0
  123. package/src/components/ui/tabs.tsx +81 -0
  124. package/src/components/ui/textarea.tsx +17 -0
  125. package/src/components/ui/toast.tsx +234 -0
  126. package/src/components/ui/toggle-group.tsx +89 -0
  127. package/src/components/ui/toggle.tsx +44 -0
  128. package/src/components/ui/tooltip.tsx +65 -0
  129. package/src/hooks/canvas/use-flow-hover.ts +18 -0
  130. package/src/hooks/canvas/use-flow-viewport-persist.ts +33 -0
  131. package/src/hooks/lang.ts +8 -0
  132. package/src/hooks/layout.ts +18 -0
  133. package/src/hooks/page/use-fit-pages.ts +91 -0
  134. package/src/hooks/page/use-location-search.ts +25 -0
  135. package/src/hooks/page/use-page-preview.ts +40 -0
  136. package/src/hooks/page/use-paged-list.ts +20 -0
  137. package/src/hooks/readability/provider.ts +86 -0
  138. package/src/hooks/search/dialog.ts +41 -0
  139. package/src/hooks/search/provider.ts +25 -0
  140. package/src/hooks/search/use-highlight-query.ts +51 -0
  141. package/src/hooks/sidebar.ts +63 -0
  142. package/src/hooks/use-async-value.ts +57 -0
  143. package/src/hooks/use-close-on-scroll.ts +23 -0
  144. package/src/hooks/use-copy-button.ts +46 -0
  145. package/src/hooks/use-cycle-index.ts +75 -0
  146. package/src/hooks/use-document-title.ts +9 -0
  147. package/src/hooks/use-element-size.ts +35 -0
  148. package/src/hooks/use-external-meta.ts +132 -0
  149. package/src/hooks/use-hot-key.ts +33 -0
  150. package/src/hooks/use-hover-follow.ts +54 -0
  151. package/src/hooks/use-in-view.ts +30 -0
  152. package/src/hooks/use-last-value.ts +15 -0
  153. package/src/hooks/use-marquee.ts +51 -0
  154. package/src/hooks/use-media-query.ts +14 -0
  155. package/src/hooks/use-mobile.ts +19 -0
  156. package/src/hooks/use-modifier-keys.ts +31 -0
  157. package/src/hooks/use-on-change.ts +35 -0
  158. package/src/hooks/use-pane-keys.ts +28 -0
  159. package/src/hooks/use-swipe.ts +26 -0
  160. package/src/hooks/use-tree.ts +102 -0
  161. package/src/hooks/viewer/use-in-float-bar.ts +6 -0
  162. package/src/hooks/viewer/use-viewer-prefs.ts +77 -0
  163. package/src/lib/canvas/edge.ts +173 -0
  164. package/src/lib/canvas/geometry.ts +53 -0
  165. package/src/lib/canvas/state.ts +47 -0
  166. package/src/lib/canvas/viewport.ts +37 -0
  167. package/src/lib/canvas/xyflow.ts +1 -0
  168. package/src/lib/config.ts +81 -0
  169. package/src/lib/mdx/callout.ts +20 -0
  170. package/src/lib/mdx/gradient.ts +15 -0
  171. package/src/lib/mdx/mermaid.ts +14 -0
  172. package/src/lib/media/crop-opaque.ts +76 -0
  173. package/src/lib/media/nearby-indexes.ts +6 -0
  174. package/src/lib/media/trim-transparent.ts +117 -0
  175. package/src/lib/readability/config.ts +10 -0
  176. package/src/lib/readability/spotlight.ts +186 -0
  177. package/src/lib/readability/store.ts +146 -0
  178. package/src/lib/tree.ts +92 -0
  179. package/src/lib/utils/async-cache.ts +32 -0
  180. package/src/lib/utils/cn.ts +1 -0
  181. package/src/lib/utils/format.ts +22 -0
  182. package/src/lib/utils/history.ts +9 -0
  183. package/src/lib/utils/merge-refs.ts +13 -0
  184. package/src/lib/utils/nav-active.ts +58 -0
  185. package/src/lib/utils/stored-value.ts +136 -0
  186. package/src/lib/utils/urls.ts +17 -0
  187. package/src/locales/en_us.json +119 -0
  188. package/src/locales/pl_pl.json +35 -0
  189. package/src/locales/ru_ru.json +119 -0
  190. package/src/locales/uk_ua.json +35 -0
  191. package/src/locales/zh_cn.json +17 -0
  192. package/src/locales/zh_tw.json +17 -0
  193. package/src/locales.ts +5 -0
  194. package/src/provider.tsx +141 -0
  195. package/src/types/canvas.ts +49 -0
  196. package/src/types/glb.ts +25 -0
  197. package/src/types/nav.ts +95 -0
  198. package/src/types/preview.ts +9 -0
  199. package/src/types/readability.ts +28 -0
  200. package/src/types/search.ts +56 -0
  201. package/src/types/tree.ts +18 -0
  202. package/src/types/viewer.ts +53 -0
  203. package/styles/content/base.css +194 -0
  204. package/styles/content/default-colors.css +51 -0
  205. package/styles/content/shiki.css +108 -0
  206. package/styles/content/typography.mjs +3941 -0
  207. package/styles/content.css +7 -0
  208. package/styles/image-zoom.css +83 -0
  209. package/styles/layout.css +28 -0
  210. package/styles/left-sidebar.css +140 -0
  211. package/styles/readability.css +50 -0
  212. package/styles/right-sidebar.css +6 -0
  213. package/styles/workspace.css +26 -0
  214. package/styles.css +413 -0
  215. package/README.md +0 -3
@@ -0,0 +1,184 @@
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('ui.copied_link', { note: 'aria-label' })
180
+ : t('ui.copy_link', { note: 'aria-label' })}
181
+ </span>
182
+ </button>
183
+ );
184
+ }
@@ -0,0 +1,154 @@
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 function Banner({
12
+ id,
13
+ variant = 'normal',
14
+ changeLayout = true,
15
+ height = '3rem',
16
+ rainbowColors = [
17
+ 'rgba(0,149,255,0.56)',
18
+ 'rgba(231,77,255,0.77)',
19
+ 'rgba(255,0,0,0.73)',
20
+ 'rgba(131,255,166,0.66)',
21
+ ],
22
+ ...props
23
+ }: HTMLAttributes<HTMLDivElement> & {
24
+
25
+ height?: string;
26
+
27
+ variant?: BannerVariant;
28
+
29
+ rainbowColors?: string[];
30
+
31
+ changeLayout?: boolean;
32
+ }) {
33
+ const t = useTranslations();
34
+ const [open, setOpen] = useState(true);
35
+ const globalKey = id ? `banner-${encodeBase32(id)}` : null;
36
+
37
+ useEffect(() => {
38
+ if (globalKey && localStorage.getItem(globalKey) === 'true') setOpen(false);
39
+ }, [globalKey]);
40
+
41
+ function onClose() {
42
+ setOpen(false);
43
+ if (globalKey) localStorage.setItem(globalKey, 'true');
44
+ }
45
+
46
+ if (!open) return null;
47
+
48
+ return (
49
+ <div
50
+ id={id}
51
+ {...props}
52
+ className={cn(
53
+ 'sticky top-0 z-40 flex flex-row items-center justify-center px-4 text-center text-sm font-medium',
54
+ variant === 'normal' && 'bg-secondary',
55
+ variant === 'rainbow' && 'bg-background',
56
+ !open && 'hidden',
57
+ props.className,
58
+ )}
59
+ style={{
60
+ height,
61
+ }}
62
+ >
63
+ {changeLayout && open ? (
64
+ <style>
65
+ {globalKey
66
+ ? `:root:not(.${globalKey}) { --banner-height: ${height}; }`
67
+ : `:root { --banner-height: ${height}; }`}
68
+ </style>
69
+ ) : null}
70
+ {globalKey ? <style>{`.${globalKey} #${id} { display: none; }`}</style> : null}
71
+ {globalKey ? (
72
+ <script
73
+ dangerouslySetInnerHTML={{
74
+ __html: `if (localStorage.getItem('${globalKey}') === 'true') document.documentElement.classList.add('${globalKey}');`,
75
+ }}
76
+ />
77
+ ) : null}
78
+
79
+ {variant === 'rainbow'
80
+ ? flow({
81
+ colors: rainbowColors,
82
+ })
83
+ : null}
84
+ {props.children}
85
+ {id ? (
86
+ <button
87
+ type="button"
88
+ aria-label={t('ui.banner_close', { note: 'aria-label' })}
89
+ onClick={onClose}
90
+ className={cn(
91
+ buttonVariants({
92
+ variant: 'ghost',
93
+ className: 'absolute inset-e-2 top-1/2 -translate-y-1/2 text-muted-foreground/50',
94
+ size: 'icon-sm',
95
+ }),
96
+ )}
97
+ >
98
+ <X />
99
+ </button>
100
+ ) : null}
101
+ </div>
102
+ );
103
+ }
104
+
105
+ const maskImage =
106
+ 'linear-gradient(to bottom,white,transparent), radial-gradient(circle at top center, white, transparent)';
107
+
108
+ function flow({ colors }: { colors: string[] }) {
109
+ return (
110
+ <>
111
+ <div
112
+ className="absolute inset-0 -z-1"
113
+ style={{
114
+ maskImage,
115
+ maskComposite: 'intersect',
116
+ animation: 'moving-banner 20s linear infinite',
117
+ backgroundImage: `repeating-linear-gradient(70deg, ${[...colors, colors[0]].map((color, i) => `${color} ${(i * 50) / colors.length}%`).join(', ')})`,
118
+ backgroundSize: '200% 100%',
119
+ filter: 'saturate(2)',
120
+ }}
121
+ />
122
+ <style>
123
+ {`@keyframes moving-banner {
124
+ from { background-position: 0% 0; }
125
+ to { background-position: 100% 0; }
126
+ }`}
127
+ </style>
128
+ </>
129
+ );
130
+ }
131
+
132
+ function encodeBase32(str: string) {
133
+ const alphabet = 'abcdefghijklmnopqrstuvwxyz234567';
134
+ let encoded = '';
135
+
136
+ let buffer = 0;
137
+ let bitsLeft = 0;
138
+
139
+ for (let i = 0; i < str.length; i++) {
140
+ buffer = (buffer << 8) | str.charCodeAt(i);
141
+ bitsLeft += 8;
142
+
143
+ while (bitsLeft >= 5) {
144
+ bitsLeft -= 5;
145
+ encoded += alphabet[(buffer >> bitsLeft) & 31];
146
+ }
147
+ }
148
+
149
+ if (bitsLeft > 0) {
150
+ encoded += alphabet[(buffer << (5 - bitsLeft)) & 31];
151
+ }
152
+
153
+ return encoded;
154
+ }
@@ -0,0 +1,96 @@
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
+ }
@@ -0,0 +1,49 @@
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
+
6
+ export function Cards(props: HTMLAttributes<HTMLDivElement>) {
7
+ return (
8
+ <div {...props} className={cn('grid grid-cols-2 gap-3 @container', props.className)}>
9
+ {props.children}
10
+ </div>
11
+ );
12
+ }
13
+
14
+ export type CardProps = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
15
+ icon?: ReactNode;
16
+ title: ReactNode;
17
+ description?: ReactNode;
18
+
19
+ href?: string;
20
+ external?: boolean;
21
+ };
22
+
23
+ export function Card({ icon, title, description, ...props }: CardProps) {
24
+ const { Link } = useUiComponents();
25
+ const E = (props.href ? Link : 'div') as ComponentType<ComponentProps<'a'>>;
26
+
27
+ return (
28
+ <E
29
+ {...props}
30
+ data-card
31
+ className={cn(
32
+ 'block rounded-xl border bg-card p-4 text-card-foreground transition-colors @max-lg:col-span-full',
33
+ props.href && 'hover:bg-accent/80',
34
+ props.className,
35
+ )}
36
+ >
37
+ {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}
40
+ </div>
41
+ ) : null}
42
+ <h3 className="not-prose mb-1 text-sm font-medium">{title}</h3>
43
+ {description ? <p className="my-0! text-sm text-muted-foreground">{description}</p> : null}
44
+ <div className="text-sm text-muted-foreground prose-no-margin empty:hidden">
45
+ {props.children}
46
+ </div>
47
+ </E>
48
+ );
49
+ }