@terrafirmagreg-team/ui 0.1.0 → 1.2.1

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
@@ -0,0 +1,231 @@
1
+ 'use client';
2
+
3
+ import type { CSSProperties, ReactNode } from 'react';
4
+ import { Skeleton } from '@terrafirmagreg-team/ui/components/ui/skeleton';
5
+ import { formatCount } from '@terrafirmagreg-team/ui/lib/utils/format';
6
+ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
7
+
8
+ export const SPRITE_CHIP_CLASS =
9
+ 'inline-flex shrink-0 items-center justify-center rounded-sm border border-border bg-secondary p-0.5 hover:bg-accent';
10
+
11
+ export function SpriteFrame({
12
+ size,
13
+ chip = false,
14
+ badge,
15
+ badgeClassName,
16
+ className,
17
+ style,
18
+ children,
19
+ }: {
20
+ size?: number;
21
+ chip?: boolean;
22
+ badge?: ReactNode;
23
+ badgeClassName?: string;
24
+ className?: string;
25
+ style?: CSSProperties;
26
+ children?: ReactNode;
27
+ }) {
28
+ return (
29
+ <span
30
+ className={cn(
31
+ 'inline-flex shrink-0 items-center justify-center',
32
+ chip && SPRITE_CHIP_CLASS,
33
+ badge !== undefined && 'relative',
34
+ className,
35
+ )}
36
+ style={{ ...(size ? { width: size, height: size } : undefined), ...style }}
37
+ >
38
+ {children}
39
+ {badge !== undefined ? (
40
+ <span
41
+ className={cn(
42
+ 'pointer-events-none absolute -right-0.5 -bottom-0.5 z-10',
43
+ badgeClassName,
44
+ )}
45
+ >
46
+ {badge}
47
+ </span>
48
+ ) : null}
49
+ </span>
50
+ );
51
+ }
52
+
53
+ export function Sprite({
54
+ src,
55
+ rect,
56
+ page,
57
+ size,
58
+ tint,
59
+ alt = '',
60
+ className,
61
+ fallback,
62
+ onError,
63
+ }: {
64
+ src?: string;
65
+
66
+ rect?: { x: number; y: number; w: number };
67
+
68
+ page?: { w: number; h: number };
69
+ size: number;
70
+ tint?: string;
71
+ alt?: string;
72
+ className?: string;
73
+ fallback?: ReactNode;
74
+ onError?: () => void;
75
+ }) {
76
+ if (!src) {
77
+ return (
78
+ fallback ?? (
79
+ <Skeleton
80
+ className={cn('inline-block shrink-0', className)}
81
+ style={{ width: size, height: size }}
82
+ />
83
+ )
84
+ );
85
+ }
86
+ if (rect && page) {
87
+ const scale = size / Math.max(rect.w, 1);
88
+ const pos = `-${rect.x * scale}px -${rect.y * scale}px`;
89
+ const bgSize = `${page.w * scale}px ${page.h * scale}px`;
90
+ return (
91
+ <span
92
+ className={className ?? 'relative inline-block shrink-0'}
93
+ style={{
94
+ width: size,
95
+ height: size,
96
+ backgroundImage: `url(${src})`,
97
+ backgroundPosition: pos,
98
+ backgroundSize: bgSize,
99
+ imageRendering: 'pixelated',
100
+ ...(tint
101
+ ? {
102
+ backgroundColor: tint,
103
+ backgroundBlendMode: 'multiply',
104
+ WebkitMaskImage: `url(${src})`,
105
+ maskImage: `url(${src})`,
106
+ WebkitMaskPosition: pos,
107
+ maskPosition: pos,
108
+ WebkitMaskSize: bgSize,
109
+ maskSize: bgSize,
110
+ WebkitMaskRepeat: 'no-repeat',
111
+ maskRepeat: 'no-repeat',
112
+ }
113
+ : {}),
114
+ }}
115
+ />
116
+ );
117
+ }
118
+ return (
119
+ // eslint-disable-next-line @next/next/no-img-element
120
+ <img
121
+ src={src}
122
+ alt={alt}
123
+ width={size}
124
+ height={size}
125
+ className={cn('rounded-sm', className)}
126
+ style={{ width: size, height: size, imageRendering: 'pixelated' }}
127
+ loading="lazy"
128
+ decoding="async"
129
+ onError={onError}
130
+ />
131
+ );
132
+ }
133
+
134
+ export const SPRITE_CLUSTER_UNITS = 18;
135
+ export const SPRITE_CLUSTER_INSET = 6;
136
+ const CLUSTER_BLEED = 1.15;
137
+ const CLUSTER_POSITIONS: readonly (readonly [number, number])[][] = [
138
+ [],
139
+ [[1.6, 1.6]],
140
+ [
141
+ [0.4, 0.8],
142
+ [2.8, 2.4],
143
+ ],
144
+ [
145
+ [-0.8, 0],
146
+ [1.6, 1.6],
147
+ [4.0, 3.2],
148
+ ],
149
+ ];
150
+
151
+ export function SpriteCluster({
152
+ items,
153
+ size = 36,
154
+ chip = true,
155
+ inset = SPRITE_CLUSTER_INSET,
156
+ limit = 3,
157
+ }: {
158
+ items: ReactNode[];
159
+ size?: number;
160
+ chip?: boolean;
161
+ inset?: number;
162
+ limit?: number;
163
+ }) {
164
+ const inner = size - (chip ? inset : 0);
165
+ const scale = inner / SPRITE_CLUSTER_UNITS;
166
+ const shown = items.slice(0, Math.min(limit, CLUSTER_POSITIONS.length - 1));
167
+ const positions = CLUSTER_POSITIONS[shown.length];
168
+ return (
169
+ <SpriteFrame size={size} chip={chip}>
170
+ <span className="relative block" style={{ width: inner, height: inner }}>
171
+ {shown.map((node, i) => (
172
+ <span
173
+ key={i}
174
+ className="absolute"
175
+ style={{
176
+ left: positions[i][0] * CLUSTER_BLEED * scale,
177
+ top: positions[i][1] * CLUSTER_BLEED * scale,
178
+ zIndex: shown.length - i,
179
+ }}
180
+ >
181
+ {node}
182
+ </span>
183
+ ))}
184
+ </span>
185
+ </SpriteFrame>
186
+ );
187
+ }
188
+
189
+ export function SpriteCount({
190
+ count,
191
+ limit,
192
+ className,
193
+ style,
194
+ }: {
195
+ count: number;
196
+ limit?: number;
197
+ className?: string;
198
+ style?: CSSProperties;
199
+ }) {
200
+ if (count < 1) return null;
201
+ return (
202
+ <span aria-hidden className={className} style={style}>
203
+ {formatCount(count, limit)}
204
+ </span>
205
+ );
206
+ }
207
+
208
+ export function SpriteList({
209
+ items,
210
+ variant = 'grid',
211
+ className,
212
+ }: {
213
+ items: { key: React.Key; node: ReactNode }[];
214
+ variant?: 'grid' | 'row';
215
+ className?: string;
216
+ }) {
217
+ return (
218
+ <ul
219
+ className={cn(
220
+ variant === 'grid'
221
+ ? 'grid grid-cols-[repeat(auto-fill,minmax(--spacing(9),1fr))] gap-1'
222
+ : 'flex flex-wrap items-center gap-3',
223
+ className,
224
+ )}
225
+ >
226
+ {items.map((item) => (
227
+ <li key={item.key}>{item.node}</li>
228
+ ))}
229
+ </ul>
230
+ );
231
+ }
@@ -0,0 +1,110 @@
1
+ import { Fragment, type CSSProperties } from 'react';
2
+
3
+ export type TextFormat = {
4
+ marker: string;
5
+ colors: Record<string, string>;
6
+ styles?: Record<string, CSSProperties>;
7
+ reset?: string;
8
+ hex?: string;
9
+ ignore?: readonly string[];
10
+ };
11
+
12
+ type Run = { text: string; style?: CSSProperties };
13
+
14
+ export function FormatText({
15
+ text,
16
+ format,
17
+ }: {
18
+ text: string;
19
+ format: TextFormat;
20
+ }) {
21
+ const codes = new Set([
22
+ ...Object.keys(format.colors),
23
+ ...Object.keys(format.styles ?? {}),
24
+ ...(format.reset ? [format.reset] : []),
25
+ ...(format.ignore ?? []),
26
+ ]);
27
+ const runs: Run[] = [];
28
+ let style: CSSProperties | undefined;
29
+ let buf = '';
30
+ const flush = () => {
31
+ if (!buf) return;
32
+ runs.push(style ? { text: buf, style } : { text: buf });
33
+ buf = '';
34
+ };
35
+ for (let i = 0; i < text.length; i += 1) {
36
+ const ch = text[i];
37
+ if (ch !== format.marker) {
38
+ buf += ch;
39
+ continue;
40
+ }
41
+ const code = text[i + 1]?.toLowerCase();
42
+ if (format.hex && code === format.hex) {
43
+ let hex = '';
44
+ let j = i + 2;
45
+ while (
46
+ hex.length < 6 &&
47
+ text[j] === format.marker &&
48
+ /^[0-9a-f]$/.test(text[j + 1]?.toLowerCase() ?? '')
49
+ ) {
50
+ hex += (text[j + 1] ?? '').toLowerCase();
51
+ j += 2;
52
+ }
53
+ if (hex.length === 6) {
54
+ flush();
55
+ i = j - 1;
56
+ const next: CSSProperties = { ...(style ?? {}), color: `#${hex}` };
57
+ delete next.fontWeight;
58
+ delete next.fontStyle;
59
+ delete next.textDecoration;
60
+ style = next;
61
+ continue;
62
+ }
63
+ buf += ch;
64
+ continue;
65
+ }
66
+ if (code === undefined || !codes.has(code)) {
67
+ buf += ch;
68
+ continue;
69
+ }
70
+ flush();
71
+ i += 1;
72
+ if (code === format.reset) {
73
+ style = undefined;
74
+ continue;
75
+ }
76
+ const next: CSSProperties = { ...(style ?? {}) };
77
+ const color = format.colors[code];
78
+ if (color) {
79
+ next.color = color;
80
+ delete next.fontWeight;
81
+ delete next.fontStyle;
82
+ delete next.textDecoration;
83
+ }
84
+ const extra = format.styles?.[code];
85
+ if (extra) {
86
+ for (const [key, value] of Object.entries(extra)) {
87
+ if (key === 'textDecoration' && next.textDecoration) {
88
+ next.textDecoration = `${next.textDecoration} ${String(value)}`;
89
+ } else {
90
+ (next as Record<string, unknown>)[key] = value;
91
+ }
92
+ }
93
+ }
94
+ style = Object.keys(next).length ? next : undefined;
95
+ }
96
+ flush();
97
+ return (
98
+ <>
99
+ {runs.map((run, index) =>
100
+ run.style ? (
101
+ <span key={index} style={run.style}>
102
+ {run.text}
103
+ </span>
104
+ ) : (
105
+ <Fragment key={index}>{run.text}</Fragment>
106
+ ),
107
+ )}
108
+ </>
109
+ );
110
+ }
@@ -17,12 +17,14 @@ export function ThemeToggle({
17
17
  modes = ['light', 'dark', 'system'],
18
18
  labels,
19
19
  className,
20
+ buttonClassName,
20
21
  ...props
21
22
  }: ComponentProps<'div'> & {
22
23
  value?: string;
23
24
  onValueChange?: (mode: ThemeToggleMode) => void;
24
25
  modes?: readonly ThemeToggleMode[];
25
26
  labels?: Partial<Record<ThemeToggleMode, string>>;
27
+ buttonClassName?: string;
26
28
  }) {
27
29
  const mounted = useSyncExternalStore(
28
30
  noop,
@@ -57,6 +59,7 @@ export function ThemeToggle({
57
59
  aria-pressed={active}
58
60
  className={cn(
59
61
  'size-6.5 p-1.5 text-muted-foreground',
62
+ buttonClassName,
60
63
  active && 'bg-accent text-accent-foreground',
61
64
  )}
62
65
  onClick={() => select(mode)}
@@ -69,3 +72,4 @@ export function ThemeToggle({
69
72
  </div>
70
73
  );
71
74
  }
75
+
@@ -25,3 +25,4 @@ export function Hint({
25
25
  </Tooltip>
26
26
  );
27
27
  }
28
+
@@ -0,0 +1,41 @@
1
+ 'use client';
2
+
3
+ import { useState, type DependencyList, type ReactElement, type ReactNode } from 'react';
4
+ import { useAsyncValue } from '@terrafirmagreg-team/ui/hooks/use-async-value';
5
+ import {
6
+ Tooltip,
7
+ TooltipContent,
8
+ TooltipTrigger,
9
+ } from '@terrafirmagreg-team/ui/components/ui/tooltip';
10
+
11
+ export function LazyTooltip<T>({
12
+ load,
13
+ deps = [],
14
+ children,
15
+ trigger,
16
+ contentClassName,
17
+ onOpenChange,
18
+ }: {
19
+ load: () => Promise<T>;
20
+ deps?: DependencyList;
21
+ children: (value: T | undefined) => ReactNode;
22
+ trigger: ReactElement;
23
+ contentClassName?: string;
24
+ onOpenChange?: (open: boolean) => void;
25
+ }) {
26
+ const [open, setOpen] = useState(false);
27
+ const value = useAsyncValue(open ? load : false, [open, ...deps]);
28
+ return (
29
+ <Tooltip
30
+ onOpenChange={(next) => {
31
+ setOpen(next);
32
+ onOpenChange?.(next);
33
+ }}
34
+ >
35
+ <TooltipTrigger render={trigger} delay={150} closeDelay={100} />
36
+ <TooltipContent className={contentClassName}>
37
+ {children(value)}
38
+ </TooltipContent>
39
+ </Tooltip>
40
+ );
41
+ }
@@ -55,16 +55,16 @@ export function ViewerZoomControls({
55
55
  const t = useTranslations();
56
56
  return (
57
57
  <>
58
- <IconButton overlay label={t('ui.canvas.zoom_in')} onClick={onZoomIn}>
58
+ <IconButton overlay label={t('Zoom in', { note: 'ui.canvas.zoom_in' })} onClick={onZoomIn}>
59
59
  <ZoomIn className="size-3.5" />
60
60
  </IconButton>
61
- <IconButton overlay label={t('ui.canvas.zoom_out')} onClick={onZoomOut}>
61
+ <IconButton overlay label={t('Zoom out', { note: 'ui.canvas.zoom_out' })} onClick={onZoomOut}>
62
62
  <ZoomOut className="size-3.5" />
63
63
  </IconButton>
64
64
  {onFit ? (
65
65
  <IconButton
66
66
  overlay
67
- label={fitLabel ?? t('ui.canvas.fit')}
67
+ label={fitLabel ?? t('Fit view', { note: 'ui.canvas.fit' })}
68
68
  onClick={onFit}
69
69
  >
70
70
  <Maximize2 className="size-3.5" />
@@ -85,7 +85,7 @@ export function ViewerPagerControls({
85
85
  <>
86
86
  <IconButton
87
87
  overlay
88
- label={t('ui.canvas.prev')}
88
+ label={t('Previous', { note: 'ui.canvas.prev' })}
89
89
  disabled={index === 0}
90
90
  onClick={() => onSelect(index - 1)}
91
91
  >
@@ -102,7 +102,7 @@ export function ViewerPagerControls({
102
102
  </ButtonGroupText>
103
103
  <IconButton
104
104
  overlay
105
- label={t('ui.canvas.next')}
105
+ label={t('Next', { note: 'ui.canvas.next' })}
106
106
  disabled={index === count - 1}
107
107
  onClick={() => onSelect(index + 1)}
108
108
  >
@@ -146,7 +146,11 @@ export function ViewerExpandButton({ target }: ExpandButtonProps) {
146
146
  <IconButton
147
147
  overlay
148
148
  aria-expanded={expanded}
149
- label={t(expanded ? 'ui.canvas.exit_fullscreen' : 'ui.canvas.fullscreen')}
149
+ label={
150
+ expanded
151
+ ? t('Exit fullscreen', { note: 'ui.canvas.exit_fullscreen' })
152
+ : t('Fullscreen', { note: 'ui.canvas.fullscreen' })
153
+ }
150
154
  onClick={() => {
151
155
  const el = target.current;
152
156
  if (fallback) {
@@ -277,3 +281,4 @@ export function ViewerControls({
277
281
  </>
278
282
  );
279
283
  }
284
+
@@ -48,3 +48,4 @@ export function FloatBarItem({
48
48
  </span>
49
49
  );
50
50
  }
51
+
@@ -9,7 +9,7 @@ import type * as PageTree from '@terrafirmagreg-team/translate/types/tree';
9
9
  import Link from '@terrafirmagreg-team/ui/components/blocks/nav/link';
10
10
  import { SidebarIcon } from 'lucide-react';
11
11
 
12
- import { LayoutContext, useLayout } from '@terrafirmagreg-team/ui/hooks/layout';
12
+ import { LayoutContext, useLayout } from '@terrafirmagreg-team/ui/hooks/use-layout';
13
13
  import { TreeContextProvider } from '@terrafirmagreg-team/ui/components/layouts/tree-context';
14
14
  import {
15
15
  SidebarProvider,
@@ -91,3 +91,4 @@ export function LayoutHeader({ className, ...props }: ComponentProps<'header'>)
91
91
  </header>
92
92
  );
93
93
  }
94
+
@@ -60,3 +60,4 @@ export function LeftSidebarContent({
60
60
  </ScrollArea.Root>
61
61
  );
62
62
  }
63
+
@@ -52,3 +52,4 @@ export function LeftSidebarPortalTarget() {
52
52
  const ctx = use(LeftSidebarPortalContext);
53
53
  return <div data-slot="left-sidebar-portal" ref={ctx?.setTarget} />;
54
54
  }
55
+
@@ -2,7 +2,7 @@
2
2
  import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
3
3
  import { buttonVariants } from '@terrafirmagreg-team/ui/components/ui/button';
4
4
  import { SidebarIcon } from 'lucide-react';
5
- import { useSidebar } from '@terrafirmagreg-team/ui/hooks/sidebar';
5
+ import { useSidebar } from '@terrafirmagreg-team/ui/hooks/use-sidebar';
6
6
  import { SidebarCollapseTrigger } from '@terrafirmagreg-team/ui/components/blocks/sidebar';
7
7
 
8
8
  export function LeftSidebarExpand() {
@@ -20,3 +20,4 @@ export function LeftSidebarExpand() {
20
20
  </SidebarCollapseTrigger>
21
21
  );
22
22
  }
23
+
@@ -36,7 +36,7 @@ export function LeftSidebarLanguage() {
36
36
  locales={locales}
37
37
  selected={locale}
38
38
  onSelect={(value) => onLocaleChange?.(value)}
39
- label={t('ui.choose_language')}
39
+ label={t('Choose a language', { note: 'ui.choose_language' })}
40
40
  />
41
41
  );
42
42
  }
@@ -67,12 +67,12 @@ export function LeftSidebarPlate({
67
67
  >
68
68
  {children}
69
69
  {(tools != null || theme != null) && (
70
- <div className="ms-auto flex items-center">
70
+ <div className="ms-auto flex items-center gap-0.5">
71
71
  {tools}
72
72
  {tools != null && theme != null && (
73
73
  <Separator
74
74
  orientation="vertical"
75
- className="mx-1.5 h-5 self-center! bg-muted-foreground/30"
75
+ className="mx-1 h-5 self-center! bg-muted-foreground/30"
76
76
  aria-hidden
77
77
  />
78
78
  )}
@@ -97,9 +97,15 @@ export function LeftSidebarActions({
97
97
  <LeftSidebarPlate
98
98
  ref={plateRef}
99
99
  tools={SettingsMenu ? <SettingsMenu anchor={plateRef} /> : undefined}
100
- theme={{ value: theme, onValueChange: setTheme }}
100
+ theme={{
101
+ value: theme,
102
+ onValueChange: setTheme,
103
+ className: 'p-0',
104
+ buttonClassName: 'size-7',
105
+ }}
101
106
  >
102
107
  {children}
103
108
  </LeftSidebarPlate>
104
109
  );
105
110
  }
111
+
@@ -26,3 +26,4 @@ export {
26
26
  LeftSidebarLabel,
27
27
  } from './items';
28
28
  export { LeftSidebarTree, LeftSidebarTreeItem } from './tree';
29
+
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import type { ComponentProps } from 'react';
3
- import { useFolderDepth, useSidebar } from '@terrafirmagreg-team/ui/hooks/sidebar';
3
+ import { useFolderDepth, useSidebar } from '@terrafirmagreg-team/ui/hooks/use-sidebar';
4
4
  import { SidebarFolder, SidebarFolderContent as BaseFolderContent, SidebarFolderLink as BaseFolderLink, SidebarFolderTrigger as BaseFolderTrigger, SidebarItem as BaseItem } from '@terrafirmagreg-team/ui/components/blocks/sidebar';
5
5
  import { Separator } from '@terrafirmagreg-team/ui/components/ui/separator';
6
6
 
@@ -100,3 +100,4 @@ export function LeftSidebarLabel({
100
100
  />
101
101
  );
102
102
  }
103
+
@@ -2,7 +2,7 @@
2
2
  import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
3
3
  import type { ComponentProps, ReactNode } from 'react';
4
4
  import { Search } from 'lucide-react';
5
- import { useSearchContext } from '@terrafirmagreg-team/ui/hooks/search/provider';
5
+ import { useSearchContext } from '@terrafirmagreg-team/ui/hooks/search/use-provider';
6
6
  import { useTranslations } from '@terrafirmagreg-team/translate/hooks/use-translations';
7
7
  import { IconButton } from '@terrafirmagreg-team/ui/components/blocks/button/icon-button';
8
8
 
@@ -55,7 +55,7 @@ export function LeftSidebarSearch({
55
55
  if (variant === 'icon')
56
56
  return (
57
57
  <IconButton
58
- label={t('ui.search_trigger')}
58
+ label={t('Search', { note: 'ui.search_trigger' })}
59
59
  variant="ghost"
60
60
  size="icon-sm"
61
61
  tooltip={false}
@@ -66,9 +66,10 @@ export function LeftSidebarSearch({
66
66
  );
67
67
  return (
68
68
  <LeftSidebarSearchTrigger
69
- label={t('ui.search_trigger')}
69
+ label={t('Search', { note: 'ui.search_trigger' })}
70
70
  onClick={() => setOpenSearch(true)}
71
71
  keys={hotKey?.map((k) => k.display)}
72
72
  />
73
73
  );
74
74
  }
75
+
@@ -3,9 +3,9 @@ import { cn } from '@terrafirmagreg-team/ui/lib/utils/cn';
3
3
  import { buttonVariants } from '@terrafirmagreg-team/ui/components/ui/button';
4
4
  import type { ComponentProps, ReactNode } from 'react';
5
5
  import { SidebarIcon } from 'lucide-react';
6
- import { useSidebar } from '@terrafirmagreg-team/ui/hooks/sidebar';
6
+ import { useSidebar } from '@terrafirmagreg-team/ui/hooks/use-sidebar';
7
7
  import { SidebarCollapseTrigger, SidebarDrawerContent, SidebarDrawerOverlay, SidebarTrigger } from '@terrafirmagreg-team/ui/components/blocks/sidebar';
8
- import { useLayout } from '@terrafirmagreg-team/ui/hooks/layout';
8
+ import { useLayout } from '@terrafirmagreg-team/ui/hooks/use-layout';
9
9
  import { LeftSidebarContent } from './content';
10
10
  import { LeftSidebarFooter } from './footer';
11
11
  import type { LeftSidebarParts } from './context';
@@ -186,3 +186,4 @@ function LeftSidebarRegions({
186
186
  </>
187
187
  );
188
188
  }
189
+
@@ -154,3 +154,4 @@ export function LeftSidebarTreeItem({ node }: { node: PageTree.Node }) {
154
154
  </LeftSidebarItem>
155
155
  );
156
156
  }
157
+
@@ -93,3 +93,4 @@ export function RightSidebarPanelBody({ className, ...props }: ComponentProps<'d
93
93
  />
94
94
  );
95
95
  }
96
+
@@ -39,3 +39,4 @@ export function TreeContextProvider({
39
39
  </TreeContext>
40
40
  );
41
41
  }
42
+
@@ -176,9 +176,10 @@ function CopyButton({ id }: { id: string }) {
176
176
  {checked ? <Check className="size-3.5" /> : <LinkIcon className="size-3.5" />}
177
177
  <span className="sr-only">
178
178
  {checked
179
- ? t('ui.copied_link', { note: 'aria-label' })
180
- : t('ui.copy_link', { note: 'aria-label' })}
179
+ ? t('Copied Link', { note: 'ui.copied_link' })
180
+ : t('Copy Link', { note: 'ui.copy_link' })}
181
181
  </span>
182
182
  </button>
183
183
  );
184
184
  }
185
+